64位环境下从自定义函数调用printf出现段错误,主函数调用正常
嘿,我帮你拆解下这个问题——在x86-64汇编(Linux/macOS通用的System V调用约定)里,自定义函数调用printf触发段错误,大概率是栈对齐违规或者调用约定遵守不当导致的。毕竟主函数的栈环境是操作系统帮你初始化好的,自定义函数得自己维护这些细节~
先结合你给出的代码片段,把核心问题和解决方法理清楚:
1. 最容易踩的坑:16字节栈对齐要求
x86-64的System V调用约定明确规定:调用任何函数之前,栈必须是16字节对齐的。
- 主函数启动时,操作系统已经把栈调整成16字节对齐状态;但自定义函数调用
printf时,很容易破坏这个对齐:- 当你用
call指令调用自定义函数时,会把8字节的返回地址压入栈,此时进入自定义函数时,栈指针rsp是16字节对齐的; - 但如果直接调用
printf,call printf又会压入8字节返回地址,此时rsp变成8字节偏移(不对齐),这就会触发段错误。
- 当你用
解决方法:在自定义函数调用printf前,手动调整栈到16字节对齐,比如临时压入一个寄存器或者直接sub rsp, 8,调用完再恢复:
; 规范的自定义函数示例 calc_and_print: push rbp ; 显式维护栈帧,避免栈结构混乱 mov rbp, rsp sub rsp, 8 ; 调整栈到16字节对齐(push rbp后rsp变为8对齐,减8回到16对齐) ; 准备printf的参数:%lf对应xmm0寄存器 movsd xmm0, [d1] addsd xmm0, [d2] ; 计算d1+d2,结果放到xmm0 mov rdi, fmt ; 格式字符串作为第一个参数放入rdi call printf ; 此时栈是16对齐的,调用正常 add rsp, 8 ; 恢复栈空间 pop rbp ret
2. 浮点参数传递不能搞错
%lf对应的是double类型,在x86-64调用约定里,浮点参数必须通过XMM寄存器传递(第一个浮点参数放xmm0,第二个放xmm1,以此类推),而不是通用寄存器(rdi/rsi这些)。如果你在自定义函数里错误地把浮点值放到通用寄存器,调用printf必然会出问题。
比如你主函数里可能正确用了movsd xmm0, [d1],但自定义函数里误写成mov rsi, [d1],这肯定会崩。
3. 栈帧维护要规范
自定义函数里最好显式维护栈帧(push rbp + mov rbp, rsp),这样不容易搞乱栈的结构,返回前记得pop rbp恢复,避免栈溢出或者寄存器污染。
完整可运行示例代码
把这些点整合起来,给你一个能正常运行的完整代码:
extern printf SECTION .data msg: db "Hello", 0 fmt: db "%lf", 10, 0 d1: dq 13.0 d2: dq 15.0 result : dq 0 SECTION .text global main main: push rbp mov rbp, rsp call calc_and_print ; 调用自定义函数 mov rsp, rbp pop rbp mov rax, 0 ret ; 自定义函数:计算d1+d2并打印结果 calc_and_print: push rbp mov rbp, rsp sub rsp, 8 ; 确保调用printf前栈是16字节对齐 movsd xmm0, [d1] addsd xmm0, [d2] mov rdi, fmt call printf add rsp, 8 pop rbp ret
Linux下编译运行命令:
nasm -f elf64 yourcode.asm -o yourcode.o gcc yourcode.o -o yourcode ./yourcode
会输出28.000000(不同环境输出格式略有差异,但不会崩溃)。
额外提醒
如果你的自定义函数用到了被调用者保存寄存器(rbx、r12-r15),必须先把它们压入栈保存,返回前再弹出恢复,不然会破坏主函数的寄存器状态。
内容的提问来源于stack exchange,提问作者Alex Lavriv

