x86_64汇编中scanf执行两次的问题排查与修复
分析与修复x86_64汇编中scanf重复执行的问题
看起来你遇到的是x86_64汇编里调用标准库函数时的栈平衡与参数传递错误,直接导致了控制流混乱和函数重复执行的问题。我来拆解原因和修复方法:
核心原因分析
你的异常流程本质是程序的栈结构被破坏,导致函数返回后跳转到了错误的指令地址(比如回到之前的scanf调用处),主要可能是以下几点:
- 未遵循栈对齐要求:x86_64 System V ABI规定,调用外部函数(比如
scanf、printf)前,栈指针rsp必须是16字节对齐。而main函数入口时,rsp是8字节对齐的(因为call main会压入8字节的返回地址),如果不提前调整,会触发未定义行为。 - 参数传递错误:
- 对于
scanf的%lf格式,你需要传递double变量的地址(指针),而非变量值;如果传错了,会导致scanf写入错误内存区域,破坏栈数据。 - 对于
printf的%lf格式,浮点参数需要放到xmm0寄存器(而非栈上),错误的传递方式会打乱栈结构,让后续函数调用读取到错误的参数或返回地址。
- 对于
- 栈指针未正确恢复:如果在函数中压栈了数据(比如保存寄存器),退出前没有恢复
rsp到初始状态,ret指令会读取到错误的返回地址,让程序跳回之前的scanf调用。
修复方案(附示例代码)
下面是符合ABI规范的代码示例,解决你的问题:
global main extern printf extern scanf section .data ; 输入格式字符串(必须以0结尾) prompt_epsilon db "epsilon = %lf", 0 prompt_order db "order = %d", 0 ; 输出格式字符串 print_epsilon db "epsilon: %lf", 10, 0 ; 10是换行符ASCII码 print_order db "order: %d", 10, 0 section .bss epsilon resq 1 ; double类型占8字节,用resq分配 order resd 1 ; int类型占4字节,用resd分配 section .text main: ; 调整栈到16字节对齐:main入口时rsp是8字节对齐,sub rsp,8后满足要求 sub rsp, 8 ; 读取epsilon mov rdi, prompt_epsilon ; 第一个参数:格式字符串地址 mov rsi, epsilon ; 第二个参数:epsilon变量的地址 call scanf ; 打印epsilon mov rdi, print_epsilon ; 第一个参数:格式字符串地址 movsd xmm0, [epsilon] ; 浮点参数放到xmm0(%lf对应xmm0) call printf ; 读取order mov rdi, prompt_order ; 第一个参数:格式字符串地址 mov rsi, order ; 第二个参数:order变量的地址 call scanf ; 打印order mov rdi, print_order ; 第一个参数:格式字符串地址 mov esi, [order] ; 整数参数放到esi(rsi的低32位,%d对应32位整数) call printf ; 恢复栈指针,准备返回 add rsp, 8 ret
关键修复点说明
- 栈对齐:进入
main后先执行sub rsp,8,确保调用外部函数前rsp是16字节对齐;退出前用add rsp,8恢复。 - scanf参数传递:所有输入格式的参数都是变量的地址,用
rdi、rsi传递前两个参数(符合ABI的寄存器传递规则)。 - printf参数传递:浮点参数
epsilon用movsd加载到xmm0,整数参数order加载到esi,严格对应格式字符串的类型要求。
按照这个规范修改你的代码,就能解决scanf重复执行、输出顺序混乱的问题了。
内容的提问来源于stack exchange,提问作者tzach94
相关产品推荐
相关产品推荐

