assembly x86-64 isr

assembly - ¿Cómo guardar los registros en x86_64 para una rutina de servicio de interrupción?



x86-64 isr (4)

AMD necesitaba espacio para agregar nuevos códigos de operación para prefijos REX y otras instrucciones nuevas cuando desarrollaron las extensiones x86 de 64 bits. Cambiaron el significado de algunos de los códigos de operación a esas nuevas instrucciones.

Varias de las instrucciones eran simplemente formas cortas de instrucciones existentes o, de lo contrario, no eran necesarias. PUSHA fue una de las víctimas. PUSHA embargo, no está claro por qué prohibieron PUSHA , no parece superponerse a ningún nuevo código de instrucción. Quizás estén reservados los PUSHA POPA PUSHA y POPA para uso futuro, ya que son completamente redundantes, no serán más rápidos y no aparecerán con la frecuencia suficiente en el código como para ser importantes.

El orden de PUSHA fue el orden de las instrucciones de codificación: eax , ecx , edx , ebx , esp , ebp , esi , edi . Tenga en cuenta que empujó redundantemente esp ! ¡Necesitas saber esp para encontrar los datos que empujó!

Si está convirtiendo el código de 64 bits, el código PUSHA no es bueno de todos modos, debe actualizarlo para impulsar los nuevos registros r8 a r15 . También debe guardar y restaurar un estado SSE mucho mayor, xmm8 a xmm15 . Asumiendo que los vas a golpear.

Si el código del controlador de interrupción es simplemente un código auxiliar que se reenvía al código C, no es necesario que guarde todos los registros. Puede suponer que el compilador de C generará código que preservará rbx , rbp , rsi , rsi y r12 a r15 . Solo debe guardar y restaurar rax , rcx , rdx y r8 a r11 . (Nota: en Linux u otras plataformas ABI de System V, el compilador conservará rbx , rbp , r12 - r15 , puede esperar que rsi y rsi ) .

Los registros de segmento no tienen ningún valor en modo largo (si el subproceso interrumpido se está ejecutando en modo de compatibilidad de 32 bits, debe conservar los registros de segmento, gracias ughoavgfhw). En realidad, eliminaron la mayor parte de la segmentación en modo largo, pero FS todavía está reservado para que los sistemas operativos lo utilicen como una dirección base para los datos locales de subprocesos. El valor del registro en sí no importa, la base de FS y GS se establece a través de los MSR 0xC0000100 y 0xC0000101 . Suponiendo que no utilizará FS , no tiene que preocuparse por eso, solo recuerde que cualquier información local de subprocesos a la que acceda el código C podría estar utilizando el TLS de cualquier subproceso aleatorio. Tenga cuidado con eso porque las bibliotecas de tiempo de ejecución de C usan TLS para algunas funcionalidades (ejemplo: strtok generalmente usa TLS).

Cargar un valor en FS o GS (incluso en modo de usuario) sobrescribirá la FSBASE o GSBASE MSR. Dado que algunos sistemas operativos utilizan GS como almacenamiento de "procesador local" (necesitan una forma de tener un puntero a una estructura para cada CPU), necesitan mantenerlo en un lugar que no se vea afectado al cargar GS en modo de usuario. Para resolver este problema, hay dos MSRs reservados para el registro GSBASE : uno activo y otro oculto. En el modo kernel, la GSBASE del kernel se mantiene en el GSBASE MSR habitual y la base del modo de usuario está en el otro (oculto) GSBASE MSR. Cuando el contexto cambia del modo de kernel al contexto de modo de usuario y cuando se guarda un contexto de modo de usuario y se ingresa en el modo de kernel, el código de cambio de contexto debe ejecutar la instrucción SWAPGS, que intercambia los valores del GSBASE MSR visible y oculto. Como la GSBASE del kernel está oculta de manera segura en el otro MSR en modo de usuario, el código del modo de usuario no puede GSBASE la GSBASE del kernel al cargar un valor en GS . Cuando la CPU vuelve a ingresar al modo kernel, el código de guardado del contexto ejecutará SWAPGS y restaurará la GSBASE del kernel.

Estoy viendo un código antiguo de un proyecto escolar, y al intentar compilarlo en mi computadora portátil me encontré con algunos problemas. Originalmente fue escrito para una versión antigua de 32 bits de gcc. De todos modos, estaba intentando convertir parte del ensamblaje a un código compatible de 64 bits y tuve algunos inconvenientes.

Aquí está el código original:

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

pusha no es válido en el modo de 64 bits. Entonces, ¿cuál sería la forma correcta de hacer esto en el ensamblaje x86_64 mientras está en modo de 64 bits?

Tiene que haber una razón por la cual pusha no es válido en el modo de 64 bits, por lo que tengo la sensación de que presionar manualmente todos los registros puede que no sea una buena idea.


Aprende del código existente que hace este tipo de cosas. Por ejemplo:

De hecho, "presionar manualmente" las reglas es la única forma en AMD64 ya que PUSHA no existe allí. AMD64 no es único en este aspecto: la mayoría de las CPU que no son x86 sí requieren guardar / restaurar registros por registro también en algún momento.

Pero si inspecciona el código fuente referenciado de cerca, encontrará que no todos los manejadores de interrupciones requieren guardar / restaurar todo el conjunto de registros, por lo que hay espacio para optimizaciones.


Hola, puede que no sea la forma correcta de hacerlo, pero se pueden crear macros como

.macro pushaq push %rax push %rcx push %rdx push %rbx push %rbp push %rsi push %rdi .endm # pushaq

y

.macro popaq pop %rdi pop %rsi pop %rbp pop %rbx pop %rdx pop %rcx pop %rax .endm # popaq

y, finalmente, agregar los otros registros r8-15 si es necesario


pusha no es válido en modo de 64 bits porque es redundante. Presionar cada registro individualmente es exactamente lo que hay que hacer.