为何编译需用-static选项?汇编与C代码链接遇段错误原因
问题描述
我编写了如下汇编代码:
global main extern println section .text main: mov rdi, message call println ret message: db "Hello, world!", 0
同时搭配如下C代码进行链接:
#include <stdio.h> void println(char *line) { puts(line); }
执行以下命令编译并链接时程序能正常运行:
nasm -f elf64 helloWorld.asm -o helloWorld.asm.o gcc -c helloWorld.c -o helloWorld.c.o gcc -static helloWorld.c.o helloWorld.asm.o -o helloWorld
运行./helloWorld输出正常,但如果移除-static选项,程序就会出现段错误,这是为什么?
原因分析
核心问题是违反了x86-64 System V调用约定的栈对齐要求:
x86-64的System V ABI明确规定,在执行call指令调用函数前,栈指针rsp必须是16字节对齐的。
当C运行时(crt0)调用你的main函数时,call main指令会将8字节的返回地址压入栈中,导致进入main时,rsp的值是16字节对齐地址减去8——也就是此时栈处于8字节偏移的未对齐状态。你的汇编代码直接在这个状态下调用println,违反了栈对齐要求。
- 静态链接时,libc的实现对栈对齐的检查较宽松,或者内部未使用依赖16字节对齐的指令(比如SSE指令),所以程序能侥幸运行。
- 动态链接时,glibc的
puts函数内部使用了需要严格16字节栈对齐的指令,未对齐的栈会直接触发段错误。
修复方法
只需要在main函数中调整栈到16字节对齐状态,再调用函数即可。常见的两种方式:
方式1:建立标准栈帧
global main extern println section .text main: push rbp ; 压入rbp,rsp减8,此时rsp变为16字节对齐 mov rbp, rsp ; 建立规范栈帧(可选) mov rdi, message call println pop rbp ; 恢复rbp ret message: db "Hello, world!", 0
方式2:直接调整栈指针
global main extern println section .text main: sub rsp, 8 ; rsp减8,补全16字节对齐 mov rdi, message call println add rsp, 8 ; 恢复栈指针 ret message: db "Hello, world!", 0
修改后,无论是否使用-static选项,程序都能正常运行。
内容的提问来源于stack exchange,提问作者doliphin
相关产品推荐
相关产品推荐

