You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

x64平台下DbgHelp获取值传递函数参数地址错误求助

解决DbgHelp中值传递参数地址计算错误的问题

我之前也踩过这个坑,核心问题是你误用了DumpStackTrace函数自身的上下文寄存器值,而非当前遍历到的栈帧对应的rbp/rsp,再加上对x64调用约定下栈帧结构的理解偏差,才导致参数地址计算出错。下面一步步拆解问题并给出解决方案:

问题根源分析

  1. 上下文寄存器不匹配:你在DumpStackTrace开头用RtlCaptureContext获取的是DumpStackTrace执行时的rbp/rsp,但当StackWalk64遍历到foo的栈帧时,必须使用该栈帧自身的基址指针(rbp)和栈指针(rsp),而非DumpStackTrace的寄存器值。
  2. 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. 修正后的关键代码片段

首先修改DumpStackTraceBaseAddresses的赋值部分:

// 替换原来的 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

火山引擎 最新活动