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

StackOverflow https://stackoverflow.com/questions/2535989

Pergunta

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

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

Foi útil?

Solução

Leitura adicional para qualquer um dos tópicos aqui: O guia definitivo para chamadas de sistema Linux


Eu verifiquei isso usando GNU Assembler (gas) no Linux.

Interface do Kernel

Convenção de chamada do sistema Linux x86-32, também conhecida como i386:

Em x86-32, os parâmetros para chamadas de sistema Linux são passados ​​usando registros. %eax para 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.

Peguei o seguinte trecho do Tutorial de montagem do Linux mas estou em dúvida sobre isso.Se alguém puder mostrar um exemplo, seria ótimo.

Se houver mais de seis argumentos, %ebx Deve conter o local da memória em que a lista de argumentos é armazenada - mas não se preocupe com isso, porque é 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 Hello World para i386 Linux usando int 0x80: Quais partes deste código assembly HelloWorld são essenciais se eu escrever o programa em assembly?

Existe uma maneira mais rápida de fazer chamadas de sistema de 32 bits:usando sysenter.O kernel mapeia uma página de memória em cada processo (o vDSO), com o lado do espaço do usuário do sysenter dance, que tem que cooperar com o kernel para poder encontrar o endereço de retorno.Arg para registrar o mapeamento é o mesmo que para int $0x80.Normalmente você deve ligar para o vDSO em vez de usar sysenter diretamente.(Ver O guia definitivo para chamadas de sistema Linux para obter informações sobre como vincular e chamar o vDSO e para obter mais informações sobre sysenter, e tudo o mais relacionado às chamadas do sistema.)

x86-32 [Free|Open|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 enviado primeiro) para a pilha.Em seguida, envie mais 32 bits de dados fictícios (na verdade, não são dados fictícios.consulte o link a seguir para obter mais informações) e, em seguida, forneça uma instrução de chamada do sistema int $0x80

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


Convenção de chamada do sistema Linux x86-64:

x86-64 Mac OS X é semelhante, mas diferente.PENDÊNCIA:verifique o que o *BSD faz.

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

Aqui está o trecho desta seção:

  1. Os aplicativos no nível do usuário usam como registros inteiros para passar na sequência %rdi, %rsi, %rdx, %rcx, %r8 e %r9. A interface do kernel usa %rdi, %rsi, %rdx, %r10, %r8 e %r9.
  2. Uma chamada de sistema é feita através do syscall instrução.Esse derrota %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 registrador %rax.
  4. As chamadas do sistema são limitadas a seis argumentos, nenhum argumento é passado diretamente na pilha.
  5. Retornando do syscall, o Register %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 da classe MEMORY são passados ​​para o kernel.

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

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

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

Convenção de chamada de função x86-32:

Em x86-32 os parâmetros foram passados ​​na pilha.O último parâmetro foi colocado primeiro na pilha até que todos os parâmetros sejam concluídos e então call instrução foi executada.Isso é usado para chamar funções da biblioteca C (libc) no Linux a partir do assembly.

Versões modernas do i386 System V ABI (usado no Linux) requerem alinhamento de 16 bytes de %esp antes de call, como o x86-64 System V ABI sempre exigiu.Os chamadores podem assumir isso e usar cargas/armazenamentos SSE de 16 bytes que falham no desalinhamento.Mas, historicamente, o Linux exigia apenas alinhamento de pilha de 4 bytes, por isso era necessário um trabalho extra para reservar espaço alinhado naturalmente, mesmo para uma pilha de 8 bytes. double ou alguma coisa.

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 x86-64 System V:

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

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

Para informações completas consulte:"3.2 Sequência de Chamada de Função" de Suplemento de processador de arquitetura AMD64 de interface binária de aplicativo System V que diz, em parte:

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

  1. Se a classe for MEMÓRIA, passe o argumento na pilha.
  2. Se a classe for inteira, o próximo registro disponível da sequência %rdi, %rsi, %rdx, %rcx, %r8 e %r9 é usado

Então %rdi, %rsi, %rdx, %rcx, %r8 and %r9 são os registros em ordem usado para passar inteiro/ponteiro (ou seja,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 call instruções devem ser dadas.A pilha (%rsp) deve estar alinhado com 16B quando call executa.

Se houver mais de 6 parâmetros INTEGER, o 7º parâmetro INTEGER e posteriores serão passados ​​na pilha.(O chamador aparece, igual ao 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 chamadas.(Uma função com uma combinação de argumentos FP e inteiros pode ter mais de 8 argumentos de registro no total.)

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

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


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

Outras dicas

Talvez você esteja procurando o X86_64 ABI?

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

As convenções de chamada definem como os parâmetros são passados ​​nos registradores ao chamar ou serem chamados por outro programa.E a melhor fonte dessas convenções está na forma dos 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 kernel.Linux/Freebsd segue a mesma ABI para x86-64 e outro conjunto para 32 bits.Mas o ABI x86-64 para Windows é diferente do Linux/FreeBSD.E geralmente a ABI não diferencia chamadas de sistema de "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 de parâmetros):

A good rendering of calling conventions vs registers usage

O desempenho é uma das razões para essas ABI (por exemplo, passar parâmetros por meio de registros em vez de salvá-los 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 em PowerPC:

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

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

E para embarcados 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

Comentários de origem do kernel Linux 5.0

Eu sabia que as especificações do x86 estão abaixo arch/x86, e esse material do syscall vai abaixo arch/x86/entry.Então, um rápido git grep rdi nesse diretório me leva a arco/x86/entrada/entrada_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 arco/x86/entrada/entrada_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 de 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 examinar a glibc que estou usando agora enquanto escrevo esta resposta?:-)

glibc 2.29 define syscalls x86_64 em sysdeps/unix/sysv/linux/x86_64/sysdep.h e 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 considero bastante autoexplicativos.Observe como isso parece ter sido projetado para corresponder exatamente à convenção de chamada das funções regulares do System V AMD64 ABI: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Lembrete rápido dos golpes:

  • cc significa registros de bandeira.Mas Comentários de Pedro Cordes que isso é desnecessário aqui.
  • memory significa que um ponteiro pode ser passado em assembly e usado para acessar a memória

Para obter um exemplo executável mínimo explícito do zero, veja esta resposta: Como invocar uma chamada de sistema via sysenter em assembly inline?

Faça alguns syscalls em assembly 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
    

    upstream do GitHub.

aarch64

Mostrei um exemplo mínimo de ambiente de usuário executável em: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 O código do kernel TODO grep aqui deve ser fácil.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top