Objective-C的@ try- @ catch实现如何在运行时执行的示例?

时间:2011-08-15 07:35:44

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

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

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

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

1 个答案:

答案 0 :(得分:7)

所以你想知道运行时如何进行异常处理?

准备失望。

因为它没有。 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_call_terminate例程。无论如何,这段代码可以分为3个重要部分。第一个是从_mainLBB0_2的开头,或者我们的尝试块发生的地方。因为我们公然抛出异常并在我们的try块中捕获它,所以编译器已经继续移除LBB0_2周围的分支并直接移动到catch处理程序。在这一点上,Objective-C,或更准确地说CoreFoundation,为我们设置了一个异常对象,libC ++已经开始在必要的展开阶段搜索异常处理程序。

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

第三部分来自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_setExceptionMatcher objc_setExceptionPreprocessor。< / p>