链接脚本(Linker Script,通常以.lds 为扩展名)是 GNU 链接器(ld)使用的脚本文件,用于控制程序的链接过程,定义内存布局、段(section)的排列方式以及符号的地址分配等。以下是其核心语法和常用结构:

1. 基本结构

链接脚本的基本格式如下,主要包含内存布局定义段布局定义两部分:

/* 注释:以/* ... */ 或 // 开头 */

/* 定义内存区域(可选)*/
MEMORY {
    内存块名称 (属性) : ORIGIN = 起始地址, LENGTH = 长度
    /* 示例: */
    ROM (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

/* 定义输出文件的段布局(必选)*/
SECTIONS {
    /* 段定义 */
    .text 0x08000000 : { /* 代码段 */
        *(.text)         /* 所有输入文件的.text段 */
    } > ROM              /* 绑定到MEMORY中定义的ROM区域 */

    .data : { /* 数据段(初始化的全局变量) */
        *(.data)
    } > RAM AT > ROM     /* 加载到ROM,运行时复制到RAM */

    .bss : { /* 未初始化数据段 */
        *(.bss)
        __bss_end = .;   /* 定义符号记录.bss段结束地址 */
    } > RAM
}

2. 核心语法元素

(1)内存区域定义(MEMORY)

用于描述目标系统的内存分布(如 ROM、RAM 的地址范围和属性),格式:

MEMORY {
    内存块名 [(属性)] : ORIGIN = 起始地址, LENGTH = 大小
}
  • 属性r(可读)、w(可写)、x(可执行)、!(禁止),如(rx)表示只读可执行。
  • 地址和大小:支持十六进制(0x前缀)、十进制,单位可加K(1024)、M(1024K)。
(2)段定义(SECTIONS)

控制输入文件中的段(如.text.data)如何映射到输出文件的段,是链接脚本的核心。

基本格式

SECTIONS {
    输出段名 [起始地址] : {
        输入段列表       /* 哪些输入文件的段放入此输出段 */
        [符号定义]       /* 在此位置定义符号 */
    } [> 内存块名] [AT 加载地址]
}
  • 输出段名:通常与输入段名一致(如.text.data),也可自定义(如.mysection)。
  • 起始地址:可选,指定段的起始地址(如.text 0x08000000)。
  • 输入段列表
    • *(.text):匹配所有文件的.text段。
    • file.o(.data):仅匹配file.o.data段。
    • *.o(.rodata*):匹配所有.o文件中以.rodata开头的段。
  • 符号定义
    • . 表示当前地址(类似指针)。
    • __text_start = .;:定义符号__text_start为当前地址(即段的起始)。
    • KEEP(*(.init)):强制保留.init段(防止链接器删除未引用的段)。
  • 内存绑定> 内存块名将段放入指定的内存区域(如> RAM)。
  • 加载地址AT 地址指定段在存储介质中的地址(运行时可能被复制到其他地址,如.data段通常存储在 ROM,运行时复制到 RAM)。
(3)符号操作
  • 内置符号
    • _start:程序入口点(可通过ENTRY(_start)指定)。
    • __executable_start:程序起始地址。
  • 自定义符号:通过符号名 = 表达式;定义,常用场景:
    .text : {
        __text_start = .;   /* 段起始地址 */
        *(.text)
        __text_end = .;     /* 段结束地址 */
    }
    
    .bss : {
        __bss_start = .;
        *(.bss)
        . = ALIGN(4);       /* 地址对齐到4字节 */
        __bss_end = .;
    }
    
(4)常用命令
  • ENTRY(symbol):指定程序入口点(如ENTRY(main))。
  • ALIGN(n):将当前地址对齐到n字节边界(如. = ALIGN(8);)。
  • INCLUDE filename:包含其他脚本文件。
  • EXTERN(symbol):声明外部符号(确保链接器不会删除引用此符号的代码)。

3. 示例:嵌入式系统常用 lds 脚本

/* 定义内存:Flash起始地址0x08000000,大小1MB;RAM起始0x20000000,大小128KB */
MEMORY {
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M
    RAM   (rwx): ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS {
    /* 代码段:放在Flash,起始地址0x08000000 */
    .text : {
        KEEP(*(.isr_vector))  /* 中断向量表(必须保留在最开始) */
        *(.text)              /* 所有代码段 */
        *(.rodata)            /* 只读数据 */
        . = ALIGN(4);
        __text_end = .;
    } > FLASH

    /* 初始化数据段:加载在Flash,运行时复制到RAM */
    .data : AT (__text_end) {
        __data_start = .;
        *(.data)
        . = ALIGN(4);
        __data_end = .;
    } > RAM

    /* 未初始化数据段:仅占用RAM空间 */
    .bss : {
        __bss_start = .;
        *(.bss)
        *(.COMMON)            /* 未初始化的全局变量 */
        . = ALIGN(4);
        __bss_end = .;
    } > RAM

    /* 栈顶地址定义(通常在RAM末尾) */
    _estack = ORIGIN(RAM) + LENGTH(RAM);
}

4. 关键作用

  • 控制程序在内存中的布局(如代码放 ROM,数据放 RAM)。
  • 定义特殊地址的符号(如中断向量表位置、栈顶地址)。
  • 处理段的加载与运行地址分离(如嵌入式系统中.data段的复制)。

通过链接脚本,开发者可以精确控制程序的内存分布,这在嵌入式开发、操作系统内核等场景中尤为重要。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐