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

... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
12036정성태10/14/201925492.NET Framework: 866. C# - 고성능이 필요한 환경에서 GC가 발생하지 않는 네이티브 힙 사용파일 다운로드1
12035정성태10/13/201919598개발 환경 구성: 461. C# 8.0의 #nulable 관련 특성을 .NET Framework 프로젝트에서 사용하는 방법 [2]파일 다운로드1
12034정성태10/12/201918919개발 환경 구성: 460. .NET Core 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 [1]
12033정성태10/11/201923133개발 환경 구성: 459. .NET Framework 프로젝트에서 C# 8.0/9.0 컴파일러를 사용하는 방법
12032정성태10/8/201919253.NET Framework: 865. .NET Core 2.2/3.0 웹 프로젝트를 IIS에서 호스팅(Inproc, out-of-proc)하는 방법 - AspNetCoreModuleV2 소개
12031정성태10/7/201916514오류 유형: 569. Azure Site Extension 업그레이드 시 "System.IO.IOException: There is not enough space on the disk" 예외 발생
12030정성태10/5/201923323.NET Framework: 864. .NET Conf 2019 Korea - "닷넷 17년의 변화 정리 및 닷넷 코어 3.0" 발표 자료 [1]파일 다운로드1
12029정성태9/27/201924142제니퍼 .NET: 29. Jennifersoft provides a trial promotion on its APM solution such as JENNIFER, PHP, and .NET in 2019 and shares the examples of their application.
12028정성태9/26/201919106.NET Framework: 863. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상을 해결하기 위한 시도파일 다운로드1
12027정성태9/26/201914817오류 유형: 568. Consider app.config remapping of assembly "..." from Version "..." [...] to Version "..." [...] to solve conflict and get rid of warning.
12026정성태9/26/201920235.NET Framework: 862. C# - Active Directory의 LDAP 경로 및 정보 조회
12025정성태9/25/201918548제니퍼 .NET: 28. APM 솔루션 제니퍼, PHP, .NET 무료 사용 프로모션 2019 및 적용 사례 (8) [1]
12024정성태9/20/201920471.NET Framework: 861. HttpClient와 HttpClientHandler의 관계 [2]
12023정성태9/18/201920936.NET Framework: 860. ServicePointManager.DefaultConnectionLimit와 HttpClient의 관계파일 다운로드1
12022정성태9/12/201924879개발 환경 구성: 458. C# 8.0 (Preview) 신규 문법을 위한 개발 환경 구성 [3]
12021정성태9/12/201940679도서: 시작하세요! C# 8.0 프로그래밍 [4]
12020정성태9/11/201923852VC++: 134. SYSTEMTIME 값 기준으로 특정 시간이 지났는지를 판단하는 함수
12019정성태9/11/201917402Linux: 23. .NET Core + 리눅스 환경에서 Environment.CurrentDirectory 접근 시 주의 사항
12018정성태9/11/201916205오류 유형: 567. IIS - Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive. (D:\lowSite4\web.config line 11)
12017정성태9/11/201920028오류 유형: 566. 비주얼 스튜디오 - Failed to register URL "http://localhost:6879/" for site "..." application "/". Error description: Access is denied. (0x80070005)
12016정성태9/5/201920025오류 유형: 565. git fetch - warning: 'C:\ProgramData/Git/config' has a dubious owner: '(unknown)'.
12015정성태9/3/201925440개발 환경 구성: 457. 윈도우 응용 프로그램의 Socket 연결 시 time-out 시간 제어
12014정성태9/3/201919198개발 환경 구성: 456. 명령행에서 AWS, Azure 등의 원격 저장소에 파일 관리하는 방법 - cyberduck/duck 소개
12013정성태8/28/201922104개발 환경 구성: 455. 윈도우에서 (테스트) 인증서 파일 만드는 방법 [3]
12012정성태8/28/201926665.NET Framework: 859. C# - HttpListener를 이용한 HTTPS 통신 방법
12011정성태8/27/201926236사물인터넷: 57. C# - Rapsberry Pi Zero W와 PC 간 Bluetooth 통신 예제 코드파일 다운로드1
... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...