Как получить адрес указателя базового стека - PullRequest
16 голосов
/ 04 декабря 2009

Я в процессе портирования приложения с x86 на x64. Я использую Visual Studio 2009; большая часть кода написана на C ++, а некоторые части - на простом C. Ключевое слово __asm ​​не поддерживается при компиляции в x64, и наше приложение содержит несколько частей встроенного ассемблера. Я не писал этот код, поэтому я не знаю точно, что должен делать et:

int CallStackSize() {
    DWORD Frame;
    PDWORD pFrame;
    __asm
        {
            mov EAX, EBP
            mov Frame, EAX
        }
    pFrame = (PDWORD)Frame;
    /*... do stuff with pFrame here*/
}

EBP - базовый указатель на стек текущей функции. Есть ли способ получить указатель стека без использования встроенного asm? Я искал особенности, которые Microsoft предлагает в качестве замены встроенного ассемблера, но я не смог найти ничего, что дало бы мне что-то полезное. Есть идеи?

Андреас спросил, что делается с pFrame. Вот полная функция:

int CallStackSize(DWORD frameEBP = 0)
{
    DWORD pc;
    int tmpint = 0;
    DWORD Frame;
    PDWORD pFrame, pPrevFrame;

    if(!frameEBP) // No frame supplied. Use current.
    {
        __asm
        {
            mov EAX, EBP
            mov Frame, EAX
        }
    }
    else Frame = frameEBP;

    pFrame = (PDWORD)Frame;
    do
    {
        pc = pFrame[1];
        pPrevFrame = pFrame;
        pFrame = (PDWORD)pFrame[0]; // precede to next higher frame on stack

        if ((DWORD)pFrame & 3) // Frame pointer must be aligned on a DWORD boundary. Bail if not so.
        break;

        if (pFrame <= pPrevFrame)
        break;

        // Can two DWORDs be read from the supposed frame address?
        if(IsBadWritePtr(pFrame, sizeof(PVOID)*2))
        break;

        tmpint++;
    } while (true);
    return tmpint;
}

Переменная pc не используется. Похоже, что эта функция идет вниз по стеку, пока не потерпит неудачу. Предполагается, что он не может читать вне стека приложений, поэтому при сбое он измеряет глубину стека вызовов. Этот код не нужно компилировать на компиляторе _EVERY_SINGLE. Просто VS2009. Приложение не должно запускаться на компьютере EVERY_SINGLE. Мы полностью контролируем развертывание, поскольку сами устанавливаем / настраиваем и доставляем все это нашим клиентам.

Ответы [ 6 ]

13 голосов
/ 04 декабря 2009

По-настоящему правильным было бы переписать все, что делает эта функция, чтобы она не требовала доступа к фактическому указателю кадра. Это определенно плохое поведение.

Но, чтобы делать то, что вы ищете, вы должны уметь:

int CallStackSize() {
    __int64 Frame = 0; /* MUST be the very first thing in the function */
    PDWORD pFrame;

    Frame++; /* make sure that Frame doesn't get optimized out */

    pFrame = (PDWORD)(&Frame);
    /*... do stuff with pFrame here*/
}

Причина, по которой это работает, в том, что в C обычно первое, что делает функция, это сохраняет расположение базового указателя (ebp) перед выделением локальных переменных. Создав локальную переменную (Frame) и затем получив адрес if, мы действительно получаем адрес начала стекового фрейма этой функции.

Примечание. Некоторые оптимизации могут привести к удалению переменной Frame. Наверное, нет, но будьте осторожны.

Второе примечание: Ваш исходный код, а также этот код манипулируют данными, на которые указывает «pFrame», когда сам «pFrame» находится в стеке. Здесь можно случайно переписать pFrame, и тогда у вас будет плохой указатель, и вы можете получить странное поведение. Будьте особенно внимательны при переходе с x86 на x64, потому что теперь pFrame теперь 8 байтов вместо 4, поэтому, если ваш старый код «делать вещи с pFrame» учитывал размер Frame и pFrame до того, как связываться с памятью, нужно учесть новый, больший размер.

7 голосов
/ 05 декабря 2009

Вы можете использовать встроенную функцию _AddressOfReturnAddress() для определения местоположения в текущем указателе кадра, предполагая, что оно не было полностью оптимизировано. Я предполагаю, что компилятор не позволит этой функции оптимизировать указатель фрейма, если вы явно на него ссылаетесь. Или, если вы используете только один поток, вы можете использовать IMAGE_NT_HEADER.OptionalHeader.SizeOfStackReserve и IMAGE_NT_HEADER.OptionalHeader.SizeOfStackCommit, чтобы определить размер стека основного потока. См. this для получения доступа к IMAGE_NT_HEADER для текущего изображения.

Я бы также рекомендовал не использовать IsBadWritePtr для определения конца стека. По крайней мере, вы, вероятно, будете вызывать рост стека до тех пор, пока не попадете в резерв, поскольку вы отключите защитную страницу. Если вы действительно хотите найти текущий размер стека, используйте VirtualQuery с адресом, который вы проверяете.

И если первоначальное использование - обход стека, вы можете использовать StackWalk64 для этого.

3 голосов
/ 07 мая 2013

Microsoft предоставляет библиотеку (DbgHelp), которая заботится о обходе стека , и вы должны использовать ее вместо использования сборочных приемов. Например, если присутствуют файлы PDB, он также может просматривать оптимизированные кадры стека (те, которые не используют EBP).

У CodeProject есть статья, в которой объясняется, как его использовать:

http://www.codeproject.com/KB/threads/StackWalker.aspx

2 голосов
/ 07 мая 2013

Нет гарантии, что RBP (x64-эквивалент EBP) на самом деле является указателем на текущий кадр в стеке вызовов. Я предполагаю, что Microsoft решила, что, несмотря на несколько новых регистров общего назначения, им нужно освободить еще один, поэтому RBP используется только как framepointer в функциях, которые вызывают alloca (), и в некоторых других случаях. Поэтому, даже если встроенная сборка будет поддерживаться, это не будет правильным решением.

Если вы просто хотите вернуться, вам нужно использовать StackWalk64 в dbghelp.dll. Он находится в dbghelp.dll, поставляемом с XP, и до XP не было 64-битной поддержки, поэтому вам не нужно поставлять dll с приложением.

Для вашей 32-битной версии просто используйте ваш текущий метод. Ваши собственные методы, вероятно, будут меньше, чем библиотека импорта для dbghelp, намного меньше фактической dll в памяти, так что это определенная оптимизация (личный опыт: я реализовал backtrace в стиле Glibc и backtrace_symbols для x86 менее чем за десятый размер библиотеки импорта dbghelp).

Кроме того, если вы используете это для отладки в процессе или генерации отчетов о сбоях после выпуска, я настоятельно рекомендую просто работать со структурой CONTEXT, предоставленной обработчику исключений.

Возможно, однажды я решу серьезно нацелиться на x64 и найти дешевый способ использования StackWalk64, которым я могу поделиться, но, поскольку я все еще нацеливаюсь на x86 для всех своих проектов, я не потрудился.

1 голос
/ 05 декабря 2009
.code

PUBLIC getStackFrameADDR _getStackFrameADDR
getStackFrameADDR:
    mov RAX, RBP
    ret 0

END

Нечто подобное может сработать для вас.

Скомпилируйте его с помощью ml64 или jwasm и позвоните, используя это в своем коде. extern "C" void getstackFrameADDR (void);

1 голос
/ 04 декабря 2009

Если вам нужен точный «базовый указатель», то встроенная сборка - единственный путь.

Удивительно, но можно написать код, который наполняет стек сравнительно небольшим кодом, специфичным для платформы, но трудно вообще избежать сборки (в зависимости от того, что вы делаете).

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

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