Microsoft MVP성태의 닷넷 이야기
VC++: 60. C/C++ Native 스레드 콜 스택 덤프를 얻는 공개 라이브러리 [링크 복사], [링크+제목 복사],
조회: 29277
글쓴 사람
정성태 (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
정성태

... [121]  122  123  124  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10899정성태2/17/201623342개발 환경 구성: 282. kernel32.dll, kernel32legacy.dll, api-ms-win-core-sysinfo-l1-2-0.dll [1]
10898정성태2/17/201621798.NET Framework: 547. PerformanceCounter의 InstanceName 지정 시 주의 사항파일 다운로드1
10897정성태2/17/201621130디버깅 기술: 76. windbg 분석 사례 - 닷넷 프로파일러의 GC 콜백 부하
10896정성태2/17/201622296오류 유형: 320. FATAL: 28000: no pg_hba.conf entry for host "fe80::1970:8120:695:a41e%12"
10895정성태2/17/201621096.NET Framework: 546. System.AppDomain으로부터 .NET Profiler의 AppDomainID 구하는 방법 [1]
10894정성태2/17/201621828오류 유형: 319. Visual Studio에서 찾기는 성공하지만 해당 소스 코드 정보가 보이지 않는 경우
10893정성태2/16/201620474.NET Framework: 545. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까? - 두 번째 이야기
10892정성태2/16/201621091오류 유형: 318. 탐색기에서 폴더 생성/삭제 시 몇 초 동안 멈추는 현상
10891정성태2/16/201624112VC++: 95. 내 CPU가 MPX/SGX를 지원할까요? [1]
10890정성태2/15/201623977.NET Framework: 544. C# 5의 Caller Info를 .NET 4.5 미만의 응용 프로그램에 적용하는 방법 [5]
10889정성태2/14/201620267.NET Framework: 543. C++의 inline asm 사용을 .NET으로 포팅하는 방법 - 두 번째 이야기파일 다운로드1
10888정성태2/14/201618625.NET Framework: 542. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까?
10887정성태2/3/201619287VC++: 94. MPX(Memory Protection Extensions) 테스트파일 다운로드1
10886정성태2/3/201620522개발 환경 구성: 281. Intel MPX Runtime Driver 수동 설치
10885정성태2/2/201620209오류 유형: 317. Sybase.Data.AseClient.AseException: The command has timed out.
10884정성태1/11/201621433개발 환경 구성: 280. 닷넷에서 SAP Adaptive Server Enterprise 데이터베이스 사용파일 다운로드1
10882정성태1/6/201620728Windows: 113. 윈도우의 2179, 26143, 47001 TCP 포트 사용 [1]
10881정성태1/3/201622164오류 유형: 316. 윈도우 10 - 바탕/돋음 체가 사라져 한글이 깨지는 현상 [2]
10880정성태12/16/201519833오류 유형: 315. 닷넷 프로파일러의 오류 코드 정보
10879정성태12/16/201521768오류 유형: 314. Error : DEP0700 : Registration of the app failed. error 0x80070005
10878정성태12/9/201524831디버깅 기술: 75. UWP(유니버설 윈도우 플랫폼) 앱에서 global::System.Diagnostics.Debugger.Break 예외 발생 시 대응 방법
10877정성태12/9/201529269VC++: 93. std::thread 사용 시 R6010 오류 [2]
10876정성태11/26/201525323.NET Framework: 541. SignedXml을 이용한 ds:Signature만드는 방법 [3]파일 다운로드1
10875정성태11/26/201530312개발 환경 구성: 279. signtool.exe의 다중 서명 기능 [2]
10874정성태11/26/201526314개발 환경 구성: 278. 인증서와 인증서를 이용한 코드 사인의 해시 구분
10873정성태11/25/201525433.NET Framework: 540. C# - 부동 소수 계산 왜 이렇게 나오죠? (2) [3]파일 다운로드1
... [121]  122  123  124  125  126  127  128  129  130  131  132  133  134  135  ...