x64汇编之系统调用详解

张开发
2026/4/17 18:10:37 15 分钟阅读

分享文章

x64汇编之系统调用详解
大家好你们可以叫我凌是个16岁的网络安全学习者。我想了想还是决定把系统调用单独拿出来讲讲毕竟该内容为汇编语言的核心内容极为重要。内容可能会有点拗口不过在文章最后我奉上了我的“焚决”懂点网络安全的人应该立马就可以茅塞顿开继上篇《x64汇编之从程序编辑到系统调用》我们已经初步了解系统调用这篇我们来进行更加细致的讲解。那话不多说我们直接开始吧什么是系统调用用户态与内核态CPU 有两种运行状态用户态权限低不能直接访问硬件、内核内存等。我们编写的普通程序运行在此状态。内核态权限高可以执行任何指令。操作系统内核运行在此状态。为什么需要系统调用如果程序需要显示文字、读取键盘、打开文件、退出等操作这些都属于特权操作必须在内核态完成。因此用户程序必须通过系统调用来请求内核代为执行。类比去银行取钱不能自己打开金库如果这样的话他们可能会问“乍进来的如此大胆”必须填单子(参数)交给柜员(内核)柜员帮你操作。系统调用就是递给柜员的那张单子。系统调用的过程1.在特定寄存器中放入系统调用号告诉内核你要做什么和参数告诉内核具体怎么做。2.执行syscall指令。3.CPU 切换到内核态内核根据系统调用号调用相应函数执行操作。4.内核将结果成功或失败放回寄存器CPU 切回用户态程序继续执行。系统调用的寄存器约定在 x64 Linux 中发起系统调用使用syscall指令参数通过固定寄存器传递返回值也通过寄存器返回。规则如下寄存器作用rax系统调用号调用前返回值调用后rdi第1个参数rsi第2个参数rdx第3个参数r10第4个参数注意不是 rcxr8第5个参数r9第6个参数说明系统调用最多支持6个参数。超过6个的情况极少见通过栈传递现在无需关注。在普通函数调用如 C 语言中第4个参数是rcx但系统调用固定使用r10。这是因为syscall指令会破坏rcx和r11的值因此内核选择用r10来避免冲突。系统调用号速查表系统调用号是定义在内核中的常量可以在/usr/include/asm/unistd_64.h中查到所有定义。下表按功能分类列出最常用的系统调用。注意mmap调用号 9实际需要 6 个参数顺序为 rdi地址建议、rsi长度、rdx保护标志、r10映射标志、r8文件描述符、r9偏移量。其他系统调用参数较少未使用的寄存器忽略即可。参数详解每个系统调用的参数都有特定的含义和取值范围。下面列出最常用的几个并给出详细说明。文件描述符fd文件描述符是一个非负整数代表一个已打开的文件或设备。程序启动时自动打开三个值名称含义0stdin标准输入通常是键盘1stdout标准输出通常是屏幕2stderr标准错误通常是屏幕其他文件描述符如 3, 4由open系统调用返回代表打开的文件。sys_write 与 sys_read 的参数rdi文件描述符如 1 表示屏幕0 表示键盘。rsi数据缓冲区的地址。可以是.data段中定义的标签如 msg也可以是 .bss 中预留的空间。rdx要读取或写入的字节数。注意不要超过缓冲区实际大小否则可能越界。示例向屏幕输出 hello6字节mov rax, 1 mov rdi, 1 mov rsi, msg mov rdx, 6 syscallsys_open 的打开标志flagsflags参数指定打开文件的方式常用值如下十六进制标志十六进制宏名称含义0x0O_RDONLY只读0x1O_WRONLY只写0x2O_RDWR读写0x200O_CREAT若文件不存在则创建0x400O_TRUNC若文件存在则清空0x800O_APPEND追加模式写入从文件末尾开始多个标志可以用按位或组合例如O_WRONLY | O_CREAT | O_TRUNC 1 | 0x200 | 0x400 0x601。mode当使用O_CREAT时需要指定新文件的权限通常用八进制表示如0644所有者读写组和其他只读。该值仅在创建文件时有效。sys_exit 的退出码退出码是一个 8 位整数0~255父进程可以通过wait系统调用获取。约定0 表示成功非零表示错误如 1 一般性错误2 误用 shell 等。可以自定义其他值。sys_lseek 的 whence 参数0SEEK_SET从文件开头偏移1SEEK_CUR从当前位置偏移2SEEK_END从文件末尾偏移偏移量offset可以是正数或负数但通常为正。返回值与错误处理系统调用执行成功后返回值通常放在rax中sys_read返回实际读取的字节数0 表示 EOF-1 表示错误sys_write返回实际写入的字节数-1 表示错误sys_open返回文件描述符-1 表示错误sys_exit无返回值程序终止错误处理如果rax中的值为-1即0xffffffffffffffff则表示调用失败。具体的错误码可以通过errno获取。在汇编中可以通过检查rax是否为负数来判断错误但获取errno需要调用另一个系统调用或使用 C 库。通常只需检查返回值是否为-1然后根据错误码做相应处理。常见错误码errno 值错误码名称含义1EPERM操作不允许2ENOENT文件或目录不存在13EACCES权限拒绝22EINVAL无效参数24EMFILE打开文件过多28ENOSPC磁盘空间不足完整示例输出 Hello 并成功退出section .data msg db Hello, World!, 10 ; 10 是换行符 LF len equ $ - msg ; 自动计算长度 section .text global _start _start: ; 1. 输出字符串 mov rax, 1 ; sys_write mov rdi, 1 ; stdout mov rsi, msg ; 字符串地址 mov rdx, len ; 长度 syscall ; 检查错误通常 write 不会出错但演示 cmp rax, 0 js error ; 如果 rax 0跳转到错误处理 ; 2. 正常退出 mov rax, 60 ; sys_exit mov rdi, 0 ; 成功 syscall error: ; 错误处理退出码 1 mov rax, 60 mov rdi, 1 syscall编译运行nasm -f elf64 hello.asm -o hello.old hello.o -o hello./hello常见问题与易混淆点为什么 rax0 不是成功而是 sys_readrax存放的是系统调用号不是退出码。成功退出是rax60rdi0。为什么 rdi1 有时是“标准输出”有时是“退出码1”在sys_write中rdi是文件描述符1表示标准输出。在sys_exit中rdi是退出码1表示错误。上下文决定含义。系统调用为什么用 r10 而不用 rcx因为syscall指令会破坏rcx和r11的值用于保存返回地址和标志位所以第4个参数被分配到了r10。总结要点说明系统调用号放在rax中告诉内核做什么参数传递rdi,rsi,rdx,r10,r8,r9依次存放最多6个参数返回值成功时返回值在rax失败时rax -1错误码需要通过errno获取汇编中通常只判断正负常用调用sys_write1、sys_exit60是入门必须记住的刚开始不必记忆所有系统调用号只要记住1写和60退出其他用到时查表即可。多写代码多调试系统调用就会变得自然。如果还是不明白我这里还有套“焚决”系统调用就像 MSFrax选模块rdi/rsi/rdx设参数syscall执行

更多文章