¿Cómo invocar una llamada al sistema a través de sysenter en el ensamblaje en línea?

¿Cómo podemos implementar la llamada al sistema utilizando sysenter / syscall directamente en x86 Linux? ¿Alguien puede ayudar? Sería incluso mejor si también puede mostrar el código para la plataforma amd64.

Sé que en x86, podemos usar

__asm__( " movl $1, %eax \n" " movl $0, %ebx \n" " call *%gs:0x10 \n" ); 

para enrutar a sysenter indirectamente.

Pero, ¿cómo podemos codificar usando sysenter / syscall directamente para emitir una llamada al sistema?

Encuentro material http://damocles.blogbus.com/tag/sysenter/ . Pero todavía lo encuentro difícil de descifrar.

Voy a mostrarte cómo ejecutar las llamadas al sistema escribiendo un progtwig que escriba Hello World! a la salida estándar mediante el uso de la llamada al sistema write() . Aquí está la fuente del progtwig sin una implementación de la llamada al sistema real:

 #include  ssize_t my_write(int fd, const void *buf, size_t size); int main(void) { const char hello[] = "Hello world!\n"; my_write(1, hello, sizeof(hello)); return 0; } 

Puede ver que nombré a mi función personalizada de llamada al sistema como my_write para evitar conflictos de nombres con la write “normal”, proporcionada por libc. El rest de esta respuesta contiene la fuente de my_write para i386 y amd64.

i386

Las llamadas al sistema en i386 Linux se implementan utilizando el 128º vector de interrupción, por ejemplo, llamando a int 0x80 en su código de ensamblaje, habiendo establecido previamente los parámetros de antemano, por supuesto. Es posible hacer lo mismo a través de SYSENTER , pero en realidad la ejecución de esta instrucción se logra mediante el VDSO prácticamente asignado a cada proceso en ejecución. Como SYSENTER nunca fue un reemplazo directo de la API int 0x80 , nunca es ejecutada directamente por aplicaciones de usuario; en cambio, cuando una aplicación necesita acceder a un código de kernel, llama a la rutina virtualmente mapeada en el VDSO (eso es lo que call *%gs:0x10 en su código es para), que contiene todo el código que admite la instrucción SYSENTER . Hay bastante de esto debido a cómo funciona realmente la instrucción.

Si quiere leer más sobre esto, eche un vistazo a este enlace . Contiene una descripción bastante breve de las técnicas aplicadas en el kernel y el VDSO.

 #define __NR_write 4 ssize_t my_write(int fd, const void *buf, size_t size) { ssize_t ret; asm volatile ( "int $0x80" : "=a" (ret) : "0"(__NR_write), "b"(fd), "c"(buf), "d"(size) : "cc", "edi", "esi", "memory" ); return ret; } 

Como puede ver, usar la API int 0x80 es relativamente simple. El número de syscall va al registro eax , mientras que todos los parámetros necesarios para el syscall entran respectivamente en ebx , ecx , edx , esi , edi y ebp . Los números de llamada del sistema se pueden obtener leyendo el archivo /usr/include/asm/unistd_32.h . Los prototipos y las descripciones de las funciones están disponibles en la segunda sección del manual, por lo que en este caso write(2) . Como el kernel puede destruir prácticamente todos los registros, coloco todos los GPR restantes en la lista de eflags , así como cc , ya que es probable que el registro eflags también cambie. Tenga en cuenta que la lista de clobber también contiene el parámetro de memory , lo que significa que la instrucción enumerada en la lista de instrucciones hace referencia a la memoria (a través del parámetro buf ).

amd64

Las cosas se ven muy diferentes en la architecture AMD64, que tiene una nueva instrucción llamada SYSCALL . Es muy diferente de la instrucción SYSENTER original, y definitivamente mucho más fácil de usar desde aplicaciones de usuario – realmente se asemeja a una CALL normal, en realidad, y la adaptación de la antigua int 0x80 a la nueva SYSCALL es bastante trivial.

En este caso, el número de la llamada al sistema aún se pasa en el registro rax , pero los registros utilizados para contener los argumentos han cambiado mucho, ya que ahora deben usarse en el siguiente orden: rsi , rsi , rdx , r10 , r8 y r9 . El kernel puede destruir el contenido de los registros rcx y r11 (se usan para guardar algunos de los otros registros mediante SYSCALL ).

 #define __NR_write 1 ssize_t my_write(int fd, const void *buf, size_t size) { ssize_t ret; asm volatile ( "syscall" : "=a" (ret) : "0"(__NR_write), "D"(fd), "S"(buf), "d"(size) : "cc", "rcx", "r11", "memory" ); return ret; } 

Observe cómo, prácticamente, lo único que se debe cambiar son los nombres de registro y la instrucción real utilizada para realizar la llamada. Esto se debe principalmente a las listas de entrada / salida proporcionadas por la syntax de ensamblaje en línea extendida de gcc, que automágicamente proporciona las instrucciones de movimiento apropiadas necesarias para ejecutar la lista de instrucciones.