commit 804542fd4d38b8663fa4fe5e4082126e2aba0211 from: Aleksey Ryndin date: Sat Oct 07 19:07:41 2023 UTC merge refs/heads/main into refs/heads/squat/pinephone commit - 28ceb95471b728bd7bb7e20cea8115bfaddde6d6 commit + 804542fd4d38b8663fa4fe5e4082126e2aba0211 blob - e096ad7fadcef7ee97a35632dcbb6378dc48d12f blob + 1d7b82f7c7b79ba71caeb5ce42aedf435a221ce9 --- capsule/squat/reports/index.gmi +++ capsule/squat/reports/index.gmi @@ -1,3 +1,4 @@ # Блог проекта squⱯt +=> aarch64_exception_model.gmi Конспект документа AArch64 Exception Model (arm, 102412_0103_01_en) (2023-10-06) => 0.0.1.gmi v0.0.1 (2023-03-02) blob - /dev/null blob + 8c947c1d7171dd4ffaa4c9393cdca351d75251df (mode 644) --- /dev/null +++ capsule/squat/reports/aarch64_exception_model.gmi @@ -0,0 +1,197 @@ +# Конспект документа AArch64 Exception Model (arm, 102412_0103_01_en) + +Под конспектом я подразумевают укороченную версию перевода оригинального документа. Я постарался исключить повторяющиеся блоки текста без потери суммарной информации. Так же пропущен перевод некоторых примеров. Информация немного реструктурирована. + +=> learn_the_architecture_-_aarch64_exception_model_102412_0103_01_en.pdf Оригинальный pdf-документ (102412_0103_01_en) + +Исключение это некоторое системное событие, требующее реакции со стороны привилегированного кода. Близким аналогом в других процессорных архитектурах является термин "прерывание" (interrupt). При возникновении исключения, вместо перехода к следующей инструкции, процессор приостанавливает выполнение текущего кода и переходит к исполнению фрагмента кода, отвечающего за обработку исключения. Когда исключение будет обработано, выполнение приостановленного кода может быть возобновлено. + +## Модель уровней исключений + +Модель уровней исключений для архитектуры AArch64 является (в числе прочего) моделью разграничения привилегий. Уровень исключений (Exception level) часто сокращают до аббревиатуры EL. Уровни нумеруются, поэтому для указания конкретного уровня используется запись EL, где x это число от 0 до 3 включительно. Чем ниже численное значение уровня исключений, тем меньшими привилегиями он обладает. То есть наименее привилегированный уровень исключения это EL0. + +Обычно распределение по уровням исключений выглядит так: +* EL0 - пользовательские приложения +* EL1 - ядро операционной системы +* EL2 - гипервизор (Hypervisor) +* EL3 - монитор безопасности (Secure Monitor) + +Обязательными к реализации являются только первые два уровня исключений (EL0 и EL1). Уровни EL2 и EL3 являются необязательными к реализации. Arm архитектура не имеет строго определения какие компоненты на каких уровнях должны располагаться. Но это руководство предполагает, что компоненты распределены как описано в предыдущем абзаце. + +Уровень исключения изменяется в случае: +* Возникновение исключения. +* Возврат из исключения. +* Сброс процессора. +* Переход в режим отладки. +* Выход из режима отладки. + +При возникновении исключения уровень исключения может увеличиться или остаться прежним (никогда не уменьшается). При возврате из исключения уровень исключения может уменьшиться или остаться прежним (никогда не увеличивается). Плюс исключение не может быть обработано на уровне 0 (EL0), то есть если исключение возникает в момент исполнения кода на EL0, то для обработки уровень исключения всегда будет повышен. + +## Привилегии и доступ + +Привилегии, на которые применяется уровень исключения, можно разделить на две группы: +* Привилегии блока управления памятью (Memory Management Unit, MMU) +* Привилегии с точки зрения доступа к ресурсам процессора. + +В архитектуре arm реализована подсистема виртуальной памяти, в которой блок управления (MMU) позволяет назначить разрешения чтения/записи памяти раздельно: для привилегированного кода и для непривилегированного. Код, исполняемый в режиме EL0 считается непривилегированным, а код на остальных уровнях исключений (EL1, EL2, EL3) рассматривается как привилегированный. + +Конфигурация процессора содержится в наборе регистров, доступ к которым контролируется текущим уровнем исключения. Например: VBAR_EL1 это регистр базового адреса векторов (Vector Base Address Register). Суффикс _EL1 говорит нам о том, что для доступа к этом регистру необходим уровень исключений 1 или выше. В arm архитектуре имеется множество регистров со схожими функциями, имена которых различается суффиксом уровня исключений (_ELx). Это физически разные регистры, имеющие собственное кодирование в наборе инструкций. + +Например системный регистр управления (System Control Register, SCTLR), который отвечает за MMU, кэш и выравнивания, в процессоре представлен тремя разными регистрами: +* SCTLR_EL1 - отвечает за уровни исключений 0 и 1. Оба уровня используют одну конфигурацию MMU, поэтому регистра SCTLR_EL0 не существует. Этот подход используется и для большинства других управляющих регистров процессора. +* SCTLR_EL2 - отвечает за уровень исключений 2. +* SCTLR_EL3 - отвечает за уровень исключений 3. + +## Текущее состояние исполнения (Execution state) + +Текущее состояние исполнения (Execution state) определяет разрядность регистров общего назначения и доступный набор инструкций. Семейства Armv8-A и Armv9-A поддерживают два состония исполнения: +* AArch32: 32-х разрядное состояние исполнения, обеспечивающие обратную совместимость с предыдущими архитектурами. В этом состоянии поддерживаются наборы инструкций T32 и A32, а ширина регистров общего назначения составляет 32 бита. +* AArch64: 64-х разрядное состояние исполнения. В этом состоянии поддерживается набор инструкций A64, а ширина регистров общего назначения составляет 64 бита. + +Состояние исполнения может измениться только в момент сброса или при изменении уровня исключений. Изменение состояния исполнения при изменении уровня исключений происходит по следующим правилам: +* При увеличении уровня исключений состояние исполнения остаётся прежним или становится AArch64. +* При уменьшении уровня исключений состояние исполнения остаётся прежним или становится AArch32. + +Это означает, что более привилегированный 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) + +Возможно два состояния безопасности: +* Безопасное состояние (Secure state), когда доступны как безопасное адресное пространство, так и небезопасное. +* Небезопасное состояние (Non-secure state) или обычный мир (Normal world), когда доступно только небезопасное адресное пространство. + +Таким образом реализуется изоляция части данных, которая помещается в безопасное адресное пространство, к которому нет доступа из небезопасного состояния. В режиме безопасного состояния, например, могут работать платёжная система или 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) исключения + +Исключение называется синхронным, если оно вызвано непосредственно исполняемой в момент возникновения инструкцией. Содержимое регистров при вызове синхронного исключения строго соответствует всем инструкциям, которые были выполнены ранее, до инструкции вызвавшей исключение. + +Более детальные примеры синхронных исключений: +* Некорректная инструкция и исключения-ловушки. Попытка выполнить инструкцию UNDEFINED, отключенную инструкцию или инструкцию, недопустимую для на текущем уровне исключений, приведёт к генерации синхронного исключения. А также архитектура позволяет устанавливать ловушки для менее привилегированного кода при попытке выполнить определенные действия. Например ядро на EL1 может установить ловушку выполнения инструкций с плавающей запятой для EL0. По умолчанию блок операций с плавающей запятой может быть совсем отключен и при переключении контекста специфические регистры не нужно сохранять. Но после срабатывания исключения-ловушки ядро может включить блок операций с плавающей запятой, взвести признак необходимости сохранять содержимое соответствующих регистров и повторить операцию, вызвавшую исключение. +* Обращение к памяти. При включенном MMU (блок управления памятью, Memory Management Unit) все инструкции load и store контролируются на предмет их корректности. Поэтому, например, при попытке получения доступа к привилегированному адресу из непривилегированного кода или попытке записи по адресу, доступному только для чтения, произойдёт синхронное исключение. Обработчик исключения получит управление до того, как будет продолжен доступ к памяти. Однако следует помнить, что фатальные ошибки при обращении к памяти могут вызвать и асинхронное исключение, которые будут рассмотрены далее (см. SError). +* Отладочные исключения: инструкция отладочного останова, отладочный останов, пошаговая трассировка и другое. +* Инструкции системных вызовов. Существуют инструкции, которые намерено генерируют исключение. Это инструкции используемые для реализации интерфейса вызова более привилегированного кода. Их часто называют инструкциями системных вызовов (system call). Такие инструкции позволяют выполнить переход между уровнями исключений: SVC (Supervisor Call) для перехода с EL0 в EL1, HVC (Hypervisor Call) для перехода с EL1 в EL2 и SMC (Secure Monitor Call) для перехода с EL1 или EL2 в EL3. + +Поскольку при обработке исключения уровень исключения не может уменьшаться, нельзя, например, вызвать инструкцию SVC с EL2 (из гипервизора), что бы попасть в EL1 (в ядро ОС). + +### Асинхронные (asynchronous) исключения + +Некоторые исключения генерируются внешним источником, поэтому они не синхронны с текущим потоком исполняемых инструкций (например: срабатывания таймера). По определению все исключения не являющиеся синхронными называются асинхронными. + +Более детальные примеры асинхронных исключений: +* Системная ошибка (System Error) SError. Это ошибка, генерируемая от подсистемы памяти в момент возникновения неожиданного события. Текущая инструкция, исполняемая процессором, в общем случае не связана с возникшей ошибкой. Примеры таких ошибок: доступ к памяти, прошедший все проверки MMU, но встретивший ошибку на шине памяти, проверка чётности или кода коррекции ошибок памяти (Error Correction Code, ECC). Генерация SError зависит от конкретной реализации. +* Физические прерывания (Physical interrupts). Одним из способ сообщить о смене состояния периферийного устройства может быть прерывание. Сложные системы могут иметь множество источников прерываний с различными уровнями приоритета, включая возможность вложенной обработки прерываний. Скорость реакции ядра на такие события может быть критически важной и называется задержкой прерывания (interrupt latency). В архитектуре arm есть два типа асинхронных исключений для обработки сигналов от периферии: IRQ и FIQ. Они имеют независимую маршрутизацию и часто используются для реализации защищённых (Secure) и незащищённых (Non-secure) прерываний. Во всех реализация arm для управления IRQ и FIQ используется универсальный контроллер прерываний (Generic Interrupt Controller, GIC). Этот контроллер приоритезирует и маршрутизирует прерывания, отправляя их процессорному ядру. +* Виртуальные прерывания (Virtual interrupts). Система, использующая виртуализацию, имеет более сложные требования к обработке прерываний. Некоторые прерывания может получать гипервизор, а другие могут быть обработаны непосредственно в виртуальной машине. Прерывания, которые видит виртуальная машина, являются виртуальными прерываниями: vSError (Virtual System Error), vIRQ (Virtual IRQ) и vFIQ (Virtual FIQ). Виртуальные прерывания могут быть сгенерированы извне устройством, подключенным к контроллеру прерываний, или могут быть сгенерированы программно. Виртуальные прерывания могут быть доставлены только на EL1. + +🛈 В более ранних версиях arm прерывания FIQ имели более высокий приоритет относительно IRQ. В AArch64 FIQ и IRQ имеют одинаковый приоритет. + +## Обработка исключений + +При обработке исключений в AArch64 используется специальная терминология: +* Принятием исключения (taking an exception) называют момент, когда процессор реагирует на исключение. +* Уровень исключения и состояние, которые исполнялись в момент принятия исключения, называются уровнем и состоянием с которого принимается исключение (taken from). +* Уровень исключения и состояние, которые будут обрабатывать исключение, называются уровнем и состоянием в которые принимается исключение (taken to). + +Например исключение может быть принято с AArch32 EL0 в AArch64 EL1. Обработка исключения будет происходить в AArch64 на уровне исключений EL1. После обработки исключения система может вернуться к состоянию с которого было принято исключение. В нашем примере возврат из исключения перейдет обратно в AArch32 на EL0. При переходе из AArch32 в AArch64 регистры, недоступные в состоянии AArch32, сохраняют свои значения из предыдущего исполнения AArch64. Для регистров, доступных в обоих состояниях исполнения, верхняя половина 64-разрядных регистров содержит либо 0, либо старое значение. + +Соответствие между регистрами AArch32 и AArch64: +* R0-R12 из AArch32 соответствуют в AArch64 регистрам X0-X12 +* Переключаемые регистры SP и LR из AArch32 соответствуют в AArch64 регистрам X13-X23 +* Переключаемые регистры FIQ из AArch32 соответствуют в AArch64 регистрам X24-X30 + +### Сохранение текущего состояния процессора + +В AArch64 существует концепция состояния процессора, известная как PSTATE. Это состояние при принятии исключения записывается в регистр сохранённого состояния программы (Saved Program Status Register): SPSR. PSTATE содержит такие сведения, как текущий уровень исключения и флаги арифметико-логического блока (ALU). В AArch64 так же содержится: +* Флаги состояния +* Элементы управления состоянием выполнения +* Биты маски исключений (DAIF: Debug, SError asynchronous, IRQ, FIQ. Исключение не происходит, если соответствующий бит установлен) +* Биты управления доступом +* Биты управления таймингом +* Биты управления спекулятивным исполнением + +Для каждого уровня исключения существует свой SPSR - SPSR_ELx. При принятии исключения используется SPSR_ELx, в которое принимается исключение. + +Для синхронных исключений и SError помимо прочего заполняется и регистр синдрома исключения (Exception Syndrome Register): ESR. В нём кодируется причина исключения. + +### Маршрутизация и контроллер прерываний + +Каждое исключение имеет свой принимающий уровень исключений (EL), который определяется одним из условий: +* Тип исключения явно указывает на принимающий уровень исключений. Например: инструкция SVC всегда порождает исключение, принимаемое на уровне EL1. +* Исключение маршрутизируется в соответствии с конфигурацией системных регистров. Маршрутизация задается независимо для IRQ, FIQ и SError. + +Настройка маршрутизации осуществляется двумя регистрами: +* Регистр конфигурации гипервизора (Hypervisor Configuration Register): HCR_EL2. Определяет какие исключения будет принимать EL2. +* Регистр конфигурации монитора безопасности (Secure Configuration Register): SCR_EL3. Определяет какие исключения будет принимать EL3. Имеет более высокий приоритет относительно HCR_EL2. + +Исключения, маршрутизация которых не задана ни в 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, где - это 1, 2 или 3. Значения регистров VBAR после сброса не определены, поэтому их необходимо задать явно до включения прерываний. + +Векторные таблицы используют единый формат. Таблица делится на четыре категории: +* VBAR_ELx+0x000 - Исключения с текущего EL при использовании SP_EL0 +* VBAR_ELx+0x200 - Исключения с текущего EL при использовании SP_ELx +* VBAR_ELx+0x400 - Исключения с меньшего EL, где среди уровней с меньшим EL хотя бы один AArch64 +* VBAR_ELx+0x600 - Исключения с меньшего EL, где среди уровней с меньшим EL все AArch32 + +Каждая категория делится на 4 вектора, по одному вектору на тип произошедшего исключения: +* +0x000 - синхронное (Synchronous) исключение +* +0x080 - IRQ/vIRQ +* +0x100 - FIQ/vFIQ +* +0x180 - SError/vSError + +Непосредственно в векторе исключения умещаются 32 процессорных инструкции, которые так же называют обработчиком первого уровня (first-level handler). Обычно код вектора исключения сохраняет в стеке содержимое регистров, которые могут быть изменены, и вызывает функцию более сложного обработчика, специфичного для текущего исключения. После возврата содержимое регистров восстанавливается и исполняется инструкция возврата из исключения (ERET). + +### Указатель стека при принятии исключения + +В AArch64 при принятии исключения можно выбрать один из двух регистров указателя стека: SP_EL0 или SP_EL, где - текущий уровень исключения. Если исполнение происходит на уровне EL0, то безусловно используется SP_EL0. Если текущий уровень исполнения выше чем 0, то указатель сетка определяется битом SP из PSTATE: +* Если PSTATE.SP равен 0, то используется SP_EL0. +* Если PSTATE.SP равен 1, то используется SP_ELx текущего уровня исключений. + +Обычно, даже если указатель стека выбран SP_ELx, обработчик исключения первого уровня сохраняет регистры, значение которых может быть изменено в результате обработки исключения, а затем всё равно переключается на использование SP_EL0. + +## Возврат из исключения + +Возврат из исключения осуществляется инструкцией ERET. Инструкция ERET восстанавливает предыдущее состояние процессора из соответствующего регистра SPSR_EL, где - уровень исключения, на котором выполняется обработчик исключения. А после инструкция передает управление на прерванную инструкцию, адрес которой сохранён в ELR. Оба действия (восстановление предыдущего состояния из SPSR_ELx и передача управления по адресу из ELR_ELx) выполняются атомарно. + +Адрес возврата, сохранённый в ELR_ELx зависит от типа исключения: +* Для инструкций SVC, HVC и SMC регистр ELR будет указывать на следующую инструкцию. +* Для остальных синхронных исключений регистр ELR будет указывать на инструкцию, породившую исключение. +* Для асинхронных исключений регистр ELR будет указывать на следующую инструкцию после последней полностью выполненной инструкции. + +Обработчик исключения может менять содержимое регистра ELR_ELx по своему усмотрению. blob - /dev/null blob + b2ce47fa6076fead9d5baac028779c8ef1d61fd1 (mode 755) Binary files /dev/null and capsule/squat/reports/learn_the_architecture_-_aarch64_exception_model_102412_0103_01_en.pdf differ blob - 875af0d7f9ac8781f8b2fda68dad2e12fa70cd36 blob + 627aabebd87c73d7b23d9057b03c55b4b9ee91d7 --- capsule/vostok/reports/0.1.0.gmi +++ capsule/vostok/reports/0.1.0.gmi @@ -47,3 +47,5 @@ Ещё раз выражаю отдельную большую благодарность всем, кто оставляет обратную связь: * nervuri , Vostok server issues + +=> 0.1.1.gmi Следующая запись блога разработки blob - 56cb604864a4f3e3ffc33fc9899a3634062dafff blob + ececc9e0680299efa56afd1517cd4b599dea0c19 --- capsule/vostok/reports/index.gmi +++ capsule/vostok/reports/index.gmi @@ -1,5 +1,6 @@ # Блог разработки сервера vostok +=> 0.1.1.gmi v0.1.1 (2023-10-07) => 0.1.0.gmi v0.1.0 (2023-09-06) => 0.0.2.gmi v0.0.2 (2023-08-23) => 0.0.1.gmi v0.0.1 (2023-08-17) blob - /dev/null blob + f97d98e8b92ffa351e25e602c0ce43fcf9fec9f9 (mode 644) --- /dev/null +++ capsule/vostok/reports/0.1.1.gmi @@ -0,0 +1,13 @@ +# vostok: сервер Gemini, версия 0.1.1 + +Новая минорная версия получилась какой-то немного выстраданной. Я довольно быстро поймал SIGPIPE, но хотел поднакопить ещё изменений, что бы новая версия стала более содержательной. Но время шло, а я переключился на другие задачи. Но это не значит, что я сворачиваю разработку сервера vostok. Просто пока буду уделять ему чуть меньше времени. + +=> 0.1.0.gmi Предыдущая запись блога разработки + +Что нового в версии 0.1.0: +* Добавлено игнорирование сигнала SIGPIPE +* По традиции: рефакторинг кода + +## Сигнал SIGPIPE + +Тут все просто до безобразия: сигнал SIGPIPE возникает в случае, когда происходит запись в сокет, который уже закрыт другой стороной. В контексте сервера это проявляется, если клиентская сторона закроет соединение до того, как сервер полностью запишет Gemini ответ в сокет. Такого поведения нетрудно добиться при скачивании больших файлов. Поведение по умолчанию при получении такого сигнала - завершение процесса. Хотя в случае сервера vostok это штатная ситуация, которая просто должна быть проигнорирована. Что и было сделано в новой версии.