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

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

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

4 ответа

Решение

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

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

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

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

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

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

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

Для примера и немного больше чтения, обратитесь к http://www.int80h.org/bsdasm/

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

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

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

http://www.int80h.org/bsdasm/


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

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

См. Раздел: "A.2 Соглашения о ядре AMD64 Linux " двоичного интерфейса приложения System V Приложение к архитектуре AMD64. Последние версии psABI i386 и x86-64 System V можно найти на этой странице в репозитории сопровождающего ABI. (См. Также вики-теги x86 для получения последних ссылок на 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 это информативно, а не нормативно. (Но это на самом деле точно.)

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

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

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


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

x86-64 передает аргументы в регистрах, что является более эффективным, чем соглашение стековых аргументов 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 для деталей и проверьте выходные данные компилятора, чтобы убедиться, что ваш код согласен с компиляторами о том, как что-то должно быть передано / возвращено.

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

что, как мне кажется, говорит само за себя, быстрое напоминание о сгустках:

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

Для явного минимального запускаемого примера с нуля смотрите этот ответ: Как вызвать системный вызов через sysenter во встроенной сборке?

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

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

Соглашения о вызовах определяют, как параметры передаются в регистрах при вызове или вызове другой программой. И лучший источник этих соглашений в форме стандартов 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):

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

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

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

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

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

Другие вопросы по тегам