【问题标题】:Example of how Objective-C's @try-@catch implementation is executed at runtime?Objective-C 的 @try-@catch 实现如何在运行时执行的示例?
【发布时间】:2011-10-27 02:47:43
【问题描述】:

在 Objective-C 的低级运行时标头 (/usr/include/objc) 中,有一个 objc-exceptions.h 文件。看起来这就是 ObjC 编译器实现 @try/@catch 的方式。

我正在尝试手动调用这些函数(用于试验 ObjC 运行时和实现)以捕获“发送到类的无法识别的选择器”异常。

所以基本上,我正在寻找的只是一个如何使用低级运行时函数执行@try/@catch 的示例。提前致谢!

【问题讨论】:

  • 考虑到obj-c++,你自己也想做stack unwinding吗?
  • Eugene,我不确定我是否遵循...我正在寻找一个类似于 uncaughtException 的示例,我可以在这里找到:gist.github.com/1073294#file_uncaught_exception.m,但我'我猜测更像begin_tryend_try 之类的东西。
  • try/catch 块的组装确实显示了对 objc_begin_catch 和 objc_end_catch 的调用。您是否尝试过查看它们以查看它们是如何被调用的?

标签: objective-c exception-handling try-catch objective-c-runtime


【解决方案1】:

那么你想知道运行时是如何处理异常的吗?

做好失望的准备。

因为它没有。 ObjC 没有异常处理 ABI,只有您已经找到的 SPI。毫无疑问,您还发现 Objective-C 异常 ABI 实际上与 C++ exception handling ABI 完全相同。为此,让我们开始编写一些代码。

#include <Foundation/Foundation.h>

int main(int argc, char **argv) {
    @try {
        @throw [NSException exceptionWithName:@"ExceptionalCircumstances" reason:@"Drunk on power" userInfo:nil];
    } @catch(...) {
        NSLog(@"Catch");
    } @finally {
        NSLog(@"Finally");
    }
}

使用-ObjC -O3 运行clang(并去除大量令人作呕的调试信息),我们得到以下信息:

_main:                                  ## @main
    push    rbp
    mov rbp, rsp
    push    r14
    push    rbx

    mov rdi, qword ptr [rip + L_OBJC_CLASSLIST_REFERENCES_$_]
    mov rsi, qword ptr [rip + L_OBJC_SELECTOR_REFERENCES_]

    lea rdx, qword ptr [rip + L__unnamed_cfstring_]
    lea rcx, qword ptr [rip + L__unnamed_cfstring_2]
    xor r8d, r8d
    call    qword ptr [rip + _objc_msgSend@GOTPCREL]

    mov rdi, rax
    call    _objc_exception_throw
LBB0_2:
    mov rdi, rax
    call    _objc_begin_catch

    lea rdi, qword ptr [rip + L__unnamed_cfstring_4]
    xor eax, eax
    call    _NSLog

    call    _objc_end_catch

    xor ebx, ebx
LBB0_8:
    lea rdi, qword ptr [rip + L__unnamed_cfstring_6]
    xor eax, eax
    call    _NSLog

    test    bl, bl
    jne LBB0_10
LBB0_11:
    xor eax, eax
    pop rbx
    pop r14
    pop rbp
    ret
LBB0_5:
    mov rbx, rax
    call    _objc_end_catch
    jmp LBB0_7
LBB0_6:
    mov rbx, rax
LBB0_7:
    mov rdi, rbx
    call    _objc_begin_catch
    mov bl, 1
    jmp LBB0_8
LBB0_12:
    mov r14, rax
    test    bl, bl
    je  LBB0_14
    jmp LBB0_13
LBB0_10:
    call    _objc_exception_rethrow
    jmp LBB0_11
LBB0_16:                                ## %.thread
    mov r14, rax
LBB0_13:
    call    _objc_end_catch
LBB0_14:
    mov rdi, r14
    call    __Unwind_Resume
LBB0_15:
    call    _objc_terminate

如果你用 ObjC++ 编译它没有任何变化。 (嗯,这并不完全正确。最后一个_objc_terminate 变成了clang 的个人___clang_call_terminate 例程)。无论如何,这段代码可以分为 3 个重要部分。第一个是从_mainLBB0_2 的开头,或者我们的try 块发生的地方。因为我们公然抛出异常并在我们的try 块中捕获它,所以编译器继续并删除了LBB0_2 周围的分支并直接移至捕获处理程序。此时,Objective-C,或者更准确地说是 CoreFoundation,已经为我们设置了一个异常对象,并且 libC++ 已经开始在必要的展开阶段搜索异常处理程序。

第二个重要的代码块是从LBB0_2LBB0_11 的末尾,我们的catchfinally 块所在的位置。因为一切都很好,下面的所有代码都死了(希望在发布时被剥离),但让我们想象它不是。

第三部分是从LBB0_8 向下,如果我们做了一些愚蠢的事情,比如试图不捕捉我们的异常,编译器会从LBB0_2 中的NSLog 发出跳转。这个处理程序在调用objc_begin_catch 后会翻转一点,这导致我们围绕ret 分支并移动到objc_exception_rethrow(),这告诉展开处理程序我们已经丢球并继续在其他地方搜索处理程序.当然,我们是主要的,所以没有其他处理程序,并且在我们离开时会调用std::terminate

所有这一切都表明,如果你想尝试手写这些东西,你会过得很糟糕。所有__cxa_* 和ObjC SPI 函数以您无法依赖的方式抛出异常对象,并且(相当悲观的许多)处理程序在very tight order 中发出,以确保C++ ABI 合同得到履行,因为如果它没有规范要求调用std::terminate。如果你想扮演一个积极的倾听角色,你可以使用自己的函数redefine the exception handling stuff,而Objective-C有objc_setUncaughtExceptionHandlerobjc_setExceptionMatcherobjc_setExceptionPreprocessor

【讨论】:

    猜你喜欢
    • 2012-06-04
    • 1970-01-01
    • 2016-02-04
    • 2011-03-22
    • 1970-01-01
    • 1970-01-01
    • 2015-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多