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

StackOverflow https://stackoverflow.com/questions/2535989

Вопрос

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

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

Это было полезно?

Решение

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

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


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

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

Обратитесь к разделу:"А.2 ДРАМА64 Linux Соглашения ядра" из Дополнение к процессору System V Application с двоичным интерфейсом Архитектуры AMD64.Последние версии i386 и x86-64 System V psABIs можно найти ссылка с этой страницы в репозитории сопровождающего ABI.(См . также отметьте wiki для получения актуальных ссылок 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 или class MEMORY.

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

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

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

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

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

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

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


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

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

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

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

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

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

Итак %rdi, %rsi, %rdx, %rcx, %r8 and %r9 являются ли регистры по порядку используется для передачи целого числа / указателя (т.е.ЦЕЛОЧИСЛЕННЫЙ класс) параметры для любой функции libc из assembly.%rdi используется для первого ЦЕЛОЧИСЛЕННОГО параметра.%rsi для 2-го, %rdx для 3-го и так далее.Тогда call должны быть даны инструкции.Стек (%rsp) должно быть выровнено по 16B, когда call выполняет.

Если имеется более 6 ЦЕЛОЧИСЛЕННЫХ параметров, в стек передается 7-й ЦЕЛОЧИСЛЕННЫЙ параметр и более поздние версии.(Появляется вызывающий абонент, такой же, как x86-32.)

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

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

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


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

Другие советы

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

Если это не совсем то, что вам нужно, используйте '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):

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

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

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

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

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

Комментарии к исходному коду ядра Linux 5.0

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

aarch64

Я показал минимальный доступный пример пользовательского пространства на: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 ВЫПОЛНИТЬ grep-код ядра здесь должно быть легко.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top