Files
NASM/docs/syscall.md
2025-09-23 17:48:53 +03:00

143 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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, R8R11).
- Выравнивание стека: сама инструкция `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/R12R15 — сохраняй, если меняешь).