Что такое вызывающие соглашения для системных вызовов UNIX и Linux на i386 и x86-64

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

Но каковы условные обозначения системных вызовов x86-64 как на UNIX, так и на Linux?

Ответы

Ответ 1

Дальнейшее чтение по любой из тем здесь: Полное руководство по системным вызовам 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 Системный вызов UNIX:

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

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


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

x86-64 Mac OS X похожа, но отличается. ТОДО: проверь, что делает * 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 user-space Соглашение о вызове функций:

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

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

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

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

  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. Стек (%rsp) должен быть выровнен по 16B при выполнении call.

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

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

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

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


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

Ответ 2

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

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

Ответ 3

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

Хороший рендеринг соглашений вызовов и использования регистров

Производительность является одной из причин для этих 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

А для встроенного есть PPC EABI:

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

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

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

Ответ 4

В дополнение к ссылке, которую дает Джонатан Леффлер в своем ответе, также может быть полезен Agner Fog "Соглашения о вызовах" .

Ответ 5

Исходные комментарии ядра 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 __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 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 определяет системные sysdeps/unix/sysv/linux/x86_64/sysdep.h 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;                       \
})

который я чувствую, довольно очевиден. Обратите внимание, что, похоже, это было разработано для точного соответствия соглашению о вызовах обычных функций ABI AMD64 в System V: 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 вверх по течению.

aarch64

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