通过 Intel PinTool 在发生异常后(在 .exe 文件中)获取异常处理程序的位置

Get location of exception handler after an exception has occurred(in a .exe file) via Intel PinTool

抱歉这么久了 POST :)

我想为 .exe 文件编写一个 pintool,它在异常情况下执行以下操作:

  1. 打印异常指令地址。
  2. 打印将处理此异常的处理程序的地址。
  3. 打印程序returns.
  4. 所在指令的地址

我已经阅读了所有关于 windows SEH 机制的内容,并且我非常熟悉 intel pin-tool 本身。为了开始,我编写了以下测试程序:

#include <stdio.h>
void bar(){
        throw 20;
}
void foo(char *s, int a){
    printf(s,a);
}
int main(){
    try{
        bar();
    }
    catch(int e){
        foo("%d\n",e );
    }
}

然后我使用 pintool 打印了所有例程(在调用 main 之后调用)及其 return 地址。 Here 是列表:

然后我把这些例程的文档都看了一遍。我认为这些例程的参数可能包含我需要的信息。但这一切都是徒劳的。 RtlRaiseException 参数确实让我可以访问 ExceptionRecord 结构,但它的 ExceptionAddress 字段包含 RaiseException 的起始地址而不是 bar 的地址。

而且我无法找到任何方法来获取将处理 throw.

的异常处理程序位置

感谢任何帮助;谢谢:)

这不是直接针对 PIN 解决方案的答案,而是有关如何找到处理程序的帮助。

下面是未经优化编译的代码中有趣的部分(我在 bar 函数下方添加了一个 printf,因此处理程序不太接近 bar;代码编译为对比 2015):

CPU Disasm
Address    Command                                Comments
011D1071   CALL bar                               ; [bar
011D1076   PUSH OFFSET 011D2110                   ; ASCII "you wont see me..."
011D107B   CALL printf                            ; [printf
011D1080   ADD ESP,4
011D1083   JMP SHORT 011D109C
011D1085   MOV EAX,DWORD PTR SS:[EBP-14]          ; Handler start
011D1088   PUSH EAX
011D1089   PUSH OFFSET 011D2124                   ; ASCII "%d"
011D108E   CALL foo                               ; [foo

这是 bar() 函数的代码:

CPU Disasm
Address    Command                                Comments
011D1000 b PUSH EBP                               ; TestCppException.bar(void)
011D1001   MOV EBP,ESP
011D1003   PUSH ECX
011D1004   MOV DWORD PTR SS:[EBP-4],14
011D100B   PUSH OFFSET _TI1H                      ; /Arg2 = TestCppException._TI1H
011D1010   LEA EAX,[EBP-4]                        ; |
011D1013   PUSH EAX                               ; |Arg1
011D1014   CALL _CxxThrowException                ; \VCRUNTIME140._CxxThrowException
011D1019   MOV ESP,EBP
011D101B   POP EBP
011D101C   RETN

C++ 异常与其他异常(访问冲突、除以 0 等)不同,因为在到达处理程序(catch 部分)之前在幕后发生了很多事情。

如您所见,使用以下方法引发 C++ 异常:

调用RtlRaiseException时,异常记录如下所示:

CPU Stack
Address   Value                Comments
0018FAF8  |E06D7363    ; ExceptionCode=E06D7363
0018FAFC  |00000001    ; Flags=EXCEPTION_NONCONTINUABLE
0018FB00  |00000000    ; pExceptionRecord=NULL
0018FB04  |7736DAA0    ; ExceptionAddress=KERNELBASE.RaiseException
0018FB08  |00000003    ; Nparm=3
0018FB0C  |19930520    ; exception version identifier
0018FB10  |0018FBA4    ; pObject
0018FB14  |011D2638    ; _ThrowInfo*
  • 异常代码0xE06D7363标识一个C++异常(所有C++异常的代码相同)。

  • Flags设置为EXCEPTION_NONCONTINUABLE因为C++异常不支持continuation(代码无法在异常发生的地方继续执行发生了)。

  • pExceptionRecord 为 NULL 因为 CPP 异常中没有异常链。

  • ExceptionAddressRaiseException 因为它不是“直接”引发异常的代码,而是系统为您完成的(即:这不像你有一个除以 0 的除法,它可以精确定位到一个非常具体的汇编指令;这里系统为你引发异常,并在 RaiseException 引发异常。

  • 此异常记录有 3 个参数:

    • 第一个是异常标识符(没有记录,但可能意味着这是一个 VC6 风格的异常)。

    • pObject 是一个指向抛出对象的指针(在我们的例子中这是一个指向整数 20 的指针)

    • 最后一个 _ThrowInfo 结构(与传递给 _CxxThrowException 的参数相同)被系统使用,更准确地说是异常调度程序,以查看哪个处理程序可以捕获此异常。

从这一点开始,我们有了“通常”的 SEH 内容,系统会在其中调度异常并搜索正确的 SEH 处理程序。在我们的示例中,“哪个 catch 块可以捕获一个 int”。

碰巧链中的最后一个 SEH 可以处理抛出的 int。

这是 SEH 的装配视图:

CPU Disasm
Address    Command                           Comments
00CA1D91   MOV EAX,OFFSET 011D2580        ; pointer to __ehfuncinfo
00CA1D96   JMP __CxxFrameHandler3            ; Jump to VCRUNTIME140.__CxxFrameHandler

我们只有一个结构(通过 eax 寄存器)传递给名为 __CxxFrameHandler3 的函数。

名为 __ehfuncinfo 的结构如下所示:

struct ehfuncinfo1200 //_s_ESTypeList
{
  /* 0x00 */  uint32_t        magic : 30;
  /* 0x04 */  ehstate_t       unwindtable_size;
  /* 0x08 */  unwindtable *   unwindtable;
  /* 0x0C */  size_t          tryblocktable_size;
  /* 0x10 */  tryblock *      tryblocktable;
  /* 0x14 */  size_t          _size;
  /* 0x18 */  void *          _;
/* … snip … */
};

在我们的例子中,我们有:

CPU 
Address    Value       Comments
011D2580   19930522    ; magic
011D2584   00000002    ; unwind table size 
011D2588   011D25A4    ; unwind table
011D258C   00000001    ; try block table size
011D2590   011D25B4    ; try block table
011D2594   00000000
011D2598   00000000

try block table 中的条目如下所示:

struct tryblock
{
  ehstate_t   trylow;
  ehstate_t   tryhigh;
  ehstate_t   catchhigh;
  int         ncatches;
  ehandler *  catchsym;

  /* snip */
};

以下是我们示例中的值:

CPU Stack
Address   Value      Comments
011D25B4   00000000  
011D25B8   00000000
011D25BC   00000001  
011D25C0   00000001 ; ncatches
011D25C4   011D25C8 ; catchsym

catchsym字段是一个ehandler结构:

/// This type represents the catch clause
struct ehandler
{
//  union { uint32_t  adjectives; void * ptr; };
  uint32_t isconst      : 1; /* + 00 */
  uint32_t isvolatile   : 1;
  uint32_t isunaligned  : 1;
  uint32_t isreference  : 1;

  const type_info *   typeinfo; /* + 04 */
  ptrdiff_t           eobject_bpoffset; // 0 = no object (catch by type)
  generic_function_t *  handler; /* + 0x0C */

  /* snip */
};

注意handler字段是catch块的地址!

并且值:

CPU Stack
Address   Value        Comments
011D25C8   00000000    ; 
011D25CC   011D3030    ; Typeinfo (OFFSET TestCppException.int `RTTI Type Descriptor')
011D25D0   FFFFFFEC    
011D25D4   011D1085    ; Handler entry point

在我们的例子中,处理程序字段指向 catch 块的精确位置:

CPU Disasm
Address    Command                            Comments
011D1085   MOV EAX,DWORD PTR SS:[EBP-14]      ; handler start
011D1088   PUSH EAX
011D1089   PUSH OFFSET 011D2124               ; ASCII "%d"
011D108E   CALL foo                           ;

总结

如果发生异常(参见Exception API in PIN Manual

  • 检查异常 ocde:如果它是 0xE06D7363 那么它是一个 C++ 异常
  • 等待 __CxxFrameHandler3 被调用(从技术上讲,它是此函数的 JMP,而不是 CALL)
  • 检查 __CxxFrameHandler3 条目上的 eax 寄存器。
  • eax寄存器是一个指向__ehfuncinfo结构的指针。
  • 跟随所有结构直到 ehandler 结构的 handler 字段。
    • handler字段是catch block
    • 的地址

关于这个主题的一些很好的建议:


Pintool 代码

/* ===================================================================== */
/* This example demonstrates finding a function by name on Windows.      */
/* ===================================================================== */

#include "pin.H"
#include <iostream>
#include <fstream>

/*
* C++ exception structures
*/

// This type represents the catch clause
typedef struct _ehandler
{
    //  union { uint32_t  adjectives; void * ptr; };
    uint32_t            adjectives;         /* + 0x00 */
    const type_info *   typeinfo;           /* + 0x04 */
    ptrdiff_t           eobject_bpoffset;   /* + 0x08 */
    void*               handler;            /* + 0x0C */ 
    /* snip */
} ehandler;

typedef struct _tryblock
{
    uint32_t   trylow;
    uint32_t   tryhigh;
    uint32_t   catchhigh;
    int         ncatches;
    ehandler *  catchsym;
    /* snip */
} tryblock;

typedef struct ehfuncinfo1200 //_s_ESTypeList
{
    /* 0x00 */  uint32_t        magic : 30;
    /* 0x04 */  uint32_t       unwindtable_size;
    /* 0x08 */  void *   unwindtable;
    /* 0x0C */  size_t          tryblocktable_size;
    /* 0x10 */  tryblock *      tryblocktable;
    /* 0x14 */  size_t          _size;
    /* 0x18 */  void *          _;
    /* snip */
} ehfuncinfo;

/* ===================================================================== */
/* Global Variables */
/* ===================================================================== */

std::ofstream TraceFile;

/* ===================================================================== */
/* Commandline Switches */
/* ===================================================================== */

KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool",
    "o", "check_handler.out", "specify trace file name");

/* ===================================================================== */
/* Print Help Message                                                    */
/* ===================================================================== */

INT32 Usage()
{
    cerr << "This tool produces a trace of calls to RtlAllocateHeap.";
    cerr << endl << endl;
    cerr << KNOB_BASE::StringKnobSummary();
    cerr << endl;
    return -1;
}

/* ===================================================================== */
/* Analysis routines                                                     */
/* ===================================================================== */

VOID Before(CHAR * name, ADDRINT RegValue)
{
    TraceFile << "At function entry (" << name << "); EAX: " << hex << RegValue << endl;

    // eax is a pointer to ehfuncinfo struct
    ehfuncinfo* funcinfo = reinterpret_cast<ehfuncinfo*>(RegValue);

    // get the tryblock table from ehfuncinfo
    tryblock* tryb = funcinfo->tryblocktable;

    // get ehandler struct from try block
    ehandler* ehand = tryb->catchsym;

    // from ehandler structure, get handler address
    void* handler = ehand->handler;

    // save it to file
    TraceFile << "Handler Address: " << hex << handler << endl;
}

/* ===================================================================== */
/* Instrumentation routines                                              */
/* ===================================================================== */

VOID Image(IMG img, VOID *v)
{
    // Walk through the symbols in the symbol table.
    //
    for (SYM sym = IMG_RegsymHead(img); SYM_Valid(sym); sym = SYM_Next(sym))
    {
        string undFuncName = PIN_UndecorateSymbolName(SYM_Name(sym), UNDECORATION_NAME_ONLY);

        //  Find function.
        if (undFuncName == "__CxxFrameHandler3")
        {
            std::cout << "OK! found __CxxFrameHandler3" << std::endl;

            RTN allocRtn = RTN_FindByAddress(IMG_LowAddress(img) + SYM_Value(sym));

            if (RTN_Valid(allocRtn))
            {
                // Instrument to print the input argument value and the return value.
                RTN_Open(allocRtn);

                RTN_InsertCall(allocRtn, IPOINT_BEFORE, (AFUNPTR)Before,
                    IARG_ADDRINT, "__CxxFrameHandler3",
                    IARG_REG_VALUE, REG::REG_EAX,
                    IARG_END);

                RTN_Close(allocRtn);
            }
        }
    }
}

/* ===================================================================== */

VOID Fini(INT32 code, VOID *v)
{
    TraceFile.close();
}

/* ===================================================================== */
/* Main                                                                  */
/* ===================================================================== */

int main(int argc, char *argv[])
{
    // Initialize pin & symbol manager
    PIN_InitSymbols();
    if (PIN_Init(argc, argv))
    {
        return Usage();
    }

    // Write to a file since cout and cerr maybe closed by the application
    TraceFile.open(KnobOutputFile.Value().c_str());
    TraceFile << hex;
    TraceFile.setf(ios::showbase);

    // Register Image to be called to instrument functions.
    IMG_AddInstrumentFunction(Image, 0);
    PIN_AddFiniFunction(Fini, 0);

    // Never returns
    PIN_StartProgram();

    return 0;
}

/* ===================================================================== */
/* eof */
/* ===================================================================== */

输出

At function entry (__CxxFrameHandler3); EAX: 0xcc2580
Handler Address: 0x00cc1085

两者都匹配给定的代码(减去由于 ASLR 造成的偏移):

 MOV EAX,OFFSET 011D2580        ; pointer to __ehfuncinfo

 ...

011D1085   MOV EAX,DWORD PTR SS:[EBP-14]  ; Handler start