在 ld 脚本中,需用 SECTIONS 块显式指定 .text 和 .data 起始地址(如 . = 0x80000000; ALIGN(0x1000);),并分别定义段内容、对齐、符号(如 _edata, _sbss),同时显式处理 .bss(带 NOLOAD)和 .init_array 等关键段,确保与编译器输出、硬件内存布局及 C++ ABI 兼容。

ld 脚本里怎么让 .text 和 .data 落到指定地址
链接器脚本(linker script)是控制最终可执行文件段布局的底层手段,ld 读取它决定每个段放哪、多大、对齐多少。关键不是“写个脚本就行”,而是得和编译器生成的段名、属性、以及目标平台的内存视图对齐。
常见错误是直接照抄网上示例,把 .text 强行塞进 0x80000000,结果运行时段错误——因为没考虑页对齐、未预留 GOT/PLT 空间、或忽略了 .rodata 实际也属于只读代码段,却被漏掉。
-
SECTIONS块里用. = 0x80000000;设置起始地址,但必须紧跟ALIGN(0x1000)(假设页大小 4KB),否则可能跨页破坏 MMU 映射 - 显式列出你关心的段:
.text : { *(.text) *(.text.*) },否则默认规则可能把.init或.fini漏掉,导致 ctor/dtor 不执行 - C++ 全局对象构造函数在
.init_array,这个段必须被包含且放在.text后、.data前,否则_init入口找不到初始化入口
C++ 全局对象的 .data 和 .bss 怎么分开控制
C++ 编译器会把带初始值的全局变量放进 .data,无初值的放进 .bss,但链接脚本里如果只写一个 .data 段,.bss 可能被合并或丢失——尤其在嵌入式裸机环境,.bss 需要运行前清零,位置错乱会导致未定义行为。
典型坑是:脚本里写了 .data : { *(.data) },但没处理 .bss,结果 static int x; 的地址和 static int y = 1; 挨着,启动代码只清了 .data 结尾到 .bss 结尾之间的区域,却因段未对齐而漏掉开头几字节。
立即学习“C++免费学习笔记(深入)”;
- 必须显式声明
.bss段:.bss (NOLOAD) : { *(.bss) *(.bss.*) *(COMMON) },NOLOAD表示不写入输出文件,只保留运行时地址 -
.data段结尾需加_edata = .;,.bss开头加_sbss = .;,供 C runtime 用符号计算清零范围 - 如果用了
-fno-common(推荐),COMMON符号基本消失,*(COMMON)可删,但保留更安全
为什么 __attribute__((section("..."))) 的变量没进你写的段
用 __attribute__((section(".mydata"))) 声明变量后,在链接脚本里写了 .mydata : { *(.mydata) } 却发现变量没进去?大概率是段名不一致或链接器忽略该输入段。
根本原因是:GCC 生成的段名实际是 .mydata,但某些目标平台(如 ARM AArch64)或启用 -ffunction-sections 时,段名会被修饰成 .mydata.foo 或带前缀;另外,如果该段完全为空(即没变量引用),链接器默认丢弃它。
- 确认段名是否真实存在:
objdump -h your.o查看目标文件中段列表,注意大小是否为 0 - 链接脚本中改用通配:
.mydata : { *(.mydata) *(.mydata.*) } - 强制保留空段:
.mydata : { KEEP(*(.mydata)) },否则即使写了也没用 - 避免和系统段名冲突,比如别用
.init、.fini这类保留名,否则可能被 ld 自动重定向
交叉编译时 ld 脚本的 MEMORY 区域必须匹配硬件
裸机或 RTOS 场景下,MEMORY 定义的是物理地址空间,写错一个字节就可能让代码跑飞。这不是“语法对就行”的问题,而是和芯片手册里的 RAM/ROM 分布严格对应。
常见错误是复制开发板例程脚本,但没改 RAM 起始地址——比如 STM32F407 的 SRAM 是 0x20000000,有人直接套用 F7 的 0x20010000,结果 .data 复制到非法地址,启动即挂。
-
MEMORY中每个区域必须有ORIGIN和LENGTH,且LENGTH要留余量(比如栈+heap+未初始化数据总和不能超 RAM 长度) - 多个
MEMORY区域(如 IRAM + DRAM)需要在SECTIONS里显式指定> REGION_NAME,否则默认落到第一个可写区域 - 使用
ASSERT校验关键段大小:ASSERT(. > ORIGIN(RAM) + LENGTH(RAM), "data overflow RAM"),编译期报错比运行期崩溃好查得多
最麻烦的其实是 C++ ABI 相关段——比如 .eh_frame 在异常禁用时可以丢,但若链接了部分启用异常的库(如 std::thread),漏掉它会导致 unwind 失败、栈回溯错乱。这种细节不会报错,只会静默失效。



















