x86_64汇编程序移除push/pop rdx指令后出现段错误的原因咨询
x86_64汇编程序移除push/pop rdx指令后出现段错误的原因咨询
嘿,我来帮你把这个问题掰明白!你遇到的段错误,核心原因和x86_64下的除法指令规则、系统调用对寄存器的破坏这两点直接相关,咱们一步步说:
1. 先搞清楚两个关键规则
div64位除法的行为:当你执行div rcx(无符号64位除法)时,CPU会把**rdx:rax当成一个128位的被除数**(rdx是高位部分,rax是低位部分),除以rcx后,商存在rax,余数存在rdx。如果执行除法前rdx里有垃圾值,这个垃圾值会被当成被除数的高位,直接导致除法结果完全错误。- Linux syscall 对寄存器的影响:调用
syscall时,它会直接破坏rdx寄存器的值(还有其他几个寄存器,这里关键是rdx)。你的PrintNewline宏里就调用了syscall来写换行,这会把rdx改成1(因为宏里mov rdx,1)。
2. 为什么移除 push rdx/pop rdx 就崩了?
咱们走一遍出问题的流程:
- 第一次调用
_printint:如果_printint里有push rdx,会先把当前rdx的值(程序初始时rdx是0)保存到栈上,然后执行div rcx时,rdx是0,所以被除数是0:rax(也就是正常的rax里的数值),除法行为完全正确,最后pop rdx恢复原值。 - 然后调用
PrintNewline:如果这个宏里没有push rdx/pop rdx,syscall会把rdx改成1;如果_printint里没有push rdx,那下次进入_printint时,rdx就带着这个1的垃圾值了。 - 第二次调用
_printint:进入后直接执行div rcx,这时候被除数是1:rax(rax是10),也就是一个128位的超大数!除法后得到的商大到离谱,会触发循环里无数次的push rdx,直接把栈撑爆,最终导致段错误。
另外,PrintNewline 里的 push rdx/pop rdx 是为了保护 rdx 不被系统调用破坏——如果你的其他代码(比如 _printint)依赖 rdx 的值,宏执行完后必须把 rdx 恢复成调用前的样子,否则后续代码用 rdx 时就会拿到垃圾值。
3. 你的代码还有个隐藏小bug
其实 _printint 里还有个问题:你应该在第一次执行 div rcx 前,手动把 rdx 清零!比如加一行 xor rdx, rdx,这样不管 rdx 之前是什么值,除法都会用正确的 0:rax 作为被除数,就算忘了 push/pop rdx 也不会出大问题(当然还是建议保留寄存器保护的习惯)。
修改后的 _printint 开头示例:
_printint: push rdx xor rdx, rdx ; 清零rdx,确保除法用正确的128位被除数 mov r10, 0 mov rcx, 10 ; ... 后面的代码不变
总结一下
_printint里的push rdx/pop rdx:一是临时保存rdx原来的值,二是(巧合地)让第一次除法时rdx是0,避免了垃圾值干扰除法。PrintNewline里的push rdx/pop rdx:是为了在syscall破坏rdx后,把它恢复成调用宏之前的状态,避免影响后续代码。
这样解释应该就通了吧?😉




