DbgHelp: неверный адрес параметра функции, когда это символ передачи по значению в x64 - PullRequest
0 голосов
/ 09 марта 2019

Я использую библиотеку Windows DbgHelp для выгрузки стека вызовов моего приложения C ++. PDB находится в правильном месте, и я успешно сообщаю о кадрах стека. У меня есть система прохождения типа, которая распечатывает всех местных жителей, и это в основном работает. Однако в некоторых случаях я получаю неправильный адрес и вызываю нарушения прав доступа. Один случай - когда функции передается объект по значению:

struct Object { float x,y,z; }

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

В этом случае адрес, рассчитанный для objectByValue, неверен. Это около правильное место в стеке, но не совсем. У меня возникают трудности с поиском информации по нужному адресу. Я делаю следующее:

  • Установить правильный контекст с помощью SymSetContext (...)
  • Вызов SymEnumSymbols с обратным вызовом
  • Внутри обратного вызова проверьте, установлен ли SYMFLAG_REGREL
  • Назначить Address = SymInfo-> Address + 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     

Вы можете видеть в примере, что адрес, вычисляемый по переменной в стеке, верен, но для передачи по значению он неверен.

Кто-нибудь знает, как я могу правильно рассчитать это значение?

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

1 Ответ

0 голосов
/ 13 марта 2019

После прочтения документации по соглашению о вызовах X64 я обнаружил следующее предложение:

Любой аргумент, который не помещается в 8 байтов или не равен 1, 2, 4 или 8 байтов, должен передаваться по ссылке 1

Так что это объясняет забавный адрес - то, что мне дают символы отладки, это адрес памяти, хранящей ссылку на полные данные.Так что мой поток кода по существу говорит:

if (символ - это параметр, а размер> 8) address = * (uint64_t) address;// разыменование

А затем передача этого адреса через мою систему типов разрешается правильно.

Я подумал, что другие люди могут найти это полезным, поскольку он не задокументирован нигде в библиотеке DbgHelp, и хотя некоторые людиможет понять соглашения о вызовах, мне не пришло в голову, что данные символов, переданные обратно, не будут содержать что-то полезное для указания этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...