gcc-arm-none-eabi工具链的内存布局设计

在编写嵌入式C/C++程序时,我们会有多个源文件,编译后生成目标文件(.o文件),目标文件按 ​​节(Section)​​ 组织,每个节存储不同类型的数据:

  1. 代码(.text)​​:函数体的二进制代码。
  2. 只读数据(.rodata)​​:常量字符串、全局常量等。
  3. ​​已初始化的全局变量(.data)​​:在定义时已经赋初值的全局变量或静态变量。
  4. 未初始化的全局变量(.bss)​​:在定义时未赋初值的全局变量或静态变量,启动时会被清零。
  5. 其他特殊段​​:如中断向量表、构造/析构函数表等。

链接器脚本是链接器的配置文件,用于指导链接器如何将多个目标文件(.o文件)合并成最终的可执行文件。
下面将会以基于libopencm3的Cortex-M微控制器的通用链接器脚本libopencm3/lib/cortex-m-generic.ld为例,详细介绍链接脚本的内容。完整代码放在最后。

链接过程详解:

  1. 输入阶段
    • 链接器读取
      • 多个目标文件(.o
      • 库文件(.a
      • 链接器脚本(通过 -T 指定)。
  2. 解析与合并
    • 段合并:
      根据脚本的 SECTIONS 规则,合并所有目标文件的同类段(如合并所有 .text 段)。
    • 符号解析:
      检查未定义符号(如 printf),从库中补充。
  3. 地址分配
    • 定位计数器:
      动态标记当前地址,例如 . = 0x10000; 表示后续段从该地址开始存放。
    • 对齐处理:
      ALIGN(4) 确保段起始地址满足 4 字节对齐。
  4. 生成输出
    • 输出可执行文件(ELF 格式),包含:
      • 合并后的段(如 .text.data
      • 符号表(解决所有符号地址)
      • 重定位信息(需加载器处理)。
    • 编译生成的可执行文件(二进制镜像)只包含ROM(Flash)中的内容,而RAM在烧录时是"空白"的

1. 链接器脚本核心结构

1.1 内存区域定义

cortex-m-generic.ld 中未提供内存区域定义,它要求用户在自己的连接器脚本中必须定义rom(代码存储器,如Flash)和ram(数据存储器,如SRAM)的区域:

/*
 * This is a generic linker script for Cortex-M targets using libopencm3.
 *
 * Memory regions MUST be defined in the ld script which includes this one!
 * Example:

	MEMORY
	{
		rom (rx) : ORIGIN = 0x08000000, LENGTH = 256K
		ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
	}

	INCLUDE cortex-m-generic.ld	/* 用户自定义连接器脚本中,需要包含通用模板 */

*/

1.2 入口点与强制符号

ENTRY(reset_handler):程序入口点为reset_handler

  • reset_handler(复位中断服务函数):CPU上电后首先从中断向量表获取该函数地址,进入该函数以执行硬件初始化(如栈指针设置、时钟配置)、数据段加载(.data/.bss),最后跳转到用户main函数。
  • ENTRY关键字定义程序入口点——生成的可执行文件从此处开始执行

EXTERN(vector_table):强制包含向量表,确保启动代码被链接。

  • EXTERN关键字强制表示包含指定符号到输出文件中,避免该符号被优化掉。
/* Define the entry point of the output file. */
ENTRY(reset_handler)

/* Enforce emmition of the vector table. */
EXTERN (vector_table)

1.3 段(SECTION)组织

  1. 关键字 SECTIONS { ... }
  • 作用:定义输出文件的内存布局
  • 结构:包含多个输出段(最终生成的可执行文件或二进制文件中的逻辑分段,包含了代码、数据、堆栈信息等内容,按照链接器脚本的指示被放置到特定的内存地址中)的定义
  1. 输出段定义 .text : { ... } >rom
  • 语法:.段名 : { 内容 } > 内存区域 (段名前面必须有点)
  • 作用:将指定的输入段组合到输出段中
  • 定义内部的关键元素:
    • . : 前位置计数器(链接地址)

      它表示当前输出段中正在填充的内存地址位置。当链接器处理输入文件时,位置计数器会随着每个字节的输出而自动递增。作用如下:

      1. 对齐操作
        . = ALIGN(4);
        → 将当前位置计数器推进到下一个4字节对齐的地址
        → 例如:若当前是地址0x1001,执行后变为0x1004
      2. 记录起始/结束地址
        __preinit_array_start = .;
        → 将当前位置值赋给符号 __preinit_array_start
        → 相当于声明:该段的起始地址 = 当前地址
    • *:通配符,匹配所有输入文件

      例:*(.vectors),匹配所有输入文件的.vectors 段,按链接顺序排列

    • (段名):输入对象文件中的段

SECTIONS
{
    .text : { ... } >rom
    .data : { ... } >ram AT >rom
    .bss : { ... } >ram
}

2. 关键段解析

2.1 代码段(.text)

.text : {
    *(.vectors)       /* 中断向量表 */
    *(.text*)         /* 程序代码 */
    *(.rodata*)       /* 只读数据 */
    . = ALIGN(4);     /* 4字节对齐 */
} >rom

2.2 数据段

2.2.1 初始化数据(.data)

包含已初始化的全局/静态变量,以及需要在RAM中运行的函数

RAM 		                    ROM
┌───────────────────┐          ┌───────────────────┐
│                   │          │                   │
│   .text (代码)    	│          │                   │
│   .rodata (常量)  	│          │                   │
├───────────────────┤          ├───────────────────┤
│   .data 初始值    	│ ←复制──┐  │  .data (变量)     │
│   (原始数据)      	│       │  │  initialized=42   │
├───────────────────┤       └──┤  str="Hello"      │
│   ...             │          ├───────────────────┤
│                   │          │  .bss             │
└───────────────────┘          │  uninitialized=0  │
                               │  zero_init=0.0    │
                               |                   │
                               └───────────────────┘
.data : {
    _data = .;        /* 数据区起始符号 */
    *(.data*)         /* 已初始化数据 */
    *(.ramtext*)      /* RAM中运行的函数 */
    _edata = .;       /* 数据区结束符号 */
} >ram AT >rom        /* 初始化值在ROM ,程序启动后把整个段复制到RAM中,形成所谓的“赋初值”*/
_data_loadaddr = LOADADDR(.data); /* ROM中初始化数据地址 */
2.2.1 未初始化数据(.bss)

包含所有初始值为0或未显式初始化的全局/静态变量

.bss : {
    *(.bss*)          /* 未初始化数据 */
    *(COMMON)         /* 公共变量 */
    _ebss = .;        /* BSS段结束 */
} >ram

2.3 C++特殊处理

.preinit_array : { ... } >rom  /* 预初始化数组 */
.init_array : { ... } >rom     /* 构造函数 */
.fini_array : { ... } >rom     /* 析构函数 */

2.4 ARM异常处理

.ARM.extab : { ... } >rom  /* 异常处理表 */
.ARM.exidx : { ... } >rom  /* 异常索引表 */

3. 关键设计特性

3.1 地址对齐

. = ALIGN(4); /* 全脚本出现6次 */
  • 保证所有段起始地址4字节对齐
  • 满足Cortex-M架构的访问要求

3.2 符号导出

_etext = .;          /* 代码结束地址 */
end = .;             /* 堆起始地址 */
PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram)); /* 栈顶指针 */
  • 为启动文件提供关键地址信息
  • 自动计算栈顶位置(RAM末尾)

3.3 优化策略

/DISCARD/ : { *(.eh_frame) } /* 丢弃C++异常帧 */
.noinit (NOLOAD) : { ... }   /* 保留不被初始化的RAM数据 */
  • 减少不必要的内存占用
  • 支持快速启动需求

4. 启动流程详解

4.1 启动代码依赖符号

/* 启动文件(startup.c)关键代码片段 */
extern uint32_t _etext;  /* .text段结束地址 */
extern uint32_t _data;   /* .data段起始(RAM) */
extern uint32_t _edata;  /* .data段结束(RAM) */
extern uint32_t _bss_start; /* .bss段起始 */
extern uint32_t _bss_end;   /* .bss段结束 */

/* 初始化数据段 */
uint32_t *src = &_etext;  /* ROM中初始化数据位置 */
uint32_t *dst = &_data;   /* RAM中数据目标位置 */
while (dst < &_edata) *dst++ = *src++;

/* 清零BSS段 */
dst = &_bss_start;
while (dst < &_bss_end) *dst++ = 0;

4.2 数据加载机制

  • AT >rom语法生成LOADADDR(.data)符号
  • 启动代码将LOADADDR到LOADADDR+(edata-data)的ROM数据复制到_data到_edata的RAM区域
  • 物理实现:芯片复位后由启动代码完成拷贝

4.3 栈初始化

/* 设置主栈指针 */
__set_MSP(*(uint32_t*)ORIGIN(ram) + LENGTH(ram));
  • _stack符号值 = RAM起始地址 + RAM长度
  • Cortex-M架构要求栈地址向下生长

5. 实际内存布局示例(STM32F103C8T6)

Memory Map (Hex)
----------------
0x08000000 +---------------+
          |  .vectors     |
          |  .text        |
          |  .rodata      | 
0x08001A00 |---------------| ← _etext
          |  .init_array  |
          |  .fini_array  |
0x08002000 +---------------+
          | 未使用ROM区域   |
0x08010000 +---------------+
          |               |
0x20000000 +---------------+ ← RAM起始
          |  .data        | ← _data
          |               |
0x20000100 +---------------+ ← _edata
          |  .bss         | ← _bss_start
          |               |
0x20000200 +---------------+ ← _ebss
          |  堆空间        | ← end
          |               |
          |               |
          |  栈空间        | 
          |  (向下生长)    |
0x20001000 +---------------+ ← _stack (栈顶)

6. 完整代码

/*
 * This is a generic linker script for Cortex-M targets using libopencm3.
 *
 * Memory regions MUST be defined in the ld script which includes this one!
 * Example:

	MEMORY
	{
		rom (rx) : ORIGIN = 0x08000000, LENGTH = 256K
		ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
	}
	
	INCLUDE cortex-m-generic.ld

*/

/* Enforce emmition of the vector table. */
EXTERN (vector_table)

/* Define the entry point of the output file. */
ENTRY(reset_handler)

/* Define sections. */
SECTIONS
{
	.text : {
		*(.vectors)	/* Vector table */
		*(.text*)	/* Program code */
		. = ALIGN(4);
		*(.rodata*)	/* Read-only data */
		. = ALIGN(4);
	} >rom

	/* C++ Static constructors/destructors, also used for __attribute__
	 * ((constructor)) and the likes */
	.preinit_array : {
		. = ALIGN(4);
		__preinit_array_start = .;
		KEEP (*(.preinit_array))
		__preinit_array_end = .;
	} >rom
	.init_array : {
		. = ALIGN(4);
		__init_array_start = .;
		KEEP (*(SORT(.init_array.*)))
		KEEP (*(.init_array))
		__init_array_end = .;
	} >rom
	.fini_array : {
		. = ALIGN(4);
		__fini_array_start = .;
		KEEP (*(.fini_array))
		KEEP (*(SORT(.fini_array.*)))
		__fini_array_end = .;
	} >rom

	/*
	 * Another section used by C++ stuff, appears when using newlib with
	 * 64bit (long long) printf support
	 */
	.ARM.extab : {
		*(.ARM.extab*)
	} >rom
	.ARM.exidx : {
		__exidx_start = .;
		*(.ARM.exidx*)
		__exidx_end = .;
	} >rom

	. = ALIGN(4);
	_etext = .;

	/* ram, but not cleared on reset, eg boot/app comms */
	.noinit (NOLOAD) : {
		*(.noinit*)
	} >ram
	. = ALIGN(4);

	.data : {
		_data = .;
		*(.data*)	/* Read-write initialized data */
		*(.ramtext*)    /* "text" functions to run in ram */
		. = ALIGN(4);
		_edata = .;
	} >ram AT >rom
	_data_loadaddr = LOADADDR(.data);

	.bss : {
		*(.bss*)	/* Read-write zero initialized data */
		*(COMMON)
		. = ALIGN(4);
		_ebss = .;
	} >ram

	/*
	 * The .eh_frame section appears to be used for C++ exception handling.
	 * You may need to fix this if you're using C++.
	 */
	/DISCARD/ : { *(.eh_frame) }

	. = ALIGN(4);
	end = .;
}

PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));

本分析基于libopencm3的cortex-m-generic.ld链接脚本
文件路径:lib/libopencm3/lib/cortex-m-generic.ld

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值