Каковы соглашения о вызовах системных вызовов UNIX и Linux (и функций пользовательского пространства) на i386 и x86-64

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

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


person claws    schedule 29.03.2010    source источник
comment
Не существует стандарта для соглашений о вызовах Unix. Конечно, для linux, но я уверен, что Solaris, OpenBSD, Linux и Minix, вероятно, имеют разные, по крайней мере, немного разные соглашения о вызовах, и все они являются unix.   -  person Earlz    schedule 29.03.2010
comment
Это не совсем так - существует набор UNIX ABI, доступных для большинства типов машин, что позволяет компиляторам C обеспечивать взаимодействие. У компиляторов C ++ есть большая проблема.   -  person Jonathan Leffler    schedule 29.03.2010
comment
Вы оба правы. Я ищу FreeBSD и Linux.   -  person claws    schedule 29.03.2010
comment
Буду признателен, если ответ будет содержать информацию о том, какие регистры сохраняются при системных вызовах. Конечно, указатель стека есть (если он не изменен контролируемым образом в вызове __NR_clone), но есть ли их другие?   -  person Albert van der Horst    schedule 22.01.2016
comment
@AlbertvanderHorst: да, я только что обновил ответ вики, указав детали для 32-битной версии. 64-битная версия уже была точной: rcx и r11 уничтожаются из-за того, как работает sysret, а rax заменяется возвращаемым значением. Все остальные регистры сохраняются на amd64.   -  person Peter Cordes    schedule 02.02.2016
comment
См. Также stackoverflow.com/a/2709009/224132 и эта запись entry_64.S   -  person Peter Cordes    schedule 02.02.2016


Ответы (4)


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


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

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

Соглашение о системных вызовах x86-32, также известное как 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: Здравствуйте, мир на языке ассемблера с системными вызовами Linux?

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

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

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

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


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

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

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

Вот отрывок из этого раздела:

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

Помните, что это из специального приложения к ABI для Linux, и даже для 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. Это позволяет избежать задержки и дополнительных инструкций по сохранению аргументов в памяти (кеш) и последующей их загрузке обратно в вызываемый объект. Это хорошо работает, потому что доступно больше регистров, и лучше для современных высокопроизводительных процессоров, где важны задержка и неупорядоченное выполнение. (ABI i386 очень старый).

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

Для получения полной информации см .: 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 инструкция. Стек (%rsp) должен быть выровнен по 16B при выполнении call.

Если имеется более 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 входит в какой регистр.

person Community    schedule 29.03.2010
comment
В linux 32 все регистры, кроме ax bx cd dx si di bp, сохраняются. Я не могу придумать ни одного ... - person Albert van der Horst; 16.03.2016
comment
В amd64, если параметров больше 6 и они передаются в стек, кто отвечает за очистку стека после вызова, вызывающий или вызываемый? - person Nicolás; 10.10.2016
comment
@ Николас: вызывающий абонент очищает стек. Я обновил ответ, добавив более подробную информацию о соглашении о вызове функций. - person Peter Cordes; 03.09.2017
comment
Если вы используете Linux int 0x80 ABI в 64-битном коде, это именно то, что происходит: stackoverflow.com/questions/46087730/ . Он обнуляет r8-r11 и работает точно так же, как при запуске в 32-битном процессе. В этих вопросах и ответах у меня есть пример, показывающий, как он работает или не работает с усечением указателя. И я также покопался в исходном коде ядра, чтобы показать, почему оно так себя ведет. - person Peter Cordes; 07.09.2017
comment
В FreeBSD возвращаемое значение системного вызова x64 не -errno, это errno, и установлен флаг переноса. См. это . - person Bilow; 15.12.2017
comment
Вы имеете в виду x86-32 как 64-битный регистр с 32-битными указателями или классический 32-битный? - person NieDzejkob; 27.02.2018
comment
@EvanCarroll: фрагмент (цитируемый текст) находится по указанной ссылке Руководство по сборке Linux, в частности в разделе 4.3 Системные вызовы Linux - person Michael Petch; 26.10.2018
comment
Каковы соглашения о вызовах для пользовательского пространства x64, если вы передаете / возвращаете структуры по значению? - person JCWasmx86; 28.07.2020
comment
Что насчет 16-битных регистров? Может кто обновится? Мне это действительно нужно - person Roi; 15.09.2020
comment
@ r0ei То же, что и с 64-битными регистрами. Это ax вместо rax, это bx вместо rbx и так далее. За исключением 16-битного соглашения о вызовах, есть и другие способы передачи аргументов. - person JCWasmx86; 15.09.2020

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

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

person Jonathan Leffler    schedule 29.03.2010
comment
на самом деле, мне нужно только соглашение о системном вызове. esp для UNIX (FreeBSD) - person claws; 29.03.2010
comment
@claws: соглашение о системных вызовах является частью ABI. - person Jonathan Leffler; 29.03.2010
comment
Ага. Я ходил в irc по разработке ядра каждой отдельной ОС и спрашивал их об этом. Они сказали мне заглянуть в источник и выяснить. Я не понимаю, не документируя, как они могут просто начать развиваться? Итак, я добавил ответ из собранной мной информации, надеясь, что другие дополнят остальные детали. - person claws; 29.03.2010
comment
@JonathanLeffler ссылка, похоже, сейчас не работает. Если у вас также возникают проблемы с переходом по ссылке, не могли бы вы обновить ее? - person Ajay Brahmakshatriya; 25.11.2018
comment
@AjayBrahmakshatriya: Спасибо за внимание; Я добавил ссылку на запись Wayback Machine. Весь веб-сайт x86-64.org не ответил никакими данными. - person Jonathan Leffler; 25.11.2018

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

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

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

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

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

person Peter Teoh    schedule 12.06.2011
comment
Совершенно неважно. Плакат с вопросом не запрашивал бы 64-битное соглашение о вызовах системных вызовов в Linux, если бы оно было таким же, как общие преобразования ABI. - person Albert van der Horst; 22.01.2016

Комментарии к исходному тексту ядра 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 в _ 6_ и содержит интересный код, например:

/* 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 означает, что указатель может быть передан в сборке и использован для доступа к памяти.

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

Выполните некоторые системные вызовы в сборке вручную

Не очень научный, но забавный:

comment
"cc" clobber не нужен: системные вызовы Linux сохраняют / восстанавливают RFLAGS (инструкции _2 _ / _ 3_ делают это с использованием R11, а ядро ​​не изменяет сохраненные R11 / RFLAGS, кроме как через ptrace системные вызовы отладчика.) Не то чтобы это когда-либо имело значение, потому что "cc" clobber неявно используется для x86 / x86-64 в GNU C Extended asm, поэтому вы ничего не получите, если его не укажете. - person Peter Cordes; 02.03.2019