第 8 章: 自定义布局 - 链接器脚本简介
- 📚 本书官网及作者联系方式
访问本书网站: Under The Hood Of Executables
联系作者: chessMan786
链接器脚本简介
链接器脚本是一种功能强大的工具,能让你精确控制程序在内存中的组织方式。虽然大多数开发者可以依赖其工具链提供的默认链接脚本,但在以下情况下,理解链接器脚本就变得至关重要:
- 开发嵌入式系统
- 创建引导加载程序
- 优化内存使用
- 实现自定义内存布局
- 使用专用硬件
基本概念
内存与节
让我们从一个基础的链接脚本来开始,该脚本会展示一些基本概念:
/* basic.ld */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
CCRAM (rwx) : ORIGIN = 0x10000000, LENGTH = 64K
}
SECTIONS
{
.text : {
*(.text*)
*(.rodata*)
} > FLASH
.data : {
_sdata = .;
*(.data*)
_edata = .;
} > SRAM AT > FLASH
.bss : {
_sbss = .;
*(.bss*)
*(COMMON)
_ebss = .;
} > SRAM
}
让我们逐一解析每个组件:
1、内存区域
MEMORY
{
region_name (attributes) : ORIGIN = address, LENGTH = size
}
属性包括:
r
: 只读w
: 可写x
: 可执行a
: 可分配i
: 已初始化
不同内存类型的示例:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K /* 程序代码 */
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K /* 主RAM */
CCRAM (rwx) : ORIGIN = 0x10000000, LENGTH = 64K /* 核心耦合内存(CCM)*/
BACKUP (rw) : ORIGIN = 0x40024000, LENGTH = 4K /* 备用 RAM */
OTP (r) : ORIGIN = 0x1FFF7800, LENGTH = 512 /* 一次性可编程 */
}
具体实施
让我们使用自定义链接脚本来创建一个完整的示例:
// startup.c
#include <stdint.h>
// 这些符号定义在我们的链接脚本中
extern uint32_t _sdata;
extern uint32_t _edata;
extern uint32_t _sidata;
extern uint32_t _sbss;
extern uint32_t _ebss;
void Reset_Handler(void) {
uint32_t *src = &_sidata;
uint32_t *dst = &_sdata;
// 将已初始化的数据从flash复制到SRAM
while (dst < &_edata) {
*dst++ = *src++;
}
// 将 BSS 节清零
dst = &_sbss;
while (dst < &_ebss) {
*dst++ = 0;
}
// 调用main
main();
}
// 向量表
__attribute__((section(".vector_table")))
const uint32_t vector_table[] = {
(uint32_t)&_estack, // 初始栈指针
(uint32_t)&Reset_Handler, // 重置处理程序
// 根据需要增加其他向量
};
对应链接脚本:
/* custom_layout.ld */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
ENTRY(Reset_Handler)
SECTIONS
{
.vector_table : {
. = ALIGN(4);
KEEP(*(.vector_table))
. = ALIGN(4);
} > FLASH
.text : {
. = ALIGN(4);
*(.text*)
*(.rodata*)
. = ALIGN(4);
_etext = .;
} > FLASH
_sidata = LOADADDR(.data);
.data : {
. = ALIGN(4);
_sdata = .;
*(.data*)
. = ALIGN(4);
_edata = .;
} > SRAM AT > FLASH
.bss : {
. = ALIGN(4);
_sbss = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > SRAM
/* 堆和栈的配置 */
.stack : {
. = ALIGN(8);
. = . + 0x2000; /* 8KB 栈 */
. = ALIGN(8);
_estack = .;
} > SRAM
}
高级链接脚本特性
1、节对齐和填充
SECTIONS
{
.text : {
. = ALIGN(4); /* 按4字节边界对齐 */
*(.text*)
. = ALIGN(4);
/* 增加填充,使下一节与cache行对齐 */
. = ALIGN(32); /* 假设为32位高速缓存行 */
} > FLASH
.rodata : {
. = ALIGN(4);
*(.rodata*)
/* 强制节达到指定大小 */
. = ALIGN(4);
. = . + 1024; /* 添加 1KB 的填充 */
} > FLASH
}
2、内存区域重叠
MEMORY
{
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}
SECTIONS
{
.overlay1 : {
*(.overlay1*)
} > RAM
.overlay2 : {
*(.overlay2*)
} > RAM AT > RAM
.overlay3 : {
*(.overlay3*)
} > RAM AT > RAM
}
OVERLAY : {
.overlay1 { *(.overlay1*) }
.overlay2 { *(.overlay2*) }
.overlay3 { *(.overlay3*) }
} > RAM
3、自定义节排序和分组
SECTIONS
{
.text : {
/* 核心函数优先 */
*(.text.startup*)
*(.text.Reset_Handler)
*(.text.interrupt_handlers)
/* 之后是常规代码 */
*(.text*)
/* 关键函数置于最后 */
*(.text.critical*)
} > FLASH
.rodata : {
/* 启动代码使用的常量 */
*(.rodata.startup*)
/* 所有其他常量 */
*(.rodata*)
} > FLASH
}
4、内存保护单元 (MPU)配置
SECTIONS
{
/* MPU 要求区域正确对齐 */
.text : ALIGN(0x20) {
_stext = .;
*(.text*)
. = ALIGN(0x20);
_etext = .;
} > FLASH
/* 单独MPU区域中的只读数据 */
.rodata : ALIGN(0x20) {
_srodata = .;
*(.rodata*)
. = ALIGN(0x20);
_erodata = .;
} > FLASH
/* 单独MPU区域中的可写数据 */
.data : ALIGN(0x20) {
_sdata = .;
*(.data*)
. = ALIGN(0x20);
_edata = .;
} > SRAM AT > FLASH
}
调试链接脚本
1、生成内存映射
$ arm-none-eabi-ld -Map=output.map -T custom_layout.ld input.o
示例映射文件内容如下:
Memory Configuration
Name Origin Length Attributes
FLASH 0x08000000 0x00080000 xr
SRAM 0x20000000 0x00020000 xrw
Linker script and memory map
.vector_table 0x08000000 0x100
0x08000000 0x40 startup.o
...
.text 0x08000100 0x1234
0x08000100 0x450 main.o
0x08000550 0x7e4 lib.o
2、节分析
$ arm-none-eabi-objdump -h program.elf
输出:
program.elf: file format elf32-littlearm
Sections:
Idx Name Size VMA LMA File off Algn
0 .vector_table 00000100 08000000 08000000 00010000 2**2
1 .text 00001234 08000100 08000100 00010100 2**2
2 .data 00000100 20000000 08001334 00020000 2**2
3 .bss 00000200 20000100 20000100 00020100 2**2
3、符号位置验证
$ arm-none-eabi-nm program.elf
实际应用案例
1、Bootloader布局
MEMORY
{
BOOTROM (rx) : ORIGIN = 0x08000000, LENGTH = 16K
APPROM (rx) : ORIGIN = 0x08004000, LENGTH = 496K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.bootloader : {
*(.bootloader*)
*(.boot_vector*)
} > BOOTROM
.app_vector_table : {
*(.app_vectors*)
} > APPROM
.text : {
*(.text*)
} > APPROM
/* ... ... */
}
2、DMA 优化布局
SECTIONS
{
/* 根据缓存行大小对齐的 DMA 缓冲区 */
.dma_buffers : ALIGN(32) {
*(.dma_tx_buffer*)
. = ALIGN(32);
*(.dma_rx_buffer*)
} > SRAM
/* DMA 缓冲区后的常规数据 */
.data : {
*(.data*)
} > SRAM
}
3、双Bank Flash布局
MEMORY
{
FLASH_BANK0 (rx) : ORIGIN = 0x08000000, LENGTH = 256K
FLASH_BANK1 (rx) : ORIGIN = 0x08040000, LENGTH = 256K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.bank0_code : {
*(.bank0_text*)
} > FLASH_BANK0
.bank1_code : {
*(.bank1_text*)
} > FLASH_BANK1
}
最佳实践与优化技巧
- 缓存对齐
.text : ALIGN(32) { /* 与缓存行大小对齐 */
*(.text*)
}
2、关键代码节布局
.ram_functions : {
*(.time_critical*) /* 需要快速执行的函数 */
} > CCRAM
3、内存使用优化
.compressed : {
*(.compressed*)
KEEP(*(.compression_table))
} > FLASH
本章全面介绍了链接脚本及其实际应用。在下一章中,我们将探讨动态链接,及其如何增加程序加载和执行的灵活性
如本文对你有些许帮助,欢迎大佬支持我一下(点赞+收藏+关注、关注公众号等),您的支持是我持续创作的竭动力
支持我的方式