如何在x86-64 AT&T汇编中正确链接main函数
手动链接x86-64汇编目标文件以匹配gcc直接编译的结果
问题根源
当你用gcc 42.s -z noexecstack -o 42直接编译时,gcc会自动链接C运行时(CRT)启动文件,这些文件提供合法的_start入口点——这是内核加载程序后执行的第一个函数,负责初始化C标准库环境、调用你的main函数,最后处理退出逻辑。而你直接用ld仅链接-lc和目标文件时,缺少了这些启动代码,导致程序没有正确的入口和栈环境,执行ret时触发段错误。
手动复现gcc的链接流程
要手动链接出和gcc完全一致的结果,需要包含gcc自动添加的所有CRT文件、标准库及链接参数,步骤如下:
查看gcc实际使用的链接命令
执行gcc -v 42.s -z noexecstack -o 42,输出的最后几行会显示gcc调用ld时的完整参数,其中包含所有需要的CRT文件路径(路径会因gcc版本和系统不同略有差异)。手动执行链接命令
基于gcc输出的参数,构造对应的ld命令。以x86-64 Linux系统为例,典型命令如下:ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \ /usr/lib/gcc/x86_64-linux-gnu/12/crt1.o \ /usr/lib/gcc/x86_64-linux-gnu/12/crti.o \ /usr/lib/gcc/x86_64-linux-gnu/12/crtbegin.o \ 42.o \ -lc \ /usr/lib/gcc/x86_64-linux-gnu/12/crtend.o \ /usr/lib/gcc/x86_64-linux-gnu/12/crtn.o \ -z noexecstack \ -o 42注意:将命令中的
12替换为你系统中实际的gcc版本号。
各组件作用说明
crt1.o:核心启动文件,提供_start入口函数,负责调用__libc_start_main完成标准库初始化、传递命令行参数,并最终调用你的main函数。crti.o/crtn.o:负责设置和清理进程栈帧,以及调用全局构造/析构函数(即使是纯汇编程序也需要,因为gcc默认依赖这个框架)。crtbegin.o/crtend.o:处理C++全局构造与析构逻辑,纯C汇编场景下虽无实际代码,但gcc链接流程默认包含。-lc:链接C标准库,提供__libc_start_main、exit等关键函数。-dynamic-linker /lib64/ld-linux-x86-64.so.2:指定动态链接器,告诉内核加载程序时需要用该程序加载依赖的动态库。
特殊情况
如果你的汇编程序没有依赖C标准库(比如直接通过sys_exit系统调用返回42),可以完全跳过CRT文件和-lc,自己定义_start入口:
.global _start _start: mov $60, %rax # sys_exit系统调用号 mov $42, %rdi # 退出码 syscall
此时直接用ld 42.s -o 42即可链接成功。
内容的提问来源于stack exchange,提问作者DrStein
相关产品推荐
相关产品推荐

