Как сохранить регистры на x86_64 для подпрограммы обработки прерываний? - PullRequest
24 голосов
/ 27 июля 2011

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

Вот оригинальный код:

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

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

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

Ответы [ 4 ]

18 голосов
/ 23 января 2013

AMD нужно было немного места для добавления новых кодов операций для префиксов REX и некоторых других новых инструкций при разработке 64-разрядных расширений x86. Они изменили значение некоторых кодов операций на эти новые инструкции.

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

Порядок PUSHA был порядком кодирования команды: eax, ecx, edx, ebx, esp, ebp, esi, edi. Обратите внимание, что он избыточно нажал esp! Вам нужно знать esp, чтобы найти данные, которые он нажал!

Если вы конвертируете код из 64-битного кода, код PUSHA все равно не годится, вам нужно обновить его, чтобы протолкнуть новые регистры r8 через 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 забиты) .

Регистры сегментов не содержат значения в длинном режиме (если прерванный поток работает в режиме 32-битной совместимости, вы должны сохранить регистры сегментов, спасибо ughoavgfhw). Фактически, они избавились от большей части сегментации в длинном режиме, но FS все еще зарезервирован для операционных систем, чтобы использовать в качестве базового адреса для локальных данных потока. Значение самого регистра не имеет значения, база FS и GS устанавливаются через MSR 0xC0000100 и 0xC0000101. Предполагая, что вы не будете использовать FS, вам не нужно об этом беспокоиться, просто помните, что любые локальные данные потока, к которым обращается код C, могут использовать TLS любого случайного потока. Будьте осторожны с этим, поскольку библиотеки времени выполнения C используют TLS для некоторых функций (например, strtok обычно использует TLS).

Загрузка значения в FS или GS (даже в пользовательском режиме) приведет к перезаписи MSR FSBASE или GSBASE. Поскольку некоторые операционные системы используют GS в качестве «процессорного локального» хранилища (им нужен способ иметь указатель на структуру для каждого ЦП), им нужно хранить его где-то, что не будет засорено загрузкой GS в пользователе Режим. Чтобы решить эту проблему, для регистра GSBASE зарезервированы две MSR: одна активная и одна скрытая. В режиме ядра GSBASE ядра хранится в обычной GSBASE MSR, а база режима пользователя находится в другой (скрытой) GSBASE MSR. При переключении контекста из режима ядра в контекст пользовательского режима, а также при сохранении контекста режима пользователя и переходе в режим ядра код переключения контекста должен выполнить инструкцию SWAPGS, которая меняет значения видимого и скрытого GSBASE MSR. Поскольку ядро ​​GSBASE безопасно скрыто в другой MSR в пользовательском режиме, код режима пользователя не может заглушить ядро ​​GSBASE путем загрузки значения в GS. Когда ЦП снова входит в режим ядра, код сохранения контекста выполнит SWAPGS и восстановит ядро ​​GSBASE.

8 голосов
/ 28 июля 2011

Учитесь на существующем коде, который делает подобные вещи.Например:

Фактически, «ручное нажатие» regs - единственный способ для AMD64, поскольку PUSHA там не существует.AMD64 не является уникальным в этом аспекте - большинству процессоров, отличных от x86, требуется, чтобы регистр за регистром также сохранял / восстанавливал также в какой-то момент.

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

6 голосов
/ 27 июля 2011

pusha недопустимо в 64-битном режиме, потому что это избыточно. Точно нажать на каждый регистр.

1 голос
/ 23 августа 2017

Привет, это может быть неправильный способ сделать это, но можно создавать макросы вроде

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

...