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

Настоятельно рекомендуется при создании 64-битного ядра (для платформы x86_64) дать указание компилятору не использовать 128-байтную красную зону, которую выполняет ABI пользовательского пространства. (Для GCC флаг компилятора -mno-red-zone).

Ядро не будет защищено от прерываний, если оно включено.

Но почему это?

Ответ 1

Цитата из AMD64 ABI:

128-байтная область за пределами местоположения, на которую указывает% rsp, считается зарезервированной и не должна изменяться обработчиками сигналов или прерываний. Поэтому функции могут использовать эту область для временных данных, которые не нужны для вызовов функций. В частности, функции листьев могут использовать эту область для всего кадра стека, а не корректировать указатель стека в прологе и эпилоге. Эта область называется красной зоной.

По сути, это оптимизация - компилятор userland точно знает, сколько красной зоны используется в любой момент времени (в простейшей реализации, весь размер локальных переменных) и может соответствующим образом настроить %rsp, прежде чем вызвать подфункции.

В особенности в листовых функциях это может привести к некоторым преимуществам производительности при отсутствии необходимости в настройке %rsp, поскольку мы можем быть уверены, что во время функции не будет работать какой-либо незнакомый код. (POSIX Signal Handlers можно рассматривать как форму совместной процедуры, но вы можете поручить компилятору настроить регистры перед использованием переменных стека в обработчике сигналов).

В пространстве ядра, когда вы начинаете думать о прерываниях, если эти прерывания делают какие-либо предположения о %rsp, они, скорее всего, будут неправильными - нет никакой определенности в отношении использования Red Zone. Таким образом, вы либо предполагаете, что все это грязно, и бесполезно тратит пространство стека (эффективно работает с гарантированной локальной гарантией на 128 байт в каждой функции), либо вы гарантируете, что прерывания не делают никаких предположений о %rsp - что сложно,

В пользовательском пространстве контекстные переключатели + 128-байтная совокупность стека обрабатывают его для вас.

Ответ 2

В ядро-пространстве вы используете тот же самый стек, который прерывает использование. Когда происходит прерывание, CPU выталкивает адрес возврата и RFLAGS. Это перекрывает 16 байтов ниже rsp. Даже если вы хотели написать обработчик прерываний, который предполагал, что полные 128 байт красной зоны были ценными, это было бы невозможно.


Возможно, у вас может быть внутренний ABI с ядром, который имел небольшую красную зону от rsp-16 до rsp-48 или что-то в этом роде. (Маленький, потому что стек ядра является ценным, и большинство функций не нуждаются во всей красной зоне в любом случае.)

Обработчики прерываний должны были бы sub rsp, 32 перед нажатием любых регистров. (и восстановить его до iret).

Эта идея не будет работать, если обработчик прерывания может быть прерван до запуска sub rsp, 32 или после восстановления rsp до iret. Будет уязвимость, где ценные данные находятся в rsp .. rsp-16.


Другая практическая проблема с этой схемой заключается в том, что AFAIK gcc не имеет настраиваемых параметров красной зоны. Он либо включен, либо выключен. Таким образом, вам нужно будет добавить поддержку ядро-аромата red-zone в gcc/clang, если вы хотите воспользоваться им.

Даже если это безопасно от вложенных прерываний, преимущества довольно малы. Трудность доказать это безопасно в ядре может сделать его не стоит. (И, как я уже сказал, я совсем не уверен, что это можно реализовать безопасно, потому что я думаю, что возможны вложенные прерывания.)


(BTW, см. тег wiki для ссылок на ABI, документирующий красную зону, и другие материалы.)

Ответ 3

В контекстах ядра можно использовать красную зону. IDTentry может указывать индекс стека (ist) 0..7, где 0 является немного особенным. TSS содержит таблицу этих стеков. 1..7 загружаются и используются для начальных регистров, сохраненных в результате исключения/прерывания, и не вложены. Если вы разделяете различные записи исключений по приоритетам (например, NMI является самым высоким и может произойти в любое время) и рассматривайте эти стеки как батуты, вы можете безопасно обрабатывать красные зоны в контекстах типа ядра. То есть вы можете вычесть 128 из сохраненного указателя стека, чтобы получить полезный стек ядра перед включением прерываний или кода, которые могут вызывать исключения.

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

Код в батуте должен быть осторожным (так, это ядро), чтобы не генерировать другие исключения, в то время как он дезинфицирует состояние машины, но обеспечивает хорошее безопасное место для обнаружения патологического гнездования ядра, повреждения стека и т.д... [извините, что так поздно опоздал, заметил это во время поиска чего-то еще].