Microsoft MVP성태의 닷넷 이야기
VC++: 60. C/C++ Native 스레드 콜 스택 덤프를 얻는 공개 라이브러리 [링크 복사], [링크+제목 복사],
조회: 22349
글쓴 사람
정성태 (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)
13495정성태12/20/20232625Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232719Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232905닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232563개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20232360Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232495개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232272개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20232234오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20232521개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20232338개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
13485정성태12/15/20232204오류 유형: 883. dotnet build/restore - error : Root element is missing
13484정성태12/14/20232331개발 환경 구성: 694. Windows 디렉터리 경로를 WSL의 /mnt 포맷으로 구하는 방법
13483정성태12/14/20232478닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법파일 다운로드1
13482정성태12/13/20233155닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20232472개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20232883개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20232506개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20232744닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20232505닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
13475정성태12/7/20232580닷넷: 2180. .NET 8 - 함수 포인터에 대한 Reflection 정보 조회파일 다운로드1
13474정성태12/6/20232398개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
13473정성태12/5/20232686닷넷: 2179. C# - 값 형식(Blittable)을 메모리 복사를 이용해 바이트 배열로 직렬화/역직렬화파일 다운로드1
13472정성태12/4/20232354C/C++: 164. Visual C++ - InterlockedCompareExchange128 사용 방법
13471정성태12/4/20232554Copilot - To enable GitHub Copilot, authorize this extension using GitHub's device flow
13470정성태12/2/20232778닷넷: 2178. C# - .NET 8부터 COM Interop에 대한 자동 소스 코드 생성 도입파일 다운로드1
13469정성태12/1/20232624닷넷: 2177. C# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법파일 다운로드1
1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...