プログラムは通常、C ライブラリを通じてカーネルに到達します。write などの C ライブラリの関数が引数を所定の位置に移し、カーネルに入る命令を実行します。ライブラリの下では、システムごとに独自の規約があります。使う命令、システムコール番号を保持するレジスター、引数のレジスター、エラーの報告方法です。これはそのプラットフォームの C の呼び出し規約に似ていますが、アセンブリ、ランタイムライブラリ、サンドボックスを書く人にとって重要な細部が異なります。

x86 のオペレーティングシステムのシステムコール規約
システム命令番号引数結果とエラー
Linux x86-64syscallRAXRDI、RSI、RDX、R10、R8、R9RAX、-4095 から -1 は -errno
Linux x32syscallRAX、ビット 30 をセットRDI、RSI、RDX、R10、R8、R9RAX、-4095 から -1 は -errno
Linux i386int 0x80 または __kernel_vsyscallEAXEBX、ECX、EDX、ESI、EDI、EBPEAX、-4095 から -1 は -errno
FreeBSD x86-64syscallRAXRDI、RSI、RDX、R10、R8、R9RAX、エラー時はキャリーフラグがセットされ、RAX に errno
FreeBSD i386int 0x80EAXスタック上EAX、エラー時はキャリーフラグがセットされる
macOS x86-64syscallRAX (クラスと番号)RDI、RSI、RDX、R10、R8、R9RAX、エラー時はキャリーフラグがセットされる
Windows文書化されていない安定していない文書化されていない文書化されていない

x86-64 の Linux

64 ビット Linux のプログラムは、RAX にシステムコール番号、RDI、RSI、RDX、R10、R8、R9 に最大 6 つの引数を置いて syscall を実行します。カーネルは結果を RAX に返し、-4095 から -1 までの値は符号を反転したエラー番号です。スタックでは何も渡しません。

引数レジスターは C の呼び出し規約のものと同じですが、4 番目だけは RCX ではなく R10 です。syscall 命令自体が RCX を戻りアドレスで、R11 をフラグで上書きするためです。この 2 つのレジスターの値は失われますが、カーネルは RAX 以外のほかのレジスターをすべて保存します。番号は Linux のシステムコール表にあり、write は 1 番です。

raw_syscall6 は、C の呼び出し規約の引数をシステムコールの引数のレジスターに移します。番号は RDI から RAX へ、各引数は 1 つずつ隣のレジスターへ、4 番目は RCX から R10 へ、6 番目はスタックから R9 へ移ります。raw_write はファイルディスクリプター 1 に対してシステムコール 1 番の write を実行します。

syscall-x86-64.c

/* A system call on x86-64 Linux: the number in RAX, arguments in RDI,
   RSI, RDX, R10, R8 and R9; the kernel returns in RAX and overwrites
   RCX and R11. The six arguments of the C function arrive in the
   registers of the C convention, which differ in the fourth. */
long raw_syscall6(long n, long a, long b, long c, long d, long e, long f)
{
	register long r10 __asm__("r10") = d;
	register long r8 __asm__("r8") = e;
	register long r9 __asm__("r9") = f;
	long ret;

	__asm__ volatile ("syscall"
			  : "=a" (ret)
			  : "a" (n), "D" (a), "S" (b), "d" (c), "r" (r10), "r" (r8), "r" (r9)
			  : "rcx", "r11", "memory");
	return ret;
}

/* write(1, buf, len): system call 1 on x86-64. */
long raw_write(const char *buf, unsigned long len)
{
	long ret;

	__asm__ volatile ("syscall"
			  : "=a" (ret)
			  : "a" (1L), "D" (1L), "S" (buf), "d" (len)
			  : "rcx", "r11", "memory");
	return ret;
}

GCC 14.2.0 (Debian 14.2.0-19) x86_64-linux-gnu

AT&T 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -S syscall-x86-64.c

raw_syscall6:
        movq    %rdi, %rax
        movq    %r8, %r10
        movq    %rsi, %rdi
        movq    %r9, %r8
        movq    %rdx, %rsi
        movq    8(%rsp), %r9
        movq    %rcx, %rdx
        syscall
        ret

raw_write:
        movq    %rdi, %r8
        movl    $1, %eax
        movq    %rsi, %rdx
        movq    %rax, %rdi
        movq    %r8, %rsi
        syscall
        ret

Intel 構文 gcc-14 -O2 -fno-asynchronous-unwind-tables -masm=intel -S syscall-x86-64.c

raw_syscall6:
        mov     rax, rdi
        mov     r10, r8
        mov     rdi, rsi
        mov     r8, r9
        mov     rsi, rdx
        mov     r9, QWORD PTR 8[rsp]
        mov     rdx, rcx
        syscall
        ret

raw_write:
        mov     r8, rdi
        mov     eax, 1
        mov     rdx, rsi
        mov     rdi, rax
        mov     rsi, r8
        syscall
        ret

Clang 23.1.2 x86_64-linux-gnu

AT&T 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -S syscall-x86-64.c

raw_syscall6:
        movq    %r9, %r11
        movq    %rdi, %rax
        movq    8(%rsp), %r9
        movq    %rsi, %rdi
        movq    %rdx, %rsi
        movq    %rcx, %rdx
        movq    %r8, %r10
        movq    %r11, %r8
        syscall
        retq

raw_write:
        movq    %rsi, %rdx
        movq    %rdi, %rsi
        movl    $1, %eax
        movl    $1, %edi
        syscall
        retq

Intel 構文 clang --target=x86_64-linux-gnu -O2 -fno-asynchronous-unwind-tables -masm=intel -S syscall-x86-64.c

raw_syscall6:
        mov     r11, r9
        mov     rax, rdi
        mov     r9, qword ptr [rsp + 8]
        mov     rdi, rsi
        mov     rsi, rdx
        mov     rdx, rcx
        mov     r10, r8
        mov     r8, r11
        syscall
        ret

raw_write:
        mov     rdx, rsi
        mov     rsi, rdi
        mov     eax, 1
        mov     edi, 1
        syscall
        ret

Linux x32

x32 のプログラムは同じ命令とレジスターを使いますが、番号の表は独自のものです。カーネルは番号のビット 30、__X32_SYSCALL_BIT (0x40000000) で x32 のシステムコールを識別します。ほとんどのシステムコールは x86-64 と同じ番号で共有されています。引数にサイズの異なるポインターや long の値を含むシステムコールには、512 から始まる独自の番号があります。

i386 の Linux

32 ビット Linux のプログラムは、EAX に番号、EBX、ECX、EDX、ESI、EDI、EBP に最大 6 つの引数を置き、int 0x80 でカーネルに入ることができます。結果は EAX に返り、エラーは x86-64 と同じく -4095 から -1 です。C ライブラリはより高速な経路を優先します。カーネルがすべてのプロセスにマップする vDSO の関数 __kernel_vsyscall を呼び出し、この関数はプロセッサーが SYSENTER を備えていればそれで、なければ int 0x80 でカーネルに入ります。

i386 の write はシステムコール 4 番です。EBX は呼び出し先保存レジスターであり、呼び出し元の位置独立コードが使っているため、保存して復元する必要があります。

syscall-i386.c

/* write(1, buf, len) on i386 Linux: system call 4 through INT 0x80,
   the number in EAX and the arguments in EBX, ECX and EDX. The
   braces give the instruction in AT&T and in Intel syntax. */
long raw_write(const char *buf, unsigned long len)
{
	long ret;

	__asm__ volatile ("int {$0x80|0x80}"
			  : "=a" (ret)
			  : "a" (4L), "b" (1L), "c" (buf), "d" (len)
			  : "memory");
	return ret;
}

GCC 14.2.0 (Debian 14.2.0-19) i386-linux-gnu

AT&T 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S syscall-i386.c

raw_write:
        pushl   %ebx
        movl    $4, %eax
        movl    8(%esp), %ecx
        movl    $1, %ebx
        movl    12(%esp), %edx
        int $0x80
        popl    %ebx
        ret

Intel 構文 gcc-14 -m32 -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S syscall-i386.c

raw_write:
        push    ebx
        mov     eax, 4
        mov     ecx, DWORD PTR 8[esp]
        mov     ebx, 1
        mov     edx, DWORD PTR 12[esp]
        int 0x80
        pop     ebx
        ret

Clang 23.1.2 i686-linux-gnu

AT&T 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -S syscall-i386.c

raw_write:
        pushl   %ebx
        movl    8(%esp), %ecx
        movl    12(%esp), %edx
        movl    $4, %eax
        movl    $1, %ebx
        int     $128
        popl    %ebx
        retl

Intel 構文 clang --target=i686-linux-gnu -march=i686 -mtune=generic -O2 -fno-asynchronous-unwind-tables -masm=intel -S syscall-i386.c

raw_write:
        push    ebx
        mov     ecx, dword ptr [esp + 8]
        mov     edx, dword ptr [esp + 12]
        mov     eax, 4
        mov     ebx, 1
        int     128
        pop     ebx
        ret

64 ビットのプログラムも int 0x80 を実行でき、その場合カーネルはその呼び出しを 32 ビットプログラムのものとして扱います。番号を i386 の表で引き、引数を EBX、ECX、EDX、ESI、EDI、EBP から、それぞれ 32 ビットずつ取ります。そのため、64 ビットのコードにコピーされた int 0x80 は、意図とは異なるシステムコールを、切り詰められたポインターで実行してしまいます。

FreeBSD

x86-64 の FreeBSD は Linux と同じレジスターを使います。番号は RAX、引数は RDI、RSI、RDX、R10、R8、R9 に置き、syscall でカーネルに入ります。エラーの報告方法は異なります。カーネルはキャリーフラグをセットして正のエラー番号を RAX に返し、C ライブラリのラッパーはキャリーフラグで分岐して errno を設定します。i386 では、FreeBSD の C ライブラリは C の呼び出し規約で呼ばれる関数の中で int 0x80 を実行し、カーネルは呼び出しが引数を置いたユーザースタックから引数を読み取ります。

macOS

x86-64 の macOS は syscall でカーネルに入り、FreeBSD や Linux と同じレジスターで引数を渡しますが、RAX の番号はビット 24 以上にクラスを持ちます。カーネルである XNU が BSD カーネルと Mach を組み合わせたものだからです。主なクラスは次のとおりです。

x86-64 の macOS のシステムコールクラス。クラスを 24 ビット左シフトして番号と組み合わせます。
クラス値システムコール
Mach1Mach トラップ (Mach への呼び出し)
Unix2write などの BSD システムコール
マシン依存3x86 固有の呼び出し

そのため、write (4 番) などの BSD システムコールは RAX に 0x2000004 を置いて実行します。FreeBSD と同じく、macOS もエラー時にキャリーフラグをセットします。

OpenBSD

OpenBSD 7.5 以降、カーネルは各システムコールを、そのシステムコールの命令がある C ライブラリ内の場所からしか受け付けません。動的リンカーが pinsyscalls でその場所をカーネルに伝え、ほかの場所からのシステムコールは SIGABRT でプログラムを終了させます。そのため、プログラムは C ライブラリを経由する必要があります。

Windows

Windows はシステムコール規約を文書化していません。プログラムは Windows API を呼び出し、その背後にあるネイティブシステムサービス、つまり Nt と Zw のルーチンには、ユーザーモードから、Microsoft が命令も番号も文書化していないシステムコールを通じて到達します。番号は Windows のリリースごとに変わります。たとえば各リリースの番号をまとめた表によれば、NtCreateUserProcess は Windows Vista から Windows 11 までの間に 6 つを超える番号を持ってきました。独自の番号で syscall を実行するコードは、別の Windows ビルドでは異なるシステムコールを実行するか、どのシステムコールも実行しない可能性があります。

出典

  1. System V Application Binary Interface, AMD64 Architecture Processor Supplement, version 1.0: コミット e1ce098331da、2025-03-12
  2. Linux 7.3: arch/x86/entry/entry_64.S: コミット c03c1a14507d、2026-10-18
  3. Linux 7.3: arch/x86/entry/entry_32.S: コミット c03c1a14507d、2026-10-18
  4. Linux 7.3: arch/x86/include/uapi/asm/unistd.h: コミット c03c1a14507d、2026-10-18
  5. FreeBSD 14.3: lib/libc/amd64/SYS.h: コミット 8c9ce319fef7、2025-06-06
  6. XNU xnu-12377.121.6: osfmk/mach/i386/syscall_sw.h: コミット ac9718fb1af6、2026-06-17
  7. OpenBSD manual: pinsyscalls(2): 2026-09-25 の公開版
  8. Microsoft Learn: Using Nt and Zw versions of the native system services routines: コミット 110f60eaf2ac、2026-09-15
  9. Mateusz Jurczyk: Windows X86-64 System Call Table: 2026-09-25 の公開版