根本原因是clang未显式指定--sysroot参数,导致链接器在宿主机路径搜索crt1.o等启动文件;正确做法是clang -target=xxx --sysroot=/path/to/sysroot,并确保该路径下包含usr/lib/目录。

LLVM交叉编译时提示 crt1.o、crti.o 或 Scrt1.o 找不到,根本原因不是文件丢了,而是 clang 没被告知目标平台的运行时根目录(sysroot),导致链接器默认在宿主机路径里翻找——而这些启动文件只存在于目标架构的 sysroot/usr/lib/ 下。
clang --sysroot 必须显式传给链接阶段
LLVM 不像 GCC 那样把 sysroot 写死在工具链路径里,它完全依赖命令行参数透传。漏掉 --sysroot,哪怕头文件能搜到,链接器仍会失败。
-
clang -target aarch64-linux-gnu hello.c→ 必然报cannot find crt1.o - 正确写法:
clang -target aarch64-linux-gnu --sysroot=/path/to/sysroot hello.c - 若用
-nostdlib或-nostartfiles,你还得手动加-lc和启动文件路径,比如:-L/path/to/sysroot/usr/lib -lcrt1.o -lcrti.o -lcrtn.o
libtool 或 autotools 会吃掉 --sysroot 参数
很多开源库走 ./configure --host=aarch64-linux-gnu,但底层 libtool 只认编译器名字前缀(如 aarch64-linux-gnu-gcc),对 clang --target=... 完全无感,直接丢弃 --sysroot,导致最终调用的 ld.lld 没有 sysroot。
- 验证方式:加
-v看实际执行的链接命令,确认--sysroot=...是否出现在ld.lld参数里 - 临时修复:改 Makefile,把
$(CC)替成clang --target=aarch64-linux-gnu --sysroot=$(SYSROOT) - 长期建议:换 CMake,或在
configure时用CC="clang --target=..." CFLAGS="--sysroot=..." LDFLAGS="--sysroot=..."强制透传
musl libc 场景下 crt 文件名和链接逻辑不同
如果你用的是 musl(如 OpenWrt、Alpine、鸿蒙),crt1.o 可能不存在,取而代之的是 Scrt1.o(PIE 默认入口)或 crt1.o(非 PIE),且 musl 不提供 libgcc,也不走 -lc 动态链接 libc。
- 检查
sysroot/usr/lib/下真实存在的启动文件:ls /path/to/sysroot/usr/lib/crt*.o - musl 推荐链接方式:
clang --sysroot=/path/to/sysroot -static -nostdlib crt1.o hello.o -lc -lgcc(注意:musl 的libc.a已含启动逻辑,-lc在静态链接时等价于链接libc.a) - 避免混用:
-shared+ musl +--sysroot容易因动态链接器路径错位失败,优先选静态链接验证
最常被忽略的一点:--sysroot 路径必须精确指向包含 usr/lib/ 和 usr/include/ 的父目录,而不是直接指向 usr/lib;少一层或错一级,clang 就不会自动拼出 $SYSROOT/usr/lib/crt1.o。











