关于 linker-scripts
为处理嵌入式目标的 Agent 提供 GNU ld 链接脚本编写与修改指导,涵盖内存布局、段放置、启动初始化和常见错误排查。
主要功能
- 01配置 MEMORY 与 SECTIONS
- 02说明 VMA 与 LMA 的区别
- 03处理 .data 与 .bss 启动初始化
- 04将代码段放入指定内存区域
- 05使用 KEEP、ALIGN、PROVIDE
- 06排查链接器错误
适用场景
- 01为 MCU 编写链接脚本
- 02将关键函数放入 RAM
- 03诊断 FLASH 或 RAM 溢出
- 04设置弱符号与启动段
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill linker-scripts安装后在 Claude Code 中输入/linker-scripts确认已加载。
附带文件references/linker-script-anatomy.md
详细介绍
链接器脚本
用途
指导 Agent 为嵌入式目标编写和修改 GNU ld 链接器脚本:MEMORY 和 SECTIONS 命令、代码重定位中的 VMA 与 LMA、启动时初始化 .bss/.data、将节放置到特定区域,以及使用 PROVIDE/KEEP/ALIGN 指令。
触发条件
- “如何为我的 MCU 编写链接器脚本?”
- “如何将函数放置在特定的闪存/RAM 区域?”
- “链接器脚本中的 VMA 和 LMA 有什么区别?”
- “启动时如何初始化 .bss 和 .data?”
- “链接错误:区域 'FLASH' 溢出”
- “如何在链接器脚本中使用弱符号?”
工作流
1. 链接器脚本结构
/* Minimal Cortex-M linker script */
ENTRY(Reset_Handler) /* entry point symbol */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.text : /* code section */
{
KEEP(*(.isr_vector)) /* interrupt vector must be first */
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
. = ALIGN(4);
_etext = .; /* end of flash content */
} > FLASH
.data : AT(_etext) /* VMA = RAM, LMA = FLASH */
{
_sdata = .;
*(.data)
*(.data.*)
. = ALIGN(4);
_edata = .;
} > RAM
.bss :
{
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > RAM
/* Stack at top of RAM */
_estack = ORIGIN(RAM) + LENGTH(RAM);
}
2. VMA 与 LMA
- VMA(虚拟内存地址):节在运行时执行的位置
- LMA(加载内存地址):节在映像中存储的位置(闪存)
对于 .data:存储在闪存中(LMA),启动时复制到 RAM(VMA)。
/* AT() sets LMA explicitly */
.data : AT(ADDR(.text) + SIZEOF(.text))
{
_sdata = .;
*(.data)
_edata = .;
} > RAM /* VMA goes in RAM */
如果使用 AT(_etext),.data 的 LMA 会自动放置在 .text 之后。
3. 启动时初始化 .bss / .data
C 运行时必须在 main() 之前将 .data 从闪存复制到 RAM,并将 .bss 清零:
// startup.c or startup.s equivalent in C
extern uint32_t _sdata, _edata, _sidata; // _sidata = LMA of .data
extern uint32_t _sbss, _ebss;
void Reset_Handler(void) {
// Copy .data from flash to RAM
uint32_t *src = &_sidata;
uint32_t *dst = &_sdata;
while (dst < &_edata) *dst++ = *src++;
// Zero-initialize .bss
dst = &_sbss;
while (dst < &_ebss) *dst++ = 0;
// Call C++ constructors
// (call __libc_init_array() if using newlib)
main();
for (;;); // should never return
}
链接器脚本提供 _sidata(.data 的 LMA):
.data : AT(_etext)
{
_sdata = .;
*(.data)
_edata = .;
} > RAM
_sidata = LOADADDR(.data); /* LMA of .data for startup code */
4. 将代码放置在特定区域
/* Place time-critical code in RAM for faster execution */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx): ORIGIN = 0x20000000, LENGTH = 128K
CCM (rwx): ORIGIN = 0x10000000, LENGTH = 64K /* Cortex-M4 CCM */
}
.fast_code : AT(_etext)
{
_sfast = .;
*(.fast_code) /* sections marked __attribute__((section(".fast_code"))) */
_efast = .;
} > CCM /* runs from CCM RAM */
// Mark a function to go in fast_code section
__attribute__((section(".fast_code")))
void critical_isr_handler(void) {
// runs from CCM RAM
}
5. KEEP、ALIGN、PROVIDE
/* KEEP — prevent garbage collection of section */
KEEP(*(.isr_vector)) /* linker gc won't remove interrupt table */
KEEP(*(.init))
KEEP(*(.fini))
/* ALIGN — advance location counter to alignment boundary */
. = ALIGN(8); /* align to 8 bytes */
/* PROVIDE — define symbol only if not already defined (weak default) */
PROVIDE(_stack_size = 0x400); /* default 1KB stack; override in code */
/* Symbols for stack */
.stack :
{
. = ALIGN(8);
. += _stack_size;
_stack_top = .;
} > RAM
/* FILL — fill unused bytes */
.text :
{
*(.text)
. = ALIGN(4);
FILL(0xFF) /* fill flash gaps with 0xFF (erased state) */
} > FLASH
6. 弱符号
/* In linker script — provide weak default ISR */
PROVIDE(NMI_Handler = Default_Handler);
PROVIDE(HardFault_Handler = Default_Handler);
PROVIDE(SysTick_Handler = Default_Handler);
// In C — weak default handler
__attribute__((weak)) void Default_Handler(void) {
for (;;); // spin — override this in application
}
// Override by defining a non-weak symbol with the same name
void SysTick_Handler(void) {
tick_count++;
}
7. 常见链接器错误
| 错误 | 原因 | 解决方法 |
|---|---|---|
region 'FLASH' overflowed | 二进制文件过大,无法放入闪存 | 启用 LTO、使用 -Os、移除未使用的代码;使用 --gc-sections |
region 'RAM' overflowed | RAM 使用量过大 | 减小栈大小,使用静态缓冲区,检查 .bss 大小 |
undefined reference to '_estack' | 缺少链接器脚本符号 | 在链接器脚本中定义 _estack |
no rule to process file | 不识别 .ld 扩展名 | 使用 -T script.ld 传入 |
cannot find linker script | 路径错误 | 使用 -L dir -T name.ld |
数据节 .data 地址错误 | 未设置 LMA | 在 .data 节定义之后添加 AT(_etext) |
# Analyze section sizes
arm-none-eabi-size firmware.elf
arm-none-eabi-size -A firmware.elf # verbose per-section
# Show all sections and their addresses
arm-none-eabi-objdump -h firmware.elf
# Check if .data LMA is in flash range
arm-none-eabi-readelf -S firmware.elf | grep -A2 "\.data"
有关链接器脚本结构的详细信息,请参阅 references/linker-script-anatomy.md。
相关技能
- 使用
skills/embedded/openocd-jtag,将固件烧录到链接器脚本定义的地址 - 使用
skills/embedded/freertos,将 FreeRTOS 堆放置在特定 RAM 区域 - 使用
skills/binaries/linkers-lto,了解链接器 LTO 和符号标志 - 使用
skills/binaries/elf-inspection,检查节的大小和地址
评论
支持 Markdown