DbgHelp教程2——栈回溯

调试的时候经常用到栈回溯来定位问题。获得栈回溯并不难,直接通过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;
}