x86-64 AT&T汇编代码输出异常问题求助
核心问题:栈操作与返回地址的误用
你的代码逻辑存在根本性错误:你误以为movq %rsp, %rsi指向的是你压入栈的$66,但实际上call print指令会自动将返回地址(即end标签处的指令地址)压入栈顶,导致%rsi指向的是返回地址的内存位置,而非你预期的字符数据。
疑问解答
现象原理
你调用write系统调用时,%rsi是返回地址的指针,%rdx被设为1,所以实际输出的是返回地址的最低1字节。每添加一条3字节的冗余指令(比如xor %rcx, %rcx),print函数的总长度就增加3字节,导致_start和end标签的地址整体后移3字节,返回地址的最低字节值也随之增加3,最终输出的ASCII码就加3。为何影响%rsi寄存器
%rsi在print函数开头就被赋值为%rsp,而此时%rsp指向的是call指令压入的返回地址,后续的pop操作只是修改了%rsp,但不会改变已经赋值的%rsi。所以无论你加多少冗余指令,%rsi始终指向返回地址,输出的自然是返回地址的低字节,而返回地址会随print的长度变化。初始值为何是33
33是无冗余指令时,end标签地址的最低字节的十进制值,对应ASCII字符!。这是由编译器链接后的内存布局决定的,属于巧合。修复方案
你需要让%rsi指向真正的字符数据$66,同时修正栈操作的逻辑,避免破坏call指令的返回地址。以下是两种规范的修复方式:
方式一:修正栈指针指向字符数据
.section .text .globl _start print: movq $1, %rax movq $1, %rdi # 跳过call压入的返回地址和$1,指向$66的位置 leaq 16(%rsp), %rsi movq $1, %rdx # 输出长度设为1 syscall ret # 正常返回,无需jmp end _start: pushq $66 pushq $1 call print end: movq $60, %rax xor %rdi, %rdi syscall
解释:leaq 16(%rsp), %rsi是因为call压入8字节返回地址,加上你压入的$1(8字节),所以rsp+16就是$66的位置。
方式二:使用寄存器传递参数(更符合x86-64规范)
.section .text .globl _start print: # %rdi: 输出字符地址,%rsi: 输出长度 movq $1, %rax movq $1, %rdi # 标准输出fd syscall ret _start: # 将字符'B'暂存到栈上 pushq $66 movq %rsp, %rdi # 传递字符地址 movq $1, %rsi # 传递输出长度 call print addq $8, %rsp # 清理栈上的临时数据 end: movq $60, %rax xor %rdi, %rdi syscall
解释:采用x86-64系统V调用约定的思路,用寄存器传递参数,避免栈操作的混乱。
内容的提问来源于stack exchange,提问作者Bog

