为何无法直接向sys_write传入立即数以实现标准输出的汇编调用?
关于x86_64汇编中SYS_WRITE系统调用参数的疑问
你有两段x86_64汇编代码,第一段可以正常运行:
SYS_READ equ 0 SYS_WRITE equ 1 SYS_EXIT equ 60 STDIN equ 0 STDOUT equ 1 section .data my_char: db 'a' global _start section .text _start: mov rax, SYS_WRITE mov rdi, STDOUT mov rsi, my_char mov rdx, 1 syscall mov rax, SYS_EXIT mov rdi, 0 syscall
但将rsi的赋值改为立即数后,代码无法运行:
SYS_READ equ 0 SYS_WRITE equ 1 SYS_EXIT equ 60 STDIN equ 0 STDOUT equ 1 global _start section .text _start: mov rax, SYS_WRITE mov rdi, STDOUT mov rsi, 'a' ; 用立即数字节操作数替换变量 mov rdx, 1 syscall mov rax, SYS_EXIT mov rdi, 0 syscall
你原本以为两种写法都是把rsi设为'a',只是后者没用到变量,想知道二者在内存层面的区别,以及有没有类似第二种写法的可行实现方式。
一、内存层面的核心区别
- 第一段代码的
mov rsi, my_char:这里的my_char是.data段中存储字符'a'的64位内存地址。这条指令是把该内存地址加载到rsi寄存器里,而SYS_WRITE系统调用要求rsi指向要输出数据的内存起始地址,内核会从这个地址读取1字节数据(对应rdx=1),也就是.data段里的'a',因此能正常输出。 - 第二段代码的
mov rsi, 'a':这里是把ASCII值0x61(即'a')直接加载到rsi寄存器里。此时rsi的值是0x61,并非有效内存地址——内核尝试访问地址0x61时,该区域属于进程未映射的低地址空间,会触发段错误,导致程序崩溃。
二、类似第二种写法的可行实现方式
如果不想用.data段的变量,有两种常见方法:
1. 在代码段嵌入数据,通过相对寻址访问
直接在.text段嵌入字符'a',利用rip相对寻址获取其地址:
SYS_WRITE equ 1 SYS_EXIT equ 60 STDOUT equ 1 global _start section .text _start: mov rax, SYS_WRITE mov rdi, STDOUT lea rsi, [rel char_data] ; 获取嵌入数据的内存地址 mov rdx, 1 syscall mov rax, SYS_EXIT mov rdi, 0 syscall char_data: db 'a' ; 在代码段后直接嵌入数据
这种方式无需单独的.data段,数据与代码同段,适合存储小量数据。
2. 将字符压入栈中,用栈地址作为参数
利用栈临时存储字符,把栈指针作为rsi的参数:
SYS_WRITE equ 1 SYS_EXIT equ 60 STDOUT equ 1 global _start section .text _start: mov rax, SYS_WRITE mov rdi, STDOUT push 'a' ; 将字符以8字节形式压入栈,栈顶即为该字符的内存地址 mov rsi, rsp ; 栈指针rsp指向栈顶的'a' mov rdx, 1 syscall add rsp, 8 ; 平衡栈,恢复栈指针位置 mov rax, SYS_EXIT mov rdi, 0 syscall
x86_64的栈操作按8字节对齐,push 'a'实际会把0x0000000000000061压入栈,内核读取1字节时只会取最低位的0x61(即'a'),不影响输出结果。
内容的提问来源于stack exchange,提问作者coulomb
相关产品推荐
相关产品推荐

