Пример того, как реализация @ try- @ catch в Objective-C выполняется во время выполнения? - PullRequest
6 голосов
/ 15 августа 2011

В низкоуровневых заголовках времени выполнения Objective-C (/usr/include/objc) есть файл objc-exceptions.h. Казалось бы, именно так @try / @catch реализуется компилятором ObjC.

Я пытаюсь вызвать эти функции вручную (для экспериментов со средой выполнения и реализацией ObjC), чтобы перехватить исключение «нераспознанный селектор, отправленный в класс».

В общем, все, что я ищу, это пример того, как сделать @try / @catch, используя низкоуровневые функции времени выполнения. Заранее спасибо!

1 Ответ

7 голосов
/ 08 февраля 2015

Итак, вы хотите знать, как среда выполнения обрабатывает исключения?

Готовьтесь разочароваться.

Потому что это не так.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.

...