调试的时候经常用到栈回溯来定位问题。获得栈回溯并不难,直接通过CaptureStackBackTrace这个API就可以获得。另外可以可以通过DbgHelp库里面的StackWalk64函数获得。
用CaptureStackBackTrace获取调用栈很简单:
DWORD depth = 0;
void* backtrace[kMaxBacktraceDepth];
depth = CaptureStackBackTrace(0, kMaxBacktraceDepth, backtrace, NULL);
StackWalk64的方法稍微复杂一点,首先要获得CONTEXT。获取CONTEXT有两种办法,一是通过内联汇编获取:
CONTEXT context = {0};
{
__asm call x;
__asm x : pop eax;
__asm mov context.Eip, eax;
__asm mov context.Ebp, ebp;
__asm mov context.Esp, esp;
}
另外一种是调用RtlCaptureContext获取:
CONTEXT context = {0};
RtlCaptureContext(&context);
获取到CONTEXT之后就用StackWalk64遍历调用栈:
STACKFRAME64 stack = {0};
stack.AddrPC.Offset = context.Eip;
stack.AddrPC.Mode = AddrModeFlat;
stack.AddrStack.Offset = context.Esp;
stack.AddrStack.Mode = AddrModeFlat;
stack.AddrFrame.Offset = context.Ebp;
stack.AddrFrame.Mode = AddrModeFlat;
while (StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(),
GetCurrentThread(), &stack, &context, NULL,
SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
printf("%llu\n", stack.AddrPC.Offset);
}
我们获得的调用栈都只是一些栈帧地址,如果要知道明确的函数名,还需要把地址翻译成符号名,封了一个PrintCallStackBackTrace,就是把栈帧地址翻译成可读的符号地址:
bool DbgHelpWrapper::PrintCallStackBackTrace(
const std::vector<DWORD_PTR> &backtrace) {
for (DWORD index = 0; index < backtrace.size(); index++) {
DWORD_PTR frame = backtrace[index];
const int kMaxNameLength = 256;
ULONG64 buffer[(sizeof(SYMBOL_INFO) + kMaxNameLength * sizeof(wchar_t) +
sizeof(ULONG64) - 1) /
sizeof(ULONG64)];
memset(buffer, 0, sizeof(buffer));
DWORD64 sym_displacement = 0;
PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = kMaxNameLength - 1;
BOOL has_symbol =
SymFromAddr(GetCurrentProcess(), frame, &sym_displacement, symbol);
DWORD line_displacement = 0;
IMAGEHLP_LINE64 line = {};
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame,
&line_displacement, &line);
cout << frame << "\t";
if (has_symbol) {
cout << symbol->Name << "\t";
}
if (has_line) {
cout << line.FileName << ":" << line.LineNumber;
}
cout << endl;
}
return true;
}