Quali sono le convenzioni di chiamata per le chiamate di sistema UNIX e Linux su i386 e x86-64


147

I seguenti collegamenti spiegano le convenzioni di chiamata del sistema x86-32 sia per UNIX (sapore BSD) che per Linux:

Ma quali sono le convenzioni di chiamata del sistema x86-64 su UNIX e Linux?


Non esiste uno "standard" per le convenzioni di chiamata Unix. Per Linux sicuramente, ma sono sicuro che Solaris, OpenBSD, Linux e Minix probabilmente hanno convenzioni di chiamata almeno leggermente diverse e sono tutte unix.
Earlz,

2
Non è del tutto vero: esiste un set di ABI UNIX disponibili per la maggior parte dei tipi di macchine, che consente ai compilatori C di raggiungere l'interoperabilità. I compilatori C ++ hanno un problema più grande.
Jonathan Leffler

1
Entrambi avete ragione. Sto cercando FreeBSD e Linux.
artigli il

Gradirei se la risposta contiene informazioni su quali registri vengono conservati attraverso le chiamate di sistema. Ovviamente il puntatore dello stack è (a meno che non sia cambiato in modo controllato nella chiamata __NR_clone), ma sono gli altri?
Albert van der Horst,

@AlbertvanderHorst: sì, ho appena aggiornato la risposta della wiki con i dettagli per 32 bit. 64 bit era già preciso: rcx e r11 vengono distrutti a causa del modo in cui sysretfunziona, insieme a rax viene sostituito con il valore di ritorno. Tutti gli altri registri sono conservati su amd64.
Peter Cordes,

Risposte:


230

Ulteriori letture per uno qualsiasi degli argomenti qui: La guida definitiva alle chiamate di sistema di Linux


Ho verificato questi usando GNU Assembler (gas) su Linux.

Interfaccia del kernel

x86-32 aka convenzione i386 Linux System Call:

In x86-32 i parametri per la chiamata di sistema Linux vengono passati usando i registri. %eaxper syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp sono usati per passare 6 parametri alle chiamate di sistema.

Il valore restituito è in %eax. Tutti gli altri registri (incluso EFLAGS) sono conservati in tutto il int $0x80.

Ho preso il seguente frammento dal Linux Assembly Tutorial ma ne dubito. Se qualcuno può mostrare un esempio, sarebbe fantastico.

Se sono presenti più di sei argomenti, %ebxdeve contenere la posizione di memoria in cui è memorizzato l'elenco degli argomenti, ma non preoccuparti perché è improbabile che utilizzerai una syscall con più di sei argomenti.

Per un esempio e un po 'più di lettura, consultare http://www.int80h.org/bsdasm/#alternate-calling-convention . Un altro esempio di Hello World per Linux i386 con int 0x80: Hello, mondo in linguaggio assembly con chiamate di sistema Linux?

Esiste un modo più veloce per effettuare chiamate di sistema a 32 bit: utilizzando sysenter. Il kernel mappa una pagina di memoria in ogni processo (il vDSO), con il lato spazio utente della sysenterdanza, che deve cooperare con il kernel per poter trovare l'indirizzo di ritorno. Arg per registrare il mapping è lo stesso di int $0x80. Dovresti normalmente chiamare in vDSO invece di usarlo sysenterdirettamente. (Consultare la Guida definitiva alle chiamate di sistema Linux per informazioni su collegamento e chiamata a vDSO e per ulteriori informazioni su sysentere tutto ciò che riguarda le chiamate di sistema.)

x86-32 [Free | Open | Net | DragonFly] Convenzione di chiamata di sistema BSD UNIX:

I parametri vengono passati sullo stack. Inserire i parametri (ultimo parametro inserito per primo) nello stack. Quindi inviare un ulteriore 32-bit di dati fittizi (i suoi dati non sono effettivamente fittizi. Fare riferimento al seguente link per ulteriori informazioni) e quindi dare un'istruzione di chiamata di sistemaint $0x80

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


x86-64 Convenzione per chiamate di sistema Linux:

x86-64 Mac OS X è simile ma diverso . TODO: controlla cosa fa * BSD.

Fare riferimento alla sezione: "A.2 AMD64 Linux Kernel Convenzioni" di System V Application Binary Interface AMD64 architettura del processore Supplement . Le ultime versioni degli psabi System V i386 e x86-64 sono reperibili collegati da questa pagina nel repository del manutentore ABI . (Vedi anche il tag wiki per collegamenti ABI aggiornati e molte altre cose utili su x86 asm.)

Ecco lo snippet di questa sezione:

  1. Le applicazioni a livello di utente utilizzano come registri interi per il passaggio della sequenza% rdi,% rsi,% rdx,% rcx,% r8 e% r9. L'interfaccia del kernel utilizza% rdi,% rsi,% rdx,% r10,% r8 e% r9.
  2. Una chiamata di sistema viene eseguita tramite l' syscallistruzione . Ciò blocca% rcx e% r11 nonché il valore di ritorno% rax, ma vengono conservati altri registri.
  3. Il numero della syscall deve essere passato nel registro% rax.
  4. Le chiamate di sistema sono limitate a sei argomenti, nessun argomento viene passato direttamente nello stack.
  5. Di ritorno dalla syscall, il registro% rax contiene il risultato della chiamata di sistema. Un valore compreso tra -4095 e -1 indica un errore, lo è -errno.
  6. Solo i valori della classe INTEGER o della classe MEMORY vengono passati al kernel.

Ricorda che questo proviene dall'appendice specifica di Linux all'ABI e anche per Linux è informativo non normativo. (Ma in realtà è accurato.)

Questa int $0x80ABI a 32 bit è utilizzabile nel codice a 64 bit (ma altamente sconsigliata). Cosa succede se si utilizza l'ABI Linux 0x80 int a 32 bit nel codice a 64 bit? Tronca ancora i suoi input a 32 bit, quindi non è adatto per i puntatori e zeri r8-r11.

Interfaccia utente: chiamata di funzione

x86-32 Convenzione per la chiamata di funzioni:

In x86-32 i parametri sono stati passati in pila. L'ultimo parametro è stato prima inserito nello stack fino a quando non sono stati eseguiti tutti i parametri e quindi è callstata eseguita l'istruzione. Questo è usato per chiamare le funzioni della libreria C (libc) su Linux dall'assembly.

Le versioni moderne dell'ABI System V i386 (usato su Linux) richiedono un allineamento di 16 byte di %espprima di un call, come l'ABI System V x86-64 ha sempre richiesto. I Calle sono autorizzati ad assumerlo e utilizzano i carichi / archivi SSE a 16 byte che presentano guasti non allineati. Ma storicamente, Linux ha richiesto solo un allineamento dello stack di 4 byte, quindi ci è voluto un lavoro extra per riservare spazio allineato naturalmente anche per un 8 byte doubleo qualcosa del genere.

Alcuni altri moderni sistemi a 32 bit non richiedono ancora un allineamento dello stack superiore a 4 byte.


x86-64 Sistema V convenzione spazio utente convenzione di chiamata:

x86-64 System V passa args nei registri, che è più efficiente della convenzione stack args di i386 System V. Evita la latenza e le istruzioni aggiuntive per la memorizzazione di args nella memoria (cache) e quindi il loro caricamento di nuovo nella chiamata. Funziona bene perché ci sono più registri disponibili ed è meglio per le moderne CPU ad alte prestazioni in cui sono importanti latenza ed esecuzione fuori servizio. (L'i386 ABI è molto vecchio).

In questo nuovo meccanismo: in primo luogo i parametri sono divisi in classi. La classe di ciascun parametro determina il modo in cui viene passato alla funzione chiamata.

Per informazioni complete, consultare: "3.2 Sequenza di chiamata delle funzioni" del supplemento al processore dell'architettura AMD64 per l'interfaccia binaria dell'applicazione System V che legge, in parte:

Una volta classificati gli argomenti, i registri vengono assegnati (nell'ordine da sinistra a destra) per passare come segue:

  1. Se la classe è MEMORY, passa l'argomento nello stack.
  2. Se la classe è INTEGER, viene utilizzato il successivo registro disponibile della sequenza% rdi,% rsi,% rdx,% rcx,% r8 e% r9

Così %rdi, %rsi, %rdx, %rcx, %r8 and %r9sono i registri al fine utilizzati per passare interi / puntatore (cioè classe INTEGER) parametri di qualsiasi funzione libc da assemblaggio. % rdi viene utilizzato per il primo parametro INTEGER. % rsi per il 2 °,% rdx per il 3 ° e così via. Quindi calldovrebbero essere fornite le istruzioni. Lo stack ( %rsp) deve essere allineato a 16B quando callviene eseguito.

Se sono presenti più di 6 parametri INTEGER, il settimo parametro INTEGER e successivi vengono passati sullo stack. (Il chiamante si apre, lo stesso di x86-32.)

I primi 8 arg in virgola mobile vengono passati in% xmm0-7, successivamente nello stack. Non ci sono registri vettoriali conservati per le chiamate. (Una funzione con una combinazione di argomenti FP e argomenti interi può avere più di 8 argomenti di registro totali.)

Le funzioni variabili ( comeprintf ) necessitano sempre %al= il numero di argomenti del registro FP.

Ci sono regole per quando comprimere le strutture nei registri ( rdx:raxal ritorno) rispetto alla memoria. Vedi l'ABI per i dettagli e controlla l'output del compilatore per assicurarti che il tuo codice sia d'accordo con i compilatori su come dovrebbe essere passato / restituito qualcosa.


Si noti che la convenzione di chiamata della funzione x64 di Windows presenta molte differenze significative rispetto al sistema V x86-64, come lo spazio ombra che deve essere riservato dal chiamante (anziché una zona rossa) e xmm6-xmm15 preservato dalla chiamata. E regole molto diverse per cui arg va in quale registro.


1
In Linux 32 "tutti i registri tranne ax bx cd dx si di bp sono conservati". Non riesco a pensare a nessun ...
Albert van der Horst,

Su amd64, se ci sono più di 6 parametri e vengono passati nello stack, chi è responsabile della pulizia dello stack dopo la chiamata, il chiamante o il chiamante?
Nicolás,

1
@Nicolás: il chiamante pulisce lo stack. Ho aggiornato la risposta con maggiori dettagli sulla convenzione di chiamata delle funzioni.
Peter Cordes,

1
Se si utilizza l' int 0x80ABI di Linux nel codice a 64 bit, questo è esattamente ciò che accade: stackoverflow.com/questions/46087730/… . Azzera r8-r11 e funziona esattamente come quando viene eseguito in un processo a 32 bit. In questa domanda e risposta ho un esempio che mostra come funziona o non riesce a troncare un puntatore. E ho anche scavato nel sorgente del kernel per mostrare perché si comporta in quel modo.
Peter Cordes,

1
@EvanCarroll: lo snippet (testo tra virgolette) si trova al link fornito Tutorial assembly Linux specificamente nella sezione 4.3 Chiamate di sistema Linux
Michael Petch,

14

Forse stai cercando l'ABI x86_64?

Se non è esattamente quello che stai cercando, usa "x86_64 abi" nel tuo motore di ricerca preferito per trovare riferimenti alternativi.


5
in realtà, voglio solo la convenzione di System Call. esp per UNIX (FreeBSD)
artigli il

3
@claws: la convenzione di chiamata di sistema fa parte dell'ABI.
Jonathan Leffler

1
si. Sono andato all'irc di sviluppo del kernel di ogni singolo sistema operativo e gli ho chiesto. Mi hanno detto di esaminare la fonte e capire. Non capisco senza documentare cose come possono iniziare a sviluppare? Quindi, ho aggiunto una risposta dalle informazioni che ho raccolto, sperando che altri inseriscano il resto dei dettagli.
artigli il

@JonathanLeffler il link sembra non funzionare in questo momento. Se riscontri anche un problema durante la visita del link, puoi aggiornarlo?
Ajay Brahmakshatriya,

@AjayBrahmakshatriya: Grazie per il testa a testa; Ho aggiunto un link al record di Wayback Machine. L'intero sito Web x86-64.org non ha risposto con alcun dato.
Jonathan Leffler,

11

Le convenzioni di chiamata definiscono il modo in cui i parametri vengono passati nei registri quando si chiama o viene chiamato da un altro programma. E la migliore fonte di queste convenzioni è sotto forma di standard ABI definiti per ciascuno di questi hardware. Per facilitare la compilazione, la stessa ABI viene utilizzata anche dallo spazio utente e dal programma del kernel. Linux / Freebsd seguono lo stesso ABI per x86-64 e un altro set per 32-bit. Ma x86-64 ABI per Windows è diverso da Linux / FreeBSD. E generalmente l'ABI non distingue le chiamate di sistema dalle normali "funzioni chiamate". Vale a dire, ecco un esempio particolare delle convenzioni di chiamata x86_64 ed è lo stesso sia per lo spazio utenti che per il kernel: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (notare la sequenza dei parametri a, b, c, d, e, f):

Un buon rendering delle convenzioni di chiamata rispetto all'uso dei registri

Le prestazioni sono uno dei motivi di queste ABI (ad es. Passaggio di parametri tramite registri anziché salvataggio in stack di memoria)

Per ARM esistono vari 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

Convenzione ARM64:

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

Per Linux su PowerPC:

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

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

E per embedded c'è l'EABI PPC:

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

Questo documento offre una buona panoramica di tutte le diverse convenzioni:

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


Totalmente oltre il punto. Il poster della domanda non richiederebbe la convenzione di chiamata syscall a 64 bit in Linux se fosse la stessa delle conversioni ABI generali.
Albert van der Horst,

6

Commenti sui sorgenti del kernel 5.0 di Linux

Sapevo che i dettagli di x86 sono sotto arch/x86e che roba di syscall va sotto arch/x86/entry. Quindi un rapido git grep rdiin quella directory mi porta ad 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.
 */

e per 32 bit su 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
 */

implementazione di chiamate di sistema Linux x86_64 glibc 2.29

Ora imbrogliamo guardando le principali implementazioni di libc e vediamo cosa stanno facendo.

Cosa c'è di meglio che guardare glibc che sto usando in questo momento mentre scrivo questa risposta? :-)

glibc 2.29 definisce syscalls x86_64 su sysdeps/unix/sysv/linux/x86_64/sysdep.he che contiene alcuni codici interessanti, ad esempio:

/* 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.  */

e:

/* 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;                       \
})

che ritengo piuttosto esplicativo. Si noti come questo sembra essere stato progettato per corrispondere esattamente alla convenzione di chiamata delle normali funzioni ABI AMD64 System V: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Promemoria rapido dei clobbers:

  • ccsignifica registri bandiera. Ma Peter Cordes commenta che qui non è necessario.
  • memory significa che un puntatore può essere passato in assembly e utilizzato per accedere alla memoria

Per un esempio eseguibile minimo esplicito da zero, vedere questa risposta: Come richiamare una chiamata di sistema tramite sysenter nell'assembly inline?

Crea manualmente alcuni syscalls nell'assembly

Non molto scientifico, ma divertente:

  • 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 a monte .

aarch64

Ho mostrato un esempio minimo di userland eseguibile su: /reverseengineering/16917/arm64-syscalls-table/18834#18834 Il codice del kernel grep TODO qui dovrebbe essere semplice.


1
Il "cc"clobber non è necessario: i syscalls di Linux salvano / ripristinano RFLAGS (Le istruzioni syscall/ lo sysretfanno usando R11, e il kernel non modifica gli R11 / RFLAGS salvati se non tramiteptrace chiamate di sistema del debugger.) Non importa che mai, perché un "cc"clobber è implicito per x86 / x86-64 in GNU C Extended asm, quindi non puoi ottenere nulla lasciandolo fuori.
Peter Cordes
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.