syscall sys_write linux system-calls

linux - syscall - sys_write x86



¿Cómo acceder a la llamada al sistema desde el espacio de usuario? (3)

Al principio me gustaría proporcionar una definición de llamada al sistema. La llamada al sistema es un proceso de solicitud explícita sincrónica del servicio del kernel en particular desde la aplicación de espacio de usuario. Sincrónico significa que el acto de llamada al sistema está predeterminado al ejecutar la secuencia de instrucciones. Interrupts es un ejemplo de solicitud de servicio del sistema asíncrono, ya que llegan al kernel de forma absolutamente independiente del código que se ejecuta en el procesador. Las excepciones en contraste con las llamadas al sistema son solicitudes síncronas pero implícitas para los servicios del kernel.

La llamada al sistema consiste en cuatro etapas:

  1. Pasar el control al punto particular en el kernel con procesador de conmutación del modo de usuario al modo de kernel y devolverlo con el procesador de conmutación al modo de usuario.
  2. Especificación de id del servicio de kernel solicitado.
  3. Paso de parámetros para el servicio solicitado.
  4. Capturando el resultado del servicio.

En general, todas estas acciones se pueden implementar como parte de una función de biblioteca grande que realiza una serie de acciones auxiliares antes y / o después de la llamada real al sistema. En este caso, podemos decir que la llamada al sistema está incorporada en esta función, pero la función en general no es una llamada al sistema. En otro caso, podemos tener una pequeña función que hace solo estos cuatro pasos y nada más. En este caso podemos decir que esta función es una llamada al sistema. En realidad, puede implementar la llamada al sistema mediante la implementación manual de las cuatro etapas mencionadas anteriormente. Tenga en cuenta que, en este caso, se verá obligado a utilizar Assembler, ya que todos estos pasos dependen completamente de la arquitectura.

Por ejemplo, el entorno Linux / i386 tiene la siguiente convención de llamadas al sistema:

  1. El control del paso del modo de usuario al modo de kernel se puede realizar mediante interrupción de software con el número 0x80 (instrucción de montaje INT 0x80) o mediante la instrucción SYSCALL (AMD) o mediante la instrucción SYSENTER (Intel)
  2. La identificación del servicio del sistema solicitado se especifica mediante el valor entero almacenado en el registro EAX durante la entrada en el modo kernel. El ID de servicio del kernel debe definirse en el formulario _ NR . Puede encontrar todos los identificadores de servicio del sistema en el árbol de origen de Linux en la ruta include/uapi/asm-generic/unistd.h .
  3. Se pueden pasar hasta 6 parámetros a través de los registros EBX (1), ECX (2), EDX (3), ESI (4), EDI (5), EBP (6). El número entre paréntesis es un número secuencial del parámetro.
  4. Kernel devuelve el estado del servicio realizado en el registro EAX. Este valor generalmente usado por glibc para configurar la variable errno.

En las versiones modernas de Linux no hay ninguna macro _syscall (por lo que yo sé). En su lugar, la biblioteca glibc, que es una biblioteca de interfaz principal del kernel de Linux, proporciona una macro especial - INTERNAL_SYSCALL , que se expande en una pequeña porción de código poblada por instrucciones de ensamblador en línea. Este fragmento de código está dirigido a una plataforma de hardware particular e implementa todas las etapas de la llamada al sistema, y ​​debido a esto, esta macro representa una llamada al sistema en sí. También hay otra macro - INLINE_SYSCALL . La última macro proporciona un manejo de errores similar a glibc, según el cual, en la llamada fallida del sistema -1, se devolverá y el número de error se almacenará en la variable errno . Ambas macros se definen en sysdep.h del paquete glibc.

Puedes invocar una llamada al sistema de la siguiente manera:

#include <sysdep.h> #define __NR_<name> <id> int my_syscall(void) { return INLINE_SYSCALL(<name>, <argc>, <argv>); }

donde <name> debe reemplazarse por la cadena de nombre syscall, <id> - por el id del número de servicio del sistema deseado, <argc> - por el número real de parámetros (de 0 a 6) y <argv> - por parámetros reales separados por comas (y comienza por comas si los parámetros están presentes).

Por ejemplo:

#include <sysdep.h> #define __NR_exit 1 int _exit(int status) { return INLINE_SYSCALL(exit, 1, status); // takes 1 parameter "status" }

u otro ejemplo:

#include <sysdep.h> #define __NR_fork 2 int _fork(void) { return INLINE_SYSCALL(fork, 0); // takes no parameters }

Leí algunos párrafos en LKD 1 y simplemente no puedo entender los contenidos a continuación:

Accediendo a la llamada al sistema desde el espacio de usuario

En general, la biblioteca C proporciona soporte para llamadas al sistema. Las aplicaciones de usuario pueden extraer prototipos de funciones de los encabezados estándar y vincularse con la biblioteca C para usar su llamada al sistema (o la rutina de la biblioteca que, a su vez, utiliza su llamada syscall). Sin embargo, si acaba de escribir la llamada al sistema, ¡es dudoso que glibc ya lo admita!

Afortunadamente, Linux proporciona un conjunto de macros para envolver el acceso a las llamadas del sistema. Establece el contenido del registro y emite las instrucciones de captura. Estas macros se denominan _syscall n () , donde n está entre cero y seis. El número corresponde al número de parámetros pasados ​​al syscall porque la macro necesita saber cuántos parámetros esperar y, por lo tanto, ingresar en los registros. Por ejemplo, considere la llamada al sistema open() , definida como

long open(const char *filename, int flags, int mode)

La macro syscall para usar esta llamada al sistema sin el soporte explícito de la biblioteca sería

#define __NR_open 5 _syscall3(long, open, const char *, filename, int, flags, int, mode)

Entonces, la aplicación puede simplemente llamar a open() .

Para cada macro, hay 2 + 2 × n parámetros. El primer parámetro corresponde al tipo de retorno del syscall. El segundo es el nombre de la llamada al sistema. A continuación sigue el tipo y el nombre de cada parámetro en el orden de la llamada del sistema. El __NR_open define está en <asm/unistd.h> ; Es el número de llamada del sistema. La macro _syscall3 expande en una función C con ensamblaje en línea; el ensamblaje realiza los pasos descritos en la sección anterior para insertar el número de llamada del sistema y los parámetros en los registros correctos y emitir la interrupción del software para atrapar el kernel. Colocar esta macro en una aplicación es todo lo que se requiere para usar la llamada al sistema open() .

Escribamos la macro para usar nuestro espléndido nuevo llamado al sistema foo() y luego escribamos un código de prueba para mostrar nuestros esfuerzos.

#define __NR_foo 283 __syscall0(long, foo) int main () { long stack_size; stack_size = foo (); printf ("The kernel stack size is %ld/n", stack_size); return 0; }

¿Qué significa la aplicación simplemente llamar open() ?

Además, para el último código, ¿dónde está la declaración de foo() ? ¿Y cómo puedo hacer que este trozo de código sea compilable y ejecutable? ¿Cuáles son los archivos de encabezado que necesito incluir?

__________
1 Linux Kernel Development , por Robert Love. Archivo PDF en wordpress.com (vaya a la página 81); Google Books result .


Primero debe comprender cuál es la función del kernel de Linux y que las aplicaciones interactúan con el kernel solo a través de las llamadas al sistema .

En efecto, una aplicación se ejecuta en la "máquina virtual" proporcionada por el kernel: se ejecuta en el espacio del usuario y solo puede hacer (en el nivel más bajo de la máquina) el conjunto de instrucciones de la máquina permitidas en el modo de CPU del usuario aumentado por la instrucción ( Por ejemplo, SYSENTER o INT 0x80 ...) se utiliza para hacer llamadas al sistema. Entonces, desde el punto de vista de la aplicación a nivel de usuario, un syscall es una instrucción atómica de pseudo máquina.

El Manual de ensamblaje de Linux explica cómo se puede realizar una llamada al sistema a nivel de ensamblaje (es decir, instrucción de máquina).

El libc de GNU proporciona funciones de C correspondientes a los syscalls. Así, por ejemplo, la función de open es un pegamento pequeño (es decir, un envoltorio) sobre el syscall del número NR__open (está haciendo que el syscall actualice errno ). La aplicación usualmente llama tales funciones C en libc en lugar de hacer el syscall.

Podrías usar algún otro libc . Por ejemplo, el libc de MUSL es de alguna manera "más simple" y su código es quizás más fácil de leer. También está envolviendo los syscalls en bruto en las correspondientes funciones de C.

Si agrega su propio syscall, es mejor que también implemente una función C similar (en su propia biblioteca). Así que deberías tener también un archivo de cabecera para tu biblioteca.

Consulte también las páginas de manual de intro(2) y syscall(2) y syscalls(2) , y el papel de VDSO en syscalls .

Tenga en cuenta que syscalls no son funciones de C. No usan la pila de llamadas (incluso podrían invocarse sin ninguna pila). Un syscall es básicamente un número como NR__open de <asm/unistd.h> , una instrucción de máquina SYSENTER con convenciones sobre qué registros se mantienen antes de los argumentos del syscall y cuáles se mantienen después del resultado [s] del syscall (incluido el fallo resultado, para establecer errno en la biblioteca C envolviendo el syscall). Las convenciones para syscalls no son las convenciones de llamada para funciones C en la especificación ABI (por ejemplo, x86-64 psABI ). Así que necesitas una envoltura de C.


Ejemplo de montaje mínimo ejecutable

hello_world.asm :

section .rodata hello_world db "hello world", 10 hello_world_len equ $ - hello_world section .text global _start _start: mov eax, 4 ; syscall number: write mov ebx, 1 ; stdout mov ecx, hello_world ; buffer mov edx, hello_world_len int 0x80 ; make the call mov eax, 1 ; syscall number: exit mov ebx, 0 ; exit status int 0x80

Compilar y ejecutar:

nasm -w+all -f elf32 -o hello_world.o hello_world.asm ld -m elf_i386 -o hello_world hello_world.o ./hello_world

A partir del código, es fácil deducir:

Por supuesto, el ensamblaje se volverá tedioso rápidamente, y pronto querrá usar los envoltorios de C provistos por glibc / POSIX siempre que pueda, o la macro SYSCALL cuando no pueda.