跳到主要内容

3. 输出段布局详解

本章逐一解析默认链接脚本中各输出段的规则与设计原理。理解各段的分工后,即可安全地进行 第 5 章 常规使用方法 所述的定制修改。

3.1 .text(代码段)​

.text 是程序的代码空间,位于 flash,依次包含:中断向量表 → 函数代码 → C++ 构造/析构函数表 → 常量区。

.text :
{
KEEP (*vector.o(.text*)) /* chip interrupt vector ,writed in file named vector.s or vector.c */
KEEP (*vector.obj(.text*)) /* compatible */
KEEP (*vector.c.obj(.text*)) /* compatible */
KEEP (*vector.asm.obj(.text*)) /*compatible*/
. = ALIGN(4);
__vec_end__ = .;

*(.text*) /* default function code space */
*(.ctors*)
*(.dtors*)
. = ALIGN(4);
PROVIDE_HIDDEN ( __preinit_array_start = .);
KEEP (*(.preinit_array*)) /* class init function list space */
PROVIDE_HIDDEN ( __preinit_array_end = .);
. = ALIGN(4);
__init_class_start = ALIGN(4);
PROVIDE_HIDDEN ( __init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*)) /* class init function list space */
PROVIDE_HIDDEN ( __init_array_end = .);
__init_class_end = .;
. = ALIGN(4);
PROVIDE_HIDDEN ( __fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN ( __fini_array_end = .);
. = ALIGN(4);
*(.gcc_except_table*)
. = ALIGN(4);
*(.init*)
*(.fini*)
. = ALIGN(4);
*(.rdata*) /* global const variable space */
*(.rodata*) /* auto const variable space */
. = ALIGN(4);
__text_end__ = .;
/*record: global variable value of initialization,size .data before __data_end__ */

} > flash

3.1.1 中断向量表(前 512 字节)​

向量表沿用独立文件设计(vector.c / vector.s 编译产物优先存放),其代码段置于 .text 起始位置。.text 前 512 字节预留给向量表(中断向量、初始 SP、程序入口地址),不可占用。入口符号 _start 与脚本指定的 ENTRY(_start) 一致。

  • KEEP 修饰使其不参与死段优化(--gc-sections),保证向量表始终存在。
  • LLVM 编译产物文件名以 .o 结尾,KEEP (*vector.o(.text*)) 即可命中;vector.obj / vector.c.obj / vector.asm.obj 三条规则为兼容保留。
向量表混合到其他文件时

若向量表内容不在独立的 vector 文件中,或同文件下存在多个 text 段,应调整 KEEP 规则使二者一致,例如:

KEEP (*(.vector*))

同时向量表内容使用 __attribute__((section(".vector"))) 修饰。

3.1.2 C++ 构造/析构函数表​

.preinit_array / .init_array / .fini_array 收集 C++ 的全局构造与析构函数入口表:

  • SORT(.init_array.*) 保证带优先级的构造函数(.init_array.0xxxx)先于普通构造函数执行;
  • startup 在 C++ 模式下依赖 __init_class_start ~ __init_class_end(即 __init_array_start ~ __init_array_end)逐个调用全局构造函数。

3.1.3 常量区​

.rdata(全局初始化常量)与 .rodata(局部常量,如字符串字面量、const 数组)位于代码末尾,到 __text_end__ 结束。

3.2 .data(已初始化数据)​

.data 是全局已初始化变量的运行空间,位于 ram;其初值内容通过 AT > flash 装载到 flash 中 .text 之后,装载地址按段内最大对齐要求对齐。

.data :
{
__data_start__ = .; /* address must aligned by 4 bytes under zero offset */
/*--------------------------------------------------*/
KEEP (*(.ramvector*)) /* default server for vector writed in ram */
/*--------------------------------------------------*/
__lpdata_start__ = ALIGN(4);
*(.lpdata*) /* Reset but Keep Space */
/* . = ORIGIN(ram) + 0x8000 ; */ /* Max length limit is 32K byte ,if need 32K all config ,delete comment symbol*/
__lpdata_end__ = ALIGN(4);
/*--------------------------------------------------*/
. = ALIGN(4);
*(.indata*) /* server space for ram function */
*(.inrdata*)
*(.inrodata*)
/*--------------------------------------------------*/
. = ALIGN(4);
*(.data*) /* global variable with initialization */
. = ALIGN(4);
__data_end__ = .;
/*--------------------------------------------------*/
} > ram AT > flash

各子区说明:

子区说明
.ramvectorRAM 向量表(需要运行中改写向量时使用),KEEP 保留
.lpdata低功耗保持区变量:复位不重新初始化、休眠不丢失,本型号可保持区为 RAM 前 32K(见 5.3 低功耗保持数据应用)
.indata / .inrdata / .inrodataRAM 中运行的函数与常量
.data常规全局已初始化变量
data_start 的约束

__data_start__ 必须 4 字节对齐且为 data 区零偏移起点。startup 函数以 __text_end__ 为源、__data_start__ ~ __data_end__ 为目标完成拷贝,__data_start__ ~ __data_end__ 之间的内容即被识别为"带初值"的全局数据。

3.3 .bss(未初始化数据)​

.bss (NOLOAD):
{
. = ALIGN(4);
__bss_start__ = .; /* global variable with no initialization */
*(COMMON*)
*(.bss*)
. = ALIGN(4);
__bss_end__ = .;
/*--------------------------------------------------*/
} > ram

本段标记 NOLOAD,为 NOBITS 段,不占用 HEX 内容,不会写入无意义的 0 填充;运行时由 startup 在 main 之前将 __bss_start__ ~ __bss_end__ 清零,未初始化全局变量由此获得确定的 0 初值。

备注

__data_end__ 与 __bss_start__ 应保持连续,中间不应插入自定义段,否则会破坏"带初值数据"的识别边界(见 5.2 定义变量与函数到指定 RAM 地址)。

3.4 .heap 与 .stack(堆栈段)​

堆与栈各为一个输出段,均不占用 HEX 内容;预留大小在脚本顶部用两个符号设置:

PROVIDE(__Heap_Size__ = 0x400 ); /* default heap size, used when Heap_Mem is not defined */
__Stack_Size__ = 0; /* 0: Stack_Mem size (0x200); change 0 for a larger stack */
.heap (NOLOAD):
{
. = ALIGN(4);
__allot_end__ = .; /* heap and stack space*/
__Heap_Start__ = __allot_end__ ;
PROVIDE_HIDDEN ( __heap_start = .);
KEEP( *(.heap_mem*) )
. = ALIGN(4);
__Logic_Heap_End__ = . ;

. = DEFINED(Heap_Mem) ? ALIGN(4) : ( __Logic_Heap_End__ + __Heap_Size__ ) ;
. = ALIGN(4);
PROVIDE_HIDDEN ( __heap_end = .);
__Heap_End__ = . ;
/*--------------------------------------------------*/
__MAX_Stack_LIMITS__ = ALIGN(4);
/*--------------------------------------------------*/
} > ram

__Stack_Size__ = __Stack_Size__ ? __Stack_Size__ : (SIZEOF(.stack) ? SIZEOF(.stack) : (ORIGIN(ram) + LENGTH(ram) - __Heap_End__));
__Stack_Size__ = (__Stack_Size__ + 3) & ~3; /* keep the stack area 4-byte aligned */
.stack ORIGIN(ram) + LENGTH(ram) - __Stack_Size__ (NOLOAD):
{
. = ALIGN(4);
__LMA_STACK_LIMITS__ = . ;
. = __LMA_STACK_LIMITS__ ;

__Logic_Stack_End__ = . ;
KEEP( *(.stack_mem*) )
__Logic_Stack_Start__ = ORIGIN(ram) + LENGTH(ram);
/*--------------------------------------------------*/
__initial_sp = ORIGIN(ram) + LENGTH(ram);
/*--------------------------------------------------*/
} > ram

3.4.1 堆区​

  • 堆从 __allot_end__(__Heap_Start__)起,至 __Heap_End__ 止。
  • 若用户显式定义了 Heap_Mem 数组(.heap_mem 段,KEEP 保留),堆大小由该数组确定;否则按 __Heap_Size__ 预留,默认 0x400(可在脚本中修改,见 5.4 修改堆与栈大小)。

3.4.2 栈区​

  • .stack 段地址为 ORIGIN(ram) + LENGTH(ram) - __Stack_Size__,即栈区始终锚定到 RAM 顶部、向下生长。
  • __Stack_Size__ 默认为 0,表示栈大小沿用启动源码(默认 vector.c)中 .stack_mem 段的 Stack_Mem 数组大小;没有该数组时取堆尾到 RAM 顶的剩余空间。也可以在脚本顶部把 __Stack_Size__ 改为非零值,直接指定栈大小并覆盖数组大小(见 5.4 修改堆与栈大小)。
  • Stack_Mem 数组放在栈区底部,作为保留空间;脚本中指定的栈大小小于该数组时,数组放不进预留区,链接报 region overflow,应同步调整两者。非 4 字节对齐的栈大小会向上取整到 4 字节。
  • __Logic_Stack_End__ 为栈区底部、__Logic_Stack_Start__ 与 __initial_sp 为 RAM 顶部;后两个符号与真实 SP 指针语义一致,无论前面各段实际占用多少,栈始终贴着 RAM 顶向下生长。
__initial_sp 必须显式加 ORIGIN
__initial_sp = ORIGIN(ram) + LENGTH(ram); /* RAM 顶地址 */

LENGTH(ram) 只返回区域长度,不是 RAM 顶地址;必须写成 ORIGIN(ram) + LENGTH(ram)(见 4.1 定位计数器 . 的语义)。

3.5 .flashdata(DFlash)​

.flashdata :
{
. = ALIGN(4);
KEEP (*(.flashdata*)) /* Flash Data */
. = ALIGN(4);
} > da_mem
  • KEEP (*(.flashdata*)) 收集 DFlash 内容,落点为 da_mem(0x00200000);区域长度 32K 即该段的容量上限,内容超出时报 will not fit in region 'da_mem'。

3.6 .debugormode / .protectmode(模式与保护配置)​

.debugormode :
{
KEEP (*(.modeconfig*)) /* Mode */
} > mode_mem

.protectmode :
{
KEEP (*(.protectconfig*)) /* Protect */
} > pro_mem

这两段收集 UI 生成的 chip_mode_protect_config.c 中的模式/加密配置字(源码中以 __attribute__((section(".modeconfig")))、__attribute__((section(".protectconfig"))) 修饰)。这两段与 .flashdata 一样会出现在 HEX 中(这是设计意图:配置字随程序一起烧录);标了 NOLOAD 的 .bss / .heap / .stack 则不占用 HEX 内容。

3.7 其他段​

脚本末尾将调试类输入段收集到地址 0,不占用运行空间:

  • Stabs 调试段:.stab 系列与 .comment;
  • DWARF 调试段:.debug 全套(info/abbrev/line/frame/str/loc/…),供仿真调试器解析源码级信息。

/DISCARD/ 直接丢弃以下无用的输入段:

/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }