LLD不完全兼容GNU ld脚本,因其采用两阶段处理机制,仅支持SECTIONS、MEMORY、ENTRY等基础语法,不支持INSERT、AS_NEEDED嵌套、PROVIDE_HIDDEN变体及GNU特有符号解析逻辑。

LLD 不兼容 GNU ld 脚本,不是因为“懒得支持”,而是设计目标和解析逻辑根本不同。
LLD 对链接脚本的支持是有限子集
LLD 并非完全拒绝 ld 脚本,而是只实现其中最常用、语义清晰的语法部分。它不处理 GNU ld 特有的扩展指令(如 INSERT AFTER、PROVIDE_HIDDEN 的某些变体)、复杂的表达式求值顺序、或依赖于 GNU 特定符号解析时机的写法。
- 能用的:基本
SECTIONS块、MEMORY定义、ENTRY、PROVIDE(简单赋值)、ALIGN、ADDR、SIZEOF等 - 大概率报错的:
INSERT BEFORE/AFTER、AS_NEEDED嵌套、带副作用的表达式(如. = . + 4在 section 内多次出现)、__eh_frame_hdr相关的 GNU 特殊段处理逻辑 - 行为差异点:GNU ld 中
.(位置计数器)在SECTIONS外部不可见,而 LLD 允许部分上下文访问;但反过来,LLD 不支持 GNU ld 的KEEP对归档成员的精确控制
为什么 LLD 不全量兼容?内存模型和链接阶段不同
GNU ld 是边读脚本边分配、边解析符号边重定位,而 LLD 采用两阶段处理:先完整解析所有输入(含脚本),再统一布局和重定位。这导致它无法复现 GNU ld 中依赖“执行顺序”的脚本行为,比如靠 . 临时偏移做段对齐微调,或用 PROVIDE 动态覆盖符号地址后再被后续段引用——LLD 会提前报 undefined symbol 或直接忽略。
- 典型报错:
undefined symbol 'foo_start' referenced in expression,即使你在脚本里写了PROVIDE(foo_start = .);,但位置不对(比如写在了引用它的段之后) - 真实限制:LLD 不支持脚本中调用外部 shell 命令(GNU ld 的
`shell cmd`),也不支持条件编译(IFDEF系列) - 调试建议:用
lld --verbose查看它实际解析出的段布局,比对着 GNU ld 的ld -verbose输出更容易定位差异
嵌入式场景下如何让 LLD 正确加载启动脚本
Cortex-M 工程常用的 STM32F4xx_FLASH.ld 类脚本,多数可直接用 LLD,但必须检查三处:
- 删掉所有
INSERT指令——改用显式*(.text)和*(.isr_vector)顺序排列 - 把
PROVIDE(__stack_size = 0x400);改成__stack_size = 0x400;(LLD 不识别PROVIDE在非SECTIONS块里的写法) - 确认
MEMORY区域名(如FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M)没拼错,LLD 对大小写和空格更敏感,LENGTH=1M会失败,必须写LENGTH = 1M - 若用
clang编译裸机代码且启用了-flto,脚本里不能依赖__start_*这类由 GNU ld 自动生成的符号——LLD 不生成它们,得手动定义或改用__attribute__((section))显式标记
真正麻烦的从来不是语法转换,而是脚本背后隐含的 GNU ld 行为假设——比如“链接器一定会把第一个 input section 的起始地址赋给 _stext”。LLD 不做这种默认绑定,你得写明 _stext = ADDR(.text);。这点最容易被忽略,也最难 debug。

















