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:
- 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.
- Especificación de id del servicio de kernel solicitado.
- Paso de parámetros para el servicio solicitado.
- 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:
- 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)
- 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
. - 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.
- 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 ()
, donden
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 sistemaopen()
, 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 sistemaopen()
.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
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:
-
eax
contiene el número de syscall, por ejemplo,4
para escribir. Lista completa de 32 bits en el código fuente del núcleo en: https://github.com/torvalds/linux/blob/v4.9/arch/x86/entry/syscalls/syscall_32.tbl#L13 -
ebx
,ecx
yedx
contienen los argumentos de entrada. Esos deben ser deducibles de la firma de cada syscall en la fuente del kernel. Vea también: ¿Cuáles son las convenciones de llamada para las llamadas de sistema de UNIX y Linux en x86-64 y la tabla de llamadas del sistema Linux o la hoja de control en lenguaje ensamblador - el valor de retorno contiene principalmente códigos de error y va a eax (no se muestra en este código por simplicidad)
-
int 0x80
hace la llamada, aunque ahora hay mejores métodos: ¿Qué es mejor "int 0x80" o "syscall"?
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.