通过静态分析判断函数是否有try/catch语句

逆向工程 静态分析 例外
2021-06-10 13:09:32

我需要通过静态分析来确定该函数是否具有异常处理程序。最初我认为如果函数中有 SEH 处理程序 prolog,那么该函数应该有 try /catch。即我想如果 x86 函数有如下指令:

.text:004125E3                 push    0FFFFFFFFh
.text:004125E5                 push    418540h
.text:004125EA                 mov     eax, large fs:0

应该有try/catch。但是在编译了一些 C++ 代码示例之后,我发现即使没有 try/catch 而是添加了对 throw() 的调用,也会添加这些指令。因此,似乎无法确定该函数是否确实具有异常处理程序。

在 AMD64 上,它更加晦涩,因为没有异常相关的指令。有关异常处理程序的信息似乎是用 PE 标头中的异常目录编码的。Exception Directory 中的数据指向函数开始、函数结束和 Unwind 信息。我认为如果函数没有 try/catch,则异常目录中将没有条目。但与 x86 类似,如果函数调用 throw(),它在异常目录中具有条目,即使源代码中没有 try/catch。

所以问题是:系统如何知道函数是否有异常处理程序,并区分没有 try/catch 的函数和有它的函数?

1个回答

TL,博士;
异常处理会导致内存泄漏。您的 C++ 编译器足够聪明,可以意识到围绕异常处理的内存泄漏的可能性,并在必要时自动插入异常处理程序以防止内存泄漏。这意味着确实存在一个 try/catch 块,您可以在其中观察异常处理程序,除了它不处理任何内容并且在清理 C++ 对象后简单地重新抛出异常。


系统如何知道函数是否有异常处理程序,并区分没有 try/catch 的函数和有它的函数

系统不知道。事实上,系统不可能知道,所以它只是沿着异常处理程序链向下走,直到找到一个告诉系统“已处理异常”的处理程序。这意味着每当你看到这 3 条汇编指令时,总会有一个异常处理程序,我相信应该总是可以将异常处理程序反转为等效的 C++ try/catch 块。

但正如您所观察到的,并非所有异常处理程序都需要明确定义。您的 C++ 编译器会将它们插入到您的程序中,以便进行对象清理,因为异常处理会导致内存泄漏:

void i_leak_memory() {
    void* memory = malloc(512);
    throw std::exception();

    // this line of code will never be executed
    free(memory);
}

void i_handle_exceptions() {
    try {
        i_leak_memory();
    }
    catch(std::exception e) {
        // exception handled
    }
}

在上面的代码中,您会发现由于异常引起的中断,内存永远不会被释放。在异常发生的确切时刻,执行立即转移到 catch in i_handle_exceptions(),处理异常后,继续执行 in i_handle_exceptions()i_leak_memory()异常发生后永远没有机会释放资源,因此我们最终会发生内存泄漏。

幸运的是,对于这种明显的内存泄漏,有一个简单的解决方案。只需替换i_leak_memory()i_do_not_leak_memory()

void i_do_not_leak_memory() {
    void* memory = 0;

    try {
        memory = malloc(512);
        throw std:exception();
        free(memory);
    }
    catch(std:exception e) {
        free(memory);
        throw;
    }
}

通过简单地向函数添加一个 try/catch 块,我们可以给它最后一次在异常事件中释放资源的机会。catch 块不一定需要处理异常,但它允许我们包含防止内存泄漏的特殊逻辑。

使用 C++ 的 RAII 设计,对象清理的任务是 C++ 编译器的责任。使用时throw(),C++编译器需要在代码中(可能在多个函数中)注入特殊的逻辑,以防止发生内存泄漏。因此,在你的观察,到底发生了什么是你的C ++编译器替换i_leak_memory()i_do_not_leak_memory(),因为如果没有,该计划将被泄漏的C ++对象时发生了异常。