在Objective-C的低级运行时 header (/usr/include/objc
)中,有一个objc-exceptions.h
文件。看来这是ObjC编译器实现@try
/@catch
的方式。
我试图手动调用这些功能(用于ObjC运行时和实现的实验),以捕获“发送给类的无法识别的选择器”异常。
因此,基本上,我要寻找的只是一个如何使用低级运行时函数执行@try
/@catch
的示例。提前致谢!
最佳答案
因此,您想知道运行时如何进行异常处理?
准备失望。
因为没有。 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个重要部分。第一个是从_main
到LBB0_2
的开头,即我们的try块发生的位置。因为我们公然抛出一个异常并将其捕获在try
块中,所以编译器已继续进行并删除了LBB0_2
周围的分支,并直接移至catch处理程序。此时,Objective-C(或更准确地说是CoreFoundation)已为我们设置了一个异常对象,并且libC++已在必要的展开阶段中开始搜索异常处理程序。第二个重要的代码块是从
LBB0_2
到LBB0_11
的末尾,即我们的catch
和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_setUncaughtExceptionHandler
,objc_setExceptionMatcher
objc_setExceptionPreprocessor
。