Microsoft MVP성태의 닷넷 이야기
VC++: 60. C/C++ Native 스레드 콜 스택 덤프를 얻는 공개 라이브러리 [링크 복사], [링크+제목 복사],
조회: 22503
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

C/C++ Native 스레드 콜 스택 덤프를 얻는 공개 라이브러리

오호... UnityBuild 소스 코드를 보면서 새로운 것을 알게 되는군요. ^^

C/C++ 프로젝트 빌드 속도 개선 - UnityBuild를 아세요?
; https://www.sysnet.pe.kr/2/0/1250

아래의 사이트에 가서 "checkout-scripts.zip" 파일을 다운로드 하고,

earlgrey - Providing the fundamental of the online game server. 
; http://code.google.com/p/earlgrey/downloads/list

압축을 해제한 후, 아래와 같이 실행하면 UnityBuild 관련 소스 코드를 다운로드 받을 수 있습니다.

MSBuild_Win32.bat checkout-engine.xml

svn.exe를 설치하지 않은 분들은 다음과 같은 오류가 발생할 텐데요.

D:\unitybuilder\checkout-scripts\checkout-scripts>MSBuild_Win32.bat checkout-engine.xml
The system cannot find the path specified.
Microsoft (R) Build Engine Version 3.5.30729.5420
[Microsoft .NET Framework, Version 2.0.50727.5448]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 2012-02-26 ?? 7:57:29.
Project "D:\unitybuilder\checkout-scripts\checkout-scripts\checkout-engine.xml" on node 0 (default targets).
  'svn.exe' is not recognized as an internal or external command, operable program or batch file.
D:\unitybuilder\checkout-scripts\checkout-scripts\checkout-engine.xml(50,2): error MSB3073: The command "svn.exe checkout https://earlgrey.googlecode.com/svn "earlgrey-engine" --depth empty" exited with code 9009.
Done Building Project "D:\unitybuilder\checkout-scripts\checkout-scripts\checkout-engine.xml" (default targets) -- FAILED.
 

svn 설치를 위해 다음의 사이트를 방문합니다.

subversion
; http://subversion.tigris.org/servlets/ProjectDocumentList

제 경우에는 위의 사이트에서 1.6.x 버전 폴더의 svn-win32-1.6.6.zip 파일을 다운로드 받았습니다.

자, 이제 MSBuild_Win32.bat 파일에 svn.exe의 경로를 "SET PATH"로 잡아주고 실행하면, 소스 코드를 다운로드 받게 됩니다.

@echo off

PATH=%PATH%;E:\svn-win32-1.6.6\bin

SETLOCAL
call "%VS90COMNTOOLS%\..\..\VC\vcvarsall.bat" x86
call "C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe" %*
SET ERR_LEVEL=%errorlevel%
exit /b %ERR_LEVEL%




재미있게도, UnityBuild 압축 해제를 하면 다음과 같은 프로젝트 경로를 볼 수 있습니다.

\checkout-scripts\checkout-scripts\earlgrey-engine\trunk\vendor\StackWalker_2009-11-01\StackWalker

정적 라이브러리 프로젝트로 되어 있는데, main 함수를 포함한 main.cpp 파일이 함께 있으므로 다음과 같이 프로젝트 속성을 바꿔주고,

native_call_stack_1.png

이어서, Linker / System에서 "SubSystem" 설정을 "Console (/SUBSYSTEM:CONSOLE)"로 변경해 준 다음, main.cpp 파일에 대해서 "Excluded From Build" 설정을 "No"로 변경해 주면 일반 콘솔 프로그램처럼 실행해 볼 수 있습니다.

main.cpp 파일에서는 자기 자신에 대해 직접 call stack을 뜨는 것을 테스트 하고 있는데요.

int _tmain(int argc, _TCHAR* argv[])
{
  StackWalkTest();

  ...[생략]...

  return 0;
}

void Func5()
{
  StackWalkerToConsole sw;
  sw.ShowCallstack();
}

void Func4() { Func5(); }
void Func3() { Func4(); }
void Func2() { Func3(); }
void Func1() { Func2(); }

void StackWalkTest() { Func1(); }

보시는 것처럼, 사용 방법이 매우 간단합니다. StackWalkerToConsole 클래스를 생성하고 ShowCallstack을 부르는 것으로 현재 실행 중인 스레드의 콜 스택을 얻을 수 있습니다.

일단, 제가 실행해 보니 다음과 같이 출력이 됩니다.

D:\StackWalker\Win32\Debug>StackWalker_VC10.exe
SymInit: Symbol-SearchPath: '.;D:\StackWalker\Win32\Debug;D:\StackWalker\Win32\Debug;C:\Windows;C:\Windows\system32;SRV*C:\websymbols*http://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'SeongTae Jeong'
OS-Version: 6.1.7601 (Service Pack 1) 0x100-0x1
D:\Settings\desktop\native_callstack\StackWalker\Win32\Debug\StackWalker_VC10.exe:StackWalker_VC10.exe (008B0000), size: 45056 (result: 0), SymType: '-nosymbols-', PDB: 'D:\Settings\desktop\native_callstack\StackWalker\Win32\Debug\StackWalker_VC10.exe'
C:\Windows\SysWOW64\ntdll.dll:ntdll.dll (77830000), size: 1572864 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\SysWOW64\ntdll.dll', fileVersion: 6.1.7601.17725
C:\Windows\syswow64\kernel32.dll:kernel32.dll (74F70000), size: 1114112 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\kernel32.dll', fileVersion: 6.1.7601.17651
C:\Windows\syswow64\KERNELBASE.dll:KERNELBASE.dll (76C10000), size: 286720 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\KERNELBASE.dll', fileVersion: 6.1.7601.17651
C:\Windows\syswow64\ADVAPI32.dll:ADVAPI32.dll (75440000), size: 655360 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\ADVAPI32.dll', fileVersion: 6.1.7601.17514
C:\Windows\syswow64\msvcrt.dll:msvcrt.dll (76EC0000), size: 704512 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\msvcrt.dll', fileVersion: 7.0.7601.17744
C:\Windows\SysWOW64\sechost.dll:sechost.dll (75680000), size: 102400 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\SysWOW64\sechost.dll', fileVersion: 6.1.7600.16385
C:\Windows\syswow64\RPCRT4.dll:RPCRT4.dll (75080000), size: 983040 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\RPCRT4.dll', fileVersion: 6.1.7601.17514
C:\Windows\syswow64\SspiCli.dll:SspiCli.dll (74F10000), size: 393216 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\SspiCli.dll', fileVersion: 6.1.7601.17725
C:\Windows\syswow64\CRYPTBASE.dll:CRYPTBASE.dll (74F00000), size: 49152 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\CRYPTBASE.dll', fileVersion: 6.1.7600.16385
C:\Windows\system32\MSVCR100D.dll:MSVCR100D.dll (57A30000), size: 1519616 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\system32\MSVCR100D.dll', fileVersion: 10.0.40219.325
C:\Windows\system32\VERSION.dll:VERSION.dll (73560000), size: 36864 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\system32\VERSION.dll', fileVersion: 6.1.7600.16385
C:\Program Files (x86)\Debugging Tools for Windows (x86)\dbghelp.dll:dbghelp.dll (578E0000), size: 1314816 (result: 0), SymType: 'PDB', PDB: 'C:\Program Files (x86)\Debugging Tools for Windows (x86)\dbghelp.dll', fileVersion: 6.12.2.633
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B1C27)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B1C27)
008B1C27 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B1063)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B1063)
008B1063 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B10C8)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B10C8)
008B10C8 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B10E8)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B10E8)
008B10E8 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B1108)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B1108)
008B1108 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B1128)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B1128)
008B1128 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B1148)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B1148)
008B1148 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B1168)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B1168)
008B1168 (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B43EF)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B43EF)
008B43EF (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetSymFromAddr64, GetLastError: 487 (Address: 008B421F)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 008B421F)
008B421F (StackWalker_VC10): (filename not available): (function-name not available)
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 74F8339A)
74F8339A (kernel32): (filename not available): BaseThreadInitThunk
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 77869EF2)
77869EF2 (ntdll): (filename not available): __RtlUserThreadStart
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 77869EC5)
77869EC5 (ntdll): (filename not available): _RtlUserThreadStart

처음에 위의 결과를 보았을 때... '에게 이게 뭐야?'라는 생각이 들더군요. ^^; 뭔가 로직이 불안정해서 안되는 것인가 라고 여기다가 문득 해당 StackWalker_VC10.exe 파일만 있을 뿐 StackWalker_VC10.PDB 파일이 없다는 것을 알게 되었습니다. 그래서, 프로젝트 속성 창에서 PDB 파일을 생성하도록 변경하고 다시 실행을 했더니... 오호~~~ 이제는 제법 그럴듯 하게 콜 스택이 나옵니다.

D:\StackWalker\Win32\Debug>StackWalker_VC10.exe
SymInit: Symbol-SearchPath: '.;D:\StackWalker\Win32\Debug;D:\StackWalker\Win32\Debug;C:\Windows;C:\Windows\system32;SRV*C:\websymbols*http://msdl.microsoft.com/download/symbols;', symOptions: 530, UserName: 'SeongTae Jeong'
OS-Version: 6.1.7601 (Service Pack 1) 0x100-0x1
D:\Settings\desktop\native_callstack\StackWalker\Win32\Debug\StackWalker_VC10.exe:StackWalker_VC10.exe (000C0000), size: 69632 (result: 0), SymType: 'PDB', PDB: 'D:\Settings\desktop\native_callstack\StackWalker\Win32\Debug\StackWalker_VC10.exe'
C:\Windows\SysWOW64\ntdll.dll:ntdll.dll (77830000), size: 1572864 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\SysWOW64\ntdll.dll', fileVersion: 6.1.7601.17725
C:\Windows\syswow64\kernel32.dll:kernel32.dll (74F70000), size: 1114112 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\kernel32.dll', fileVersion: 6.1.7601.17651
C:\Windows\syswow64\KERNELBASE.dll:KERNELBASE.dll (76C10000), size: 286720 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\KERNELBASE.dll', fileVersion: 6.1.7601.17651
C:\Windows\syswow64\ADVAPI32.dll:ADVAPI32.dll (75440000), size: 655360 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\ADVAPI32.dll', fileVersion: 6.1.7601.17514
C:\Windows\syswow64\msvcrt.dll:msvcrt.dll (76EC0000), size: 704512 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\msvcrt.dll', fileVersion: 7.0.7601.17744
C:\Windows\SysWOW64\sechost.dll:sechost.dll (75680000), size: 102400 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\SysWOW64\sechost.dll', fileVersion: 6.1.7600.16385
C:\Windows\syswow64\RPCRT4.dll:RPCRT4.dll (75080000), size: 983040 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\RPCRT4.dll', fileVersion: 6.1.7601.17514
C:\Windows\syswow64\SspiCli.dll:SspiCli.dll (74F10000), size: 393216 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\SspiCli.dll', fileVersion: 6.1.7601.17725
C:\Windows\syswow64\CRYPTBASE.dll:CRYPTBASE.dll (74F00000), size: 49152 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\syswow64\CRYPTBASE.dll', fileVersion: 6.1.7600.16385
C:\Windows\system32\MSVCR100D.dll:MSVCR100D.dll (578B0000), size: 1519616 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\system32\MSVCR100D.dll', fileVersion: 10.0.40219.325
C:\Windows\system32\VERSION.dll:VERSION.dll (73560000), size: 36864 (result: 0), SymType: 'PDB', PDB: 'C:\Windows\system32\VERSION.dll', fileVersion: 6.1.7600.16385
C:\Program Files (x86)\Debugging Tools for Windows (x86)\dbghelp.dll:dbghelp.dll (57A60000), size: 1314816 (result: 0), SymType: 'PDB', PDB: 'C:\Program Files (x86)\Debugging Tools for Windows (x86)\dbghelp.dll', fileVersion: 6.12.2.633
d:\settings\desktop\native_callstack\stackwalker\stackwalker.cpp (1017): StackWalker::ShowCallstack
d:\settings\desktop\native_callstack\stackwalker\main.cpp (43): Func5
d:\settings\desktop\native_callstack\stackwalker\main.cpp (47): Func4
d:\settings\desktop\native_callstack\stackwalker\main.cpp (51): Func3
d:\settings\desktop\native_callstack\stackwalker\main.cpp (55): Func2
d:\settings\desktop\native_callstack\stackwalker\main.cpp (59): Func1
d:\settings\desktop\native_callstack\stackwalker\main.cpp (64): StackWalkTest
d:\settings\desktop\native_callstack\stackwalker\main.cpp (220): wmain
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (552): __tmainCRTStartup
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (371): wmainCRTStartup
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 74F8339A)
74F8339A (kernel32): (filename not available): BaseThreadInitThunk
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 77869EF2)
77869EF2 (ntdll): (filename not available): __RtlUserThreadStart
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 77869EC5)
77869EC5 (ntdll): (filename not available): _RtlUserThreadStart

근데... 대부분의 경우에 콜스택만을 얻고 싶을 텐데, 부가적인 문자열 출력이 너무 많아서 심란해 보입니다. 어디... 한번 다듬어 볼까요? ^^

우선, 디버깅을 해보면 StackWalker.cpp 파일의 LoadModules 함수를 보면 Init 함수와 LoadModules 함수 실행 시에 부가적인 메시지가 나오는 것을 알 수 있습니다.

// First Init the whole stuff...
BOOL bRet = this->m_sw->Init(szSymPath);
if (szSymPath != NULL) free(szSymPath); szSymPath = NULL;
if (bRet == FALSE)
{
    this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0);
    SetLastError(ERROR_DLL_INIT_FAILED);
    return FALSE;
}

bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId);
if (bRet != FALSE)
    m_modulesLoaded = TRUE;
return bRet;

Init 출력 부분을 제거하기 위해 따라가다 보면, OnSymInit 함수까지 오게 되는데요. 아래의 함수에서 OnOutput 부분만 주석처리해 주시면 됩니다.

void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName)
{
  CHAR buffer[STACKWALK_MAX_NAMELEN];
  _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName);
  OnOutput(buffer);
  // Also display the OS-version
#if _MSC_VER <= 1200
  OSVERSIONINFOA ver;
  ZeroMemory(&ver, sizeof(OSVERSIONINFOA));
  ver.dwOSVersionInfoSize = sizeof(ver);
  if (GetVersionExA(&ver) != FALSE)
  {
    _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion);
    // OnOutput(buffer);
  }
#else
  OSVERSIONINFOEXA ver;
  ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA));
  ver.dwOSVersionInfoSize = sizeof(ver);
  if (GetVersionExA( (OSVERSIONINFOA*) &ver) != FALSE)
  {
    _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, ver.wProductType);
    // OnOutput(buffer);
  }
#endif
}

그다음, LoadModules 출력은 다음과 같이 OnLoadModule의 OnOutput 출력을 주석 처리하면 됩니다.

void StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion)
{
  CHAR buffer[STACKWALK_MAX_NAMELEN];
  if (fileVersion == 0)
    _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s'\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName);
  else
  {
    DWORD v4 = (DWORD) (fileVersion & 0xFFFF);
    DWORD v3 = (DWORD) ((fileVersion>>16) & 0xFFFF);
    DWORD v2 = (DWORD) ((fileVersion>>32) & 0xFFFF);
    DWORD v1 = (DWORD) ((fileVersion>>48) & 0xFFFF);
    _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s', fileVersion: %d.%d.%d.%d\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName, v1, v2, v3, v4);
  }
  // OnOutput(buffer);
}

이렇게 해서 실행하면 출력 결과는 다음과 같이 정제됩니다.

d:\settings\desktop\native_callstack\stackwalker\stackwalker.cpp (1017): StackWalker::ShowCallstack
d:\settings\desktop\native_callstack\stackwalker\main.cpp (55): Func5
d:\settings\desktop\native_callstack\stackwalker\main.cpp (59): Func4
d:\settings\desktop\native_callstack\stackwalker\main.cpp (63): Func3
d:\settings\desktop\native_callstack\stackwalker\main.cpp (67): Func2
d:\settings\desktop\native_callstack\stackwalker\main.cpp (71): Func1
d:\settings\desktop\native_callstack\stackwalker\main.cpp (80): StackWalkTest
d:\settings\desktop\native_callstack\stackwalker\main.cpp (236): wmain
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (552): __tmainCRTStartup
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (371): wmainCRTStartup
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 74F8339A)
74F8339A (kernel32): (filename not available): BaseThreadInitThunk
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 77869EF2)
77869EF2 (ntdll): (filename not available): __RtlUserThreadStart
ERROR: SymGetLineFromAddr64, GetLastError: 487 (Address: 77869EC5)
77869EC5 (ntdll): (filename not available): _RtlUserThreadStart

그래도 약간 마음에 안 드는 부분이 있는데, "ERROR" 라인은 굳이 없어도 될 것 같다는 생각이 듭니다. 이를 제거하기 위해서는 ShowCallstack 메서드에서 OnDbgHelpErr 라인을 주석 처리해 주시면 됩니다.

BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData)
{
      ... [생략]...
      // show line number info, NT5.0-method (SymGetLineFromAddr64())
      if (this->m_sw->pSGLFA != NULL )
      { // yes, we have SymGetLineFromAddr64()
        if (this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), &Line) != FALSE)
        {
          csEntry.lineNumber = Line.LineNumber;
          // TODO: Mache dies sicher...!
          strcpy_s(csEntry.lineFileName, Line.FileName);
        }
        else
        {
          // this->OnDbgHelpErr("SymGetLineFromAddr64", GetLastError(), s.AddrPC.Offset);
        }
      } // yes, we have SymGetLineFromAddr64()
      ... [생략]...
}

아래는 이렇게 해서 얻은 최종 결과입니다.

d:\settings\desktop\native_callstack\stackwalker\stackwalker.cpp (1017): StackWalker::ShowCallstack
d:\settings\desktop\native_callstack\stackwalker\main.cpp (55): Func5
d:\settings\desktop\native_callstack\stackwalker\main.cpp (59): Func4
d:\settings\desktop\native_callstack\stackwalker\main.cpp (63): Func3
d:\settings\desktop\native_callstack\stackwalker\main.cpp (67): Func2
d:\settings\desktop\native_callstack\stackwalker\main.cpp (71): Func1
d:\settings\desktop\native_callstack\stackwalker\main.cpp (80): StackWalkTest
d:\settings\desktop\native_callstack\stackwalker\main.cpp (236): wmain
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (552): __tmainCRTStartup
f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (371): wmainCRTStartup
74F8339A (kernel32): (filename not available): BaseThreadInitThunk
77869EF2 (ntdll): (filename not available): __RtlUserThreadStart
77869EC5 (ntdll): (filename not available): _RtlUserThreadStart

훨씬 낫군요. ^^

디버그 빌드로 했으니, 이번엔 릴리스 빌드로 해보면 결과가 어떨까요?

d:\settings\desktop\native_callstack\stackwalker\stackwalker.cpp (1017): StackWalker::ShowCallstack
d:\settings\desktop\native_callstack\stackwalker\main.cpp (226): wmainf:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c (278): __tmainCRTStartup
76AC33CA (kernel32): (filename not available): BaseThreadInitThunk
771F9ED2 (ntdll): (filename not available): RtlInitializeExceptionChain
771F9EA5 (ntdll): (filename not available): RtlInitializeExceptionChain

Func... 함수들이 모두 inline 되는 식의 최적화가 이뤄져서 다 없어지고 StackWalker::ShowCallstack만 보입니다. 와~~~ 잘 만들어졌군요. ^^

StackWalker 소스 코드가 마음에 드는 점이 하나 더 있다면, 자신의 스레드 뿐만 아니라 다른 스레드의 콜스택을 뜨는 것도 가능하다는 점입니다. 그래서, 다음과 같이 소스 코드를 변경해 주면,

void StackWalkTest()
{
    HANDLE targetThread = ::CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
    Sleep(2000);

    StackWalkerToConsole sw;
    sw.ShowCallstack(targetThread);
}

DWORD WINAPI ThreadFunc(LPVOID lpParameter)
{
    do
    {
        Sleep(1000);
    } while (true);
}

다음과 같은 Call 스택을 얻을 수 있습니다.

7784FD71 (ntdll): (filename not available): ZwDelayExecution
76C231BB (KERNELBASE): (filename not available): SleepEx
76C23A8B (KERNELBASE): (filename not available): Sleep
d:\settings\desktop\native_callstack\stackwalker\main.cpp (44): ThreadFunc
74F8339A (kernel32): (filename not available): BaseThreadInitThunk
77869EF2 (ntdll): (filename not available): __RtlUserThreadStart
77869EC5 (ntdll): (filename not available): _RtlUserThreadStart

이쯤에서, 가장 중요한 라이선스가 어떻게 되는지 궁금하실 텐데요. BSD LICENSE (http://www.opensource.org/licenses/bsd-license.php)이니 상용 제품에도 걱정없이 적용하실 수 있습니다.

(첨부된 파일은 StackWalker를 포함하고 있습니다.)





[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 7/5/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2014-06-18 01시22분
Walking the stack of the current thread
; http://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/

---------------------------------------------

Opening a Crash Dump File (Automating Crash Dump Analysis Part 1)
; https://learn.microsoft.com/en-us/archive/blogs/joshpoley/opening-a-crash-dump-file-automating-crash-dump-analysis-part-1

Getting the Stack from a .DMP File (Automating Crash Dump Analysis Part 2)
; https://learn.microsoft.com/en-us/archive/blogs/joshpoley/getting-the-stack-from-a-dmp-file-automating-crash-dump-analysis-part-2

Getting the Crash Details from a .DMP File (Automating Crash Dump Analysis Part 3)
; https://learn.microsoft.com/en-us/archive/blogs/joshpoley/getting-the-crash-details-from-a-dmp-file-automating-crash-dump-analysis-part-3

Showing the Disassembly from a .DMP File (Automating Crash Dump Analysis Part 4)
; https://learn.microsoft.com/en-us/archive/blogs/joshpoley/showing-the-disassembly-from-a-dmp-file-automating-crash-dump-analysis-part-4
정성태
2014-06-18 01시23분
Debugging Tips (4) - Call Stack 추적하기 (StackWalk)
; http://kuaaan.tistory.com/115
정성태

1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...
NoWriterDateCnt.TitleFile(s)
13273정성태2/28/20234344.NET Framework: 2099. C# - 관리 포인터로서의 ref 예약어 의미
13272정성태2/27/20234589오류 유형: 850. SSMS - mdf 파일을 Attach 시킬 때 Operating system error 5: "5(Access is denied.)" 에러
13271정성태2/25/20234578오류 유형: 849. Sql Server Configuration Manager가 시작 메뉴에 없는 경우
13270정성태2/24/20234078.NET Framework: 2098. dotnet build에 /p 옵션을 적용 시 유의점
13269정성태2/23/20234714스크립트: 46. 파이썬 - uvicorn의 콘솔 출력을 UDP로 전송
13268정성태2/22/20235218개발 환경 구성: 667. WSL 2 내부에서 열고 있는 UDP 서버를 호스트 측에서 접속하는 방법
13267정성태2/21/20235103.NET Framework: 2097. C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례파일 다운로드1
13266정성태2/20/20234750오류 유형: 848. .NET Core/5+ - Process terminated. Couldn't find a valid ICU package installed on the system
13265정성태2/18/20234629.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
13264정성태2/17/20236236스크립트: 45. 파이썬 - uvicorn 사용자 정의 Logger 작성
13263정성태2/16/20234388개발 환경 구성: 666. 최신 버전의 ilasm.exe/ildasm.exe 사용하는 방법
13262정성태2/15/20235471디버깅 기술: 191. dnSpy를 이용한 (소스 코드가 없는) 닷넷 응용 프로그램 디버깅 방법 [1]
13261정성태2/15/20234846Windows: 224. Visual Studio - 영문 폰트가 Fullwidth Latin Character로 바뀌는 문제
13260정성태2/14/20234589오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/20234748.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/20234574오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/20234667.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [1]파일 다운로드1
13256정성태2/10/20235413개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
13255정성태2/10/20234823오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/20234736Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/20235547Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
13252정성태2/9/20234337오류 유형: 844. ssh로 명령어 수행 시 멈춤 현상
13251정성태2/8/20234721스크립트: 44. 파이썬의 3가지 스레드 ID
13250정성태2/8/20236617오류 유형: 843. System.InvalidOperationException - Unable to configure HTTPS endpoint
13249정성태2/7/20235448오류 유형: 842. 리눅스 - You must wait longer to change your password
13248정성태2/7/20234375오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...