
实战经验 | STM32CubeIDE 实用技巧之 ld 链接文件 STM32CubeIDE 是 ST 推出的免费集成编译环境,基于 Eclipse 开源框架,集成了 GCC、GDB 等免费的编译器、链接器,支持 STM32 全系列芯片,可以创建 C/C++工程,支持调试、波形实时仿真、一键下载等。5 S2 h6 A- N& a. } 在实际项目中,有时候需要对内存进行细分时,比如指定变量/函数/文件到特殊地址等等,KEIL 可以通过“*.sct”文件来实现;IAR 可以通过“*.icf”文件来实现;对于 STM32CubeIDE,可以通过“*.ld”链接文件来实现。本文将介绍 GCC 的“*.ld”链接文件的常见用法,供大家参考使用。5 x) _5 `- w; k0 J0 K( { + d9 A7 J- S8 R. J STM32CubeIDE 实用技巧之ld 链接文件 - q) y6 i) A* D- X( M' F3 w6 C$ x# V7 b e g) \/ _ 7 r5 j7 v0 [) ?% E 基本概念 - K% s# x! ]( h. d6 h; E$ l“*.ld”链接文件组合了许多对象和归档文件,重新定位它们的数据并绑定符号引用。通常,编译程序的最后一步是运行“*.ld”链接文件。! q6 O1 ~, f' S 通俗来讲,链接文件可以描述输入文件中的段,将其映射到输出文件中,并指定输出文件中的内存分配。 2 _2 M8 Z9 C7 E2 ^, {/ u4 } 以下就是链接文件涉及到的相关概念: PART1.内存(Memory) # P' t& L6 I0 I8 X" { ' I8 a, e: K; ?4 g7 u# ~ 语法: - A) ?# ~4 F4 N j 4 s. A/ G: D) M( o# v8 L: Z2 W ![]() 注释: 这里的“attr”只能由以下特性组成:( [5 q/ B+ V; R% P: ]3 t0 n# U6 X h : D- I) t+ k$ J' w# x2 } ‘R’ Read-only section% L+ q5 a4 w4 T8 x ‘W’ -- Read/write section ‘X’ -- Executable section6 a! y- N1 \7 t& M# j, ], W0 }1 J) Q ‘A’ -- Allocatable section8 }+ l! X, ?* r7 y( _7 L- F* s ‘I’ -- Initialized section$ J: z8 G/ G6 Q9 x0 i/ D ‘L’ -- Same as ‘I’' J, a3 ^; D: r) e* q7 f ‘!’ -- Invert the sense of any of the attributes that follow. w, M: ?; j ?7 _# A! f, h: n % s, q( N( e8 | 示例: ![]() + r+ |% @ n1 x' ` 注释:1 o' U3 p/ Y: b Y# N& j1 Z “xrw”表示“RAM”区是可读、可写和可执行的,且 RAM 的起始地址为“0x20000000”,长度为 36K。 “rx”表示“FLASH”区是可读和可执行的,FLASH 的起始地址为“0x08000000”,长度为 128K。0 B' J/ o& T! n+ W, h0 g' ` 7 [ T7 J; F% |* c3 j; ?: ~ PART2.段(Section) - [, |0 |6 a1 H/ y1 |' y1 F4 { , M! k/ |. c% M2 e Section 有 loadable(可加载) 和 allocatable(可分配)两种类型。不可加载也不可分配的内存段,通常包含某些调试信息。 3 S0 ~! T; K4 m) A2 t9 b loadable(可加载)是指:程序运行时,该段内容应该被加载到内存中。 \* G: L' U0 F+ q r9 H 1 j3 l1 M% W$ }: K# L- s allocatable(可分配)是指:该段的内容应该被预留出,但不应该加载任何别的内容(某些情况下,这些内存必须归零)。 X# V* V, o; s; K0 }6 Q( h “可加载”和“可分配”的 section 都有两个地址:“VMA”和“LMA”。+ o% S5 o) ?2 m$ \1 X" q! P/ h$ K % g" k) h# Q/ r" ` + f) Z4 @2 M& J$ [ VMA(the virtual memory address):这是运行输出文件时,该 section 的地址。VMA 是可选项,可以不设置。/ l0 [+ [& r( T! f& i8 i" a 7 M) `# d1 K2 `: S) U2 E {. H) O: N, \# C- } LMA(load memory address):这是加载 section 时的地址。! a; T! ~+ T, a( d 0 M4 _8 S) F4 Q' r; x W 在大多数情况下,这两个地址是相同的。当然也可以不相等,比如下面的例子就是 LMA 和 VMA 不同的案例:数据段被加载到 ROM 中,然后在程序启动时复制到 RAM 中(通常用于初始化全局变量)。此时 ROM 地址就是LMA,RAM 地址就是 VMA。 \5 K! j: F/ ] 语法: ![]() 注释:$ n/ P) B1 y7 ~8 { 大多数的段仅使用了上述的一部分属性。 / W2 T* x/ |% j* S 示例:' q9 [' _/ ?! Q ![]() / h' S1 W* }: k8 \, \ 注释:6 v) t6 j# ~2 U/ b1 J, Z5 k7 j * f. _" x1 A' e5 W( n 上述示例中“.isr_vector”的 LMA 与 VMA 是相等的。“.data”因为有“>RAM AT> FLASH”的修饰,表示.data 段的 VMA 为 RAM,LMA 为 FLASH。即.data 段的内容会放在 FLASH 中,但是运行时,会加载到 RAM 中。 u$ r8 D2 B3 d5 { 3 i; X3 ]! @. }4 b# Y4 B. K' E 链接脚本 ) A1 s. Z- ]; Z" r2 O! n0 D( T 7 R; h4 |1 K( Z' ?+ s( i9 D0 t 完整的“*.ld”链接文件通常会包含入口点、memory 以及 section 的内容。1 T3 N5 g0 @" [5 I6 U/ w v8 Y5 A. |5 C' Y 5 b1 l" d* @# B1 ^0 _% f( f' ~ PART1.入口点, P8 j* m: t1 k6 I6 y/ O c * \3 r2 H! }$ h: D% w- z 语法:ENTRY(symbol)5 n& z8 A& Z$ {' ~ 用途:程序中要执行的第一个指令,也称为入口点。 示例:6 f% l) P2 L) i0 V- t4 A. U' [ ![]() 注释: 在 STM32 的工程中,默认的入口点是“Reset_Handler”函数。 1 g: t$ \. N- I% W % A$ e9 c4 i# z4 ?4 a6 x8 y* ?. o PART2. 常用命令 0 x3 `. a6 w/ v/ ^0 W; e 1. ASSERT ( F9 A/ O! {+ i& @; o5 M+ F 语法:ASSERT(exp, message)确保 exp 是非零值,如果为零,将以错误码的形式退出链接文件,并输出 message。 用途:在必要的位置添加断言,可以清晰的定位问题。, D! d9 [. Z0 X- `( w% B L 示例: z. Z. k! p& [$ d ![]() 注释: 当示例中的“_estack”大于“_Min_Stack_Size + _Min_Heap_Size”时,会出现如下的信息。8 r1 y7 P; b7 i5 H6 L* N ![]() 2. PROVIDE5 k8 D+ M) s% f/ `/ l' J6 A- C 语法:PROVIDE(symbol = expression)$ `: W8 e, _2 s 用途:在某些情况下,链接器脚本只需要定义一个被引用的符号,并且该符号不是由链接中包含的任何对象定义的。/ }( [* `) c$ e, |& t) h" Q5 d1 q' I 示例:4 u4 U' k N2 u# a0 x, G7 h ![]() + P& A" d) |6 P% W; J7 s& X# p& o 3. HIDDEN4 ?3 j5 q$ U9 I, B( r% g 语法:HIDDEN(symbol = expression)- H+ D0 I, ^) d, ?! {. f1 J7 r 用途:对于 ELF 目标端口,符号将被隐藏且不被导出。( t: [# E K- _) V% p) Y2 X3 H0 X 示例: ![]() ! \! v) M. z& o" j# W$ H. l9 D 4. PROVIDE_HIDDEN 3 {% v( E6 Q' w6 W# u' [, }6 E 语法:PROVIDE_HIDDEN(symbol = expression) 用途:和 PROVIDE 用法相同,是 PROVIDE 和 HIDDEN 的结合体。 5 t$ D5 Z: Z: }6 m 示例:7 l% L* A) @5 {4 i9 @5 C4 F8 x9 l ![]() 7 ]+ @7 ^ O' C; d 9 P' N- Q# i/ j8 P; A 5. KEEP ' g4 u# r4 }# T7 g2 q5 j $ j. ]7 ?& h6 o: d% V- e5 P 语法:PROVIDE_HIDDEN(symbol = expression) 用途:当链接器使用('--gc-sections') 进行垃圾回收时,KEEP()可以使得被标记段的内容不被清除。 0 ~3 M3 g; M, b, Q+ x- e 示例: ![]() PART1. 入口点/ d. }' i8 ]; I1 v+ } - P. F- r& h; g PART3.简单脚本示例 ( u& W9 x" ]+ B5 S4 { 首先我们来看一个简单的脚本示例:$ e) T% ^9 l4 ]- \( s3 H3 v Y ![]() " L2 i% }& c: k1 m 5 H7 S4 P5 Z- ^2 l/ ^ 注释: 这里指定了 code、已初始化的数据以及未初始化的数据的内存分布。对于这个示例,程序会在地址 0x10000 处装载代码,并且数据会从地址 0x8000000 开始。特殊符号“.”,是位置计数器(location counter),按输出段的大小递增,设置位置计数器可以改变输出段的地址在“SECTIONS”命令的开头,位置计数器的值为“0”。位置计数器可以进行算数运算。* ?6 _7 }5 o/ v* s n2 w2 {; u$ n/ A5 Y$ A7 `3 ^ 2 k! o% b' R- G( {! \- t 高阶使用 PART1.位置计数器 ! j( e6 O' ^3 L& Y3 q+ O 5 _! P* b; h$ S. X5 {2 L 在 section 的描述中,位置计数器“.”可以进行算术运算,由此产生空隙来满足特定需求。 ' M8 W6 I- ?: ]0 n: Y) G7 Q) B8 C # Z3 e. m; T) r6 }5 a$ b ~8 e 注释: 例如这里的“. = . + 0x04;”就实现了 SE_CallGate_Fun 段的空间中,插入一段 0x04 的空隙。# O& `: A' V/ R. K0 T! G ; l o0 ?4 V- L# g1 h8 [; _ PART2. 指定“变量”的输出地址 可以定义如下的 memory,然后将“变量”存放于该 memory,就能控制“变量”的输出地址。; i, q2 h, M& L9 D' ^ ![]() ! @; z( G, b! s3 `1 X! h 同时在 c 文件中,在定义“变量”时,添加如下对应的属性 ![]() ; F4 t& Z- ~7 }- V' h 注释: 变量将位于“0x20000000 ~ 0x200002FF”区域(如果仅仅只有 key 数组位于该区域,将从 0x20000000 开始存放,如果有多个变量存储于该区域,将按照编译的顺序,从 0x20000000 依次存放)。 ( s* d* q% n2 J: g4 M: @ PART3. 指定“函数”的输出地址 可以定义如下的 memory 和 section,然后将“函数”存放于该 section,就能控制“函数”的输出地址。 ![]() 9 e- l u# k- k. z( S. W; [4 T 同时在 c 文件中,在“函数”的实现部分,添加如下对应的属性:: y& H: z8 b2 j5 R m1 r- { ![]() + `9 F/ l5 I' P+ z( m* ` 注释:. G: `# S% |7 r) J . P F* g: O" R, \; R 函数“call_gate”将存放于 0x08000304 处(留意此处的位置计数器将产生 0x04 的内存间隙)。 ' j3 f' Q4 D0 U" J4 ^ PART4. 指定“文件”的输出地址 ) E' t. S5 a, N( I4 M: F 可以定义如下的 memory 和 section,然后将指定的文件存放于该 section,就能控制“文件”的输出地址。 ![]() . {5 b# X1 i3 N/ A/ _! @ ` ! G3 k7 }) F4 Z4 x/ e, r 注释: 示例中将 main.o 指定到 FLASH 区域中;更改 FLASH 的地址或者 main_section 的 LMA,就可以实现将特定文件指定到特定内存区域。, o+ r+ ^2 D( h6 F& o i; X4 i# h/ W, l 总结 3 Z! T7 k) G( W) N' D) t ( c( U* c6 ?7 t4 } STM32CubeIDE 链接文件(*.ld)的详细说明文档位于“Help”->“Read STM32CubeIDE Documentation”->“C/C++ Linker”(ld.pdf)。该文档对 ld 文件的格式和内容进行了详细的说明,需要时可以查阅。实际项目中,通过综合利用上述的技巧,可以实现对内存的准确分配,来满足不同的应用场景。 # ?, a7 i1 S m- U. | ! w' b0 r! g4 e0 D3 w |
【2025·STM32峰会】GUI解决方案实训分享1-对LVGL咖啡机例程的牛刀小试以及问题排查
新版STM32Cube for Visual Studio Code开发体验
【STM32N6570-DK评测】摄像头video encoder
实战经验 | STM32CubeIDE实用技巧之工程联调
【STM32H745I-DISCO】基于TouchGFX的工业控制器界面设计
【下载问题解决】关于ST官网下载软件问题解决
【STM32N6570-DK评测】开发环境及LED debug
实战经验 | 基于STM32CubeIDE下载TouchGFX GUI应用的出错分析
实战经验 | 如何修改STM32Cube固件包的存储位置
兔哥的杂谈【002】——如何性价比更高地去编译STM32