1. 程式人生 > >使用dbghelp獲取呼叫堆疊--release下的除錯方法(轉)

使用dbghelp獲取呼叫堆疊--release下的除錯方法(轉)

當軟體作為release模式被髮布給使用者時,當程式崩潰時我們很難去查詢原因。常見的手法是輸出LOG檔案,根據LOG檔案分析
程式崩潰時的執行情況。我們可以通過SEH來捕獲程式錯誤,然後輸出一些有用的資訊作為我們分析錯誤的資料。一般我們需要
輸出的資訊包括:系統資訊、CPU暫存器資訊、堆疊資訊、呼叫堆疊等。而呼叫堆疊則是最有用的部分,它可以直接幫我們定位
到程式崩潰時所處的位置(在何處崩潰)。(codeproject上關於這個專題的常見開場白 = =#)

要獲取call stack(所謂的呼叫堆疊),就需要檢視(unwind)stack的內容。We could conceivably attempt to unwind the
stack ourselves using inline assembly. But stack frames can be organized in different ways, depending on compiler
optimizations and calling conventions, so it could become complicated to do it that way.(摘自vld文件)要獲取棧的
內容,我們可以自己使用內聯彙編獲取,但是考慮到相容性,內聯彙編並不是一個好的解決方案。我們可以使用微軟的dbghelp
中的StackWalk64來獲取棧的內容。

StackWalk64宣告如下:
BOOL StackWalk64(
DWORD MachineType,
HANDLE hProcess,
HANDLE hThread,
LPSTACKFRAME64 StackFrame,
PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);

具體每個引數的含義可以參見MSDN。這裡說下ContextRecord引數,該引數指定了CPU各個暫存器的內容。StackFrame指定了stack
frame的內容。stack frame是什麼,我也不知道。(= =) StackWalk64函式需要使用者指定當前frame的地址,以及當前程式的指令
地址。這兩個資訊都被填充進ContextRecord,然後傳進StackWalk64函式。

那麼如何獲取當前的stack frame地址和當前程式指令地址呢?如前所說,你可以使用內聯彙編。(對於程式指令地址,因為要獲取
EIP暫存器的內容,而該暫存器不能被軟體訪問)也可以使用GetThreadContext一次性獲取當前執行緒當前執行情況下的CPU各個暫存器
內容。補充下,當前frame地址被放在EBP暫存器裡,當前程式指令地址放在EIP暫存器裡。但是,如同MSDN對GetThreadContext函式
的說明一樣,該函式可能獲取到錯誤的暫存器內容(You cannot get a valid context for a running thread)。

另一種獲取Context(包含EBP and EIP)的方法就是使用SEH(結構化異常處理),在__except中使用GetExceptionInformation獲取。

GetExceptionInformation 傳回一個LPEXCEPTION_POINTERS指標,該指標指向一個EXCEPTION_POINTERS結構,該結構裡包含一個
Context的指標,即達到目標,可以使用StackWalk函式。

補充一下,你可以直接使用StackWalk函式,StackWalk被define為StackWalk64(windows平臺相關)。

unwind棧後,可以進一步獲取一個stack frame的內容,例如函式名。這裡涉及到SymFromAddr函式,該函式可以根據一個地址返回
符號名(函式名)。還有一個有意思的函式:SymGetLineFromAddr,可以獲取函式對應的原始碼的檔名和行號。

當然,這一切都依賴於VC產生的程式資料庫檔案(pdb),以及提供以上API函式的dbghelp.dll。

參考一段簡單的程式碼:
#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>

#pragma comment( lib, "dbghelp.lib" )

void dump_callstack( CONTEXT *context )
{
STACKFRAME sf;
memset( &sf, 0, sizeof( STACKFRAME ) );

sf.AddrPC.Offset = context->Eip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrStack.Offset = context->Esp;
sf.AddrStack.Mode = AddrModeFlat;
sf.AddrFrame.Offset = context->Ebp;
sf.AddrFrame.Mode = AddrModeFlat;

DWORD machineType = IMAGE_FILE_MACHINE_I386;

HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();

for( ; ; )
{
if( !StackWalk(machineType, hProcess, hThread, &sf, context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) )
{
   break;
}

if( sf.AddrFrame.Offset == 0 )
{
   break;
}
BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ];
PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer;

pSymbol->SizeOfStruct = sizeof( symbolBuffer );
pSymbol->MaxNameLen = 1024;

DWORD64 symDisplacement = 0;
if( SymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) )
{
   printf( "Function : %s\n", pSymbol->Name );
}
else
{
   printf( "SymFromAdd failed!\n" );
}

IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) };
DWORD dwLineDisplacement;

if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
{
   printf( "[Source File : %s]\n", lineInfo.FileName );
   printf( "[Source Line : %u]\n", lineInfo.LineNumber );
}
else
{
   printf( "SymGetLineFromAddr failed!\n" );
}
}
}

DWORD excep_filter( LPEXCEPTION_POINTERS lpEP )
{
/**//// init dbghelp.dll
if( SymInitialize( GetCurrentProcess(), NULL, TRUE ) )
{
printf( "Init dbghelp ok.\n" );
}

dump_callstack( lpEP->ContextRecord );

if( SymCleanup( GetCurrentProcess() ) )
{
printf( "Cleanup dbghelp ok.\n" );
}

return EXCEPTION_EXECUTE_HANDLER;
}

void func1( int i )
{
int *p = 0;
*p = i;
}

void func2( int i )
{
func1( i - 1 );
}

void func3( int i )
{
func2( i - 1 );
}

void test( int i )
{
func3( i - 1 );
}

int main()
{
__try
{
test( 10 );
}
__except( excep_filter( GetExceptionInformation() ) )
{
printf( "Some exception occures.\n" );
}

return 0;
}

以上程式碼在release模式下需要關掉優化,否則呼叫堆疊顯示不正確(某些函式被去掉了?),同時需要pdb檔案。
參考資料:
http://www.codeproject.com/KB/threads/StackWalker.aspx
http://www.cnblogs.com/protalfox/articles/84723.html
http://www.codeproject.com/KB/debug/XCrashReportPt1.aspx
http://www.codeproject.com/KB/applications/visualleakdetector.aspx