Які умови виклику для системних дзвінків UNIX та Linux на i386 та x86-64


147

Наступні посилання пояснюють умови використання системних викликів x86-32 для UNIX (BSD-аромат) та Linux:

Але які умови системного виклику x86-64 як для UNIX, так і для Linux?


Не існує "стандарту" для умов викликів Unix. Напевно для Linux, але я впевнений, що Solaris, OpenBSD, Linux і Minix, ймовірно, мають різні принаймні трохи різні умови викликів, і всі вони є Unix.
Граф

2
Це не зовсім вірно - для більшості типів машин доступний набір UNIX ABI, що дозволяє компіляторам C домогтися сумісності. У компіляторів C ++ є більша проблема.
Джонатан Леффлер

1
Ви обоє правильні. Я шукаю FreeBSD та Linux.
кігті

Буду вдячний, якщо відповідь містить інформацію про те, які регістри зберігаються в системних викликах. Звичайно, покажчик стека є (якщо вони не змінюються контрольованим чином у виклику __NR_clone), але чи є їх іншими?
Альберт ван дер Хорст

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

Відповіді:


230

Подальше читання для будь-якої з тем тут: Постійний посібник із системних дзвінків Linux


Я перевірив їх за допомогою GNU Assembler (газ) в Linux.

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

x86-32 aka i386 Конвенція про системний виклик Linux:

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

Повертається значення в %eax. Усі інші регістри (включаючи EFLAGS) зберігаються по всій території int $0x80.

Я взяв наступний фрагмент з підручника Linux Assembly, але я сумніваюся з цього приводу. Якщо хтось може показати приклад, було б чудово.

Якщо аргументів більше шести, %ebxповинно містити місце пам’яті, де зберігається список аргументів, - але не хвилюйтеся з цього приводу, оскільки навряд чи ви будете використовувати системний виклик з більш ніж шістьма аргументами.

Для прикладу та трохи більше прочитання див. Http://www.int80h.org/bsdasm/#alternate-calling-convention . Ще один приклад Hello World для i386 Linux, що використовує int 0x80: Привіт, світ у мові складання з системними дзвінками Linux?

Існує більш швидкий спосіб здійснення 32-бітних системних дзвінків: використання sysenter. Ядро відображає сторінку пам'яті у кожному процесі (vDSO), з боку простору користувача sysenterтанцю, який повинен співпрацювати з ядром, щоб він міг знайти зворотну адресу. Аргумент для реєстрації відображення такий же, як і для int $0x80. Зазвичай ви повинні зателефонувати в vDSO, а не використовувати sysenterбезпосередньо. (Дивіться Посібник з остаточних системних викликів Linux для отримання інформації про з'єднання та виклик у vDSO, а також про додаткову інформацію про sysenterвсе та інше, що стосується системних дзвінків.)

x86-32 [Безкоштовно | Відкрити | Net | DragonFly] Конвенція про системний виклик BSD UNIX:

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

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


x86-64 Конвенція про системний виклик Linux:

x86-64 Mac OS X схожий, але різний . TODO: перевірити, що робить * BSD.

Дивіться розділ: "А.2 AMD64 Linux Kernel конвенції" про System V Application Binary Interface AMD64 архітектури процесора доповнення . Останні версії iAB-i386 та x86-64 System V psABI можна знайти на цій сторінці в репо- репортажі технічного обслуговування ABI . (Див. Також тег-вікі для оновлених посилань ABI та багатьох інших хороших матеріалів про x86 asm.)

Ось фрагмент із цього розділу:

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

Пам'ятайте, що це додаток для ABI, що стосується Linux, і навіть для Linux це інформативно не є нормативним. (Але насправді це точно.)

Цей 32-розрядний int $0x80ABI можна використовувати в 64-бітовому коді (але настійно не рекомендується). Що станеться, якщо ви використовуєте 32-розрядний int 0x80 Linux ABI у 64-бітному коді? Він все ще скорочує свої входи до 32-розрядних, тому він не підходить для покажчиків, і це нулі r8-r11.

Інтерфейс користувача: виклик функції

Конвенція про виклик x86-32:

У x86-32 параметри передавались на стек. Останній параметр був спочатку натиснений на стек, поки всі параметри не виконані, а потім callвиконана інструкція. Це використовується для виклику функцій бібліотеки С (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 Система 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. І дуже різні правила, за якими аргументами йде в який реєстр.


1
У Linux 32 "збережені всі регістри, крім ax bx cd dx si di bp". Я не можу придумати жодного ...
Альберт ван дер Хорст

Якщо на amd64 є більше 6 параметрів і вони передаються на стек, хто відповідає за очищення стека після виклику, абонента чи виклику?
Ніколас

1
@ Nicolás: абонент очищає стек. Я оновив відповідь більш детально про конвенцію про виклик функцій.
Пітер Кордес

1
Якщо ви використовуєте int 0x80ABI Linux в 64-бітовому коді, саме так і відбувається: stackoverflow.com/questions/46087730/… . Він нулі r8-r11 і працює точно так само, як під час запуску в 32-бітному процесі. У цьому запитанні я маю приклад, який показує, як він працює, або не працює з обрізанням вказівника. А також я заглибився у джерело ядра, щоб показати, чому він так поводиться.
Пітер Кордес

1
@EvanCarroll: Фрагмент (текст цитується) знаходиться за посиланням, що дається Підручником з асамблеї Linux, зокрема в розділі 4.3. Системні дзвінки Linux
Michael Petch

14

Можливо, ви шукаєте x86_64 ABI?

Якщо ви не дуже точно шукаєте цього, використовуйте "x86_64 abi" у вибраній пошуковій системі, щоб знайти альтернативні довідники.


5
насправді я хочу лише конвенцію системного виклику. esp для UNIX (FreeBSD)
кігті

3
@claws: система системних викликів є однією з частин ABI.
Джонатан Леффлер

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

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

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

Я показав приклад мінімального користувальницького користування на веб- сайті: /reverseengineering/16917/arm64-syscalls-table/18834#18834 Код ядра грепового TODO тут повинен бути простим.


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