ld.lld 生成 .so 文件最直接方式是先用 clang -fpic -c 编译源码为位置无关目标文件,再执行 ld.lld --shared func.o -o libfunc.so;必须确保 -fpic 编译且 --shared 在链接阶段显式指定,否则会静默生成可执行文件或报重定位溢出错误。

用 ld.lld 链接生成 .so 文件最直接
LLVM 工具链本身不提供类似 gcc -shared 那样封装好的“一键共享库”命令,但它的链接器 ld.lld 完全支持 ELF 共享对象(.so)生成。关键不是“怎么编译”,而是“怎么链接”——源码先用 clang 编译成位置无关目标文件(.o),再交给 ld.lld 加 --shared 选项链接。
常见错误现象:ld.lld: error: func1.o: requires dynamic R_X86_64_PC32 reloc against 'foo' which may overflow at runtime,本质是没加 -fPIC 编译目标文件。
- 必须用
clang -fPIC -c func.c -o func.o生成位置无关代码;-fPIE不够,它只适用于可执行文件 - 链接时显式指定
--shared,不能省略;ld.lld func.o -o libfunc.so会静默生成可执行文件而非共享库 - 如需导出特定符号(比如 C++ 的 unmangled 名),可加
--export-dynamic或配合version-script -
ld.lld --shared默认不检查未定义符号,若要严格报错,加--no-undefined
clang 命令行也能一步到位,但参数顺序很关键
虽然底层仍是调用 ld.lld,但用 clang 封装更符合日常习惯。问题在于:它对参数顺序敏感,-shared 必须出现在所有输入文件之后,否则会被忽略或误判为传递给前端。
正确写法:clang -fPIC -shared func1.o fmain.o -o libfunc.so
错误写法:clang -shared -fPIC func1.o -o libfunc.so(-shared 被 clang 当作前端选项吃掉,实际链接时不生效)
- 如果源文件里有
main函数,-shared放错位置会导致最终生成的是可执行文件,且运行时报cannot dynamically load executable -
clang默认启用-Wl,--no-as-needed,但如果你链接了系统库(如-lcurl),仍建议显式加-Wl,--as-needed控制依赖粒度 - 调试信息保留在
.so中需要加-g,且确保clang和ld.lld都支持 DWARF 版本一致(通常默认即可)
为什么不用 gcc 而坚持用 LLVM 工具链?
不是为了“替代”,而是为了控制构建链路的确定性。比如你正在调试一个因 llvmpipe 触发的渲染性能问题,而它依赖的 libLLVM 是你自己从 llvm-project 源码构建的,那么配套的 libfunc.so 也必须用同一套 clang + ld.lld 构建,否则 ABI、异常模型(libc++abi vs libstdc++)、甚至栈展开逻辑都可能不兼容。
- 发行版预装的
gcc默认链接libstdc++,而 LLVM 工具链推荐搭配libc++;混用会导致std::string等类型在 so 边界崩溃 -
ld.lld对--version-script和符号可见性(__attribute__((visibility("hidden"))))的支持更贴近现代 ELF 实践,比 GNUld更少隐式导出 - 若后续要插桩(如使用
llvm-pass修改 IR),必须保证整个工具链(包括 so 的生成过程)全程走 LLVM IR 流程,绕过 GCC 的中间表示
容易被忽略的 ABI 兼容点:符号版本与 soname
生成的 .so 要被其他程序 dlopen 或动态链接,光有文件不行,还得有正确的 SONAME 和符号版本控制。LLVM 工具链默认不设 SONAME,得手动加。
示例:ld.lld --shared -soname libfunc.so.1 func.o -o libfunc.so.1.0.0
-
-soname决定运行时链接器记录的名称,readelf -d libfunc.so.1.0.0 | grep SONAME可验证 - 若用
clang,等价写法是clang -fPIC -shared -Wl,-soname,libfunc.so.1 func.o -o libfunc.so.1.0.0 - 没有
version-script时,所有全局符号默认导出;加了--default-symver会让每个符号带基础版本(如func@VERS_1.0),避免升级时意外破坏旧接口 - 真正麻烦的是 C++ 模板实例化和 inline 函数——它们在头文件里定义,但符号可能在多个
.so中重复出现,导致dlsym返回不可预测地址;这时必须用visibility=hidden+ 显式extern template控制











