Подтвердить что ты не робот

Как сохранить регистры на x86_64 для процедуры обслуживания прерываний?

Я смотрю на старый код из школьного проекта, и, пытаясь скомпилировать его на своем ноутбуке, я столкнулся с некоторыми проблемами. Он был первоначально написан для старой 32-разрядной версии gcc. Во всяком случае, я пытался преобразовать часть сборки в 64-разрядный совместимый код и поразить несколько промахов.

Вот исходный код:

pusha
pushl   %ds
pushl   %es
pushl   %fs
pushl   %gs
pushl   %ss

pusha недействителен в режиме 64 бит. Итак, каков был бы правильный способ сделать это в сборке x86_64 в режиме 64 бит?

Должна быть причина, по которой pusha недействительна в 64-битном режиме, поэтому я чувствую, что ручное нажатие всех регистров может быть не очень хорошей идеей.

4b9b3361

Ответ 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, если нужно