Конспект документа AArch64 Exception Model (arm, 102412_0103_01_en)

Под конспектом я подразумевают укороченную версию перевода оригинального документа. Я постарался исключить повторяющиеся блоки текста без потери суммарной информации. Так же пропущен перевод некоторых примеров. Информация немного реструктурирована.

Оригинальный pdf-документ (102412_0103_01_en)

Исключение это некоторое системное событие, требующее реакции со стороны привилегированного кода. Близким аналогом в других процессорных архитектурах является термин "прерывание" (interrupt). При возникновении исключения, вместо перехода к следующей инструкции, процессор приостанавливает выполнение текущего кода и переходит к исполнению фрагмента кода, отвечающего за обработку исключения. Когда исключение будет обработано, выполнение приостановленного кода может быть возобновлено.

Модель уровней исключений

Модель уровней исключений для архитектуры AArch64 является (в числе прочего) моделью разграничения привилегий. Уровень исключений (Exception level) часто сокращают до аббревиатуры EL. Уровни нумеруются, поэтому для указания конкретного уровня используется запись EL<x>, где x это число от 0 до 3 включительно. Чем ниже численное значение уровня исключений, тем меньшими привилегиями он обладает. То есть наименее привилегированный уровень исключения это EL0.

Обычно распределение по уровням исключений выглядит так:

Обязательными к реализации являются только первые два уровня исключений (EL0 и EL1). Уровни EL2 и EL3 являются необязательными к реализации. Arm архитектура не имеет строго определения какие компоненты на каких уровнях должны располагаться. Но это руководство предполагает, что компоненты распределены как описано в предыдущем абзаце.

Уровень исключения изменяется в случае:

При возникновении исключения уровень исключения может увеличиться или остаться прежним (никогда не уменьшается). При возврате из исключения уровень исключения может уменьшиться или остаться прежним (никогда не увеличивается). Плюс исключение не может быть обработано на уровне 0 (EL0), то есть если исключение возникает в момент исполнения кода на EL0, то для обработки уровень исключения всегда будет повышен.

Привилегии и доступ

Привилегии, на которые применяется уровень исключения, можно разделить на две группы:

В архитектуре arm реализована подсистема виртуальной памяти, в которой блок управления (MMU) позволяет назначить разрешения чтения/записи памяти раздельно: для привилегированного кода и для непривилегированного. Код, исполняемый в режиме EL0 считается непривилегированным, а код на остальных уровнях исключений (EL1, EL2, EL3) рассматривается как привилегированный.

Конфигурация процессора содержится в наборе регистров, доступ к которым контролируется текущим уровнем исключения. Например: VBAR_EL1 это регистр базового адреса векторов (Vector Base Address Register). Суффикс _EL1 говорит нам о том, что для доступа к этом регистру необходим уровень исключений 1 или выше. В arm архитектуре имеется множество регистров со схожими функциями, имена которых различается суффиксом уровня исключений (_ELx). Это физически разные регистры, имеющие собственное кодирование в наборе инструкций.

Например системный регистр управления (System Control Register, SCTLR), который отвечает за MMU, кэш и выравнивания, в процессоре представлен тремя разными регистрами:

Текущее состояние исполнения (Execution state)

Текущее состояние исполнения (Execution state) определяет разрядность регистров общего назначения и доступный набор инструкций. Семейства Armv8-A и Armv9-A поддерживают два состония исполнения:

Состояние исполнения может измениться только в момент сброса или при изменении уровня исключений. Изменение состояния исполнения при изменении уровня исключений происходит по следующим правилам:

Это означает, что более привилегированный 32-х разрядный код (AArch32) не может существовать при менее привилегированном 64-х разрядном (AArch64). То есть, например, 32-х битное ядро ОС не может обслуживать 64-х битное пользовательское приложение.

Архитектура Armv8-A поддерживает AArch32 и AArch64 на всех уровнях исключений. То есть все четыре уровня, теоретически, могут быть 32-х битными, но это может быть ограничено конрекретной реализацией. Начальное состояние после сброса не задано и определяется конкретной реализацией. Для более конкретной информации стоит обратиться к техническому справочному руководству (Technical Reference Manual, TRM) процессора.

Архитектура Armv9-A поддерживает AArch64 на всех уровнях исключений. А AArch32 может быть только на нулевом уровне исключений (EL0). Начальное состояние после сброса всегда AArch64.

Состояние исполнения текущего уровня исключений определяется управляющим регистром следующего (более привилегированного) уровня исключений: регистры HCR_EL2 (ypervisor Configuration Register) и SCR_EL3 (Secure Configuration Register).

Текущее состояние безопасности (Security state)

Возможно два состояния безопасности:

Таким образом реализуется изоляция части данных, которая помещается в безопасное адресное пространство, к которому нет доступа из небезопасного состояния. В режиме безопасного состояния, например, могут работать платёжная система или DRM, храня изолировано платёжную информацию или криптографические ключи.

Безопасный режим возможен только на процессорах с TrustZone. В этом случае режим состояния безопасности выбирается битом SCR_EL3.NS.

Оба состояния безопасности (Secure state и Non-secure state) могут в общем случае присутствовать на первых трех уровнях исключений (EL0, EL1, EL2). В Armv8-A EL3 всегда работает в безопасном режиме. Armv9-A поддерживает расширение Realm Management (Realm Management Extension, RME). RME вводит еще одно состояние исполнения (Realm) и дополнительно изолирует EL3 от всех других, помещая его в новое корневое состояние (root).

Типы исключений

В архитектуре arm исключения делятся на два больших типа: синхронные (synchronous) и асинхронные (asynchronous).

Синхронные (synchronous) исключения

Исключение называется синхронным, если оно вызвано непосредственно исполняемой в момент возникновения инструкцией. Содержимое регистров при вызове синхронного исключения строго соответствует всем инструкциям, которые были выполнены ранее, до инструкции вызвавшей исключение.

Более детальные примеры синхронных исключений:

Поскольку при обработке исключения уровень исключения не может уменьшаться, нельзя, например, вызвать инструкцию SVC с EL2 (из гипервизора), что бы попасть в EL1 (в ядро ОС).

Асинхронные (asynchronous) исключения

Некоторые исключения генерируются внешним источником, поэтому они не синхронны с текущим потоком исполняемых инструкций (например: срабатывания таймера). По определению все исключения не являющиеся синхронными называются асинхронными.

Более детальные примеры асинхронных исключений:

🛈 В более ранних версиях arm прерывания FIQ имели более высокий приоритет относительно IRQ. В AArch64 FIQ и IRQ имеют одинаковый приоритет.

Обработка исключений

При обработке исключений в AArch64 используется специальная терминология:

Например исключение может быть принято с AArch32 EL0 в AArch64 EL1. Обработка исключения будет происходить в AArch64 на уровне исключений EL1. После обработки исключения система может вернуться к состоянию с которого было принято исключение. В нашем примере возврат из исключения перейдет обратно в AArch32 на EL0. При переходе из AArch32 в AArch64 регистры, недоступные в состоянии AArch32, сохраняют свои значения из предыдущего исполнения AArch64. Для регистров, доступных в обоих состояниях исполнения, верхняя половина 64-разрядных регистров содержит либо 0, либо старое значение.

Соответствие между регистрами AArch32 и AArch64:

Сохранение текущего состояния процессора

В AArch64 существует концепция состояния процессора, известная как PSTATE. Это состояние при принятии исключения записывается в регистр сохранённого состояния программы (Saved Program Status Register): SPSR. PSTATE содержит такие сведения, как текущий уровень исключения и флаги арифметико-логического блока (ALU). В AArch64 так же содержится:

Для каждого уровня исключения существует свой SPSR - SPSR_ELx. При принятии исключения используется SPSR_ELx, в которое принимается исключение.

Для синхронных исключений и SError помимо прочего заполняется и регистр синдрома исключения (Exception Syndrome Register): ESR. В нём кодируется причина исключения.

Маршрутизация и контроллер прерываний

Каждое исключение имеет свой принимающий уровень исключений (EL), который определяется одним из условий:

Настройка маршрутизации осуществляется двумя регистрами:

Исключения, маршрутизация которых не задана ни в SCR_EL3, ни в HCR_EL2 будут приниматься в EL1.

При сбросе значение регистров HCR_EL2 и SCR_EL3 не определено и должно быть задано явным образом.

Для более детальной маршрутизации, управления и приоритизации используется контроллер прерываний arm (Generic Interrupt Controller, GIC). Он позволяет существенно снизить накладные расходы при виртуализации. Технические детали можно найти в техническом руководстве GIC.

🛈 Нельзя перенаправлять исключение на нереализованный уровень исключений. Такое поведение является неопределенным (UNDEFINED). Так же нельзя возвращаться из исключения на отключенный или нереализованный уровень исключений. Попытка выполнения инструкции возврата приведет к возникновению ошибки.

Маскирование (Masking)

Физические и виртуальные асинхронные исключения могут быть временно замаскированы. При этом исключения будут оставаться в ожидании начала обработки до момента, пока маска не будет снята.

Синхронные исключения не могут быть замаскированы, так как они непосредственно связаны с текущей инструкцией и не могут быть отложены или проигнорированы.

Исключения, принимаемые на более высоком уровне исключений, не могут быть замаскированы с более низкого уровня исключений. Исключения принимаемые на более низком уровне исключений, замаскированы всегда, пока исполняется более высокий уровень исключений. Это, в числе прочего, согласуется с тем, что нельзя понизить привилегии, принимая возникшее исключение.

В расширениях 2021 года, Armv8.8-A и Armv9.3-A, добавлена поддержка немаскируемых прерываний (Non-maskable interrupt, NMI). Если эта возможность включена, то такое прерывание может быть доставлено процессору несмотря на маски.

Таблицы векторов (vector tables)

При принятии исключения процессор исполняет код обработчика исключения. Место в памяти, где хранится обработчик, называется вектором исключения.

🛈 В отличии от других архитектур, в arm вектор исключения хранит не адрес обработчика, а непосредственно код самого обработчика.

Векторы исключений вместе образуют таблицу векторов исключений. Каждый уровень исключений, который может принимать исключение, имеет свою собственную таблицу векторов, базовый адрес которой определяется собственным регистром базового адреса векторов (Vector Base Address Register) - VBAR_EL<x>, где <x> - это 1, 2 или 3. Значения регистров VBAR после сброса не определены, поэтому их необходимо задать явно до включения прерываний.

Векторные таблицы используют единый формат. Таблица делится на четыре категории:

Каждая категория делится на 4 вектора, по одному вектору на тип произошедшего исключения:

Непосредственно в векторе исключения умещаются 32 процессорных инструкции, которые так же называют обра��отчиком первого уровня (first-level handler). Обычно код вектора исключения сохраняет в стеке содержимое регистров, которые могут быть изменены, и вызывает функцию более сложного обработчика, специфичного для текущего исключения. После возврата содержимое регистров восстанавливается и исполняется инструкция возврата из исключения (ERET).

Указатель стека при принятии исключения

В AArch64 при принятии исключения можно выбрать один из двух регистров указателя стека: SP_EL0 или SP_EL<x>, где <x> - текущий уровень исключения. Если исполнение происходит на уровне EL0, то безусловно используется SP_EL0. Если текущий уровень исполнения выше чем 0, то указатель сетка определяется битом SP из PSTATE:

Обычно, даже если указатель стека выбран SP_ELx, обработчик исключения первого уровня сохраняет регистры, значение которых может быть изменено в результате обработки исключения, а затем всё равно переключается на использование SP_EL0.

Возврат из исключения

Возврат из исключения осуществляется инструкцией ERET. Инструкция ERET восстанавливает предыдущее состояние процессора из соответствующего регистра SPSR_EL<x>, где <x> - уровень исключения, на котором выполняется обработчик исключения. А после инструкция передает управление на прерванную инструкцию, адрес которой сохранён в ELR. Оба действия (восстановление предыдущего состояния из SPSR_ELx и передача управления по адресу из ELR_ELx) выполняются атомарно.

Адрес возврата, сохранённый в ELR_ELx зависит от типа исключения:

Обработчик исключения может менять содержимое регистра ELR_ELx по своему усмотрению.