¿Cuáles son las convenciones de llamadas para las llamadas al sistema UNIX y Linux en i386 y x86-64?

147

Los siguientes enlaces explican las convenciones de llamadas al sistema x86-32 tanto para UNIX (sabor BSD) como para Linux:

Pero, ¿cuáles son las convenciones de llamadas al sistema x86-64 en UNIX y Linux?

garras
fuente
No existe un "estándar" para las convenciones de llamadas de Unix. Por supuesto, Linux, pero estoy seguro de que Solaris, OpenBSD, Linux y Minix probablemente tengan convenciones de llamada diferentes, al menos ligeramente diferentes, y todas son unix.
Earlz
2
Eso no es del todo cierto: hay un conjunto de ABI de UNIX disponibles para la mayoría de los tipos de máquinas, lo que permite a los compiladores de C lograr la interoperabilidad. Los compiladores de C ++ tienen un problema mayor.
Jonathan Leffler
1
Los dos están en lo correcto. Estoy buscando FreeBSD y Linux.
garras
Agradecería si la respuesta contiene información sobre qué registros se conservan en todas las llamadas al sistema. Por supuesto, el puntero de la pila es, (a menos que se cambie de forma controlada en la llamada __NR_clone), pero ¿son sus otros?
Albert van der Horst
@AlbertvanderHorst: sí, acabo de actualizar la respuesta wiki con los detalles de 32 bits. 64 bits ya era preciso: rcx y r11 se destruyen debido a la forma en que sysretfunciona, junto con el reemplazo de rax con el valor de retorno. Todos los demás registros se conservan en amd64.
Peter Cordes

Respuestas:

230

Lectura adicional para cualquiera de los temas aquí: La guía definitiva para las llamadas al sistema Linux


Verifiqué esto usando GNU Assembler (gas) en Linux.

Interfaz de kernel

x86-32 también conocido como i386 Linux System Call Convention:

En x86-32, los parámetros para la llamada al sistema Linux se pasan mediante registros. %eaxpara syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp se utilizan para pasar 6 parámetros a las llamadas del sistema.

El valor de retorno está en %eax. Todos los demás registros (incluidos los EFLAGS) se conservan en todo el int $0x80.

Tomé el siguiente fragmento del Tutorial de ensamblaje de Linux, pero tengo dudas al respecto. Si alguien puede mostrar un ejemplo, sería genial.

Si hay más de seis argumentos, %ebxdebe contener la ubicación de la memoria donde se almacena la lista de argumentos, pero no se preocupe por esto porque es poco probable que use una llamada al sistema con más de seis argumentos.

Para ver un ejemplo y un poco más de lectura, consulte http://www.int80h.org/bsdasm/#alternate-calling-convention . Otro ejemplo de Hello World para i386 Linux usando int 0x80: Hola, ¿mundo en lenguaje ensamblador con llamadas al sistema Linux?

Hay una forma más rápida de hacer llamadas al sistema de 32 bits: usando sysenter. El núcleo mapea una página de memoria en cada proceso (el vDSO), con el lado del espacio de usuario de la sysenterdanza, que tiene que cooperar con el núcleo para que pueda encontrar la dirección de retorno. Arg para registrar la asignación es lo mismo que para int $0x80. Normalmente debería llamar al vDSO en lugar de usarlo sysenterdirectamente. (Consulte la Guía definitiva para las llamadas al sistema Linux para obtener información sobre cómo vincular y llamar al vDSO, y para obtener más información sysentery todo lo demás relacionado con las llamadas al sistema).

x86-32 [Gratis | Abrir | Red | DragonFly] Convención del sistema BSD UNIX:

Los parámetros se pasan en la pila. Empuje los parámetros (último parámetro empujado primero) en la pila. Luego inserte una información ficticia adicional de 32 bits (en realidad no son datos ficticios. Consulte el siguiente enlace para obtener más información) y luego brinde una instrucción de llamada al sistemaint $0x80

http://www.int80h.org/bsdasm/#default-calling-convention


x86-64 Convención de llamada al sistema Linux:

x86-64 Mac OS X es similar pero diferente . TODO: comprueba lo que * BSD hace.

Consulte la sección: "A.2 Convenciones del kernel de Linux AMD64 " de la interfaz binaria del sistema V Suplemento del procesador de arquitectura AMD64 . Las últimas versiones de las psABI i386 y x86-64 System V se pueden encontrar enlazadas desde esta página en el repositorio del mantenedor de ABI . (Ver también el etiqueta wiki para enlaces ABI actualizados y muchas otras cosas buenas sobre x86 asm.)

Aquí está el fragmento de esta sección:

  1. Las aplicaciones de nivel de usuario se utilizan como registros enteros para pasar la secuencia% rdi,% rsi,% rdx,% rcx,% r8 y% r9. La interfaz del kernel utiliza% rdi,% rsi,% rdx,% r10,% r8 y% r9.
  2. Una llamada al sistema se realiza a través de las syscallinstrucciones . Este clobbers% rcx y% r11 , así como el valor de retorno% rax, pero se conservan otros registros.
  3. El número de la llamada al sistema debe pasarse en el registro% rax.
  4. Las llamadas al sistema están limitadas a seis argumentos, no se pasa ningún argumento directamente en la pila.
  5. Al regresar de la llamada al sistema, el registro% rax contiene el resultado de la llamada al sistema. Un valor en el rango entre -4095 y -1 indica un error, lo es -errno.
  6. Solo los valores de la clase INTEGER o de la clase MEMORY se pasan al kernel.

Recuerde que esto es del apéndice específico de Linux al ABI, e incluso para Linux es informativo, no normativo. (Pero de hecho es exacto).

Este int $0x80ABI de 32 bits se puede usar en código de 64 bits (pero no se recomienda). ¿Qué sucede si usa la ABI de Linux int 0x80 de 32 bits en código de 64 bits? Todavía trunca sus entradas a 32 bits, por lo que no es adecuado para punteros, y pone a cero r8-r11.

Interfaz de usuario: función de llamada

Convención de llamada a funciones x86-32:

En x86-32 los parámetros se pasaron en la pila. El último parámetro se introdujo primero en la pila hasta que se hayan completado todos los parámetros y luego callse haya ejecutado la instrucción. Esto se usa para llamar a las funciones de la biblioteca C (libc) en Linux desde el ensamblado.

Las versiones modernas del i386 System V ABI (utilizado en Linux) requieren una alineación de 16 bytes %espantes de a call, como siempre ha requerido el x86-64 System V ABI. Los Callees pueden asumir eso y usar cargas / almacenes SSE de 16 bytes que fallan sin alinearse. Pero históricamente, Linux solo requería una alineación de pila de 4 bytes, por lo que se requería un trabajo adicional para reservar espacio alineado naturalmente incluso para un byte de 8 bytes doubleo algo así.

Algunos otros sistemas modernos de 32 bits aún no requieren una alineación de pila de más de 4 bytes.


x86-64 Convención de llamada a la función de espacio de usuario del sistema V:

x86-64 System V pasa args en registros, que es más eficiente que la convención de args de pila de i386 System V. Evita la latencia y las instrucciones adicionales de almacenar los argumentos en la memoria (caché) y luego volver a cargarlos en la persona que llama. Esto funciona bien porque hay más registros disponibles y es mejor para las CPU modernas de alto rendimiento donde la latencia y la ejecución fuera de orden son importantes. (El i386 ABI es muy antiguo).

En este nuevo mecanismo: Primero, los parámetros se dividen en clases. La clase de cada parámetro determina la manera en que se pasa a la función llamada.

Para obtener información completa, consulte: "3.2 Secuencia de llamada de función" de la interfaz binaria de la aplicación System V AMD64 Architecture Processor Supplement que lee, en parte:

Una vez que se clasifican los argumentos, los registros se asignan (en orden de izquierda a derecha) para pasar de la siguiente manera:

  1. Si la clase es MEMORY, pase el argumento en la pila.
  2. Si la clase es INTEGER, se utiliza el siguiente registro disponible de la secuencia% rdi,% rsi,% rdx,% rcx,% r8 y% r9

También lo %rdi, %rsi, %rdx, %rcx, %r8 and %r9son los registros en orden utilizados para pasar parámetros de entero / puntero (es decir, clase INTEGER) a cualquier función libc desde el ensamblado. % rdi se usa para el primer parámetro INTEGER. % rsi para el segundo,% rdx para el tercero y así sucesivamente. Entonces se calldeben dar instrucciones. La pila ( %rsp) debe estar alineada con 16B cuando se callejecuta.

Si hay más de 6 parámetros INTEGER, el séptimo parámetro INTEGER y más tarde se pasan a la pila. (La persona que llama aparece, igual que x86-32.)

Los primeros 8 argumentos de coma flotante se pasan en% xmm0-7, más tarde en la pila. No hay registros de vector de llamada preservada. (Una función con una combinación de FP y argumentos enteros puede tener más de 8 argumentos de registro total).

Las funciones variables ( comoprintf ) siempre necesitan %al= el número de argumentos de registro FP.

Hay reglas sobre cuándo empacar estructuras en registros ( rdx:raxal regreso) vs. en memoria. Consulte la ABI para obtener detalles y verifique la salida del compilador para asegurarse de que su código concuerde con los compiladores sobre cómo se debe pasar / devolver algo.


Tenga en cuenta que la convención de llamada de función x64 de Windows tiene múltiples diferencias significativas con respecto al Sistema x86-64 V, como el espacio de sombra que debe ser reservado por la persona que llama (en lugar de una zona roja) y xmm6-xmm15 con llamada preservada. Y reglas muy diferentes para qué argumento va en qué registro.

garras
fuente
1
En Linux 32 "todos los registros excepto ax bx cd dx si di bp se conservan". No puedo pensar en ninguna ...
Albert van der Horst
En amd64, si hay más de 6 parámetros y se pasan en la pila, ¿quién es responsable de limpiar la pila después de la llamada, la persona que llama o la persona que llama?
Nicolás
1
@ Nicolás: la persona que llama limpia la pila. Actualicé la respuesta con más detalles sobre la convención de llamada a funciones.
Peter Cordes
1
Si usa el int 0x80ABI de Linux en código de 64 bits, esto es exactamente lo que sucede: stackoverflow.com/questions/46087730/… . Tiene ceros r8-r11 y funciona exactamente igual que cuando se ejecuta en un proceso de 32 bits. En esas preguntas y respuestas, tengo un ejemplo que muestra que funciona o falla al truncar un puntero. Y también busqué en la fuente del núcleo para mostrar por qué se comporta de esa manera.
Peter Cordes
1
@EvanCarroll: El fragmento (texto citado) se encuentra en el enlace dado Tutorial de ensamblaje de Linux específicamente en la sección 4.3 Llamadas del sistema Linux
Michael Petch
14

¿Quizás estás buscando el x86_64 ABI?

Si eso no es exactamente lo que busca, use 'x86_64 abi' en su motor de búsqueda preferido para encontrar referencias alternativas.

Jonathan Leffler
fuente
55
en realidad, solo quiero la convención de llamadas al sistema. esp para UNIX (FreeBSD)
garras
3
@claws: la convención de llamadas del sistema es una parte de la ABI.
Jonathan Leffler
1
Si. Fui a la irc de desarrollo de kernel de cada sistema operativo individual y les pregunté al respecto. Me han dicho que investigue la fuente y descubra. No entiendo sin documentar cosas, ¿cómo pueden comenzar a desarrollarse? Entonces, agregué una respuesta de la información que recopilé, esperando que otros completen el resto de los detalles.
garras
@JonathanLeffler el enlace parece no funcionar en este momento. Si también tiene problemas para visitar el enlace, ¿puede actualizarlo?
Ajay Brahmakshatriya
@AjayBrahmakshatriya: Gracias por el aviso; He agregado un enlace al registro de Wayback Machine. Todo el sitio web x86-64.org no respondió con ningún dato.
Jonathan Leffler el
11

Las convenciones de llamada definen cómo se pasan los parámetros en los registros al llamar o ser llamado por otro programa. Y la mejor fuente de estas convenciones es en forma de estándares ABI definidos para cada uno de estos hardware. Para facilitar la compilación, el mismo espacio de usuario también es utilizado por el espacio de usuario y el programa del núcleo. Linux / Freebsd sigue el mismo ABI para x86-64 y otro conjunto para 32 bits. Pero x86-64 ABI para Windows es diferente de Linux / FreeBSD. Y, en general, ABI no diferencia la llamada del sistema frente a las "llamadas de funciones" normales. Es decir, aquí hay un ejemplo particular de convenciones de llamadas x86_64 y es lo mismo para el espacio de usuario de Linux y el núcleo: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (tenga en cuenta la secuencia a, b, c, d, e, f de parámetros):

Una buena representación de las convenciones de llamadas frente al uso de registros

El rendimiento es una de las razones de estos ABI (por ejemplo, pasar parámetros a través de registros en lugar de guardarlos en pilas de memoria)

Para ARM hay varios ABI:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.abi/index.html

https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/iPhoneOSABIReference.pdf

Convención ARM64:

http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf

Para Linux en PowerPC:

http://refspecs.freestandards.org/elf/elfspec_ppc.pdf

http://www.0x04.net/doc/elf/psABI-ppc64.pdf

Y para incrustado está el PPC EABI:

http://www.freescale.com/files/32bit/doc/app_note/PPCEABI.pdf

Este documento es una buena descripción de todas las diferentes convenciones:

http://www.agner.org/optimize/calling_conventions.pdf

Peter Teoh
fuente
Totalmente aparte del punto. El póster de la pregunta no pediría la convención de llamada syscall de 64 bits en Linux si fuera lo mismo que las conversiones ABI generales.
Albert van der Horst
6

Comentarios de fuente del kernel 5.0 de Linux

Sabía que los detalles de x86 están por debajo arch/x86, y que las cosas de syscall se hunden arch/x86/entry. Entonces, un rápido git grep rdien ese directorio me lleva a arch / x86 / entry / entry_64.S :

/*
 * 64-bit SYSCALL instruction entry. Up to 6 arguments in registers.
 *
 * This is the only entry point used for 64-bit system calls.  The
 * hardware interface is reasonably well designed and the register to
 * argument mapping Linux uses fits well with the registers that are
 * available when SYSCALL is used.
 *
 * SYSCALL instructions can be found inlined in libc implementations as
 * well as some other programs and libraries.  There are also a handful
 * of SYSCALL instructions in the vDSO used, for example, as a
 * clock_gettimeofday fallback.
 *
 * 64-bit SYSCALL saves rip to rcx, clears rflags.RF, then saves rflags to r11,
 * then loads new ss, cs, and rip from previously programmed MSRs.
 * rflags gets masked by a value from another MSR (so CLD and CLAC
 * are not needed). SYSCALL does not save anything on the stack
 * and does not change rsp.
 *
 * Registers on entry:
 * rax  system call number
 * rcx  return address
 * r11  saved rflags (note: r11 is callee-clobbered register in C ABI)
 * rdi  arg0
 * rsi  arg1
 * rdx  arg2
 * r10  arg3 (needs to be moved to rcx to conform to C ABI)
 * r8   arg4
 * r9   arg5
 * (note: r12-r15, rbp, rbx are callee-preserved in C ABI)
 *
 * Only called from user space.
 *
 * When user can change pt_regs->foo always force IRET. That is because
 * it deals with uncanonical addresses better. SYSRET has trouble
 * with them due to bugs in both AMD and Intel CPUs.
 */

y para 32 bits en arch / x86 / entry / entry_32.S :

/*
 * 32-bit SYSENTER entry.
 *
 * 32-bit system calls through the vDSO's __kernel_vsyscall enter here
 * if X86_FEATURE_SEP is available.  This is the preferred system call
 * entry on 32-bit systems.
 *
 * The SYSENTER instruction, in principle, should *only* occur in the
 * vDSO.  In practice, a small number of Android devices were shipped
 * with a copy of Bionic that inlined a SYSENTER instruction.  This
 * never happened in any of Google's Bionic versions -- it only happened
 * in a narrow range of Intel-provided versions.
 *
 * SYSENTER loads SS, ESP, CS, and EIP from previously programmed MSRs.
 * IF and VM in RFLAGS are cleared (IOW: interrupts are off).
 * SYSENTER does not save anything on the stack,
 * and does not save old EIP (!!!), ESP, or EFLAGS.
 *
 * To avoid losing track of EFLAGS.VM (and thus potentially corrupting
 * user and/or vm86 state), we explicitly disable the SYSENTER
 * instruction in vm86 mode by reprogramming the MSRs.
 *
 * Arguments:
 * eax  system call number
 * ebx  arg1
 * ecx  arg2
 * edx  arg3
 * esi  arg4
 * edi  arg5
 * ebp  user stack
 * 0(%ebp) arg6
 */

Implementación de llamada al sistema glibc 2.29 Linux x86_64

Ahora hagamos trampa mirando las principales implementaciones de libc y veamos qué están haciendo.

¿Qué podría ser mejor que mirar glibc que estoy usando ahora mientras escribo esta respuesta? :-)

glibc 2.29 define x86_64 syscalls en sysdeps/unix/sysv/linux/x86_64/sysdep.hy que contiene algún código interesante, por ejemplo:

/* The Linux/x86-64 kernel expects the system call parameters in
   registers according to the following table:

    syscall number  rax
    arg 1       rdi
    arg 2       rsi
    arg 3       rdx
    arg 4       r10
    arg 5       r8
    arg 6       r9

    The Linux kernel uses and destroys internally these registers:
    return address from
    syscall     rcx
    eflags from syscall r11

    Normal function call, including calls to the system call stub
    functions in the libc, get the first six parameters passed in
    registers and the seventh parameter and later on the stack.  The
    register use is as follows:

     system call number in the DO_CALL macro
     arg 1      rdi
     arg 2      rsi
     arg 3      rdx
     arg 4      rcx
     arg 5      r8
     arg 6      r9

    We have to take care that the stack is aligned to 16 bytes.  When
    called the stack is not aligned since the return address has just
    been pushed.


    Syscalls of more than 6 arguments are not supported.  */

y:

/* Registers clobbered by syscall.  */
# define REGISTERS_CLOBBERED_BY_SYSCALL "cc", "r11", "cx"

#undef internal_syscall6
#define internal_syscall6(number, err, arg1, arg2, arg3, arg4, arg5, arg6) \
({                                  \
    unsigned long int resultvar;                    \
    TYPEFY (arg6, __arg6) = ARGIFY (arg6);              \
    TYPEFY (arg5, __arg5) = ARGIFY (arg5);              \
    TYPEFY (arg4, __arg4) = ARGIFY (arg4);              \
    TYPEFY (arg3, __arg3) = ARGIFY (arg3);              \
    TYPEFY (arg2, __arg2) = ARGIFY (arg2);              \
    TYPEFY (arg1, __arg1) = ARGIFY (arg1);              \
    register TYPEFY (arg6, _a6) asm ("r9") = __arg6;            \
    register TYPEFY (arg5, _a5) asm ("r8") = __arg5;            \
    register TYPEFY (arg4, _a4) asm ("r10") = __arg4;           \
    register TYPEFY (arg3, _a3) asm ("rdx") = __arg3;           \
    register TYPEFY (arg2, _a2) asm ("rsi") = __arg2;           \
    register TYPEFY (arg1, _a1) asm ("rdi") = __arg1;           \
    asm volatile (                          \
    "syscall\n\t"                           \
    : "=a" (resultvar)                          \
    : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4),     \
      "r" (_a5), "r" (_a6)                      \
    : "memory", REGISTERS_CLOBBERED_BY_SYSCALL);            \
    (long int) resultvar;                       \
})

que creo que se explican por sí mismas. Observe cómo esto parece haber sido diseñado para coincidir exactamente con la convención de llamada de las funciones regulares ABI AMD64 de System V: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Recordatorio rápido de los clobbers:

  • ccsignifica registros de bandera. Pero Peter Cordes comenta que esto es innecesario aquí.
  • memory significa que se puede pasar un puntero en el ensamblaje y usarlo para acceder a la memoria

Para obtener un ejemplo explícito mínimo ejecutable desde cero, vea esta respuesta: ¿Cómo invocar una llamada del sistema a través del sysenter en el ensamblaje en línea?

Hacer algunas llamadas de sistema en ensamblaje manualmente

No muy científico, pero divertido:

  • x86_64.S

    .text
    .global _start
    _start:
    asm_main_after_prologue:
        /* write */
        mov $1, %rax    /* syscall number */
        mov $1, %rdi    /* stdout */
        mov $msg, %rsi  /* buffer */
        mov $len, %rdx  /* len */
        syscall
    
        /* exit */
        mov $60, %rax   /* syscall number */
        mov $0, %rdi    /* exit status */
        syscall
    msg:
        .ascii "hello\n"
    len = . - msg
    

    GitHub aguas arriba .

aarch64

He mostrado un ejemplo de tierra de usuario mínima ejecutable en: /reverseengineering/16917/arm64-syscalls-table/18834#18834 TODO el código de kernel grep aquí, debería ser fácil.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
El "cc"clobber es innecesario: los syscalls de Linux guardan / restauran RFLAGS (las instrucciones syscall/ lo sysrethacen usando R11, y el kernel no modifica los R11 / RFLAGS guardados de otra manera que no sea a través de ptracellamadas al sistema del depurador)."cc" clobber es implícito para x86 / x86-64 en GNU C Extended asm, por lo que no puede obtener nada al dejarlo fuera.
Peter Cordes