El lenguaje de programación estándar en Linux es C. Debido a eso, las mejores descripciones de las llamadas al sistema las mostrarán como funciones C para llamar. Dada su descripción como una función C y el conocimiento de cómo asignarlas a la llamada al sistema real en el ensamblaje, podrá usar cualquier llamada al sistema que desee con facilidad.
En primer lugar, necesita una referencia para todas las llamadas al sistema como aparecerían en un programador C. El mejor que conozco es el Linux man-pages project, en particular la sección system calls.
Tomemos como ejemplo la llamada al sistema write
, ya que es la que está en su pregunta. Como puede ver, el primer parámetro es un entero con signo, que generalmente es un descriptor de archivo devuelto por el open
syscall. Estos descriptores de archivo también podrían haberse heredado de su proceso principal, como suele ocurrir con los tres primeros descriptores de archivos (0 = stdin, 1 = stdout, 2 = stderr). El segundo parámetro es un puntero a un búfer, y el tercer parámetro es el tamaño del búfer (como un entero sin signo). Finalmente, la función devuelve un entero con signo, que es el número de bytes escritos, o un número negativo para un error.
Ahora, ¿cómo asignar esto a la llamada al sistema real? Hay muchas maneras de hacer una llamada al sistema en 32-bit x86 (que es probablemente lo que está usando, de acuerdo con sus nombres de registro); tenga cuidado de que sea completamente diferente en 64-bit x86 (asegúrese de que se está armando en modo de 32 bits y vinculando un ejecutable de 32 bits; consulte this question para ver un ejemplo de cómo las cosas pueden salir mal de lo contrario). El más antiguo, el más simple y el más lento de los de 32 bits x86 es el método int $0x80
.
Para el método int $0x80
, se pone el número de llamadas al sistema en %eax
, y los parámetros en %ebx
, %ecx
, %edx
, %esi
, %edi
y %ebp
, en ese orden. Luego, llama al int $0x80
, y el valor de retorno de la llamada al sistema está en %eax
. Tenga en cuenta que este valor de retorno es diferente de lo que dice la referencia; la referencia muestra cómo la biblioteca C lo devolverá, pero la llamada al sistema devuelve -errno
en caso de error (por ejemplo, -EINVAL
). La biblioteca C moverá esto a errno
y devolverá -1
en ese caso. Vea syscalls(2) y intro(2) para más detalles.
Así, en el ejemplo write
, que pondría el número write
sistema de llamada en %eax
, el primer parámetro (número de descriptor de archivo) en %ebx
, el segundo parámetro (puntero a la cadena) en %ecx
, y el tercer parámetro (longitud de la cadena) en %edx
. La llamada al sistema devolverá en %eax
el número de bytes escritos o el número de error negado (si el valor de retorno está entre -1 y -4095, es un número de error negado).
Finalmente, ¿cómo se encuentran los números de llamada del sistema? Se pueden encontrar en /usr/include/linux/unistd.h
. En mi sistema, esto solo incluye /usr/include/asm/unistd.h
, que finalmente incluye /usr/include/asm/unistd_32.h
, por lo que los números están ahí (para write
, puede ver __NR_write
es 4
). Lo mismo ocurre con los números de error, que provienen del /usr/include/linux/errno.h
(en mi sistema, después de perseguir la cadena de inclusión, encuentro los primeros en /usr/include/asm-generic/errno-base.h
y el resto en /usr/include/asm-generic/errno.h
). Para las llamadas al sistema que usan otras constantes o estructuras, su documentación indica qué encabezados debe mirar para encontrar las definiciones correspondientes.
Ahora, como dije, int $0x80
es el método más antiguo y más lento. Los procesadores más nuevos tienen instrucciones de llamada de sistema especiales que son más rápidas. Para usarlos, el kernel pone a disposición un objeto compartido virtual dinámico (el vDSO
, es como una biblioteca compartida, pero solo en memoria) con una función a la que puede llamar para hacer una llamada al sistema utilizando el mejor método disponible para su hardware. También pone a disposición funciones especiales para obtener la hora actual sin tener que hacer una llamada al sistema, y algunas otras cosas más. Por supuesto, es un poco más difícil de usar si no está utilizando un enlazador dinámico.
También existe otro método anterior, el vsyscall
, que es similar al vDSO
pero utiliza una sola página en una dirección fija. Este método está en desuso, dará lugar a advertencias en el registro del sistema si está utilizando núcleos recientes, puede deshabilitarse en el arranque en núcleos aún más recientes y podría eliminarse en el futuro. No lo uses.
Por supuesto, incluso si busca cada uno en google o en cualquier fuente de código fuente para su implementación, puede encontrar ejemplos directos de cómo se usan, lo que hice. El objetivo de mi publicación es omitir la búsqueda y el análisis con una buena hoja de trucos. Gracias. –