步骤:
1. 调用RtlCaptureContext函数(MSDN)获取上下文(context)。
2. 把寄存器地址填充到STACKFRAME结构中。
3. 循环调用StackWalk64函数(MSDN)回溯调用栈(call stack)。
4. 调用SymFromAddr函数(MSDN)获得符号(symbol),再调用SymGetLineFromAddr64函数(MSDN)获取源码地址与行号。
Sample:
void dumpStack(void) { const UINT max_name_length = 256; // Max length of symbols' name. CONTEXT context; // Store register addresses. STACKFRAME64 stackframe; // Call stack. HANDLE process, thread; // Handle to current process & thread. // Generally it can be subsitituted with 0xFFFFFFFF & 0xFFFFFFFE. PSYMBOL_INFO symbol; // Debugging symbol's information. IMAGEHLP_LINE64 source_info; // Source information (file name & line number) DWORD displacement; // Source line displacement. // Initialize PSYMBOL_INFO structure. // Allocate a properly-sized block. symbol = (PSYMBOL_INFO)malloc(sizeof(SYMBOL_INFO) + (max_name_length - 1) * sizeof(TCHAR)); memset(symbol, 0, sizeof(SYMBOL_INFO) + (max_name_length - 1) * sizeof(TCHAR)); symbol->SizeOfStruct = sizeof(SYMBOL_INFO); // SizeOfStruct *MUST BE* set to sizeof(SYMBOL_INFO). symbol->MaxNameLen = max_name_length; // Initialize IMAGEHLP_LINE64 structure. memset(&source_info, 0, sizeof(IMAGEHLP_LINE64)); source_info.SizeOfStruct = sizeof(IMAGEHLP_LINE64); // Initialize STACKFRAME64 structure. RtlCaptureContext(&context); // Get context. memset(&stackframe, 0, sizeof(STACKFRAME64)); stackframe.AddrPC.Offset = context.Eip; // Fill in register addresses (EIP, ESP, EBP). stackframe.AddrPC.Mode = AddrModeFlat; stackframe.AddrStack.Offset = context.Esp; stackframe.AddrStack.Mode = AddrModeFlat; stackframe.AddrFrame.Offset = context.Ebp; stackframe.AddrFrame.Mode = AddrModeFlat; process = GetCurrentProcess(); // Get current process & thread. thread = GetCurrentThread(); // Initialize dbghelp library. if(!SymInitialize(process, NULL, TRUE)) return ; _putts(__T("Call stack: \n\n")); // Enumerate call stack frame. while(StackWalk64(IMAGE_FILE_MACHINE_I386, process, thread, &stackframe, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { if(stackframe.AddrFrame.Offset == 0) // End reaches. break; if(SymFromAddr(process, stackframe.AddrPC.Offset, NULL, symbol)) // Get symbol. _tprintf(__T(" > %s\n"), symbol->Name); if(SymGetLineFromAddr64(process, stackframe.AddrPC.Offset, &displacement, &source_info)) { // Get source information. _tprintf(__T("\t[%s:%d] at addr 0x%08LX\n"), source_info.FileName, source_info.LineNumber, stackframe.AddrPC.Offset); } else { if(GetLastError() == 0x1E7) { // If err_code == 0x1e7, no symbol was found. _tprintf(__T("\tNo debug symbol loaded for this function.\n")); } } } SymCleanup(process); // Clean up and exit. free(symbol); }
Result:
Note:
1. 在程序头加入:
#include <windows.h> #include <dbghelp.h> #pragma comment (lib, "dbghelp.lib")
#define DBGHELP_TRANSLATE_TCHAR
2. 使用dbghelp库前先初始化:
if(!SymInitialize(hProc, NULL, TRUE)) return ;
SymCleanup(hProc);
3. PSYMBOL_INFO structure使用前须分配内存,并设置MaxNameLen。
SYMBOL_INFO仅定义了TCHAR name[1],SymFromAddr function可能会写到其他内存去了。
MSDN建议SYMBOL_INFO应分配sizeof(SYMBOL_INFO) + (MaxNameLen - 1) * sizeof(TCHAR)大小的内存。
4.与SymFromAddr不同,SymGetLineFromAddr64的pdwDisplacement参数不可省。参见MSDN。
Reference:
[Win32]一个调试器的实现(六)显示源代码:http://www.cnblogs.com/zplutor/archive/2011/03/27/1997198.html
Retrieving Symbol Information by Address:http://msdn.microsoft.com/en-us/library/windows/desktop/ms680578(v=vs.85).aspx
使用dbghelp获取调用堆栈--release下的调试方法学:http://www.cppblog.com/kevinlynx/archive/2008/03/28/45628.html
DbgHelp Reference:http://msdn.microsoft.com/en-us/library/windows/desktop/ms679292(v=VS.85).aspx