Каковы соглашения о вызовах для системных вызовов UNIX и Linux на i386 и x86-64 - PullRequest
125 голосов
/ 29 марта 2010

Следующие ссылки объясняют соглашения о системных вызовах x86-32 для UNIX (BSD-версия) и Linux:

Но каковы соглашения о системных вызовах x86-64 в UNIX и Linux?

Ответы [ 4 ]

200 голосов
/ 29 марта 2010

Дополнительная информация по любой из тем здесь: Полное руководство по системным вызовам Linux


Я проверил это с помощью GNU Assembler (gas) в Linux.

Интерфейс ядра

x86-32 aka i386 Соглашение о системных вызовах Linux:

В x86-32 параметры для системного вызова Linux передаются с использованием регистров. %eax для syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp используются для передачи 6 параметров системным вызовам.

Возвращаемое значение в %eax. Все остальные регистры (включая EFLAGS) сохраняются в int $0x80.

Я взял следующий фрагмент из Учебника по сборке Linux , но я сомневаюсь в этом. Если кто-то может показать пример, было бы здорово.

Если существует более шести аргументов, %ebx должен содержать память место, где список аргументов хранится - но не беспокойтесь об этом потому что вряд ли вы будете использовать системный вызов с более чем шестью аргументы.

Пример и немного больше чтения см. В http://www.int80h.org/bsdasm/#alternate-calling-convention. Другой пример Hello World для i386 Linux с использованием int 0x80: Какие части этого кода сборки HelloWorld необходимы, если бы я написать программу в сборке?

Существует более быстрый способ совершать 32-битные системные вызовы: использование sysenter. Ядро отображает страницу памяти в каждый процесс (vDSO) со стороной пользовательского пространства танца sysenter, который должен взаимодействовать с ядром, чтобы он мог найти адрес возврата. Аргумент для регистрации сопоставления такой же, как для int $0x80. Обычно вы должны звонить в vDSO, а не использовать sysenter напрямую. (См. Подробное руководство по системным вызовам Linux для получения информации о подключении и вызове в vDSO, а также для получения дополнительной информации о sysenter и обо всем остальном, что касается системных вызовов.)

x86-32 [Free | Open | Net | DragonFly] Соглашение о системных вызовах BSD UNIX:

Параметры передаются в стек. Перенесите параметры (последний параметр вставлен первым) в стек. Затем добавьте дополнительные 32-битные фиктивные данные (это не фиктивные данные. Для получения дополнительной информации обратитесь к следующей ссылке), а затем дайте инструкцию системного вызова int $0x80

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


x86-64 Соглашение о системных вызовах Linux:

x86-64 Mac OS X похожа, но отличается . TODO: проверь, что делает * BSD.

См. Раздел: «A.2 AMD64 Linux Соглашения о ядре» из Двоичный интерфейс приложения System V Приложение AMD64 для архитектуры архитектуры . Последние версии psABI для i386 и x86-64 System V можно найти на этой странице в репозитории сопровождающего ABI . (См. Также вики-тег для последних ссылок на ABI и много других полезных вещей о x86 asm.)

Вот фрагмент из этого раздела:

  1. Пользовательские приложения используют в качестве целочисленных регистров для передачи последовательность% rdi,% rsi,% rdx,% rcx, % r8 и% r9. Интерфейс ядра использует% rdi,% rsi,% rdx,% r10,% r8 и% r9.
  2. Системный вызов выполняется с помощью инструкции syscall . Это clobbers% rcx и% r11 , а также возвращаемое значение% rax, но другие регистры сохраняются.
  3. Номер системного вызова должен быть передан в регистр% rax.
  4. Системные вызовы ограничены шестью аргументами, аргумент не передается прямо в стеке.
  5. Возвращаясь из системного вызова, регистр% rax содержит результат системный вызов. Значение в диапазоне от -4095 до -1 указывает ошибка, это -errno.
  6. В ядро ​​передаются только значения класса INTEGER или класса MEMORY.

Помните, что это из специфичного для Linux приложения к ABI, и даже для Linux это информативно, а не нормативно. (Но на самом деле это точно.)

Этот 32-битный int $0x80 ABI может использоваться в 64-битном коде (но настоятельно не рекомендуется). Что произойдет, если вы используете 32-битный int 0x80 Linux ABI в 64-битном коде? Он все еще усекает свои входные данные до 32-битного, поэтому он не подходит для указателей и имеет нули r8-r11.

Пользовательский интерфейс: вызов функции

x86-32 Соглашение о вызове функции:

В x86-32 параметры были переданы в стек. Последний параметр сначала помещался в стек, пока все параметры не были выполнены, а затем была выполнена инструкция call. Это используется для вызова функций библиотеки C (libc) в Linux из сборки.

Современные версии i386 System V ABI (используемые в Linux) требуют 16-байтового выравнивания %esp перед call, как всегда требовалось для x86-64 System V ABI. Вызывающим разрешено допускать, что это происходит, и использовать 16-байтовые загрузки / хранилища SSE, в которых произошел сбой при выравнивании. Но исторически в Linux требовалось только 4-байтовое выравнивание стека, поэтому потребовалась дополнительная работа, чтобы зарезервировать естественно выровненное пространство даже для 8-байтового double или чего-то еще.

Некоторые другие современные 32-разрядные системы все еще не требуют выравнивания стека более 4 байтов.


x86-64 System V пользовательское пространство Соглашение о вызове функций:

x86-64 System V передает аргументы в регистрах, что более эффективно, чем в соглашении i386 System V. Это позволяет избежать задержек и дополнительных инструкций по сохранению аргументов в памяти (кеш), а затем снова загружать их в вызываемый объект. Это хорошо работает, потому что доступно больше регистров, и лучше для современных высокопроизводительных ЦП, где важны задержки и неупорядоченное выполнение. (I386 ABI очень старый).

В этом новом механизме: сначала параметры делятся на классы. Класс каждого параметра определяет способ его передачи в вызываемую функцию.

Для получения полной информации см. «3.2 Последовательность вызова функций» из Двоичный интерфейс приложения System V Приложение к архитектуре AMD64 , которое гласит:

После классификации аргументов регистры присваиваются (в порядок слева направо) для прохождения следующим образом:

  1. Если класс MEMORY, передать аргумент в стек.
  2. Если класс INTEGER, следующий доступный регистр используется последовательность% rdi,% rsi,% rdx,% rcx,% r8 и% r9

То есть %rdi, %rsi, %rdx, %rcx, %r8 and %r9 - это регистры в порядке , используемые для передачи параметров целочисленного значения / указателя (т. Е. Класса INTEGER) в любую функцию libc из сборки. % rdi используется для первого параметра INTEGER. % rsi для 2-го,% rdx для 3-го и так далее. Затем call инструкция должна быть дана. При выполнении call стек (%rsp) должен быть выровнен по 16B.

Если имеется более 6 параметров INTEGER, 7-й параметр INTEGER и более поздние передаются в стек. (Звонящий звонит, как x86-32.)

Первые 8 аргументов с плавающей запятой передаются в% xmm0-7, позже в стеке. Не существует сохраняемых при вызове векторных регистров. (Функция с сочетанием FP и целочисленных аргументов может иметь более 8 аргументов в регистре.)

Вариативным функциям ( подобно printf) всегда требуется %al = количество аргументов регистра FP.

Существуют правила, когда упаковывать структуры в регистры (rdx:rax при возврате) по сравнению с памятью. Подробности смотрите в ABI и проверьте выходные данные компилятора, чтобы убедиться, что ваш код согласен с компиляторами относительно того, как что-то должно быть передано / возвращено.


Обратите внимание, что соглашение о вызове функций Windows x64 имеет несколько существенных отличий от x86-64 System V, как и теневое пространство, которое должно быть зарезервировано вызывающей стороной (вместо красного зона) и сохраненный вызов xmm6-xmm15. И совсем другие правила, по которым arg идет в какой регистр.

15 голосов
/ 29 марта 2010

Возможно, вы ищете ABI x86_64?

Если это не совсем то, что вам нужно, используйте «x86_64 abi» в предпочитаемой вами поисковой системе, чтобы найти альтернативные ссылки.

11 голосов
/ 12 июня 2011

Соглашения о вызовах определяют, как параметры передаются в регистрах при вызове или при вызове другой программой. И лучший источник этих соглашений в форме стандартов ABI, определенных для каждого из этих аппаратных средств. Для простоты компиляции тот же ABI также используется в пользовательском пространстве и программе ядра. Linux / Freebsd используют тот же ABI для x86-64 и другой набор для 32-разрядных. Но x86-64 ABI для Windows отличается от Linux / FreeBSD. И вообще ABI не различает системные вызовы от обычных «вызовов функций». То есть, вот конкретный пример соглашений о вызовах x86_64, и он одинаков как для пользовательского пространства Linux, так и для ядра: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ (обратите внимание на последовательность параметров a, b, c, d, e, f):

A good rendering of calling conventions vs registers usage

Производительность является одной из причин этих ABI (например, передача параметров через регистры вместо сохранения в стеки памяти)

Для ARM есть различные 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

Соглашение ARM64:

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

Для Linux на PowerPC:

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

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

А для встраивания есть КПП EABI:

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

Этот документ является хорошим обзором всех различных соглашений:

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

4 голосов

Исходные комментарии ядра Linux 5.0

Я знал, что специфика x86 ниже arch/x86, и что системные вызовы идут под arch/x86/entry. Так что быстрый git grep rdi в этом каталоге приводит меня к 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.
 */

и для 32-разрядных в 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
 */

glibc 2.29 Реализация системного вызова Linux x86_64

Теперь давайте обманем, посмотрев основные реализации libc и посмотрим, что они делают.

Что может быть лучше, чем смотреть на glibc, который я использую сейчас, когда пишу этот ответ? : -)

glibc 2.29 определяет системные вызовы x86_64 в sysdeps/unix/sysv/linux/x86_64/sysdep.h и содержит некоторый интересный код, например ::

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

и

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

что я чувствую, само собой разумеется. Обратите внимание, что, похоже, это было разработано для точного соответствия соглашению о вызовах обычных функций System V AMD64 ABI: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Быстрое напоминание о гвоздях:

  • cc означает регистры флагов. Но Питер Кордес комментирует , что в этом нет необходимости.
  • memory означает, что указатель может передаваться в сборке и использоваться для доступа к памяти

Подробный пример минимального запуска с нуля см. В следующем ответе: Как вызвать системный вызов через sysenter во встроенной сборке?

Сделать несколько системных вызовов в сборке вручную

Не очень научно, но весело:

  • 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

Я показал минимальный пример запускаемого пользовательского пространства по адресу: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 Код ядра TODO grep здесь, должно быть просто.

...