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

... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12211정성태4/27/202019280개발 환경 구성: 486. WSL에서 Makefile로 공개된 리눅스 환경의 C/C++ 소스 코드 빌드
12210정성태4/20/202020741.NET Framework: 903. .NET Framework의 Strong-named 어셈블리 바인딩 (1) - app.config을 이용한 바인딩 리디렉션 [1]파일 다운로드1
12209정성태4/13/202017436오류 유형: 614. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우 (2)
12208정성태4/12/202016006Linux: 29. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우
12207정성태4/2/202015865스크립트: 19. Windows PowerShell의 NonInteractive 모드
12206정성태4/2/202018456오류 유형: 613. 파일 잠금이 바로 안 풀린다면? - The process cannot access the file '...' because it is being used by another process.
12205정성태4/2/202015121스크립트: 18. Powershell에서는 cmd.exe의 명령어를 지원하진 않습니다.
12204정성태4/1/202015141스크립트: 17. Powershell 명령어에 ';' (semi-colon) 문자가 포함된 경우
12203정성태3/18/202017971오류 유형: 612. warning: 'C:\ProgramData/Git/config' has a dubious owner: '...'.
12202정성태3/18/202021218개발 환경 구성: 486. .NET Framework 프로젝트를 위한 GitLab CI/CD Runner 구성
12201정성태3/18/202018461오류 유형: 611. git-credential-manager.exe: Using credentials for username "Personal Access Token". [1]
12200정성태3/18/202018557VS.NET IDE: 145. NuGet + Github 라이브러리 디버깅 관련 옵션 3가지 - "Enable Just My Code" / "Enable Source Link support" / "Suppress JIT optimization on module load (Managed only)"
12199정성태3/17/202016183오류 유형: 610. C# - CodeDomProvider 사용 시 Unhandled Exception: System.IO.DirectoryNotFoundException: Could not find a part of the path '...\f2_6uod0.tmp'.
12198정성태3/17/202019546오류 유형: 609. SQL 서버 접속 시 "Cannot open user default database. Login failed."
12197정성태3/17/202018855VS.NET IDE: 144. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 - 두 번째 이야기 [1]
12196정성태3/17/202015983오류 유형: 608. The ServicedComponent being invoked is not correctly configured (Use regsvcs to re-register).
12195정성태3/16/202018290.NET Framework: 902. C# - 프로세스의 모든 핸들을 열람 - 세 번째 이야기
12194정성태3/16/202021014오류 유형: 607. PostgreSQL - Npgsql.NpgsqlException: sorry, too many clients already
12193정성태3/16/202017976개발 환경 구성: 485. docker - SAP Adaptive Server Enterprise 컨테이너 실행 [1]
12192정성태3/14/202020022개발 환경 구성: 484. docker - Sybase Anywhere 16 컨테이너 실행
12191정성태3/14/202021077개발 환경 구성: 483. docker - OracleXE 컨테이너 실행 [1]
12190정성태3/14/202015669오류 유형: 606. Docker Desktop 업그레이드 시 "The process cannot access the file 'C:\Program Files\Docker\Docker\resources\dockerd.exe' because it is being used by another process."
12189정성태3/13/202021261개발 환경 구성: 482. Facebook OAuth 처리 시 상태 정보 전달 방법과 "유효한 OAuth 리디렉션 URI" 설정 규칙
12188정성태3/13/202026050Windows: 169. 부팅 시점에 실행되는 chkdsk 결과를 확인하는 방법
12187정성태3/12/202015643오류 유형: 605. NtpClient was unable to set a manual peer to use as a time source because of duplicate error on '...'.
12186정성태3/12/202017423오류 유형: 604. The SysVol Permissions for one or more GPOs on this domain controller and not in sync with the permissions for the GPOs on the Baseline domain controller.
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...