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


147

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

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


Не существует «стандарта» для соглашений о вызовах Unix. Для Linux, конечно, но я уверен, что Solaris, OpenBSD, Linux и Minix, вероятно, имеют разные, по крайней мере, немного разные соглашения о вызовах, и все они Unix.
Earlz

2
Это не совсем так - для большинства типов машин существует набор ABI-интерфейсов UNIX, который позволяет компиляторам C достигать функциональной совместимости. Компиляторы C ++ имеют большую проблему.
Джонатан Леффлер

1
Вы оба правы. Я ищу FreeBSD и Linux.
когти

Буду признателен, если ответ содержит информацию о том, какие регистры сохраняются в системных вызовах. Конечно, указатель стека есть (если он не был изменен контролируемым образом в вызове __NR_clone), но есть ли другие?
Альберт ван дер Хорст

@AlbertvanderHorst: да, я только что обновил ответ на вики с подробностями для 32-битной версии. 64bit был уже точен: rcx и r11 уничтожаются из-за способа sysretработы, а rax заменяется возвращаемым значением. Все остальные регистры сохраняются на amd64.
Питер Кордес

Ответы:


230

Дальнейшее чтение по любой из тем здесь: Полное руководство по системным вызовам 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 для Linux на i386 int 0x80: Hello, world на ассемблере с системными вызовами Linux?

Существует более быстрый способ совершать 32-битные системные вызовы: использование sysenter. Ядро отображает страницу памяти в каждый процесс (vDSO) со стороной sysenterтанца в пользовательской области, которая должна взаимодействовать с ядром, чтобы он мог найти адрес возврата. Arg для регистрации сопоставления такой же, как и для 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 $0x80ABI может использоваться в 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перед a 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 идет в каком регистре.


1
В Linux 32 «все регистры, кроме ax bx cd dx si di bp, сохраняются». Я не могу думать ни о каком ...
Альберт ван дер Хорст

На amd64, если в стеке более 6 параметров и они передаются, кто отвечает за очистку стека после вызова, звонящего или вызываемого абонента?
Николас

1
@ Николас: звонящий очищает стек. Я обновил ответ более подробной информацией о соглашении о вызовах функций.
Питер Кордес

1
Если вы используете int 0x80ABI Linux в 64-битном коде, это именно то, что происходит: stackoverflow.com/questions/46087730/… . Это нули R8-R11, и работает точно так же, как при запуске в 32-разрядном процессе. В этом Q & A у меня есть пример, показывающий, что он работает или не работает с усечением указателя. И я также покопался в исходном коде ядра, чтобы показать, почему он так себя ведет.
Питер Кордес

1
@EvanCarroll: фрагмент (цитируемый текст) находится по ссылке, приведенной в Учебном руководстве по сборке Linux, в частности, в разделе 4.3. Системные вызовы Linux
Майкл Петч,

14

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

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


5
на самом деле, я хочу только соглашение о системных вызовах. esp для UNIX (FreeBSD)
когти

3
@claws: соглашение о системных вызовах является частью ABI.
Джонатан Леффлер

1
Да. Я пошел в irc разработки ядра каждой отдельной ОС и спросил их об этом. Они сказали мне, чтобы посмотреть на источник и выяснить. Я не понимаю, не документируя вещи, как они могут начать развиваться? Итак, я добавил ответ из собранной информации, надеясь, что другие заполнят остальные детали.
когти

@JonathanLeffler ссылка, кажется, сейчас не работает. Если вы также столкнулись с проблемой при переходе по ссылке, можете ли вы обновить ее?
Аджай Брахмакшатрия

@AjayBrahmakshatriya: Спасибо за внимание; Я добавил ссылку на запись Wayback Machine. Весь сайт x86-64.org не ответил ни с какими данными.
Джонатан Леффлер

11

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

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

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


Совершенно помимо сути. Постер вопроса не будет запрашивать соглашение о 64-битном вызове системного вызова в linux, если оно будет таким же, как обычные преобразования ABI.
Альберт ван дер Хорст

6

Исходные комментарии ядра 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;                       \
})

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

aarch64

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


1
"cc"Колошматить ненужно: Системные вызовы Linux сохранение / восстановление RFLAGS (В syscall/ sysretинструкции сделать это с помощью R11, и ядро не изменяет сохраненную R11 / RFLAGS только посредством ptraceсистемных вызовов отладчика.) Не то, что он никогда не имеет значение, потому что "cc"колошматить является неявный для x86 / x86-64 в GNU C Extended asm, поэтому вы ничего не можете получить, оставив его вне.
Питер Кордес
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.