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

7.2 KiB
Raw Permalink Blame History

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)

; 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. У syscall 4-й аргумент — 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/R12R15 — сохраняй, если меняешь).