lld是LLVM内置平台无关链接器,以.o文件为输入生成可执行文件或DSO,需显式指定crt、libc等运行时依赖,并注意符号可见性、重定位类型及ABI兼容性。

直接用 lld 或系统默认链接器(如 ld)就能完成,但必须注意符号可见性、重定位类型和 ABI 兼容性——不是所有 .o 文件都能无脑链接。
用 lld 链接多个 .o 文件生成可执行文件
lld 是 LLVM 自带的平台无关链接器,行为接近 GNU ld 但更轻量、启动更快。它不自动查找 libc 或 crt0,所以显式指定运行时启动代码和标准库是常见出错点。
- 基础命令:
lld -flavor gnu main.o util.o -o program(Linux/macOS 上需加-flavor gnu兼容传统参数) - 若报错
undefined reference to '_start':说明缺启动代码,得加上crt1.o、crti.o、crtn.o和libc.a,例如:lld -flavor gnu /usr/lib/crt1.o /usr/lib/crti.o main.o util.o /usr/lib/crtn.o -lc -o program - macOS 上要用
-flavor darwin,且路径换成/usr/lib/dylib1.o等,不能混用 Linux 的crt*.o -
lld默认不解析动态符号,加--allow-shlib-undefined才能容忍未定义的动态库符号(比如调用了printf却没连libc)
clang 驱动自动链接 vs 手动调用 lld
用 clang main.o util.o -o program 看似简单,其实是 clang 在后台调用 lld(或系统 ld),并自动注入 crt、libc、链接脚本等。这掩盖了底层依赖,导致你拿不到中间链接命令,调试符号问题时反而更难定位。
- 想看 clang 实际执行的链接命令?加
-v:clang -v main.o util.o -o program,输出里会显示完整的lld调用行 - 想跳过 clang 驱动、完全手动控制?那就别用
clang做链接器,直接调lld,并自己处理--sysroot、-L、-l路径 - clang 默认启用
--dynamic-list-data等安全特性,而裸lld不开,可能导致符号导出行为不一致
目标文件兼容性检查(容易被忽略)
两个 .o 文件即使都是 x86_64,也可能因编译参数不同而无法链接:比如一个用了 -fPIC,另一个没用;一个启用了 -mavx2,另一个只支持 SSE;或者一个用 -mcmodel=large,另一个是默认 small。这些差异在链接时报错往往只显示 relocation truncated to fit 或 invalid relocation type,不指明具体原因。
- 用
llvm-readobj --file-headers *.o检查Flags字段是否都含HAS_SYMS、DYNAMIC等标志 - 用
llvm-readobj --sections *.o看.text的Type是SHT_PROGBITS还是SHT_NOBITS,以及Flags是否含ALLOC/EXECWRITE - 用
llvm-readelf -d *.o | grep NEEDED(如果有的话)确认是否意外引入了动态依赖——纯目标文件不该有NEEDED条目
真正卡住的往往不是“怎么连”,而是“为什么连不上”:符号名修饰、C++ ABI 版本不匹配、rela 与 rel 重定位格式混用、甚至只是 .o 文件被 strip 过导致符号表丢失——这些细节不查 llvm-readobj 输出,光看错误信息根本没法推断。

















