跳到主要内容

系统调用

系统调用是用户程序与操作系统内核交互的接口。本章介绍 Linux 和 Windows 下的系统调用方法。

系统调用概述

用户态与内核态

现代操作系统将程序运行分为两个特权级:

  • 用户态(Ring 3):应用程序运行,权限受限
  • 内核态(Ring 0):操作系统内核运行,完全权限

系统调用是用户态程序请求内核服务的机制。

系统调用过程

用户程序

│ 1. 准备参数
│ 2. 执行系统调用指令

┌─────────────┐
│ 系统调用 │
│ 入口/出口 │
└─────────────┘

│ 3. 切换到内核态

┌─────────────┐
│ 内核处理 │
│ 系统调用 │
└─────────────┘

│ 4. 返回结果

用户程序继续

Linux 系统调用

x86-64 系统调用

在 x86-64 Linux 中,使用 syscall 指令进行系统调用:

参数传递

寄存器用途
RAX系统调用号
RDI第 1 个参数
RSI第 2 个参数
RDX第 3 个参数
R10第 4 个参数
R8第 5 个参数
R9第 6 个参数
RAX返回值

常用系统调用号

编号名称描述
0read读取文件
1write写入文件
2open打开文件
3close关闭文件
9mmap内存映射
12brk调整堆
39getpid获取进程 ID
57fork创建进程
59execve执行程序
60exit退出进程
61wait4等待进程
62kill发送信号

基本系统调用示例

退出程序

; void _start()
_start:
mov rax, 60 ; exit 系统调用号
xor rdi, rdi ; 退出码 0
syscall

输出字符串

section .data
message db "Hello, World!", 0xA
len equ $ - message

section .text
global _start

_start:
; write(stdout, message, len)
mov rax, 1 ; write 系统调用号
mov rdi, 1 ; 文件描述符:stdout
mov rsi, message ; 缓冲区地址
mov rdx, len ; 长度
syscall

; exit(0)
mov rax, 60
xor rdi, rdi
syscall

读取输入

section .bss
buffer resb 1024

section .text
global _start

_start:
; read(stdin, buffer, 1024)
mov rax, 0 ; read 系统调用号
xor rdi, rdi ; 文件描述符:stdin
mov rsi, buffer ; 缓冲区地址
mov rdx, 1024 ; 最大读取字节数
syscall

; RAX = 实际读取的字节数

; exit(0)
mov rax, 60
xor rdi, rdi
syscall

文件操作

打开文件

; int open(const char *filename, int flags, mode_t mode)
; 参数:RDI = 文件名,ESI = 标志,EDX = 模式
; 返回:RAX = 文件描述符(-1 表示错误)

; 标志定义
O_RDONLY equ 0
O_WRONLY equ 1
O_RDWR equ 2
O_CREAT equ 0100o
O_TRUNC equ 01000o

section .data
filename db "test.txt", 0

section .text
mov rax, 2 ; open 系统调用号
mov rdi, filename
mov rsi, O_WRONLY | O_CREAT | O_TRUNC
mov rdx, 0644o ; 文件权限
syscall
; RAX = 文件描述符

读写文件

; 假设 RAX = 文件描述符
mov r12, rax ; 保存文件描述符

; 写入数据
mov rax, 1 ; write
mov rdi, r12 ; 文件描述符
mov rsi, buffer ; 数据地址
mov rdx, buffer_len ; 数据长度
syscall

; 关闭文件
mov rax, 3 ; close
mov rdi, r12
syscall

内存操作

内存映射

; void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
; 参数:RDI=addr, RSI=length, RDX=prot, R10=flags, R8=fd, R9=offset
; 返回:RAX = 映射地址

; 保护标志
PROT_READ equ 1
PROT_WRITE equ 2
PROT_EXEC equ 4

; 映射标志
MAP_SHARED equ 1
MAP_PRIVATE equ 2
MAP_ANONYMOUS equ 32

; 分配 4096 字节内存
mov rax, 9 ; mmap 系统调用号
xor rdi, rdi ; 让内核选择地址
mov rsi, 4096 ; 大小
mov rdx, PROT_READ | PROT_WRITE
mov r10, MAP_PRIVATE | MAP_ANONYMOUS
xor r8, r8 ; fd = -1(匿名映射)
dec r8
xor r9, r9 ; offset = 0
syscall
; RAX = 映射地址

进程操作

创建进程

; pid_t fork(void)
mov rax, 57 ; fork 系统调用号
syscall

; RAX = 0(子进程)或子进程 PID(父进程)
test rax, rax
jz child_process

parent_process:
; 父进程代码
jmp done

child_process:
; 子进程代码

done:

执行程序

; int execve(const char *filename, char *const argv[], char *const envp[])
section .data
program db "/bin/ls", 0
argv0 db "ls", 0
argv1 db "-l", 0
argv dq argv0, argv1, 0
envp dq 0

section .text
mov rax, 59 ; execve 系统调用号
mov rdi, program
mov rsi, argv
mov rdx, envp
syscall
; 如果成功,不会返回

32 位系统调用

在 32 位模式下使用 int 0x80 进行系统调用:

; 32 位系统调用
mov eax, 4 ; write 系统调用号
mov ebx, 1 ; stdout
mov ecx, message ; 缓冲区
mov edx, len ; 长度
int 0x80

Windows 系统调用

系统调用方式

Windows 不直接暴露系统调用接口,而是通过 DLL 导出函数:

; Windows x64 调用约定
; 参数:RCX, RDX, R8, R9,然后栈

extern ExitProcess
extern GetStdHandle
extern WriteFile

section .data
message db "Hello, Windows!", 0xD, 0xA
len equ $ - message

section .bss
written resq 1

section .text
global main

main:
sub rsp, 40 ; 影子空间 + 对齐

; GetStdHandle(STD_OUTPUT_HANDLE)
mov rcx, -11 ; STD_OUTPUT_HANDLE
call GetStdHandle
mov r12, rax ; 保存句柄

; WriteFile(hStdOut, message, len, &written, NULL)
mov rcx, r12 ; 文件句柄
mov rdx, message ; 缓冲区
mov r8, len ; 长度
mov r9, written ; 实际写入字节数
mov qword [rsp + 32], 0 ; OVERLAPPED 结构
call WriteFile

; ExitProcess(0)
xor ecx, ecx
call ExitProcess

使用 Windows API

; MessageBox 示例
extern MessageBoxA

section .data
title db "Message", 0
text db "Hello from Assembly!", 0

section .text
global main

main:
sub rsp, 40

; MessageBox(NULL, text, title, MB_OK)
xor rcx, rcx ; hWnd = NULL
mov rdx, text ; 文本
mov r8, title ; 标题
xor r9, r9 ; MB_OK = 0
call MessageBoxA

add rsp, 40
ret

封装系统调用

创建辅助函数

; 辅助函数:输出字符串
; 参数:RSI = 字符串地址
print_string:
push rax
push rdi
push rdx

; 计算长度
mov rdi, rsi
xor eax, eax
mov rcx, -1
cld
repne scasb
not rcx
dec rcx ; RCX = 长度

; write(stdout, rsi, rcx)
mov rax, 1
mov rdi, 1
mov rdx, rcx
syscall

pop rdx
pop rdi
pop rax
ret

; 辅助函数:输出数字
; 参数:RAX = 数字
print_number:
push rax
push rbx
push rcx
push rdx
push rsi

mov rbx, 10
xor ecx, ecx

.divide_loop:
xor edx, edx
div rbx
push dx ; 保存余数
inc ecx
test eax, eax
jnz .divide_loop

mov rsi, rsp
.print_loop:
mov al, [rsi]
add al, '0'
mov [rsp], al
mov rax, 1
mov rdi, 1
mov rdx, 1
syscall
pop ax
loop .print_loop

pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret

完整示例程序

; 简单的命令行参数打印程序
section .data
newline db 0xA
prefix db "Argument: "

section .text
global _start

_start:
; [rsp] = argc
; [rsp + 8] = argv[0]
; [rsp + 16] = argv[1]
; ...

mov r12, [rsp] ; argc
mov r13, rsp
add r13, 8 ; 指向 argv[0]

xor r14, r14 ; 循环计数器

.print_loop:
cmp r14, r12
jge .done

; 打印前缀
mov rax, 1
mov rdi, 1
mov rsi, prefix
mov rdx, 10
syscall

; 打印参数
mov rsi, [r13 + r14*8]
call print_string

; 打印换行
mov rax, 1
mov rdi, 1
mov rsi, newline
mov rdx, 1
syscall

inc r14
jmp .print_loop

.done:
; exit(0)
mov rax, 60
xor rdi, rdi
syscall

; 辅助函数
print_string:
push rax
push rdi
push rdx

mov rdi, rsi
xor eax, eax
mov rcx, -1
cld
repne scasb
not rcx
dec rcx

mov rax, 1
mov rdi, 1
mov rdx, rcx
syscall

pop rdx
pop rdi
pop rax
ret

小结

本章介绍了系统调用:

  • Linux x86-64:使用 syscall 指令,参数通过寄存器传递
  • Linux 32 位:使用 int 0x80
  • Windows:通过 DLL 导出函数调用 API
  • 常用系统调用:read、write、open、close、exit、mmap、fork、execve

系统调用是汇编程序与操作系统交互的基础。下一章将学习汇编与 C 的混合编程。