x86-64 GNU汇编移动代码行后寄存器值异常 阶乘计算结果错误
异常原因分析
x86-64 Linux遵循的System V AMD64调用约定明确规定:%rbx、%rbp、%r12~%r15属于被调用者保存寄存器。也就是说如果函数内部需要修改这些寄存器,必须先在栈上备份寄存器的原值,函数返回前再恢复原值,否则会破坏上层调用者存在这些寄存器里的数据。
你当前的factorial函数没有对%rbx做任何备份就直接修改,本身就不符合ABI规范,只是原来把mov 16(%rbp), %rbx放在call factorial之后,修改完%rbx马上就用来做乘法,之后函数直接返回,没有上层逻辑依赖%rbx的原值,才刚好没有触发问题。
当你把这行移到call factorial之前时,执行逻辑变成:
- 当前递归层把参数n存入%rbx
- 调用下一层的
factorial - 下一层
factorial执行时也会修改%rbx,且没有备份 - 下层执行完毕返回到当前层时,%rbx里之前存的n已经被下层覆盖成了其他值
- 最终相乘时用了错误的%rbx值,导致阶乘计算错误。
退出码为16正是计算过程中寄存器值被反复覆盖、最终乘法结果错误导致的。
解决方法
提供两种可直接落地的修复方案:
方案1:遵守ABI规范,备份%rbx寄存器
在factorial函数的栈帧创建逻辑中增加%rbx的备份和恢复步骤,修改后的栈帧操作代码如下:
factorial: # 创建栈帧,同时备份%rbx push %rbp push %rbx mov %rsp, %rbp # ...... 原有函数逻辑不变 ...... return_factorial: mov %rbp, %rsp pop %rbx # 恢复%rbx原值 pop %rbp ret
修改后无论mov 16(%rbp), %rbx放在函数内的哪个位置,都不会出现值被异常覆盖的问题。
方案2:消除%rbx的使用,直接用内存参数计算
imul指令支持直接和内存地址中的值相乘,完全不需要用%rbx做中转,可以直接把相关两行代码替换为一行:
# 替换原来的mov 16(%rbp), %rbx和imul %rbx, %rax两行 imul 16(%rbp), %rax
该方案彻底规避了寄存器使用冲突,代码更简洁,执行效率也更高。
内容的提问来源于stack exchange,提问作者rencedm112
相关产品推荐
相关产品推荐

