Итак, вы хотите знать, как среда выполнения обрабатывает исключения?
Готовьтесь разочароваться.
Потому что это не так.ObjC не имеет ABI для обработки исключений, только SPI, который вы уже нашли.Несомненно, вы также обнаружили, что исключение ABI Objective-C на самом деле точно такое же, как и исключение 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");
}
}
Запустив clang с -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_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()
, который сообщает обработчику раскручивания, что мы сбросили мяч, и продолжаем искать обработчики где-то еще,Конечно, мы главные, поэтому других обработчиков нет, и std::terminate
вызывается, когда мы уходим.
Все это говорит о том, что у вас будет плохое время, если вы захотите написатьэтот материал от руки.Все функции __cxa_*
и ObjC SPI генерируют объекты исключений таким образом, на которые вы не можете положиться, и (пессимистически много) обработчики испускаются в очень узком порядке , чтобы убедиться, что контракт C ++ ABI выполненпотому что, если это не спецификация, мандаты std::terminate
будут вызваны.Если вы хотите взять на себя активную роль прослушивания, вам разрешено переопределить материал обработки исключений с вашими собственными функциями, а Objective-C имеет objc_setUncaughtExceptionHandler
, objc_setExceptionMatcher
objc_setExceptionPreprocessor
.