Как сохранить регистры на x86_64 для процедуры обслуживания прерываний?
Я смотрю на старый код из школьного проекта, и, пытаясь скомпилировать его на своем ноутбуке, я столкнулся с некоторыми проблемами. Он был первоначально написан для старой 32-разрядной версии gcc. Во всяком случае, я пытался преобразовать часть сборки в 64-разрядный совместимый код и поразить несколько промахов.
Вот исходный код:
pusha
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushl %ss
pusha
недействителен в режиме 64 бит. Итак, каков был бы правильный способ сделать это в сборке x86_64 в режиме 64 бит?
Должна быть причина, по которой pusha
недействительна в 64-битном режиме, поэтому я чувствую, что ручное нажатие всех регистров может быть не очень хорошей идеей.
Ответы
Ответ 1
Учитесь на существующем коде, который делает подобные вещи. Например:
Фактически, "ручное нажатие" regs - единственный способ для AMD64, так как PUSHA
там не существует. AMD64 не уникален в этом аспекте - большинству процессоров не-x86 требуется регистр за регистром сохранения/восстановления также в некоторый момент.
Но если вы внимательно изучите ссылочный исходный код, то обнаружите, что не все обработчики прерываний требуют сохранения/восстановления всего набора регистров, поэтому есть место для оптимизации.
Ответ 2
AMD нуждалась в некоторой возможности для добавления новых кодов операций для префиксов REX
и некоторых других новых инструкций, когда они разрабатывали 64-разрядные расширения x86. Они изменили значение некоторых кодов операций на эти новые инструкции.
Некоторые из инструкций были просто короткими формами существующих инструкций или в противном случае не были необходимы. PUSHA
была одной из жертв. Неясно, почему они запретили PUSHA
, хотя, похоже, это не перекрывает никаких новых кодов операций с инструкциями. Возможно, они зарезервированы для кодов PUSHA
и POPA
для будущего использования, поскольку они полностью избыточны и не будут быстрее и не будут встречаться достаточно часто в коде, чтобы иметь значение.
Порядок PUSHA
был порядком кодирования команд: eax
, ecx
, edx
, ebx
, esp
, ebp
, esi
, edi
. Обратите внимание, что он избыточно нажал esp
! Вы должны знать esp
, чтобы найти данные, которые он нажал!
Если вы конвертируете код из 64-разрядного кода PUSHA
, то все равно не нужно, вам нужно его обновить, чтобы нажать новые регистры r8
thru r15
. Вам также необходимо сохранить и восстановить намного большее состояние SSE, xmm8
thru xmm15
. Предполагая, что вы собираетесь их сбивать.
Если код обработчика прерываний - это просто заглушка, которая пересылает код C, вам не нужно сохранять все регистры. Вы можете предположить, что компилятор C будет генерировать код, который будет сохранять rbx
, rbp
, rsi
, rdi
и r12
thru r15
. Вам нужно сохранить и восстановить rax
, rcx
, rdx
и r8
thru r11
. (Примечание: на Linux или других платформах System V ABI компилятор будет сохранять rbx
, rbp
, r12
- r15
, вы можете ожидать rsi
и rdi
clobbered).
Регистры сегментов не сохраняют значения в длинном режиме (если прерываемый поток работает в режиме 32-разрядной совместимости, вы должны сохранять регистры сегментов, спасибо ughoavgfhw). Фактически, они избавились от большей части сегментации в длинном режиме, но FS
по-прежнему зарезервирован для использования операционными системами в качестве базового адреса для локальных данных потока. Само значение регистра не имеет значения, база FS
и GS
устанавливается через MSR 0xC0000100
и 0xC0000101
. Предполагая, что вы не будете использовать FS
, вам не нужно об этом беспокоиться, просто помните, что любые локальные данные потока, к которым обращается код C, могут использовать любой случайный поток TLS. Будьте осторожны, потому что библиотеки времени выполнения C используют TLS для некоторых функций (например: strtok обычно использует TLS).
Загрузка значения в FS
или GS
(даже в пользовательском режиме) перезапишет FSBASE
или GSBASE
MSR. Поскольку некоторые операционные системы используют GS
как "локальное" процессорное хранилище (им нужен способ иметь указатель на структуру для каждого ЦП), им необходимо сохранить его где-то, что не будет сбиваться, загрузив GS
в пользователя Режим. Для решения этой проблемы существуют два MSR, зарезервированных для регистра GSBASE
: один активный и один скрытый. В режиме ядра ядро GSBASE
хранится в обычном GSBASE
MSR, а база пользовательского режима находится в другом (скрытом) GSBASE
MSR. Когда контекст переключается из режима ядра в контекст пользовательского режима, а при сохранении контекста пользовательского режима и входа в режим ядра, код переключения контекста должен выполнять команду SWAPGS, которая меняет значения видимого и скрытого GSBASE
MSR. Поскольку ядро GSBASE
безопасно скрыто в другом MSR в пользовательском режиме, код режима пользователя не может сжать ядро GSBASE
, загрузив значение в GS
. Когда CPU снова загрузит режим ядра, код сохранения контекста выполнит SWAPGS
и восстановит ядро GSBASE
.
Ответ 3
pusha
недействителен в 64-битном режиме, поскольку он является избыточным. Нажатие каждого регистра по отдельности - это именно то, что нужно сделать.
Ответ 4
Привет, возможно, это не правильный способ сделать это, но можно создать макросы, например
.macro pushaq
push %rax
push %rcx
push %rdx
push %rbx
push %rbp
push %rsi
push %rdi
.endm # pushaq
и
.macro popaq
pop %rdi
pop %rsi
pop %rbp
pop %rbx
pop %rdx
pop %rcx
pop %rax
.endm # popaq
и, в конце концов, добавить другие регистры r8-15, если нужно