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
各子区说明:
| 子区 | 说明 |
|---|---|
.ramvector | RAM 向量表(需要运行中改写向量时使用),KEEP 保留 |
.lpdata | 低功耗保持区变量:复位不重新初始化、休眠不丢失,本型号可保持区为 RAM 前 32K(见 5.3 低功耗保持数据应用) |
.indata / .inrdata / .inrodata | RAM 中运行的函数与常量 |
.data | 常规全局已初始化变量 |
__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(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_*) }