13

では、SO に関する 1 日で 2 番目の質問です。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であることは奇妙に思えます...助けていただければ幸いです:)

みんなありがとう!

編集

サードパーティのライブラリを使用せずに、単純な C ソリューションを探しています...

4

6 に答える 6

7

255に設定symbol.MaxNameLengthしましたが、スタックに「シンボル」を割り当てましたIMAGEHLP_SYMBOL64 symbol;。そのタイプは次のように定義されます。

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

[名前]フィールドには、デフォルトで1文字しかないことに注意してください。より大きな名前を保存したい場合は、次のようなことを行う必要があります。

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

そうしないと、SymGetSymFromAddr64()メモリが上書きされる可能性があります。構造のヘルプページには次のように書かれています(強調が追加されています)。

MaxNameLength:Nameメンバーに含めることができる文字列の最大長(文字数)。ヌル終了文字は含まれません。シンボル名は長さが異なる可能性があるため、このデータ構造は 呼び出し元によって割り当てられます。このメンバーは、シンボル名で使用できるメモリの量をライブラリが認識できるようにするために使用されます。

于 2011-04-18T18:31:26.800 に答える
5

codeplex のStackwalker プロジェクトを確認してください。これはオープン ソースです。うまく動作します。

于 2011-04-18T16:07:51.767 に答える
2

私はあなたのコードを使用しましたが、ドキュメントでSymInitialize(process、NULL、TRUE)のように最初にSymInitializeを呼び出す必要があることに気付くまで、最初は機能しませんでした。これは、RtlCaptureContextの前に呼び出すことができます。

于 2012-05-18T09:59:42.567 に答える
0

本質的に同じ質問に対するこの回答を参照してください。

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

ユーザーが .pdb ファイルを持っていること、およびユーザーのプロセスがそれを見つけられることを確認する必要があることに注意してください。詳細については、その回答を参照してください。

于 2015-03-06T05:50:31.643 に答える
0

最初に対処すべき問題が 2 つあります。

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 を正しく設定します。これを行う1つの方法は次のとおりです。

__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 のトレース スタックの皆さん、頑張ってください。

于 2013-06-19T15:09:37.033 に答える