为了跟踪错误,经常需要获取程序中各线程的函数调用栈信息,通过函数 StackWalk 来获取。
BOOL WINAPI StackWalk64(
__in DWORD MachineType,
__in HANDLE hProcess,
__in HANDLE hThread,
__inout LPSTACKFRAME64 StackFrame,
__inout PVOID ContextRecord,
__in_opt PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
__in_opt PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
__in_opt PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
__in_opt PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
以下是示例代码:
void get_callstack() { HANDLE hProcess; // 目标进程(需设置) HANDLE hThread; // 目标线程(需设置) CONTEXT* context; // 目标线程上下文(需设置) DWORD machineType; // 机器Cpu类型 STACKFRAME sf; // 信息 memset( &sf, 0, sizeof( STACKFRAME ) ); #ifdef _M_IX86 machineType = IMAGE_FILE_MACHINE_I386; 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; #elif _M_X64 machineType = IMAGE_FILE_MACHINE_AMD64; sf.AddrPC.Offset = context->Rip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = context->Rsp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = context->Rsp; sf.AddrFrame.Mode = AddrModeFlat; #elif _M_IA64 machineType = IMAGE_FILE_MACHINE_IA64; sf.AddrPC.Offset = context->StIIP; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = context->IntSp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = context->IntSp; sf.AddrFrame.Mode = AddrModeFlat; sf.AddrBStore.Offset = context->RsBSP; sf.AddrBStore.Mode = AddrModeFlat; #else #error "platform not supported!" #endif unsigned deep = 0; for( ; ; ) { if( !StackWalk(machineType, hProcess, hThread, &sf, context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) ) break; if( sf.AddrFrame.Offset == 0 ) break; // method name BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ]; PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer; memset(pSymbol, 0, sizeof( SYMBOL_INFO ) + 1024); pSymbol->SizeOfStruct = sizeof( symbolBuffer ); pSymbol->MaxNameLen = 1024; if( SymGetSymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) ) { printf( "[Function : %s]\n", pSymbol->Name ); // 函数名 } // file and line IMAGEHLP_LINE lineInfo; memset(&lineInfo, 0 , sizeof(IMAGEHLP_LINE)); lineInfo.SizeOfStruct = 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 ); // 代码所在行 } // module IMAGEHLP_MODULE moduleInfo; memset(&moduleInfo, 0, sizeof(IMAGEHLP_MODULE)); moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE); if (SymGetModuleInfo( hProcess, sf.AddrPC.Offset, &moduleInfo)) { printf( "[Module : %s]\n", moduleInfo.ModuleName ); // 模块 } // 防止死循环 if (++deep > 100) break; } }
注意事项:使用StackWalk获取线程函数调用栈,需要获取线程上下文,两种情况
(1)线程抛出异常:这种情况下,异常结构体信息中就包含上下文信息,且线程处于已挂起状态,可通过 GetThreadContext 获取
(2)线程中运行中:此时使用 GetThreadContext 获取上下文必须先将线程挂起,使用 SuspendThread ,当结束后再使用 ResumeThread 恢复线程。这是一个比较危险的操作,若线程挂起后,代码异常导致未调用 ResumeThread ,将导致该线程一直处于挂起状态。可以使用临时安全类的析构来解决,如下:
class SafeSuspend { public: SafeSuspend(HANDLE hThread) : m_hThread(hThread) { if (m_hThread != INVALID_HANDLE_VALUE) ::SuspendThread(m_hThread); } ~SafeSuspend() { if (m_hThread != INVALID_HANDLE_VALUE) ::ResumeThread(m_hThread); } private: HANDLE m_hThread; };