You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86-64架构下无栈动态打印字符串子程序异常问题排查

问题分析与修复方案

哈哈,这个问题我一眼就看穿了——你踩了x86-64汇编里寄存器使用的经典坑:修改了输入寄存器rcx的原始值,却没保留字符串的起始地址,导致系统调用时传错了要打印的内容。

到底哪里错了?

咱们一步步捋你的_output子程序流程:

  1. 调用_output时,rcx明明指向output1的首字符'h'
  2. 进入_length_loop后,你反复inc rcx遍历字符串,直到找到结束符0。这时候rcx已经跑到output1末尾的0那个位置了,完全不是原来的起始地址了!
  3. 然后你直接把这个已经跑偏的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

几个关键改进点

  1. 用rbx保存起始地址:它是被调用者保存寄存器,不会被系统调用或者其他操作篡改,完美替代栈的保存功能
  2. 调整长度计算逻辑:先判断当前字符是不是0,再递增长度和指针,这样计算出来的长度是字符串的实际有效长度(不包含结束符),之前的逻辑会多算一个结束符的位置,还好没影响结果,但规范写法更稳妥
  3. 直接用rdx存长度:避免了后续还要把rbx转赋值给rdx,减少出错环节

内容的提问来源于stack exchange,提问作者maybe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 06:37:50