网站Logo 网友马大帅的博客

ld链接文件的语法与配置

ughostx
14
2026-01-29

在 C 语言开发中,链接脚本(Linker Script)用于控制可执行文件或库的内存布局,尤其在嵌入式系统和底层开发中非常重要。

一、链接脚本的主要作用

1. 内存布局控制

  • 指定代码段(.text)、数据段(.data)、BSS段(.bss)的存放位置

  • 定义内存区域(RAM、ROM、Flash等)的起始地址和大小

2. 符号定义与管理

  • 定义全局符号(如堆栈起始地址)

  • 控制符号的可见性和导出

3. 特殊需求处理

  • 启动代码的特定放置

  • 中断向量表的精确定位

  • 自定义段的创建和布局

二、基本语法结构

一个示例的链接配置文件如下:

换行复制代码

/* 最简单的链接脚本示例 */
MEMORY
{
    ROM (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    /* 代码段 */
    .text :
    {
        *(.vectors)    /* 中断向量表优先 */
        *(.text*)      /* 所有代码 */
        *(.rodata*)    /* 只读数据 */
    } > ROM
    
    /* 初始化数据 */
    .data : AT(ADDR(.text) + SIZEOF(.text))
    {
        _sdata = .;    /* 数据段起始地址 */
        *(.data*)
        _edata = .;    /* 数据段结束地址 */
    } > RAM
    
    /* 未初始化数据 */
    .bss :
    {
        _sbss = .;
        *(.bss*)
        *(COMMON)
        _ebss = .;
    } > RAM
    
    /* 堆栈定义 */
    _estack = ORIGIN(RAM) + LENGTH(RAM);
}

1. 注释

换行复制代码

/* 多行注释 */
// 单行注释(某些链接器支持)

2. 命令格式

换行复制代码

命令名 参数 {
    子命令;
    符号 = 表达式;
}

二、核心关键字

1. MEMORY - 定义内存区域

换行复制代码

MEMORY
{
    名称 (属性) : ORIGIN = 起始地址, LENGTH = 长度
}

属性说明:

  • r - 可读

  • w - 可写

  • x - 可执行

  • a - 可分配

  • i - 初始化

  • l - 与i相同

  • ! - 反转属性

示例:

换行复制代码

MEMORY
{
    ROM (rx)  : ORIGIN = 0x0000, LENGTH = 256K
    RAM (rwx) : ORIGIN = 0x4000, LENGTH = 64K
    EEPROM (rw) : ORIGIN = 0xF000, LENGTH = 4K
}

2. SECTIONS - 定义输出段

换行复制代码

SECTIONS
{
    输出段名 [地址] : [AT(加载地址)]
    {
        内容描述
    } [>区域] [AT>区域]
}

3. 输出段内容描述符

输入段选择器

换行复制代码

*(.text)           /* 所有文件的.text段 */
file.o(.data)      /* 特定文件的.data段 */
*(.text.*)         /* 匹配所有.text.开头的段 */

特殊命令

换行复制代码

KEEP(*(.init))     /* 强制保留,即使未引用 */
PROVIDE(symbol = .) /* 定义符号,不重复定义 */
SORT(CONSTRUCTORS) /* 按名称排序 */

三、位置计数器 (.)

基本用法

换行复制代码

. = 0x1000;        /* 设置当前位置 */
. = ALIGN(4);      /* 4字节对齐 */
. += 0x100;        /* 当前位置向后移动256字节 */

常用表达式

换行复制代码

. = DEFINED(symbol) ? symbol : 0x1000;  /* 条件设置 */
. = ( . + 3 ) & ~3;                     /* 4字节对齐(手动) */

四、符号定义

1. 赋值操作符

换行复制代码

symbol = expression;     /* 简单赋值 */                                                                                                                                                                                            
symbol .= expression;    /* 相对赋值 */                                             
symbol += expression;    /* 加法赋值 */                                             
symbol -= expression;    /* 减法赋值 */                                             
symbol *= expression;    /* 乘法赋值 */                                             
symbol /= expression;    /* 除法赋值 */                                             
symbol <<= expression;   /* 左移赋值 */                                             
symbol >>= expression;   /* 右移赋值 */                                             
symbol |= expression;    /* 或赋值 */                                               
0symbol &= expression;    /* 与赋值 */ 

2. 特殊符号

换行复制代码

/* 预定义符号 */                                                                 
__executable_start    /* 程序起始地址 */                                         
etext, _etext, etext  /* .text段结束地址 */                                                                                                                                                                                        
edata, _edata         /* .data段结束地址 */                                      
end, _end             /* BSS段结束地址 */                                        
                                                                                 
/* 自定义符号 */                                                                 
_stack_start = .;     /* 栈起始地址 */                                           
_stack_size = 0x400;  /* 栈大小 */                                               
_heap_end = ORIGIN(RAM) + LENGTH(RAM); /* 堆结束地址 */ 

五、函数和运算符

1. 地址相关函数

换行复制代码

ADDR(section)          /* 获取段的运行时地址 */
LOADADDR(section)      /* 获取段的加载地址 */
SIZEOF(section)        /* 获取段的大小 */
ALIGN(exp)             /* 对齐到exp的倍数 */
ALIGNOF(section)       /* 获取段的对齐要求 */

2. 符号相关函数

换行复制代码

DEFINED(symbol)        /* 检查符号是否已定义 */
MAX(exp1, exp2)        /* 取最大值 */
MIN(exp1, exp2)        /* 取最小值 */

3. 运算符优先级

1. () /* 括号 */ 2. ! ~ - + /* 一元运算符 */ 3. * / % /* 乘除 */ 4. + - /* 加减 */ 5. >> << /* 移位 */ 6. == != > < >= <= /* 关系运算 */ 7. & | ^ /* 位运算 */ 8. && || /* 逻辑运算 */ 9. ?: /* 条件运算 */ 10. = += -= *= /= /* 赋值 */

六、段属性控制

1. 段类型

换行复制代码

.text : { *(.text) } TYPE=PT_LOAD  /* 可加载段 */
.rodata : { *(.rodata) } TYPE=PT_LOAD

2. 段标志

换行复制代码

.data : { *(.data) } :data         /* 使用内存区域属性 */

七、条件语句

IF-ELSE 语句

换行复制代码

SECTIONS                                                                         
{                                                                                
    .text : {                                                                    
        *(.text)                                                                 
        . = ALIGN(4);                                                            
        IF (DEFINED(__USE_SOFT_FLOAT__)) {                                       
            *(.text.softfloat)                                                                                                                                                                                                     
        } ELSE {                                                                 
            *(.text.hardfloat)                                                   
        }                                                                        
    } > ROM                                                                      
} 

八、包含指令

INCLUDE 指令

换行复制代码

/* 包含其他链接脚本 */
INCLUDE memory.ld
INCLUDE sections.ld

/* 条件包含 */
IF (MCU == "STM32F103")
    INCLUDE stm32f103.ld
ELSE
    INCLUDE generic.ld
ENDIF

九、输出格式控制

OUTPUT_FORMAT

换行复制代码

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(Reset_Handler)  /* 指定入口点 */

十、完整示例分解

换行复制代码

/* 1. 输出格式设置 */
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(Reset_Handler)

/* 2. 内存区域定义 */
MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
    CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
}

/* 3. 段定义 */
SECTIONS
{
    /* 中断向量表 - 必须放在起始位置 */
    .isr_vector :
    {
        . = ALIGN(4);
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } > FLASH

    /* 代码段 */
    .text :
    {
        *(.text)           /* .text段(代码) */
        *(.text.*)         /* .text.*段 */
        *(.glue_7)         /* ARM/Thumb胶水代码 */
        *(.glue_7t)        /* Thumb/ARM胶水代码 */
        *(.eh_frame)       /* 异常处理框架 */

        /* 构造函数 */
        KEEP(*(.init))
        KEEP(*(.fini))

        . = ALIGN(4);
        _etext = .;        /* 代码段结束地址 */
    } > FLASH

    /* 只读数据 */
    .rodata :
    {
        *(.rodata)
        *(.rodata.*)
        . = ALIGN(4);
    } > FLASH

    /* ARM特定段 */
    .ARM.extab :
    {
        *(.ARM.extab*)
        *(.gnu.linkonce.armextab.*)
    } > FLASH

    .ARM.exidx :
    {
        __exidx_start = .;
        *(.ARM.exidx*)
        *(.gnu.linkonce.armexidx.*)
        __exidx_end = .;
    } > FLASH

    /* 预初始化数组 */
    .preinit_array :
    {
        PROVIDE_HIDDEN(__preinit_array_start = .);
        KEEP(*(.preinit_array*))
        PROVIDE_HIDDEN(__preinit_array_end = .);
    } > FLASH

    /* 初始化数组 */
    .init_array :
    {
        PROVIDE_HIDDEN(__init_array_start = .);
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array*))
        PROVIDE_HIDDEN(__init_array_end = .);
    } > FLASH

    /* 结束数组 */
    .fini_array :
    {
        PROVIDE_HIDDEN(__fini_array_start = .);
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array*))
        PROVIDE_HIDDEN(__fini_array_end = .);
    } > FLASH

    /* 初始化数据(从Flash加载到RAM) */
    _sidata = LOADADDR(.data);

    .data :
    {
        . = ALIGN(4);
        _sdata = .;

        *(.data)
        *(.data.*)

        . = ALIGN(4);
        _edata = .;
    } > RAM AT > FLASH

    /* 未初始化数据 */
    .bss :
    {
        . = ALIGN(4);
        _sbss = .;

        *(.bss)
        *(.bss.*)
        *(COMMON)

        . = ALIGN(4);
        _ebss = .;
    } > RAM

    /* 用户堆栈 */
    ._user_heap_stack :
    {
        . = ALIGN(8);
        PROVIDE(end = .);
        PROVIDE(_end = .);
        . = . + _Min_Heap_Size;
        . = . + _Min_Stack_Size;
        . = ALIGN(8);
    } > RAM

    /* 移除调试信息 */
    /DISCARD/ :
    {
        libc.a(*)
        libm.a(*)
        libgcc.a(*)
    }

    /* 自定义段 */
    .my_section :
    {
        __my_section_start = .;
        KEEP(*(.my_section))
        __my_section_end = .;
    } > CCMRAM

    /* 绝对符号定义 */
    .abs_section :
    {
        __abs_start = ABSOLUTE(.);
        *(.abs_section)
        __abs_end = ABSOLUTE(.);
    } > FLASH
}

/* 4. 全局符号定义 */
PROVIDE(_estack = ORIGIN(RAM) + LENGTH(RAM));
PROVIDE(_Min_Heap_Size = 0x200);
PROVIDE(_Min_Stack_Size = 0x400);

十一、调试技巧

1. 查看链接映射

换行复制代码

arm-none-eabi-ld -T script.ld -Map=output.map ...

//riscv-rtos
riscv32-unknown-elf-objdump -h RTOSDemo.elf | less
or
riscv32-unknown-elf-nm -n RTOSDemo.elf | less

2. 常见错误处理

换行复制代码

/* 确保关键段不被优化 */
KEEP(*(.vector_table))
KEEP(*(.isr_vector))

/* 处理未使用段 */
/DISCARD/ : { *(.note.GNU-stack) }

3. 版本兼容性

换行复制代码

/* GNU LD版本检查 */
__LD_VERSION__ = 0x23800;  /* 2.56.0 */

/* 条件编译 */
VERSION {
    CURRENT {
        local: *;
    };
}

动物装饰