StackWalk64 в Windows - Получить имя символа - PullRequest
10 голосов
/ 18 апреля 2011

Хорошо, второй вопрос по SO за один день. Похоже, программирование Windows делает меня счастливым ...: S

В настоящее время я пытаюсь получить стек вызовов функций в исполняемом файле Win32.

Этим утром я также задал вопрос об этом:

Win32 - Возврат из кода C

Теперь я уверен, что ключом для этого является функция StackWalk64. Я прочитал несколько статей о том, как его использовать, а также документацию MS.

На самом деле в моей тестовой программе отображаются кадры, так что это вроде работает ...

Проблема в том, что я не могу получить имя символа из информации стека.

Для этого я использую функцию SymGetSymFromAddr64 с UnDecorateSymbolName. Но я получаю только ненужные символы.

Вот мой код. Надеюсь, что это не грязно, так как я не привык к программированию Windows:

void printStack( void )
{
    BOOL                result;
    HANDLE              process;
    HANDLE              thread;
    CONTEXT             context;
    STACKFRAME64        stack;
    ULONG               frame;
    IMAGEHLP_SYMBOL64   symbol;
    DWORD64             displacement;
    char name[ 256 ];

    RtlCaptureContext( &context );
    memset( &stack, 0, sizeof( STACKFRAME64 ) );

    process                = GetCurrentProcess();
    thread                 = GetCurrentThread();
    displacement           = 0;
    stack.AddrPC.Offset    = context.Eip;
    stack.AddrPC.Mode      = AddrModeFlat;
    stack.AddrStack.Offset = context.Esp;
    stack.AddrStack.Mode   = AddrModeFlat;
    stack.AddrFrame.Offset = context.Ebp;
    stack.AddrFrame.Mode   = AddrModeFlat;

    for( frame = 0; ; frame++ )
    {
        result = StackWalk64
        (
            IMAGE_FILE_MACHINE_I386,
            process,
            thread,
            &stack,
            &context,
            NULL,
            SymFunctionTableAccess64,
            SymGetModuleBase64,
            NULL
        );

        symbol.SizeOfStruct  = sizeof( IMAGEHLP_SYMBOL64 );
        symbol.MaxNameLength = 255;

        SymGetSymFromAddr64( process, ( ULONG64 )stack.AddrPC.Offset, &displacement, &symbol );
        UnDecorateSymbolName( symbol.Name, ( PSTR )name, 256, UNDNAME_COMPLETE );

        printf
        (
            "Frame %lu:\n"
            "    Symbol name:    %s\n"
            "    PC address:     0x%08LX\n"
            "    Stack address:  0x%08LX\n"
            "    Frame address:  0x%08LX\n"
            "\n",
            frame,
            symbol.Name,
            ( ULONG64 )stack.AddrPC.Offset,
            ( ULONG64 )stack.AddrStack.Offset,
            ( ULONG64 )stack.AddrFrame.Offset
        );

        if( !result )
        {
            break;
        }
    }
}

Фактический результат:

Frame 0:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠
    PC address:     0x00BA2763
    Stack address:  0x00000000
    Frame address:  0x0031F7E8

Frame 1:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠☺
    PC address:     0x00BB4FFF
    Stack address:  0x00000000
    Frame address:  0x0031F940

Frame 2:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠☻
    PC address:     0x00BB4E2F
    Stack address:  0x00000000
    Frame address:  0x0031F990

Frame 3:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♥
    PC address:     0x75BE3677
    Stack address:  0x00000000
    Frame address:  0x0031F998

Frame 4:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♦
    PC address:     0x770F9D72
    Stack address:  0x00000000
    Frame address:  0x0031F9A4

Frame 5:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♣
    PC address:     0x770F9D45
    Stack address:  0x00000000
    Frame address:  0x0031F9E4

Frame 6:
    Symbol name:    ╠╠╠╠╠╠╠╠╠╠╠╠♠
    PC address:     0x770F9D45
    Stack address:  0x00000000
    Frame address:  0x0031F9E4

Кажется странным, что адрес стека всегда равен 0, кстати ... Любая помощь приветствуется:)

Спасибо всем!

EDIT

Я ищу простое C-решение без сторонних библиотек ...

Ответы [ 5 ]

6 голосов
/ 18 апреля 2011

Вы установили symbol.MaxNameLength на 255, но вы выделили «символ» в стеке с помощью IMAGEHLP_SYMBOL64 symbol;.Этот тип определяется как:

typedef struct _IMAGEHLP_SYMBOL64 {
  DWORD   SizeOfStruct;
  DWORD64 Address;
  DWORD   Size;
  DWORD   Flags;
  DWORD   MaxNameLength;
  TCHAR   Name[1];
} IMAGEHLP_SYMBOL64;

Обратите внимание, что поле Имя по умолчанию имеет только один символ.Если вы хотите хранить большие имена, вам нужно сделать что-то вроде:

 const int MaxNameLen = 255;
 IMAGEHLP_SYMBOL64* pSymbol = 
       malloc(sizeof(IMAGEHLP_SYMBOL64)+MaxNameLen*sizeof(TCHAR));
 pSymbol->MaxNameLength = MaxNameLen;

В противном случае SymGetSymFromAddr64() может перезаписать память.Вот что страница справки для структуры говорит (выделение добавлено):

MaxNameLength : максимальная длина строки, которую может содержать член Name, в символах, не включая завершающий ноль символ.Поскольку имена символов могут различаться по длине, эта структура данных выделяется вызывающей стороной .Этот член используется, поэтому библиотека знает, сколько памяти доступно для использования именем символа.

5 голосов
/ 18 апреля 2011

Проверьте проект Stackwalker на codeplex - это открытый исходный код Работает красиво.

2 голосов
/ 18 мая 2012

Я использовал ваш код, и он тоже сначала не работал, пока в документации не заметил, что сначала нужно вызвать SymInitialize, например SymInitialize (process, NULL, TRUE)Вы можете вызвать это до RtlCaptureContext.

0 голосов
/ 06 марта 2015

Смотрите этот ответ на тот же вопрос:

https://stackoverflow.com/a/28276227/10592

Обратите внимание, что вам нужно убедиться, что у ваших пользователей есть файл .pdb и что их процесс может найтиэто - см. этот ответ для более подробной информации.

0 голосов
/ 19 июня 2013

Сначала нужно решить две проблемы:

1) Необходимо предварительно выделить имя, как указано AShelly.Для этого вам не нужен malloc:

#define MY_MAX_SYM_LEN 255
printStack()
{
    struct sym_pack_tag {
        IMAGEHLP_SYMBOL64  sym;
        char               name[MY_MAX_SYM_LEN];
    } sym_pack;
    IMAGEHLP_SYMBOL64     *symbol = &sym_pack.sym;
    ...
    symbol->SizeOfStruct  = sizeof(IMAGEHLP_SYMBOL64 );
    symbol->MaxNameLength = MY_MAX_SYM_LEN;
    if (!SymGetSymFromAddr64( process, stack.AddrPC.Offset, &displacement, symbol )) ...

2) Нельзя использовать RtlCaptureContext () для получения контекста в 32-битных сборках.Если у вас 64-битный компьютер, измените IMAGE_FILE_MACHINE_I386 на соответствующий 64-битный тип.Если у вас 32-битная сборка, используйте встроенную сборку для правильной настройки EBP, ESP и EIP.Вот один из способов сделать это:

__declspec(naked) void WINAPI CaptureContext_X86ControlOnly(CONTEXT *context) {
  __asm {
    push ebp
    mov  ebp, esp
    mov  ecx, context            //ecx = [ebp + 8]
    pop  ebp                     //restore old frame
    pop  eax                     //pop return address
    pop  ecx                     //pop context as WINAPI needs. Note: ecx will stay the same
    mov [ecx]CONTEXT.ContextFlags, CONTEXT_CONTROL
    mov [ecx]CONTEXT.Ebp, ebp
    mov [ecx]CONTEXT.Eip, eax
    mov [ecx]CONTEXT.Esp, esp
    jmp  eax
  }
} //I'm writing from my memory - so step through the code above to double check.

Незначительная точка - SymGetSymFromAddr64 в порядке, но рекомендуется вместо этого использовать SymFromAddr.

Удачи всем этим стекам трассировки в Windows.1011 *

...