Acabo de navegar por el árbol de fuentes del kernel de Linux y leí el archivo tools/include/nolibc/nolibc.h .
Vi que la syscall
al sistema en este archivo usa %r8
, %r9
y %r10
en la lista de clobber.
También hay un comentario que dice:
rcx y r8..r11 pueden ser golpeados, otros se conservan.
Hasta donde yo sé, syscall
solo golpea %rax
, %rcx
y %r11
(y memoria).
¿Hay algún ejemplo real de syscall
al sistema que aplaste a %r8
, %r9
y %r10
?
Solo las llamadas al sistema de 32 bits (por ejemplo, a través de int 0x80
) en modo de 64 bits pasan por esos registros, junto con R11. ( ¿Qué sucede si usa la ABI de Linux int 0x80 de 32 bits en código de 64 bits? ).
syscall
guarda o restaura correctamente todos los registros, incluidos R8, R9 y R10, por lo que el espacio de usuario que lo usa puede suponer que mantienen sus valores, excepto el valor de retorno de RAX. (El punto de entrada de llamada al sistema del kernel incluso guarda RCX y R11, pero en ese momento ya han sido sobrescritos por la instrucción de syscall
al sistema con el RIP original y el valor de RFLAGS antes del enmascaramiento).
Esos, con R11, son los registros no heredados que se bloquean en la convención de llamadas a funciones , por lo que el código generado por el compilador para las funciones C dentro del kernel conserva naturalmente R12-R15, incluso si un punto de entrada de ASM no se guardó. a ellos.
Actualmente, el punto de entrada int 0x80
de 64 bits solo presiona 0
para los registros R8-R11 golpeados por llamadas en la estructura de estado del proceso desde la que se restaurará antes de regresar al espacio del usuario, en lugar de los valores de registro originales.
Históricamente, el punto de entrada int 0x80
del espacio de usuario de 32 bits no guardaba ni restauraba esos registros en absoluto. Por lo tanto, sus valores eran cualquier código kernel generado por el compilador que quedara sentado. Se pensó que esto era inocente porque el modo de 32 bits no puede leer esos registros, hasta que se dio cuenta de que el espacio del usuario puede saltar al modo de 64 bits, usando el mismo valor CS que usa el kernel para los 64 bits normales. procesos de espacio de usuario, seleccionando esa entrada GDT de todo el sistema. Así que hubo una fuga de información real de los datos del kernel, que se solucionó poniendo a cero esos registros.
IDK si solía haber o todavía hay un punto de entrada separado del espacio de usuario de 64 bits frente a 32 bits, o cómo difieren en el diseño de struct pt_regs
. La situación histórica en la que int 0x80
filtró r8..r11 no habría tenido sentido para el espacio de usuario de 64 bits; esa fuga habría sido obvia. Entonces, si están unificados ahora, no deben haberlo estado en el pasado.
De acuerdo con x86-64 ABI sobre la sección syscall A.2 AMD64 Linux Kernel Conventions, A.2.1 Calling Conventions [1]:
Las aplicaciones de nivel de usuario se utilizan como registros enteros para pasar la secuencia
%rdi
%rsi
,%rdx
,%rcx
,%r8
y%r9
. La interfaz del núcleo utiliza%rdi
%rsi
,%rdx
,%r10
,%r8
y%r9
.Una llamada al sistema se realiza a través de la instrucción
syscall
. El núcleo destruye los registros%rcx
y%r11
.El número de la
syscall
al sistema debe pasarse en el registro%rax
.Las llamadas al sistema están limitadas a seis argumentos, ningún argumento se pasa directamente a la pila.
Volviendo de la llamada al sistema, el registro
%rax
syscall
el resultado de la llamada al sistema. Un valor en el rango entre -4095 y -1 indica un error, es -errno.Solo los valores de clase INTEGER o clase MEMORY se pasan al núcleo.
De (2), (5) y (6), podemos concluir que Linux x86-64 syscall golpea %rax
, %rcx
y %r11
(y "memory"
).
Enlace: https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/x86-64-psABI [1]