Quais são as convenções de chamada para chamadas de sistema UNIX e Linux em i386 e x86-64

147

Os links a seguir explicam as convenções de chamada do sistema x86-32 para UNIX (sabor BSD) e Linux:

Mas quais são as convenções de chamada do sistema x86-64 no UNIX e Linux?

garras
fonte
Não existe um "padrão" para convenções de chamada Unix. Para o linux, com certeza, mas tenho certeza de que o Solaris, o OpenBSD, o Linux e o Minix provavelmente têm convenções de chamada diferentes, pelo menos um pouco diferentes, e todos são unix.
Earlz 29/03/10
2
Isso não é inteiramente verdade - há um conjunto de ABIs UNIX disponíveis para a maioria dos tipos de máquinas, o que permite que os compiladores C obtenham interoperabilidade. Compiladores C ++ têm um problema maior.
Jonathan Leffler
1
Ambos estão corretos. Estou procurando pelo FreeBSD e Linux.
garras
Gostaria que a resposta contivesse informações sobre quais registros são preservados nas chamadas do sistema. É claro que o ponteiro da pilha é (a menos que tenha sido alterado de maneira controlada na chamada __NR_clone), mas são os outros?
Albert van der Horst
@ AlbertvanderHorst: sim, acabei de atualizar a resposta do wiki com os detalhes de 32 bits. 64 bits já era preciso: rcx e r11 são destruídos devido à maneira como sysretfunciona, além de o rax ser substituído pelo valor de retorno. Todos os outros registradores são preservados no amd64.
Peter Cordes

Respostas:

230

Leitura adicional para qualquer um dos tópicos aqui: O Guia Definitivo para Chamadas do Sistema Linux


Eu os verifiquei usando o GNU Assembler (gas) no Linux.

Interface do Kernel

x86-32 aka convenção de chamada do sistema i386 Linux:

No x86-32, os parâmetros para a chamada do sistema Linux são passados ​​usando registradores. %eaxpara syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp são usados ​​para passar 6 parâmetros para chamadas do sistema.

O valor de retorno está em %eax . Todos os outros registros (incluindo EFLAGS) são preservados em todo o int $0x80.

Tirei o seguinte trecho do Tutorial de Montagem do Linux, mas tenho dúvidas sobre isso. Se alguém puder mostrar um exemplo, seria ótimo.

Se houver mais de seis argumentos, %ebxdeve conter o local da memória em que a lista de argumentos está armazenada - mas não se preocupe, pois é improvável que você use um syscall com mais de seis argumentos.

Para um exemplo e um pouco mais de leitura, consulte http://www.int80h.org/bsdasm/#alternate-calling-convention . Outro exemplo de um Hello World para i386 Linux usando int 0x80: Olá, mundo em linguagem assembly com chamadas de sistema Linux?

Existe uma maneira mais rápida de fazer chamadas ao sistema de 32 bits: using sysenter. O kernel mapeia uma página de memória em todos os processos (o vDSO), com o lado do espaço do usuário da sysenterdança, que precisa cooperar com o kernel para que ele possa encontrar o endereço de retorno. Arg para registrar o mapeamento é o mesmo que para int $0x80. Você normalmente deve chamar o vDSO em vez de usar sysenterdiretamente. (Consulte o Guia definitivo para chamadas do sistema Linux para obter informações sobre como vincular e ligar para o vDSO, para obter mais informações sobre sysentere tudo mais a ver com chamadas do sistema.)

x86-32 [Grátis | Aberto | Net | DragonFly] Convenção de chamada do sistema BSD UNIX:

Os parâmetros são passados ​​na pilha. Empurre os parâmetros (último parâmetro pressionado primeiro) para a pilha. Em seguida, empurre um número adicional de dados fictícios de 32 bits (não são dados fictícios. Consulte o link a seguir para obter mais informações) e depois forneça uma instrução de chamada do sistemaint $0x80

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


Convenção de Chamada de Sistema Linux x86-64:

x86-64 O Mac OS X é semelhante, mas diferente . TODO: verifique o que o * BSD faz.

Consulte a seção: " Convenções do A.2 AMD64 Linux Kernel" do Suplemento do processador de arquitetura da interface binária do aplicativo System V da AMD64 . As versões mais recentes dos psABIs do System V i386 e x86-64 podem ser encontradas no link desta página no do mantenedor da ABI . (Veja também o tag wiki para links ABI atualizados e muitas outras coisas boas sobre o x86 asm.)

Aqui está o trecho desta seção:

  1. Aplicativos em nível de usuário usam como registros inteiros para passar a sequência% rdi,% rsi,% rdx,% rcx,% r8 e% r9. A interface do kernel usa% rdi,% rsi,% rdx,% r10,% r8 e% r9.
  2. Uma chamada do sistema é feita através do syscall instrução . Isso derruba% rcx e% r11 , bem como o valor de retorno% rax, mas outros registros são preservados.
  3. O número do syscall deve ser passado no registro% rax.
  4. As chamadas de sistema são limitadas a seis argumentos, nenhum argumento é passado diretamente na pilha.
  5. Retornando do syscall, o registro% rax contém o resultado da chamada do sistema. Um valor no intervalo entre -4095 e -1 indica um erro, é-errno .
  6. Somente valores da classe INTEGER ou MEMORY são passados ​​para o kernel.

Lembre-se de que isso é do apêndice específico do Linux para a ABI, e mesmo para o Linux é informativo, não normativo. (Mas é de fato preciso.)

Essa int $0x80ABI de 32 bits é utilizável no código de 64 bits (mas altamente não recomendado). O que acontece se você usar a ABI int 0x80 Linux de 32 bits no código de 64 bits? Ele ainda trunca suas entradas para 32 bits, portanto não é adequado para ponteiros e zera r8-r11.

Interface do usuário: chamada de função

Convenção de Chamada de Função x86-32:

No x86-32, os parâmetros foram passados ​​na pilha. O último parâmetro foi pressionado primeiro na pilha até que todos os parâmetros estejam concluídos e, em seguida, a callinstrução foi executada. Isso é usado para chamar funções de biblioteca C (libc) no Linux a partir do assembly.

As versões modernas do i386 System V ABI (usado no Linux) requerem alinhamento de 16 bytes %espantes de a call, como o x86-64 System V ABI sempre exigiu. Os Callees podem assumir isso e usar cargas / lojas SSE de 16 bytes que causam falhas em desalinhados. Porém, historicamente, o Linux exigia apenas alinhamento de pilha de 4 bytes, por isso foi necessário um trabalho extra para reservar espaço alinhado naturalmente, mesmo para 8 bytes doubleou algo assim.

Alguns outros sistemas modernos de 32 bits ainda não exigem alinhamento de pilha de mais de 4 bytes.


Convenção de Chamada de Função do Espaço do Usuário do System V x86-64:

O x86-64 System V passa args nos registradores, o que é mais eficiente que a convenção de pilha de args do i386 System V. Isso evita a latência e as instruções extras de armazenar argumentos na memória (cache) e, em seguida, carregá-los novamente no chamado. Isso funciona bem porque há mais registros disponíveis e é melhor para CPUs modernas de alto desempenho em que a latência e a execução fora de ordem são importantes. (O AB38 i386 é muito antigo).

Neste novo mecanismo: Primeiro, os parâmetros são divididos em classes. A classe de cada parâmetro determina a maneira pela qual ele é passado para a função chamada.

Para obter informações completas, consulte: "3.2 Sequência de chamada de função" do suplemento ao processador de arquitetura AMD64 da interface do sistema V de aplicativos que lê, em parte:

Depois que os argumentos são classificados, os registros são atribuídos (na ordem da esquerda para a direita) para passar da seguinte maneira:

  1. Se a classe for MEMORY, passe o argumento na pilha.
  2. Se a classe for INTEGER, o próximo registro disponível da sequência% rdi,% rsi,% rdx,% rcx,% r8 e% r9 será usado

O mesmo %rdi, %rsi, %rdx, %rcx, %r8 and %r9acontece com os registradores na ordem usada para passar parâmetros inteiros / ponteiros (por exemplo, classe INTEGER) para qualquer função libc do assembly. % rdi é usado para o primeiro parâmetro INTEGER. % rsi para o 2º,% rdx para o 3º e assim por diante. Então a callinstrução deve ser dada. A pilha ( %rsp) deve estar alinhada com 16B ao callexecutar.

Se houver mais de 6 parâmetros INTEGER, o sétimo parâmetro INTEGER e mais tarde serão passados ​​para a pilha. (O chamador aparece, o mesmo que x86-32.)

Os primeiros 8 argumentos de ponto flutuante são passados ​​em% xmm0-7, posteriormente na pilha. Não há registros vetoriais preservados por chamada. (Uma função com uma combinação de argumentos FP e número inteiro pode ter mais de 8 argumentos no total do registro.)

Funções variáveis ​​( comoprintf ) sempre precisam %al= o número de argumentos do registro FP.

Existem regras para quando empacotar estruturas em registradores ( rdx:raxno retorno) vs. na memória. Consulte a ABI para obter detalhes e verifique a saída do compilador para garantir que seu código concorda com os compiladores sobre como algo deve ser passado / retornado.


Observe que a convenção de chamada de função do Windows x64 possui várias diferenças significativas em relação ao x86-64 System V, como espaço de sombra que deve ser reservado pelo chamador (em vez de uma zona vermelha) e xmm6-xmm15 preservado na chamada. E regras muito diferentes para as quais arg entra e em que registro.

garras
fonte
1
No linux 32 "todos os registros, exceto ax bx cd dx si di bp são preservados". Eu não posso pensar de qualquer ...
Albert van der Horst
No amd64, se houver mais de 6 parâmetros e eles forem passados ​​para a pilha, quem é responsável pela limpeza da pilha após a chamada, o chamador ou o chamado?
Nicolás
1
@ Nicolás: chamador limpa a pilha. Atualizei a resposta com mais detalhes sobre a convenção de chamada de função.
Peter Cordes
1
Se você usa a int 0x80ABI do Linux em código de 64 bits, é exatamente isso que acontece: stackoverflow.com/questions/46087730/… . Ele zera r8-r11 e funciona exatamente como quando executado em um processo de 32 bits. Nessa sessão de perguntas e respostas, tenho um exemplo mostrando como funciona ou falha ao truncar um ponteiro. E eu também procurei na fonte do kernel para mostrar por que ela se comporta dessa maneira.
6606 Peter Cordes #
1
@EvanCarroll: O trecho (texto citado) está no link fornecido Linux Assembléia Tutorial especificamente na seção 4.3 Sistema Linux Chamadas
Michael Petch
14

Talvez você esteja procurando a ABI x86_64?

Se não é exatamente isso que você procura, use 'x86_64 abi' em seu mecanismo de pesquisa preferido para encontrar referências alternativas.

Jonathan Leffler
fonte
5
na verdade, eu só quero a convenção de chamada do sistema. esp para UNIX (FreeBSD)
garras
3
@claws: a convenção de chamada do sistema é uma parte da ABI.
Jonathan Leffler
1
sim. Eu fui ao irc de desenvolvimento de kernel de cada sistema operacional individual e perguntei sobre isso. Eles me disseram para procurar a fonte e descobrir. Eu não entendo, sem documentar as coisas, como eles podem começar a se desenvolver? Então, adicionei uma resposta das informações que coletei, esperando que outras pessoas preenchessem o restante dos detalhes.
garras
@ JonathanLeffler, o link parece não estar funcionando no momento. Se você também estiver com problemas para acessar o link, pode atualizá-lo?
Ajay Brahmakshatriya
@AjayBrahmakshatriya: Obrigado pelo aviso; Adicionei um link ao registro da Wayback Machine. Todo o site x86-64.org não respondeu com nenhum dado.
precisa
11

As convenções de chamada definem como os parâmetros são passados ​​nos registradores ao chamar ou ser chamado por outro programa. E a melhor fonte dessas convenções está na forma de padrões ABI definidos para cada um desses hardwares. Para facilitar a compilação, a mesma ABI também é usada pelo espaço do usuário e pelo programa do kernel. O Linux / Freebsd segue a mesma ABI para x86-64 e outro conjunto para 32 bits. Mas o x86-64 ABI para Windows é diferente do Linux / FreeBSD. E, geralmente, a ABI não diferencia a chamada do sistema versus as "chamadas de funções" normais. Ou seja, aqui está um exemplo específico de convenções de chamada x86_64 e é o mesmo para o espaço de usuário e o kernel do Linux: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (observe a sequência a, b, c, d, e, f dos parâmetros):

Uma boa renderização de convenções de chamada versus uso de registradores

O desempenho é uma das razões para essas ABI (por exemplo, passar parâmetros por meio de registradores em vez de salvar em pilhas de memória)

Para ARM, existem várias 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

Convenção ARM64:

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

Para Linux no PowerPC:

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

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

E para incorporado existe o PPC EABI:

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

Este documento é uma boa visão geral de todas as diferentes convenções:

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

Peter Teoh
fonte
Totalmente além do ponto. O pôster da pergunta não solicitaria a convenção de chamada de syscall de 64 bits no linux se fosse a mesma que as conversões gerais da ABI.
Albert van der Horst
6

Comentários de origem do kernel 5.0 do Linux

Eu sabia que as especificidades do x86 estão abaixo arch/x86e que o syscall está abaixo arch/x86/entry. Portanto, um rápido git grep rdinesse diretório me leva 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.
 */

e para 32 bits em 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
 */

implementação de chamada do sistema glibc 2.29 Linux x86_64

Agora vamos trapacear observando as principais implementações da libc e ver o que elas estão fazendo.

O que poderia ser melhor do que pesquisar na glibc que estou usando agora enquanto escrevo esta resposta? :-)

O glibc 2.29 define syscalls x86_64 em sysdeps/unix/sysv/linux/x86_64/sysdep.he que contém algum código interessante, por exemplo:

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

que eu sinto que são bastante auto-explicativas. Observe como isso parece ter sido projetado para corresponder exatamente à convenção de chamada das funções ABI AMD64 regulares do Sistema V: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Lembrete rápido dos clobbers:

  • ccsignifica registros de sinalizadores. Mas Peter Cordes comenta que isso é desnecessário aqui.
  • memory significa que um ponteiro pode ser passado na montagem e usado para acessar a memória

Para obter um exemplo executável mínimo explícito do zero, consulte esta resposta: Como chamar uma chamada de sistema via sysenter no assembly embutido?

Faça alguns syscalls na montagem manualmente

Não é muito científico, mas 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 upstream .

aarch64

Eu mostrei um exemplo mínimo de userland executável em: /reverseengineering/16917/arm64-syscalls-table/18834#18834 TODO o código do kernel grep aqui, deve ser fácil.

Ciro Santilli adicionou uma nova foto
fonte
1
O "cc"clobber é desnecessário: os syscalls do Linux salvam / restauram RFLAGS (as instruções syscall/ sysretfazem isso usando R11, e o kernel não modifica o R11 / RFLAGS salvo, exceto por meio de ptracechamadas do sistema depurador.) Não que isso importe, porque um "cc"clobber é implícito para x86 / x86-64 no GNU C Extended asm, portanto você não pode obter nada deixando de fora.
Peter Cordes