跳转到内容
新建笔记

链接脚本(.ld文件)语法详解

链接脚本是告诉链接器如何将编译后的代码和数据放置到内存中的配置文件。

1. MEMORY 块 - 定义物理内存区域

跳转到“1. MEMORY 块 - 定义物理内存区域”
MEMORY
{
名称 (属性) : ORIGIN = 起始地址, LENGTH = 大小
}
  • r = readable (可读)
  • w = writable (可写)
  • x = executable (可执行)
MEMORY
{
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K # 指令紧耦合RAM,64KB,可执行
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K # 数据紧耦合RAM,128KB
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K # Flash存储器,2MB,只读可执行
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K # D1域RAM,512KB
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K # D2域RAM,288KB
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K # D3域RAM,64KB
BACKUP (xrw) : ORIGIN = 0x38800000, LENGTH = 4K # 备份RAM,4KB
}

2. SECTIONS 块 - 定义段的放置规则

跳转到“2. SECTIONS 块 - 定义段的放置规则”
SECTIONS
{
段名称 :
{
内容规则
} >目标内存区域 AT>加载地址
}
.itcm_text : # 定义一个名为.itcm_text的段
{
. = ALIGN(4); # 当前地址对齐到4字节边界
*(.itcm_text) # 所有输入文件中.itcm_text段的内容
*(.itcm_text*) # 所有以.itcm_text开头的段(如.itcm_text.func1)
} >ITCMRAM AT> FLASH # 运行时在ITCMRAM,但存储在FLASH中

关键概念:

  • >ITCMRAM = VMA(Virtual Memory Address) - 运行时地址
  • AT> FLASH = LMA(Load Memory Address) - 存储地址
  • 启动时需要将代码从FLASH复制到ITCMRAM

链接脚本(.ld文件)完整语法教程

跳转到“链接脚本(.ld文件)完整语法教程”

链接脚本(Linker Script)控制链接器如何将多个目标文件(.o)组合成最终的可执行文件。它定义了:

  • 程序的内存布局
  • 段(section)的放置位置
  • 符号(symbol)的地址分配

程序\段\符号

/* 注释使用C风格 */
/* 1. 入口点定义 */
ENTRY(Reset_Handler)
/* 2. 内存区域定义 */
MEMORY
{
/* 定义格式: 名称(属性) : ORIGIN = 起始地址, LENGTH = 大小 */
}
/* 3. 段定义 */
SECTIONS
{
/* 定义各个段的内容和位置 */
}
/* 4. 符号定义 */
PROVIDE(symbol = value);
MEMORY
{
/* 格式: NAME (ATTR) : ORIGIN = ADDR, LENGTH = SIZE */
/* 属性说明:
r = read (可读)
w = write (可写)
x = execute (可执行)
a = allocatable (可分配)
i = initialized (已初始化)
l = load (同i)
! = 反转属性
*/
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 256K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2M
SRAM (!x) : ORIGIN = 0x20000000, LENGTH = 128K /* 不可执行 */
}
SECTIONS
{
/* 段名 : {内容} >输出区域 */
.text :
{
/* 特殊符号 */
. = ALIGN(4); /* 当前位置对齐到4字节 */
_stext = .; /* 定义段开始符号 */
/* 输入段 */
*(.text) /* 所有文件的.text段 */
*(.text*) /* 所有以.text开头的段 */
*(.glue_7) /* ARM特定的粘合代码 */
*(.glue_7t)
KEEP(*(.init)) /* 保持段不被垃圾回收 */
KEEP(*(.fini))
. = ALIGN(4);
_etext = .; /* 定义段结束符号 */
} >ROM
}
SECTIONS
{
.text :
{
/* 文件通配符 */
main.o(.text) /* 特定文件的.text段 */
*(.text) /* 所有文件的.text段 */
lib*.a:(.text) /* 所有lib开头的库文件 */
/* 段名通配符 */
*(.text.*) /* 如 .text.startup, .text.exit */
*(.text.foo) /* 特定的.text.foo段 */
/* 排除规则 */
*(EXCLUDE_FILE(boot.o debug.o) .text) /* 排除特定文件 */
/* 排序规则 */
*(SORT(.text.*)) /* 按名称排序 */
*(SORT_BY_NAME(.text.*))
*(SORT_BY_ALIGNMENT(.text.*))
} >ROM
}

*(.text)
│
└── 表示"所有输入文件"
*(.text*)
│
└── 表示"所有以.text开头的段"
*(.rodata)
│ │
│ └── 段名:.rodata段
└──── 文件:所有输入文件
# 含义:将所有输入文件中的.rodata段放到这里
*(.rodata*)
│ │ │
│ │ └── 通配符:匹配.rodata开头的所有段
│ └──────── 段名前缀:.rodata
└──────────── 文件:所有输入文件
含义:将所有输入文件中以.rodata开头的段都放到这里
匹配:.rodata、.rodata.str1.4、.rodata.constants等

SECTIONS
{
.text :
{
/* 1. 特定文件的特定段 */
main.o(.text) /* 只要main.o文件的.text段 */
/* 2. 所有文件的特定段 */
*(.text) /* 所有文件的.text段 */
/* 3. 所有文件的匹配段 */
*(.text*) /* 所有文件中以.text开头的段 */
/* 匹配:.text、.text.startup、.text.exit等 */
/* 4. 多个段名 */
*(.text .text.*) /* .text段和所有.text.开头的段 */
/* 5. 特定库文件 */
libxxx.a:(.text) /* libxxx.a库中的.text段 */
/* 6. 库文件通配符 */
lib*.a:(.text) /* 所有lib开头的库文件的.text段 */
/* 7. 排除特定文件 */
*(EXCLUDE_FILE(boot.o debug.o) .text) /* 除了boot.o和debug.o外的所有.text段 */
/* 8. 特定文件的所有段 */
startup.o(*) /* startup.o的所有段 */
} >ROM
}

查看实际段名:

// C代码
const char str1[] = "Hello"; // → .rodata.str1.1
const int array[] = {1,2,3}; // → .rodata.array
void func1(void) { } // → .text.func1
static void func2(void) { } // → .text.func2
int __attribute__((section(".mydata"))) var = 5; // → .mydata
终端窗口
arm-none-eabi-objdump -h program.o
# 输出示例:
Sections:
0 .text 00000040 # 主代码段
1 .text.func1 00000020 # func1函数的段
2 .text.func2 00000018 # func2函数的段
3 .rodata 00000010 # 主只读数据段
4 .rodata.str1.1 00000006 # 字符串常量
5 .rodata.array 0000000c # 数组常量

为什么使用通配符

/* 不使用通配符(繁琐) */
.text :
{
main.o(.text)
uart.o(.text)
timer.o(.text)
/* 需要列出每个文件... */
} >ROM
/* 使用通配符(简洁) */
.text :
{
*(.text) /* 一行搞定所有文件 */
*(.text*) /* 包括编译器生成的子段 */
} >ROM

常见模式对比

模式含义匹配示例
*(.text)所有文件的.text段只匹配.text
*(.text*)所有文件中.text开头的段.text, .text.startup, .text.exit
*(.text.*)所有文件中.text.开头的子段.text.startup, .text.exit(不匹配.text)
*(.text .text.*)上述两者的组合.text, .text.startup, .text.exit
main.o(.text)main.o的.text段只有main.o中的.text
*.o(.text)所有.o文件的.text段所有目标文件的.text
lib*.a:(.text)lib开头的库的.text段libm.a, libc.a中的.text

3. VMA和LMA(重要概念)

跳转到“3. VMA和LMA(重要概念)”
SECTIONS
{
/* VMA = Virtual Memory Address (运行地址) */
/* LMA = Load Memory Address (加载/存储地址) */
/* 方式1: AT关键字 */
.data :
{
*(.data)
} >RAM AT>ROM /* 运行在RAM,存储在ROM */
/* 方式2: AT()函数 */
.data : AT(0x08010000)
{
*(.data)
} >RAM
/* 方式3: 使用变量 */
.data : AT(_sidata)
{
_sdata = .; /* RAM中的起始地址 */
*(.data)
_edata = .; /* RAM中的结束地址 */
} >RAM
}

五、特殊符号和内置函数

跳转到“五、特殊符号和内置函数”
SECTIONS
{
.text :
{
. = 0x1000; /* 设置当前地址 */
*(.text)
. = . + 0x100; /* 预留256字节空间 */
*(.rodata)
. = ALIGN(0x10); /* 对齐到16字节 */
} >ROM
}
SECTIONS
{
.text :
{
/* 对齐函数 */
. = ALIGN(4); /* 对齐到4字节 */
. = ALIGN(0x100); /* 对齐到256字节 */
/* 地址函数 */
_data_lma = LOADADDR(.data); /* 获取.data段的LMA */
_text_size = SIZEOF(.text); /* 获取.text段大小 */
_ram_start = ORIGIN(RAM); /* 获取RAM起始地址 */
_ram_size = LENGTH(RAM); /* 获取RAM大小 */
/* 数学函数 */
_value = MAX(0x1000, 0x2000); /* 最大值 */
_value = MIN(0x1000, 0x2000); /* 最小值 */
_value = ABSOLUTE(-100); /* 绝对值 */
} >ROM
}

位置计数器点号(.)的含义

.(点号)是链接脚本中的”位置计数器”(Location Counter),表示当前的内存地址位置。把它想象成一个指针,指向当前正在安排内容的内存地址。

工作原理图解

SECTIONS
{
.text 0x08000000 : /* .text段从0x08000000开始 */
{
. = ALIGN(4); /* 步骤1: 确保当前地址是4的倍数 */
*(.text) /* 步骤2: 放置所有.text内容 */
. = ALIGN(4); /* 步骤3: 再次对齐 */
} >FLASH
}

. = 赋值操作

操作含义示例结果
. = 0x1000设置当前地址为0x1000. = 0x1000当前地址变为0x1000
. = . + 100当前地址前进100字节. = . + 100预留100字节空间
. = ALIGN(4)对齐到4字节边界见下方详解地址变为4的倍数
. = ALIGN(0x100)对齐到256字节边界见下方详解地址变为256的倍数

为什么需要对齐?

对齐要求原因示例
4字节对齐ARM指令要求函数地址必须4字节对齐
8字节对齐栈指针要求栈必须8字节对齐
32字节对齐DMA/Cache要求DMA缓冲区需要32字节对齐
256字节对齐页边界某些硬件特性需要页对齐
4K对齐MMU页面内存保护单元要求

ALIGN(4)的计算过程:

当前地址 → 对齐后地址
0x08000000 → 0x08000000 (已对齐,不变)
0x08000001 → 0x08000004 (向上对齐到4)
0x08000002 → 0x08000004 (向上对齐到4)
0x08000003 → 0x08000004 (向上对齐到4)
0x08000004 → 0x08000004 (已对齐,不变)
0x08000005 → 0x08000008 (向上对齐到4)

/* 全局符号定义 */
_stack_size = 0x400; /* 简单赋值 */
_heap_size = DEFINED(_heap_size) ? _heap_size : 0x200; /* 条件赋值 */
PROVIDE(_stack = ORIGIN(RAM) + LENGTH(RAM)); /* 弱符号定义 */
PROVIDE_HIDDEN(__preinit_array_start = .); /* 隐藏符号 */
SECTIONS
{
/* 段内符号定义 */
.text :
{
__text_start = .; /* 强符号 */
*(.text)
__text_end = .;
} >ROM
}
SECTIONS
{
/* 多个段共享同一内存区域 */
OVERLAY 0x20000000 : AT(0x08010000)
{
.overlay1
{
*(.overlay1)
}
.overlay2
{
*(.overlay2)
}
} >RAM
}
SECTIONS
{
/* NOLOAD: 不从文件加载 */
.bss (NOLOAD) :
{
*(.bss)
*(COMMON)
} >RAM
/* DSECT: 不分配内存 */
.debug (DSECT) :
{
*(.debug)
}
/* COPY: 复制段 */
/* INFO: 信息段 */
/* OVERLAY: 覆盖段 */
}
SECTIONS
{
.init_array :
{
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
} >ROM
/* 中断向量表必须保留 */
.isr_vector :
{
KEEP(*(.isr_vector))
} >ROM
}

七、完整的STM32H743示例

跳转到“七、完整的STM32H743示例”
/* 入口点 */
ENTRY(Reset_Handler)
/* 最小栈大小 */
_Min_Heap_Size = 0x200;
_Min_Stack_Size = 0x400;
/* 内存定义 */
MEMORY
{
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
}
/* 段定义 */
SECTIONS
{
/* 中断向量表 */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} >FLASH
/* 代码段 */
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
*(.glue_7)
*(.glue_7t)
*(.eh_frame)
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
_etext = .;
} >FLASH
/* 只读数据 */
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} >FLASH
/* ARM异常处理 */
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} >FLASH
.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
/* 初始化数组 */
.preinit_array :
{
PROVIDE_HIDDEN(__preinit_array_start = .);
KEEP(*(.preinit_array*))
PROVIDE_HIDDEN(__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN(__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array*))
PROVIDE_HIDDEN(__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN(__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array*))
PROVIDE_HIDDEN(__fini_array_end = .);
} >FLASH
/* ITCM代码段 */
_siitcmram = LOADADDR(.itcmram);
.itcmram :
{
. = ALIGN(4);
_sitcmram = .;
*(.itcmram)
*(.itcmram*)
. = ALIGN(4);
_eitcmram = .;
} >ITCMRAM AT> FLASH
/* 初始化数据段 */
_sidata = LOADADDR(.data);
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .;
} >DTCMRAM AT> FLASH
/* 未初始化数据 */
.bss :
{
. = ALIGN(4);
_sbss = .;
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = _ebss;
} >DTCMRAM
/* 用户堆栈 */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE(end = .);
PROVIDE(_end = .);
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >DTCMRAM
/* D1域段 */
.ram_d1 (NOLOAD) :
{
. = ALIGN(4);
*(.ram_d1)
*(.ram_d1*)
} >RAM_D1
/* D2域段 - DMA缓冲 */
.ram_d2 (NOLOAD) :
{
. = ALIGN(32);
*(.ram_d2)
*(.ram_d2*)
} >RAM_D2
/* D3域段 - 低功耗 */
.ram_d3 (NOLOAD) :
{
. = ALIGN(4);
*(.ram_d3)
*(.ram_d3*)
} >RAM_D3
/* 丢弃段 */
/DISCARD/ :
{
libc.a(*)
libm.a(*)
libgcc.a(*)
}
/* 调试符号 */
.ARM.attributes 0 : { *(.ARM.attributes) }
}
/* 导出符号供C代码使用 */
PROVIDE(_stack = ORIGIN(DTCMRAM) + LENGTH(DTCMRAM));
PROVIDE(_estack = _stack);
PROVIDE(_sidata = LOADADDR(.data));
终端窗口
# 生成MAP文件
arm-none-eabi-gcc -Wl,-Map=output.map
# MAP文件显示每个符号的地址
终端窗口
# 查看段大小和地址
arm-none-eabi-size -A -x output.elf
# 查看所有段
arm-none-eabi-objdump -h output.elf
# 查看符号表
arm-none-eabi-nm output.elf
/* ASSERT: 断言检查 */
ASSERT(_etext <= (ORIGIN(FLASH) + LENGTH(FLASH)), "FLASH overflow")
ASSERT(_edata <= (ORIGIN(RAM) + LENGTH(RAM)), "RAM overflow")
/* INCLUDE: 包含其他脚本 */
INCLUDE "common.ld"
/* INSERT: 插入内容 */
INSERT AFTER .text;
/* NOCROSSREFS: 禁止段间引用 */
NOCROSSREFS(.text .data)
SECTIONS
{
.text :
{
__text_start = .;
*(.text*)
__text_end = .;
} >FLASH
/* 在C代码中使用 */
/* extern uint32_t __text_start, __text_end; */
/* size = &__text_end - &__text_start; */
}
MEMORY
{
BOOTLOADER (rx) : ORIGIN = 0x08000000, LENGTH = 32K
FIRMWARE (rx) : ORIGIN = 0x08008000, LENGTH = 480K
}
SECTIONS
{
.jump_table 0x08000100 :
{
KEEP(*(.jump_table))
} >FLASH
}

来源:CSDN 原文,首次发布于 2025-08-24。