x64平台下DbgHelp获取值传递函数参数地址错误求助
解决DbgHelp中值传递参数地址计算错误的问题
我之前也踩过这个坑,核心问题是你误用了DumpStackTrace函数自身的上下文寄存器值,而非当前遍历到的栈帧对应的rbp/rsp,再加上对x64调用约定下栈帧结构的理解偏差,才导致参数地址计算出错。下面一步步拆解问题并给出解决方案:
问题根源分析
- 上下文寄存器不匹配:你在
DumpStackTrace开头用RtlCaptureContext获取的是DumpStackTrace执行时的rbp/rsp,但当StackWalk64遍历到foo的栈帧时,必须使用该栈帧自身的基址指针(rbp)和栈指针(rsp),而非DumpStackTrace的寄存器值。 - x64调用约定的参数位置:在Microsoft x64调用约定中,对于大小超过8字节的结构体(比如你的
Structure是12字节),调用者会在自己的栈上分配空间并复制参数,函数内部通过rbp的正偏移访问这些参数(函数入口执行push rbp; mov rbp, rsp后,rbp指向栈帧基址,参数位于rbp上方,即正偏移方向)。
修正步骤
1. 获取当前栈帧的rbp/rsp
StackWalk64返回的STACKFRAME64结构体中,AddrFrame.Offset就是当前栈帧的rbp值,AddrStack.Offset是当前栈帧的rsp值。你需要用这两个值替换原来的c.Rbp/c.Rsp。
2. 调整符号地址计算逻辑
枚举每个栈帧的符号时,必须使用该栈帧对应的基址寄存器计算地址,而非全局上下文值。
3. 修正后的关键代码片段
首先修改DumpStackTrace中BaseAddresses的赋值部分:
// 替换原来的 addresses.Rbp = c.Rbp; addresses.Rsp = c.Rsp; addresses.Rbp = frame.AddrFrame.Offset; addresses.Rsp = frame.AddrStack.Offset;
同时,x64平台下建议初始化STACKFRAME64时设置地址模式为AddrModeFlat,避免解析错误:
STACKFRAME64 frame; memset(&frame, 0, sizeof(STACKFRAME64)); #if defined(_M_AMD64) frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat; #endif
完整修正后的DumpStackTrace函数
DWORD DumpStackTrace() { HANDLE mProcess = GetCurrentProcess(); HANDLE mThread = GetCurrentThread(); if (!SymInitialize(mProcess, NULL, TRUE)) // load symbols, invasive return 0; CONTEXT c; memset(&c, 0, sizeof(CONTEXT)); c.ContextFlags = USED_CONTEXT_FLAGS; RtlCaptureContext(&c); // SYMBOL_INFO & buffer storage char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; STACKFRAME64 frame; memset(&frame, 0, sizeof(STACKFRAME64)); #if defined(_M_AMD64) frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat; #endif DWORD64 displacement_from_symbol = 0; printf("Print stack from bottom up:\n"); int framesToSkip = 1; // skip reporting this frame do { // Get next stack frame if (!StackWalk64(ImageFileMachine, mProcess, mThread, &frame, &c, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) { break; } // Lookup symbol name using the address pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); pSymbol->MaxNameLen = MAX_SYM_NAME; if (!SymFromAddr(mProcess, (ULONG64)frame.AddrPC.Offset, &displacement_from_symbol, pSymbol)) return false; if (framesToSkip > 0) { framesToSkip--; continue; } printf("Frame: %s\n", pSymbol->Name); // Setup the context to get to the parameters IMAGEHLP_STACK_FRAME imSFrame = { 0 }; imSFrame.InstructionOffset = frame.AddrPC.Offset; if (!SymSetContext(mProcess, &imSFrame, NULL)) return false; BaseAddresses addresses; // 使用当前栈帧的rbp和rsp,而非DumpStackTrace自身的寄存器值 addresses.Rbp = frame.AddrFrame.Offset; addresses.Rsp = frame.AddrStack.Offset; if (!SymEnumSymbols(mProcess, 0, 0, EnumSymbolsCallback, &addresses)) { return false; } if (strcmp(pSymbol->Name, "main") == 0) break; } while (frame.AddrReturn.Offset != 0); SymCleanup(mProcess); return 0; }
补充说明
SYMFLAG_REGREL标志表示符号地址是相对于某个寄存器的偏移:函数参数通常对应rbp的正偏移,局部变量对应rbp的负偏移。- 确保PDB文件与二进制完全匹配,符号加载正确是所有地址计算的前提。
测试修正后的代码,你会发现objByValue的计算地址会和foo函数中打印的&objByValue完全一致。
内容的提问来源于stack exchange,提问作者Steven




