## Linux x86-64 syscall: шпаргалка Кратко о низкоуровневых системных вызовах в Linux на x86-64 через инструкцию `syscall` (без libc/glibc-обёрток). ### Быстрые правила - Номер системного вызова: в RAX. - Аргументы (целые/указатели): RDI, RSI, RDX, R10, R8, R9 - Возврат: RAX (неотрицательно при успехе; при ошибке — отрицательное значение `-errno` в диапазоне [-4095..-1]). - Порченные регистры инструкцией `syscall`: RCX, R11 (всегда). RAX меняется на код возврата. - Остальные GPR обычно возвращаются неизменёнными ядром, но в пользовательском коде всё равно соблюдай ABI: после syscalls/функций не полагайся на caller-saved (RAX, RCX, RDX, RSI, RDI, R8–R11). - Выравнивание стека: сама инструкция `syscall` не требует 16-байтового выравнивания RSP, но если ты в функции, которая также вызывает обычные функции по ABI System V, держи RSP кратным 16 перед `call`. - Red zone (128 байт под RSP) по ABI доступна и не трогается ядром/обработчиками сигналов — её можно использовать в leaf-коде. Если есть сомнения/нестандартное окружение — не полагайся на неё. ### Возврат и обработка ошибок - При успехе RAX содержит неотрицательный результат (часто количество байт, файловый дескриптор и т.п.). - При ошибке RAX содержит отрицательное число `-errno` (в диапазоне [-4095..-1]). Никаких флагов (CF/ZF) для ошибки не используется — проверяй знак RAX. - Обёртки libc (например, `write(2)`) конвертируют `-errno` в `-1` и устанавливают `errno`. В чистом `syscall` этого нет — обрабатывай сам. ### Где взять номера системных вызовов - Заголовки ядра: `/usr/include/x86_64-linux-gnu/asm/unistd_64.h` (или `/usr/include/asm/unistd_64.h` в зависимости от дистрибутива). - `man 2 syscall`, `man 2 <имя>` (например, `man 2 write`). Часто используется `openat` вместо устаревающего `open`. ### Примеры (NASM) #### write(1, msg, len) ```nasm ; ssize_t write(int fd, const void *buf, size_t count) ; x86-64: rax=1 (SYS_write), rdi=fd, rsi=buf, rdx=count mov rax, 1 ; SYS_write mov rdi, 1 ; fd = stdout lea rsi, [rel msg] ; buf mov rdx, msg_end - msg ; count syscall ; при успехе: rax = число записанных байт (>=0) ; при ошибке: rax < 0 ( = -errno ) msg: db "Hello, syscall!\n", 0x0A msg_end: ``` #### exit(status) ```nasm ; void _exit(int status) — завершает текущий поток/процесс (exit_group завершит все потоки процесса) mov rax, 60 ; SYS_exit xor rdi, rdi ; status = 0 syscall ; не возвращается ``` #### openat(AT_FDCWD, path, flags, mode) ```nasm ; long openat(int dirfd, const char *pathname, int flags, mode_t mode) ; x86-64: rax=257 (SYS_openat), rdi=dirfd, rsi=pathname, rdx=flags, r10=mode mov rax, 257 ; SYS_openat mov rdi, -100 ; AT_FDCWD lea rsi, [rel path] mov rdx, 0x0002 ; O_RDWR (пример) mov r10, 0o644 ; mode syscall ; rax >= 0 => fd, rax < 0 => -errno path: db "./file.txt", 0 ``` #### mmap(addr, length, prot, flags, fd, offset) — пример с 6 аргументами ```nasm ; void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) ; x86-64: rax=9, rdi=addr, rsi=length, rdx=prot, r10=flags, r8=fd, r9=offset mov rax, 9 ; SYS_mmap xor rdi, rdi ; addr = NULL (hint) mov rsi, 4096 ; length mov rdx, 3 ; PROT_READ|PROT_WRITE mov r10, 0x22 ; MAP_PRIVATE|MAP_ANONYMOUS (пример) mov r8, -1 ; fd = -1 (анонимно) xor r9, r9 ; offset = 0 syscall ; при успехе: rax = адрес (неотрицательный с точки зрения знака) ; при ошибке: rax < 0 => -errno ``` ### Часто используемые номера (x86-64) - 0 — read - 1 — write - 2 — open (лучше использовать 257 — openat) - 3 — close - 9 — mmap - 11 — munmap - 12 — brk - 60 — exit - 61 — wait4 - 62 — kill - 63 — uname - 78 — gettimeofday (устар.; лучше clock_gettime: 228) - 202 — futex - 231 — exit_group (завершает все потоки процесса) - 257 — openat Подробный список — в заголовках ядра и `man 2`. ### Замечания и подводные камни - Не путай ABI функций и ABI `syscall`. У `syscall` 4-й аргумент — R10 (не RCX), и `syscall` портит RCX/R11. - Не используй `int 0x80` на x86-64: это 32-битный интерфейс, он не совместим и может вести к неверной работе. - Проверка ошибок только по знаку RAX, флаги процессора неинформативны. - Если смешиваешь `syscall` и обычные вызовы функций, следи за выравниванием стека (RSP%16==0 перед `call`). - В многопоточных программах предпочитай `exit_group(231)` для завершения целого процесса; `exit(60)` завершает только текущий поток. ### Мини-макрос для NASM (опционально) ```nasm ; Использование: SYS SYS_write, fd, buf, len %define SYS_read 0 %define SYS_write 1 %define SYS_openat 257 %define SYS_exit 60 %macro SYS 1-7 mov rax, %1 %if %0 > 1 mov rdi, %2 %endif %if %0 > 2 mov rsi, %3 %endif %if %0 > 3 mov rdx, %4 %endif %if %0 > 4 mov r10, %5 %endif %if %0 > 5 mov r8, %6 %endif %if %0 > 6 mov r9, %7 %endif syscall %endmacro ``` ### Памятка при написании кода - Положи номер вызова в RAX, аргументы — в RDI, RSI, RDX, R10, R8, R9. - Вызови `syscall`; помни, что RCX и R11 будут испорчены. - Проверяй RAX: `rax < 0` — ошибка (`-errno`), `rax >= 0` — успех. - Соблюдай сохранность регистров по ABI System V для своего кода (RBX/RBP/R12–R15 — сохраняй, если меняешь).