欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > 简说stm32的startup.s文件和ld链接脚本

简说stm32的startup.s文件和ld链接脚本

2025/11/9 12:41:23 来源:https://blog.csdn.net/is0815/article/details/148544956  浏览:    关键词:简说stm32的startup.s文件和ld链接脚本

在STM32的开发中,除了像CMSISI库和驱动库这些源码文件外,还会用到startup.s 文件和链接脚本(通常是 .ld 文件),通常这两个件都是IDE根据相应的MCU型号自动获取放入工程中,普通开发不需要关注,但是这两个文件对程序的初始化、内存布局以及执行流程有着重要影响,做一些高级应用时需要手动配置修改。


1. startup.s 的影响

startup.s 是汇编语言编写的启动文件,通常用于初始化硬件和设置运行环境。以下是它可能影响的代码部分:

  • 堆栈初始化

    /* 设置初始堆栈指针 */
    ldr sp, =_estack
    

    解释:startup.s 会初始化堆栈指针(SP),确保 C/C++ 程序能够正确使用堆栈进行函数调用和局部变量存储。

  • 全局变量初始化

    /* 将 .data 段从 Flash 复制到 RAM */
    ldr r0, =__etext
    ldr r1, =__data_start__
    ldr r2, =__data_end__
    mov r3, #0
    copy_data:ldrb r4, [r0], #1strb r4, [r1], #1add r3, r3, #1cmp r3, r2bne copy_data/* 清零 .bss 段 */
    ldr r0, =__bss_start__
    ldr r1, =__bss_end__
    mov r2, #0
    zero_bss:strb r2, [r0], #1cmp r0, r1bne zero_bss
    

    解释:startup.s 负责将 .data 段从 Flash 复制到 RAM,并清零 .bss 段中的全局变量。如果这些步骤未正确完成,程序中依赖于全局变量的代码可能会出现异常行为。

  • 主函数调用

    /* 调用 main 函数 */
    bl main
    

    解释:startup.s 最终调用 main 函数,启动用户代码的执行。


2. 链接文件的影响

链接文件定义了程序的内存布局,包括各个段的位置和大小。以下是它可能影响的代码部分:

  • 内存段分配

    MEMORY {FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512KSRAM  (rw)  : ORIGIN = 0x20000000, LENGTH = 64K
    }SECTIONS {.text : {*(.text*)} > FLASH.rodata : {*(.rodata*)} > FLASH.data : {_sidata = LOADADDR(.data);*(.data*)} > SRAM AT> FLASH.bss : {*(.bss*)} > SRAM
    }
    

    解释:

    • 链接文件将代码段(.text)、只读数据段(.rodata)放置在 Flash 中。
    • 可写数据段(.data)和未初始化数据段(.bss)放置在 SRAM 中。
    • 如果链接文件配置错误,可能导致程序无法正确访问数据或执行代码。
  • 符号定义

    PROVIDE(_estack = ORIGIN(SRAM) + LENGTH(SRAM));
    PROVIDE(__etext = .);
    PROVIDE(__data_start__ = .);
    PROVIDE(__data_end__ = .);
    PROVIDE(__bss_start__ = .);
    PROVIDE(__bss_end__ = .);
    

    解释:链接文件定义了一些关键符号,例如堆栈顶部地址 _estack 和数据段的起始/结束地址。这些符号被 startup.s 使用来完成初始化。


示例代码

以下是一个简单的 STM32 项目结构,展示 startup.s 和链接文件如何协作影响代码:

startup.s
/* 启动文件 */.syntax unified.cpu cortex-m4.fpu softvfp.thumb.global Reset_Handler.type Reset_Handler, %functionReset_Handler:/* 初始化堆栈指针 */ldr sp, =_estack/* 复制 .data 段 */ldr r0, =__etextldr r1, =__data_start__ldr r2, =__data_end__
copy_data_loop:cmp r0, r2beq clear_bssldrb r3, [r0], #1strb r3, [r1], #1b copy_data_loopclear_bss:/* 清零 .bss 段 */ldr r0, =__bss_start__ldr r1, =__bss_end__
zero_loop:cmp r0, r1beq call_mainmovs r2, #0strb r2, [r0], #1b zero_loopcall_main:/* 调用 main 函数 */bl mainhang:b hang
链接文件 (STM32.ld)
/* 链接脚本 */
MEMORY {FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512KSRAM  (rw)  : ORIGIN = 0x20000000, LENGTH = 64K
}SECTIONS {.text : {*(.text*)} > FLASH.rodata : {*(.rodata*)} > FLASH.data : {_sidata = LOADADDR(.data);*(.data*)} > SRAM AT> FLASH.bss : {*(.bss*)} > SRAMPROVIDE(_estack = ORIGIN(SRAM) + LENGTH(SRAM));
}
主程序 (main.c)
#include <stdio.h>int global_var = 42;void main() {static int static_var = 10;printf("Global Var: %d\n", global_var);printf("Static Var: %d\n", static_var);while (1);
}

解释

  1. startup.s 的作用

    • 初始化堆栈指针。
    • .data 段从 Flash 复制到 SRAM。
    • 清零 .bss 段。
    • 调用 main 函数。
  2. 链接文件的作用

    • 定义内存区域(Flash 和 SRAM)。
    • 分配代码段(.text)、只读数据段(.rodata)、可写数据段(.data)和未初始化数据段(.bss)。
    • 提供符号地址(如 _estack__data_start__)供 startup.s 使用。

链接脚本(Linker Script)用于控制程序在内存中的布局,嵌入式开发中常用于指定代码段、堆栈、向量表等放置在哪些具体的内存地址。STM32 或其他 ARM Cortex-M 芯片中,链接脚本非常关键。


📚 常见用于嵌入式的链接脚本格式

你可能使用:

  • GNU 链接器脚本(.ld)— GCC/CubeIDE/Makefile 项目用这个。
  • Keil scatter file(.sct)— Keil MDK 项目用这个。
  • IAR linker configuration(.icf)— IAR 项目用这个。

下面主要讲 GNU .ld 脚本语法(最常见、最可控)👇


✅ 链接脚本基本结构(.ld

ENTRY(Reset_Handler)MEMORY
{FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 64KRAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}SECTIONS
{/* 向量表和启动代码 */.isr_vector :{KEEP(*(.isr_vector))} >FLASH/* 程序代码段 */.text :{*(.text)           /* 所有代码 */*(.text*)          /* 所有函数 */*(.rodata)         /* 常量 */*(.rodata*)        /* 字符串常量等 */KEEP(*(.init))KEEP(*(.fini))} >FLASH/* 初始化数据,运行时从 FLASH 复制到 RAM */.data : AT(__etext){__data_start__ = .;*(.data)*(.data*)__data_end__ = .;} >RAM/* 未初始化变量 */.bss :{__bss_start__ = .;*(.bss)*(.bss*)*(COMMON)__bss_end__ = .;} >RAM/* 堆区 */.heap (COPY):{__heap_start__ = .;. = . + 4K;__heap_end__ = .;} >RAM/* 栈区 */.stack (COPY):{__stack_start__ = .;. = . + 2K;__stack_end__ = .;} >RAM/* 结束地址 */_end = .;
}

🔍 语法要点速查

语法含义
MEMORY定义物理内存区域和大小
SECTIONS定义逻辑段,决定哪些代码/变量放哪
>表示把段放入哪个 MEMORY 区域
AT(x)指定链接地址和加载地址不一致(常用于 .data
KEEP()强制保留被 GCC 可能优化掉的段(如 .isr_vector
.当前地址指针,可控制偏移和大小
__symbol__ = .;保存当前地址为符号,用于初始化、拷贝等
*(.text)收集所有 .text 段的内容(函数体)

💡 例子:配置一个堆栈在 SRAM 顶部

假设 20KB RAM 从 0x20000000 开始,我们将 .stack 放到最顶:

MEMORY
{RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}SECTIONS
{....stack (COPY):{. = ORIGIN(RAM) + LENGTH(RAM) - 1K; /* 栈在最后 1KB */__stack_top = .;. = . + 1K;__stack_bottom = .;} >RAM
}

🧩 用途示例

你可以通过链接脚本来:

  • 测试 SRAM 某段是否可用(限制变量位置)
  • 把栈搬到 SRAM 最尾部(避免和测试冲突)
  • 把某段变量放入专用 RAM 区(比如 .dma 用于 DMA)
  • 定义 bootloader + app 的 FLASH 分区

🛠 工具辅助

如果你使用的是 STM32CubeIDEMakefile + GCC,链接脚本通常位于:

project/STM32xxxx_FLASH.ld

你可以直接编辑此文件来自定义内存布局。


📌 小结

内容示例
定义内存区域MEMORY { FLASH(rx): ORIGIN = 0x08000000, LENGTH = 64K }
放入段.text : { *(.text) } >FLASH
指定偏移. = . + 4K;
定义符号__bss_start__ = .;
强制保留KEEP(*(.isr_vector))

链接脚本里同名段(比如 .text)理论上可以定义多个,只要它们分别位于不同的地址位置,链接器会把它们当作同一个逻辑段的多个“分片”,在内存中连续或不连续放置。


举例说明

.text : { /* 第一部分 */*(.text_start)
}. = ALIGN(512);.isr_vector : { /* 中断向量 */KEEP(*(.isr_vector*))
}. = ALIGN(1024);.text ALIGN(4) : { /* 第二部分 */*(.text_main)
}. = ALIGN(2048);.text ALIGN(4) : { /* 第三部分 */*(.text_extra)
}

结果

  • 链接器会把所有 .text 段内容合并成逻辑上的一个 .text 段;
  • 物理上这 .text 段被拆成了 3 个片段,分别位于不同地址(由你写的对齐和地址移动决定);
  • 这中间可以插入别的段(比如 .isr_vector),造成不连续内存布局。

使用场景

  • 放启动代码、向量表、主程序代码、额外代码段等,需要特定地址对齐或空间;
  • 对应硬件启动要求或内存映射设计。

注意点

  • 多段 .text 分片可能使调试器显示段不连续,调试体验可能稍差;
  • 需要确认链接器脚本和启动文件匹配,避免地址冲突;
  • 链接器输出的 map 文件能帮你查看段实际地址布局。

.text : {} 第一个空段

-Ttext=<addr> 是链接器(ld)的一个命令行参数,用来 指定程序的 .text 段在内存中的起始加载地址


具体作用:

  • 链接器默认把 .text 段放在链接脚本里定义的默认地址(通常是 Flash 起始地址,比如 0x08000000);
  • 使用 -Ttext=<addr> 可以覆盖链接脚本里 .text 段的默认起始地址,让代码从你指定的 <addr> 地址开始放置;
  • 这样可以灵活安排程序代码在内存中的位置,比如给 Bootloader 留空间,把主程序放在偏移地址,或者放到 RAM 运行等。

举例

arm-none-eabi-ld ... -Ttext=0x08004000 ...
  • 让程序代码从 Flash 地址 0x08004000 开始加载,而不是从 0x08000000
  • 常见于需要把程序放在 Flash 某个偏移位置的场景。

什么时候用?

  • 有 Bootloader,需要主程序放在 Bootloader后面固定偏移位置;
  • 需要把程序放在某个特殊内存区域;
  • 多程序分区加载和升级设计。

注意

  • -Ttext 只影响 .text 段的起始地址,不影响其他段(如 .data, .bss);
  • 链接脚本可能还需要配合修改,确保其他段地址合理。

简单总结:

作用说明
指定 .text 段起始地址灵活调整代码在内存中的加载位置
适应 Bootloader 或分区设计保证程序代码地址满足硬件或升级需求

能用,但可能会有一些限制和风险,具体看你的链接脚本和硬件需求。


1. 没有第一段空的 .text : {},链接器会怎样?

  • 链接器会把第一个定义的 .text 段内容放在链接脚本里定义的默认起始地址;
  • 如果没有空的占位段,后面实际代码段会紧贴起始地址放置,不会留出预留空间
  • 这可能导致无法插入其他必须放在起始地址的段(比如中断向量表),或者启动代码和程序代码无法分区。

2. 可能的影响

  • 如果中断向量表 .isr_vector 也放在 .text,没空段也没关系,代码会连续放置,程序照常运行;
  • 如果你需要预留空间给 Bootloader 或特殊段,没空段就没法“留地方”,升级和分区就麻烦;
  • 如果启动代码和程序代码有地址冲突,程序可能启动失败或异常。

3. 总结

情况结论
简单项目,无特殊预留需求可以不用空 .text : {}
有 Bootloader 或特殊布局需求建议用空 .text 占位,方便布局

简单说:

如果你的链接脚本和启动流程设计不依赖那个空段,没它程序也能跑;但如果你需要分区、升级、或对齐,空段是很有用的“预留地”。


简单示例:


RAM_START   = 0x20000000;
RAM_END     = 0x20005000;_estack     = RAM_END;/* ENTRY(main) */SECTIONS
{/* This is for ability to change link address with `-Ttext=<addr>` ld option */.text : {}/* Align interrupts vectors table to 512-byte boundary */. = ALIGN(512);/* C generated vectors sections of name `.isr_vector.__vec_*` */INCLUDE vectors.ld/* ASM/C generated vectors */.isr_vector : { KEEP(*(.isr_vector*)) KEEP(*(.iv))  KEEP(*(.vt)) }/* Code and read-only data; can be aligned to (2) */.text ALIGN(4) : { *(.text*) *(.rodata*) }/* Data alignment is not stricly required *//* Save .text end address; .data init values retain here */_sidata = ALIGN(4);/* Move .data and .bss to ram if . isn't already there */. = . < RAM_START ? RAM_START : . ;/* Link .data always to RAM */.data ALIGN(4) : AT (_sidata) { _sdata = . ; *(.data*) _edata = . ; }/* Link .bss always to RAM after the .data */.bss ALIGN(4) : { _sbss = . ; *(.bss*) *(COMMON) _ebss = . ; }/* Remove sections that are not required *//DISCARD/ : { *(.ARM.attributes) *(.comment*) *(.note*) }
}

启动代码

负责把.data.bss 段的数据搬运到 RAM


1. .data.bss 段的区别

段名含义初始存储位置运行时存储位置
.data初始化的全局/静态变量FLASH(非易失存储)RAM(可读写)
.bss未初始化的全局/静态变量不占FLASH空间(全0)RAM,且初始化为0

2. 为什么 .data 要放 FLASH?

  • .data 变量需要初始化为非零的初始值。
  • 这些初始值必须存储在非易失存储器(通常是 FLASH)中。
  • 程序启动时,把 FLASH 中的 .data 初始值“搬运”到 RAM。

3. .bss 的初始化

  • .bss 是未初始化的变量,标准要求它们启动时被清零
  • .bss 不占 FLASH 空间,直接在 RAM 中分配空间,启动时由启动代码清零。

4. 搬运时机:启动代码(启动汇编/初始化C库)

  • 搬运 .data 和清零 .bss 是由启动代码完成的,通常写在启动汇编文件或 C 库初始化函数中。
  • 这个代码在 Reset_Handler_start 入口处运行,最先执行。

典型伪代码流程:

// 搬运 .data 段初始化值:从FLASH复制到RAM
for (p = &_ldata; p < &_edata; p++) {*p = *p_flash++;
}// 清零 .bss 段
for (p = &_sbss; p < &_ebss; p++) {*p = 0;
}

这里:

  • _ldata 是 FLASH 中 .data 初始值起始地址
  • _edata.data 末尾地址(RAM 中)
  • _sbss_ebss.bss 的 RAM 区间地址

5. 链接脚本的作用

链接脚本会:

  • .data加载地址(LOADADDR)放在 FLASH(含初始值)
  • .data运行地址(VMA,Virtual Memory Address)放在 RAM(变量实际存放处)
  • 定义 _ldata_sdata_edata 等符号,供启动代码使用

6. .bss 在链接脚本中只分配 RAM 空间

它不存初始值,启动时清零即可。


7. 具体示例链接脚本片段(简化版)

.data : AT (LOADADDR(.data)) {_sdata = .;           /* 运行时起始地址,RAM */*(.data)_edata = .;
} >RAM AT >FLASH.bss : {_sbss = .;*(.bss)*(COMMON)_ebss = .;
} >RAM

总结

内容说明
.data 初始值存储在 FLASH,程序启动时复制到 RAM
.data 运行区RAM,CPU 读写变量空间
.bssRAM,启动时清零,无FLASH初始值
搬运时机复位启动代码(Reset_Handler)执行时,C 库初始化部分

bss段

程序代码(比如 .text 段里的启动代码)在编译和链接阶段就已经知道 .bss 段的起始和结束地址了,这是通过链接器脚本定义的符号来实现的。


原理

  • 链接器脚本定义了 .bss 段,并在其中定义符号,如:

    _sbss = .;          /* .bss段起始地址 */
    *(.bss*)
    _ebss = .;          /* .bss段结束地址 */
    
  • 这些符号 _sbss_ebss 在链接后成为固定地址常量,写入可执行文件的符号表和重定位表里。

  • 编译器/汇编器在编译 .text 里的代码时,会引用这些符号作为外部变量(extern),链接后程序代码中使用的就是具体地址。


结果

启动代码中类似:

extern uint32_t _sbss;
extern uint32_t _ebss;void Reset_Handler(void) {uint32_t *p = &_sbss;while (p < &_ebss) {*p++ = 0;}// 继续执行其他初始化
}
  • _sbss_ebss 不是“运行时查符号表”,而是编译链接后就固定成具体的地址值;
  • CPU 执行时用的就是这些实际的 RAM 地址,完成 .bss 的清零。

总结

阶段符号作用
链接时符号被解析为具体内存地址
编译时代码引用符号作为变量或地址
运行时直接使用地址,清零 .bss

我们可以分两步来看清楚这个过程:


✅ 第一步:链接器脚本定义 .bss 段和它的边界符号

链接器脚本(.ld 文件)中有类似如下定义:

.bss (NOLOAD) : {_sbss = .;         /* 记录 .bss 起始地址 */*(.bss*)*(COMMON)_ebss = .;         /* 记录 .bss 结束地址 */
} > RAM

链接器的作用:

  • 把所有未初始化的全局/静态变量放进 .bss 段;
  • 设置 _sbss_ebss 这两个符号,代表 .bss 段的起止地址;
  • 在 ELF 文件的符号表中记录这两个符号的位置;
  • 最终,.text 段中引用这两个符号的位置,会被替换成它们的实际地址值

✅ 第二步:启动代码引用这些符号,用于清零 .bss

在启动代码中(通常是 C 或汇编),你会看到类似如下代码:

extern uint32_t _sbss;
extern uint32_t _ebss;void Reset_Handler(void) {uint32_t *dst = &_sbss;while (dst < &_ebss) {*dst++ = 0;}
}
  • 这段代码使用 _sbss_ebss,就是在清空 .bss 区域;
  • 编译时,这些符号被当成外部变量;
  • 链接时,它们被替换成 .bss 段实际的起止地址。

✅ 本质

你说的“启动代码清 .bss 的代码,和 .bss 段的符号是链接时对应上的”——本质上是:

链接器把 .bss 段确定了地址范围,并生成对应的符号 _sbss_ebss,这些符号的地址被内嵌到 .text 段的启动代码中,所以两者天然就对得上。


✅ 可视化理解(Flash / RAM 布局)

FLASH:
0x08000000  ───────────────────[ .isr_vector ][ .text ]       ← 启动代码在这里,包含 bss 清零指令[ .rodata ][ .data (initial values) ]RAM:
0x20000000  ───────────────────[ .data ]        ← 从 Flash 拷贝来的初始化数据[ .bss ]         ← 启动时清零的区域(由 _sbss/_ebss 指定)[ heap/stack ]

在链接脚本中,AT 关键字用于显式指定数据段或代码段的 ‌加载内存地址(Load Memory Address, LMA)‌,与其运行时地址(Virtual Memory Address, VMA)分离。具体解析如下:

一、AT 的作用与语法‌

核心功能‌
AT 指令定义数据在 ‌存储介质(如 Flash)中的物理存放位置‌:

LMA‌:程序烧录时数据存储的位置地址(如 Flash)。
VMA‌:程序运行时数据被加载到内存(如 RAM)的地址。

语法格式‌
在 .ld 文件中的典型用法:

ld
Copy Code
.section_name : {
/* 段内容 */
} > VMA_REGION AT> LMA_REGION // 方式1:指定存储器区域

或:

ld
Copy Code
.section_name : AT(LMA_ADDRESS) {
/* 段内容 */
} > VMA_REGION // 方式2:直接指定地址

二、关键应用场景‌

初始化全局变量(.data 段)‌
需将初始值存储在 Flash(LMA),运行时复制到 RAM(VMA):

ld
Copy Code
.data : ALIGN(4) {
_sdata = .; // VMA 起始地址(RAM)
(.data .data.)
_edata = .; // VMA 结束地址(RAM)
} > RAM AT> FLASH // VMA 在 RAM,LMA 在 FLASH

启动代码通过 _sidata = LOADADDR(.data) 获取初始值位置并复制到 RAM。

自定义数据段存储‌
将特定数据(如配置表)固定存储到 Flash 的独立区域:

ld
Copy Code
MEMORY {
FLASH_CONFIG (rx) : ORIGIN = 0x0800C000, LENGTH = 16K
}
.config_data : {
*(.config_section)
} AT> FLASH_CONFIG // LMA 指定到 FLASH_CONFIG

三、AT Vs. 默认行为‌
特性‌ ‌默认行为(无 AT)‌ ‌显式 AT 指令‌
LMA 地址‌ 等于 VMA(可能导致数据未初始化) 独立于 VMA(通常为 Flash)
初始化需求‌ 需手动初始化数据 启动代码自动从 LMA 复制到 VMA
典型用例‌ 纯 RAM 运行 嵌入式系统初始化变量/常量
四、技术原理‌
分离 LMA 与 VMA‌:AT 确保数据在编译阶段被写入正确的存储位置(如 Flash),运行时再加载到目标地址(如 RAM)。
符号关联‌:通过 LOADADDR(.section) 获取 LMA,ADDR(.section) 获取 VMA ,供启动代码使用。
对齐处理‌:常结合 ALIGN(n) 确保地址对齐(如 ALIGN(4) 满足 32 位访问)。

综上,AT 是链接脚本中管理存储与运行地址分离的核心指令,对嵌入式系统的数据初始化和存储器布局至关重要。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com