7.2 KiB
7.2 KiB
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)
; 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)
; void _exit(int status) — завершает текущий поток/процесс (exit_group завершит все потоки процесса)
mov rax, 60 ; SYS_exit
xor rdi, rdi ; status = 0
syscall ; не возвращается
openat(AT_FDCWD, path, flags, mode)
; 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 аргументами
; 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. Уsyscall4-й аргумент — R10 (не RCX), иsyscallпортит RCX/R11. - Не используй
int 0x80на x86-64: это 32-битный интерфейс, он не совместим и может вести к неверной работе. - Проверка ошибок только по знаку RAX, флаги процессора неинформативны.
- Если смешиваешь
syscallи обычные вызовы функций, следи за выравниванием стека (RSP%16==0 передcall). - В многопоточных программах предпочитай
exit_group(231)для завершения целого процесса;exit(60)завершает только текущий поток.
Мини-макрос для 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 — сохраняй, если меняешь).