在 Objective-C 的低级运行时标头(/usr/include/objc
)中,有一个objc-exceptions.h
文件。看起来这就是ObjC 编译器如何实现@try
/的。@catch
我正在尝试手动调用这些函数(用于 ObjC 运行时和实现的实验),以捕获“发送到类的无法识别的选择器”异常。
所以基本上,我正在寻找的只是一个如何使用低级运行时函数@try
的示例。@catch
提前致谢!
在 Objective-C 的低级运行时标头(/usr/include/objc
)中,有一个objc-exceptions.h
文件。看起来这就是ObjC 编译器如何实现@try
/的。@catch
我正在尝试手动调用这些函数(用于 ObjC 运行时和实现的实验),以捕获“发送到类的无法识别的选择器”异常。
所以基本上,我正在寻找的只是一个如何使用低级运行时函数@try
的示例。@catch
提前致谢!
所以你想知道运行时是如何处理异常的吗?
准备失望。
因为它没有。ObjC 没有异常处理 ABI,只有您已经找到的 SPI。毫无疑问,您还发现 Objective-C 异常 ABI 实际上与C++ 异常处理 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
(并剔除大量令人作呕的调试信息),我们得到:
_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 个重要部分。第一个是从_main
到开始LBB0_2
,或者我们的 try 块发生的地方。因为我们公然抛出异常并在我们的代码try
块中捕获它,所以编译器已经继续并删除了周围的分支LBB0_2
并直接移动到捕获处理程序。此时,Objective-C,或者更准确地说是 CoreFoundation,已经为我们设置了一个异常对象,并且 libC++ 已经开始在必要的展开阶段搜索异常处理程序。
第二个重要的代码块是我们的和块所在LBB0_2
的位置。因为一切都很好,下面的所有代码都死了(希望在发布时被剥离),但让我们想象它不是。 LBB0_11
catch
finally
第三部分是从下面开始,如果我们做了一些愚蠢的事情,比如试图不捕捉我们的异常,LBB0_8
编译器就会从 NSLog 发出跳转。LBB0_2
这个处理程序在调用之后会翻转一点,objc_begin_catch
这导致我们绕过ret
并移动到objc_exception_rethrow()
告诉 unwind 处理程序我们已经丢球并继续在其他地方搜索处理程序。当然,我们是主要的,所以没有其他处理程序,并std::terminate
在我们离开时被调用。
所有这一切都表明,如果你想尝试用手写这些东西,你会度过一段糟糕的时光。所有__cxa_*
SPI 和 ObjC SPI 函数都以您无法依赖的方式抛出异常对象,并且(相当悲观的许多)处理程序以非常严格的顺序发出,以确保 C++ ABI 合同得到履行,因为如果不是规范要求std::terminate
叫做。如果你想扮演一个积极的倾听角色,你可以用你自己的函数重新定义异常处理的东西objc_setUncaughtExceptionHandler
,Objective-C 有, objc_setExceptionMatcher
objc_setExceptionPreprocessor
.