Uno que no existe y, por lo tanto, devuelve -ENOSYS rápidamente.
Desde arco/x86/entrada/entrada_64.S:
#if __SYSCALL_MASK == ~0
cmpq $__NR_syscall_max, %rax
#else
andl $__SYSCALL_MASK, %eax
cmpl $__NR_syscall_max, %eax
#endif
ja 1f /* return -ENOSYS (already in pt_regs->ax) */
movq %r10, %rcx
/*
* This call instruction is handled specially in stub_ptregs_64.
* It might end up jumping to the slow path. If it jumps, RAX
* and all argument registers are clobbered.
*/
#ifdef CONFIG_RETPOLINE
movq sys_call_table(, %rax, 8), %rax
call __x86_indirect_thunk_rax
#else
call *sys_call_table(, %rax, 8)
#endif
.Lentry_SYSCALL_64_after_fastpath_call:
movq %rax, RAX(%rsp)
1:
Utilice un número de llamada del sistema no válido para que el código de despacho simplemente regrese con
eax = -ENOSYS
en lugar de enviar a una función de manejo de llamadas del sistema.
A menos que esto haga que el kernel use el iret
ruta lenta en lugar de sysret
/ sysexit
. Eso podría explicar las mediciones que muestran que un número no válido es 17 ciclos más lento que syscall(SYS_getpid)
, porque el manejo de errores de glibc (estableciendo errno
) probablemente no lo explica. Pero a partir de mi lectura del código fuente del kernel, no veo ninguna razón por la que no siga usando sysret
mientras devuelve -ENOSYS
.
Esta respuesta es para sysenter
, no syscall
. La pregunta originalmente decía sysenter
/ sysret
(lo cual fue raro porque sysexit
va con sysenter
, mientras que sysret
va con syscall
). Respondí basado en sysenter
para un proceso de 32 bits en un kernel x86-64.
Nativo de 64 bits syscall
se maneja de manera más eficiente dentro del kernel. (Actualización; con los parches de mitigación de Meltdown/Spectre, todavía se envía a través de C do_syscall_64
en 4.16-rc2).
Mi ¿Qué sucede si usa la ABI de Linux int 0x80 de 32 bits en código de 64 bits? Preguntas y respuestas brinda una descripción general del lado del kernel de los puntos de entrada de llamadas al sistema desde el modo de compatibilidad en un kernel x86-64 (entry_64_compat.S
). Esta respuesta solo toma las partes relevantes de eso.
Los enlaces en esa respuesta y esto son a las fuentes de Linux 4.12, que no contiene la manipulación de la tabla de páginas de mitigación de Meltdown, por lo que será significativo gastos generales adicionales.
int 0x80
y sysenter
tienen diferentes puntos de entrada. Estás buscando entry_SYSENTER_compat
. AFAIK, sysenter
siempre va allí, incluso si lo ejecuta en un proceso de espacio de usuario de 64 bits. El punto de entrada de Linux empuja una constante __USER32_CS
como el valor de CS guardado, por lo que siempre volverá al espacio de usuario en modo de 32 bits.
Después de presionar registros para construir un struct pt_regs
en la pila del kernel, hay un TRACE_IRQS_OFF
gancho (no tengo idea de cuántas instrucciones equivale a eso), luego call do_fast_syscall_32
que está escrito en C. (Nativo de 64 bits syscall
el envío se realiza directamente desde asm, pero las llamadas al sistema de compatibilidad de 32 bits siempre se envían a través de C).
do_syscall_32_irqs_on
en arch/x86/entry/common.c
es bastante liviano:solo verifique si se está rastreando el proceso (creo que así es como strace
puede conectar llamadas al sistema a través de ptrace
), entonces
...
if (likely(nr < IA32_NR_syscalls)) {
regs->ax = ia32_sys_call_table[nr]( ... arg );
}
syscall_return_slowpath(regs);
}
AFAIK, el kernel puede usar sysexit
después de que esta función regrese.
Por lo tanto, la ruta de retorno es la misma, ya sea que EAX tenga o no un número de llamada de sistema válido, y obviamente regresar sin despachar nada es la ruta más rápida a través de esa función, especialmente en un kernel con mitigación de Spectre donde la rama indirecta en la tabla de punteros de función pasaría por un retpoline y siempre predeciría mal.
Si realmente desea probar sysenter/sysexit sin toda esa sobrecarga adicional, deberá modificar Linux para poner un punto de entrada mucho más simple sin verificar el seguimiento o empujar / abrir todos los registros.
Probablemente también desee modificar la ABI para pasar una dirección de retorno en un registro (como syscall
lo hace por sí solo) en lugar de guardarlo en la pila de espacio de usuario que el sysenter
actual de Linux ABI lo hace; tiene que get_user()
para leer el valor de EIP al que debería volver.
Si toda esta sobrecarga es parte de lo que desea medir, definitivamente está listo con un eax que le brinda -ENOSYS
; en el peor de los casos, se perderá una bifurcación adicional de la verificación de rango si los predictores de bifurcación están activos para esa bifurcación en función de las llamadas normales al sistema de 32 bits.
En este punto de referencia de Brendan Gregg (vinculado desde esta publicación de blog que es una lectura interesante sobre el tema) close(999)
(o algún otro fd que no esté en uso).