An example of how the implementation of Objective-C @ try- @catch is executed at runtime?

Objective-C's low-level runtime headers ( /usr/include/objc ) have the objc-exceptions.h file. It would seem that this is how @try / @catch is implemented by the ObjC compiler.

I am trying to call these functions manually (for experimenting with runtime and implementing ObjC) to catch the "unrecognized selector sent to class" exception.

So basically, all I'm looking for is an example of how to make @try / @catch using low-level execution functions. Thanks in advance!

+6
source share
1 answer

So you want to know how the runtime handles exceptions?

Get ready for disappointment.

Because it is not. ObjC does not have an ABI exception handler, only the SPI you already found. Undoubtedly, you also found that the ABF Objective-C exception is actually the same as the C ++ handling of ABI exceptions . To this end, let's start with the code.

 #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"); } } 

Run clang with -ObjC -O3 (and lose the disgusting amount of debugging information), we get the following:

 _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 

If you compile it with ObjC ++, nothing will change. (Well, that’s not entirely true. The last _objc_terminate turns into a clang personal ___clang_call_terminate ). In any case, this code can be divided into 3 important sections. The first is from _main to the start of LBB0_2 or where our try block occurs. As we scream and throw an exception and catch it in our try block, the compiler went ahead and removed the branch around LBB0_2 and went straight to catch handlers. At the moment, Objective-C, or rather, CoreFoundation, set up an exception object for us, and lib ++ began to search for an exception handler during the necessary unwinding phase.

The second important block of code is from LBB0_2 to the end of LBB0_11 , where our catch and finally blocks live. Because everything is in order, all the code below this is dead (and, I hope, he is being deprived of his freedom), but imagine that this is not so.

The third part is from LBB0_8 until the compiler LBB0_2 transition from NSLog to LBB0_2 , if we did something stupid, for example, try not to catch our exception. This handler instead flips a bit after a call to objc_begin_catch , which forces us to turn around ret and go to objc_exception_rethrow() , which tells the turn handler that we dropped the ball and continue to search for handlers elsewhere.Of course, we are the main ones, therefore there are no other handlers, and std::terminate is called upon exit.

All this to say that you will have a bad time if you want to try to write this material manually. All __cxa_* and ObjC SPI functions throw exception objects in ways you cannot rely on, and (quite pessimistic many) handlers are thrown in very tight order to make sure that the C ++ ABI contract is executed, because if it's not mandates std::terminate specifications are invoked. If you want to take an active listening role, you can override the exception handling material with your own functions and Objective-C has objc_setUncaughtExceptionHandler , objc_setExceptionMatcher objc_setExceptionPreprocessor .

+7
source

Source: https://habr.com/ru/post/895047/


All Articles