x86_64架构下通过栈或固定内存传参的系统调用示例
x86_64架构非寄存器传递系统调用参数的实例
目前大家熟知的RDI、RSI、RDX等寄存器传参是Linux等主流操作系统为了提升系统调用效率设计的快速路径ABI,并不是x86_64架构的强制要求,内核完全可以根据自身设计选择其他传参路径,两类非寄存器传参的具体实例如下:
1. 通过栈传递系统调用参数的实例
栈传参是操作系统发展过程中非常经典的传参实现,典型落地场景分为两类:
- 全量栈传参:以MINIX 3 x86_64版本为代表,所有系统调用号、调用参数全部通过用户栈传递,完全不依赖通用寄存器承载参数。用户态触发系统调用前,会按照约定顺序把系统调用号、所有参数压入用户栈,再通过软中断陷入内核,内核直接从保存的用户栈指针位置依次读取参数完成处理。
对应write系统调用(向标准输出打印字符串)的汇编实现示例:; MINIX 3 x86_64 栈传参逻辑 mov rax, 4 ; write系统调用号 push 6 ; 第三个参数:输出长度为6 push offset hello_str ; 第二个参数:输出字符串的内存地址 push 1 ; 第一个参数:标准输出文件描述符fd=1 push rax ; 压入系统调用号 int 0x80 ; 触发软中断进入内核 add rsp, 32 ; 调用完成后平衡栈指针 - 混合栈传参:以Windows x86_64系统调用ABI为代表,前4个系统调用参数通过RCX、RDX、R8、R9寄存器传递,超过4个的剩余参数全部压入用户栈传递,内核在陷入流程中会直接从用户栈的约定偏移位置读取多余参数。比如Windows的
NtCreateFile系统调用共有7个参数,前4个走寄存器传递,后3个就通过栈传递。
2. 通过固定内存位置传递系统调用参数的实例
这种传参方式的核心逻辑是,操作系统为每个用户进程预先映射一块固定虚拟地址的专属内存块作为系统调用参数区,用户态触发系统调用前把系统调用号、所有参数写入该固定地址,陷入内核后内核直接从该约定地址读取参数,不需要额外从寄存器、用户栈中解析参数,典型代表是Plan 9的x86_64实现。
Plan 9为每个用户进程固定在0x10000000虚拟地址映射系统调用参数块,结构定义如下:
// 固定地址映射的系统调用参数块结构 struct syscall_block { uint64_t syscall_nr; // 存储系统调用号 uint64_t arg1; uint64_t arg2; uint64_t arg3; uint64_t arg4; uint64_t arg5; uint64_t ret_val; // 内核处理完成后写入的系统调用返回值 }; #define ARG_BLOCK ((struct syscall_block*)0x10000000ULL)
对应的write系统调用汇编实现示例:
; Plan 9 x86_64 固定内存传参逻辑 mov qword [ARG_BLOCK + 0], 4 ; 写入write系统调用号 mov qword [ARG_BLOCK + 8], 1 ; 第一个参数:标准输出fd=1 mov qword [ARG_BLOCK + 16], offset hello_str ; 第二个参数:输出字符串地址 mov qword [ARG_BLOCK + 24], 6 ; 第三个参数:输出长度 syscall ; 触发系统调用陷入内核 mov rax, qword [ARG_BLOCK + 48] ; 从固定内存块读取返回值
除了通用操作系统的实现外,部分半虚拟化场景下的hypercall、做了侧信道攻击防护的特殊内核,也会采用固定内存传参的方式,避免寄存器中残留的敏感参数被窃取。
寄存器传参成为主流方案的核心原因是寄存器访问延迟远低于栈和独立内存块,能大幅缩短系统调用的处理路径、提升性能,但这只是操作系统根据自身需求选择的ABI设计,不是硬件层面的强制规则。
内容的提问来源于stack exchange,提问作者Captain Trojan
相关产品推荐
相关产品推荐

