x86-64架构下无栈动态打印字符串子程序异常问题排查
问题分析与修复方案
哈哈,这个问题我一眼就看穿了——你踩了x86-64汇编里寄存器使用的经典坑:修改了输入寄存器rcx的原始值,却没保留字符串的起始地址,导致系统调用时传错了要打印的内容。
到底哪里错了?
咱们一步步捋你的_output子程序流程:
- 调用
_output时,rcx明明指向output1的首字符'h' - 进入
_length_loop后,你反复inc rcx遍历字符串,直到找到结束符0。这时候rcx已经跑到output1末尾的0那个位置了,完全不是原来的起始地址了! - 然后你直接把这个已经跑偏的
rcx赋值给rsi(write系统调用要求的字符串首地址参数)——这就等于告诉内核:"从output1的结束符位置开始读rbx个字节"。而你的output1和output2在.data段是连续存储的,所以内核自然就从output1后面的output2里读了11个字节,也就是你看到的"again hell"。
你说rbx的长度是对的,这很正常——因为长度计算确实是从起始地址到结束符的字节数,但你把起始地址弄丢了啊!这就像你数完一本书的页数,然后把书扔了,却告诉别人"去拿那本有X页的书来读",结果别人拿了旁边的另一本。
无栈版本的正确写法
既然要避免用栈,咱们得找个"安全"的寄存器来存原始起始地址。根据x86-64 Linux的调用规范,rbx、rbp、r12-r15是被调用者保存寄存器——也就是说,子程序调用前后这些寄存器的值会被保留,用它们存东西不会乱。
修改后的可运行代码
section .data output1 db "hello world",10,0 ; 加个换行符,输出更清爽 output2 db "again hello world",10,0 section .bss section .text global _start _start: mov rcx,output1 call _output mov rcx,output2 call _output call _exit _output: mov rbx, rcx ; 把原始起始地址存到rbx里,稳得一批 mov rdx, 0 ; 用rdx存长度,省得后面和系统调用的rdx搞混 _length_loop: cmp byte [rcx], 0 ; 先判断当前字符是不是结束符,再递增 je _system_call inc rdx inc rcx jmp _length_loop _system_call: mov rax,1 ; write系统调用号 mov rdi,1 ; 标准输出stdout mov rsi, rbx ; 用之前保存的原始起始地址,这才是对的! ; rdx已经是计算好的有效长度,直接用 syscall ret _exit: mov rax,60 mov rdi,0 syscall ret
几个关键改进点
- 用
rbx保存起始地址:它是被调用者保存寄存器,不会被系统调用或者其他操作篡改,完美替代栈的保存功能 - 调整长度计算逻辑:先判断当前字符是不是
0,再递增长度和指针,这样计算出来的长度是字符串的实际有效长度(不包含结束符),之前的逻辑会多算一个结束符的位置,还好没影响结果,但规范写法更稳妥 - 直接用
rdx存长度:避免了后续还要把rbx转赋值给rdx,减少出错环节
内容的提问来源于stack exchange,提问作者maybe
相关产品推荐
相关产品推荐

