【译】《可执行文件背后的原理》—— 第8章 自定义布局

第 8 章: 自定义布局 - 链接器脚本简介

链接器脚本简介

链接器脚本是一种功能强大的工具,能让你精确控制程序在内存中的组织方式。虽然大多数开发者可以依赖其工具链提供的默认链接脚本,但在以下情况下,理解链接器脚本就变得至关重要:

  • 开发嵌入式系统
  • 创建引导加载程序
  • 优化内存使用
  • 实现自定义内存布局
  • 使用专用硬件

基本概念

内存与节

让我们从一个基础的链接脚本来开始,该脚本会展示一些基本概念:

/* 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
}

最佳实践与优化技巧

  1. 缓存对齐
.text : ALIGN(32) {  /* 与缓存行大小对齐 */
    *(.text*)
}

2、关键代码节布局

.ram_functions : {
    *(.time_critical*)  /* 需要快速执行的函数 */
} > CCRAM

3、内存使用优化

.compressed : {
    *(.compressed*)
    KEEP(*(.compression_table))
} > FLASH

本章全面介绍了链接脚本及其实际应用。在下一章中,我们将探讨动态链接,及其如何增加程序加载和执行的灵活性

如本文对你有些许帮助,欢迎大佬支持我一下(点赞+收藏+关注、关注公众号等),您的支持是我持续创作的竭动力
支持我的方式

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花神庙码农

你的鼓励是我码字的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值