DbgHelp:函数参数的错误地址,它是x64上的值传递符号

问题描述 投票:0回答:1

我正在使用Windows DbgHelp库来转储我的C ++应用程序的callstack。 PDB位于正确的位置,我成功报告了堆栈帧。我有一个涉及的类型遍历系统打印出所有本地人,这主要是工作。但是,在某些情况下,我收到错误的地址并导致访问冲突。一种情况是函数按值传递一个对象:

struct Object { float x,y,z; }

void foo( Object objectByValue)
{
  // dump callstack
}

在这种情况下,为objectByValue计算的地址是错误的。它靠近堆栈上的正确位置,但不完全正确。我在找到正确地址的信息时遇到了一些困难。我正在做以下事情:

  • 使用SymSetContext(...)设置正确的上下文
  • 使用回调调用SymEnumSymbols
  • 在回调内部,检查是否设置了SYMFLAG_REGREL
  • 分配地址= SymInfo->地址+ context.rbp或context.rsp,具体取决于SymInfo.Register值(CV_AMD64_RBP或CV_AMD64_RSP仅存在)
  • 然后我使用该地址来访问变量。

对于堆栈上的变量,此地址是正确的,因为对于大多数其他情况。但是,它不属于某些情况,包括这种情况。

我在下面输入了一个工作示例,其中包含以下输出:

main-> Address of on stack: 000000000020B0D0 = { 1.000000, 2.000000,
3.000000 } 
foo-> Address of parameters: 000000000020D6F8 = { 1.000000, 2.000000, 3.000000 }
Print stack from bottom up:
Frame: foo Variable: objByValue offset=0xe0 address=0x20b090 size=8204 
Frame: main Variable: objOnStack offset=0x10 address=0x20b0d0 size=8204     

您可以在示例中看到从堆栈上的变量计算的地址是正确的,但是对于pass by value,它是错误的。

有没有人对如何正确计算这个值有任何见解?

#include "stdafx.h"
#include <windows.h>
#include <stdint.h>

#pragma comment(lib, "dbghelp.lib")

#pragma pack( push, before_imagehlp, 8 )
#include <imagehlp.h>
#pragma pack( pop, before_imagehlp )

// Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL')
#define USED_CONTEXT_FLAGS CONTEXT_FULL

#if defined(_M_AMD64)
const int ImageFileMachine = IMAGE_FILE_MACHINE_AMD64;
#else
const int ImageFileMachine = IMAGE_FILE_MACHINE_I386;
#endif
struct BaseAddresses
{
    uint64_t Rsp;
    uint64_t Rbp;
};

const int C_X64_REGISTER_RBP = 334; // Frame Base Pointer register
const int C_X64_REGISTER_RSP = 335; // Stack Pointer register (common in release builds with frame pointer removal)

BOOL EnumSymbolsCallback(PSYMBOL_INFO pSymInfo, ULONG SymbolSize, PVOID UserContext)
{
    BaseAddresses * pBaseAddresses = (BaseAddresses*)UserContext;
    ULONG64 base = 0;

    if ((pSymInfo->Flags & SYMFLAG_REGREL) != 0)
    {
        switch (pSymInfo->Register)
        {
        case C_X64_REGISTER_RBP:
            base = (ULONG64)pBaseAddresses->Rbp;
            break;
        case C_X64_REGISTER_RSP:
            base = (ULONG64)pBaseAddresses->Rsp;
            break;
        default:
            exit(0);
        }
    }

    ULONG64 address = base + pSymInfo->Address;

    printf("Variable: %s offset=0x%llx address=0x%llx size=%lu\n", pSymInfo->Name, pSymInfo->Address, address, pSymInfo->Size);

    return TRUE;
}

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));

    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;
        addresses.Rbp = c.Rbp;
        addresses.Rsp = c.Rsp;

        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;
}

struct Structure
{
    float x, y, z;
};

void foo(Structure objByValue)
{
    printf("foo-> Address of parameters: %p = { %f, %f, %f }\n", &objByValue, objByValue.x, objByValue.y, objByValue.z);
    DumpStackTrace();
}

int main()
{
    Structure objOnStack = { 1, 2, 3 };

    printf("main-> Address of on stack: %p = { %f, %f, %f }\n", &objOnStack, objOnStack.x, objOnStack.y, objOnStack.z);

    foo(objOnStack);
        return 0;
}
c++ windows debugging pdb-files diagnostics
1个回答
0
投票

在阅读有关X64调用约定的文档后,我发现了以下句子:

任何不适合8个字节或不是1,2,4或8个字节的参数必须通过引用传递1

这就解释了有趣的地址 - 调试符号给我的是存储完整数据引用的内存地址。所以我的代码流基本上说:

if(符号是参数且大小> 8)address = *(uint64_t)地址; //取消引用

然后通过我的类型系统传递该地址可以正确解析。

我认为其他人可能会觉得这很有用,因为它没有在DbgHelp库中的任何地方记录,虽然有些人可能理解调用约定,但我没有想到传回的符号数据不包含有助于指示的东西这个。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.