4
0
0

.s启动文件详解

2026-08-25
文章摘要
|

一、作用与背景:

  • MCU 上电或复位后,CPU 会从固定地址开始执行。

  • 此处存放的是 向量表(Vector Table),第一个条目是栈顶地址(SP),第二个是复位处理函数地址(PC)。

  • 启动文件负责配置这些初始值,并引导程序进入 C 语言环境。

  • 流程:上电 → 执行启动代码 → 设置 SP 和 PC → 填充中断向量表 → main()

二、为什么修改:

(1)修改栈空间大小:

  • 启动文件中定义 Stack_Size / Heap_Size(如 EQU 0x00000400),修改后影响链接时分配的内存。

  • 更推荐修改链接脚本(.ld):现代项目常将堆栈大小定义在链接脚本中,避免与启动文件冲突。

(2)中断向量表重定位

  • Bootloader 需将向量表移至 RAM(如 SCB->VTOR = RAM_ADDR),需在 Reset_Handler 中添加重定位代码。

(3)自定义复位流程

  • 需在 SystemInit() 前执行关键操作(如关闭看门狗、特殊时钟配置)。

三、详细配置内容:

(1)声明段

.syntax unified //告诉汇编器使用统一语法
.cpu cortex-m3 //指定目标处理器为 Cortex-M3 架构
.fpu softfp //指定浮点运算使用软件模拟
.thumb  //使用 Thumb 指令集(16位压缩指令)

.syntax命令是ARM架构独有的命令,用法很简单.syntax [unified | divided]。作用是在汇编ARM汇编源时,指定按照怎样的语法规则进行汇编。如果在编写汇编语言时不使用该命令指定语法规则,那么默认采用.syntax divided,此时使用旧的汇编风格,ARM和THUMB指令有着各自的语法。

使用.syntax unified时,ARM和THUMB指令使用统一的语法,有着以下的主要特性:

  • 立即数不需要#前缀

  • 可能会出现IT指令,如果出现,则根据后面的条件码进行验证。在ARM模式下,它不会生成机器码,而在THUMB模式下则会生成机器码

  • 对于ARM指令,条件码总是出现在指令的最后;对于THUMB指令,条件码可以被使用,但仅限于IT指令的范围内

.global g_pfnVectors 
.global Default_Handler
  • 声明一个名为 g_pfnVectors 的全局变量,它指向中断向量表的起始地址

  • 在 C 代码中可以引用这个符号,比如用于动态重定位向量表(如 Bootloader 场景)。

  • 声明一个默认中断服务程序(ISR)。当某个中断没有专门处理函数时,会跳转到这里。 通常是一个空循环或死循环,防止程序崩溃。

//指示data bss段的起始位置,赋值在ld script文件中
.word _sidata //初始值地址 放置一个标识data段在RAM中开始地址的word型内存位置
//.data对应初始化了的全局变量,编译后将位于可执行文件中,由启动代码负责加载到数据区中(在单片机中这部分数据会存于flash中,需要由启动代码把这部分内容拷贝到ram中)
.word _sdata
.word _edata
//.bss块的起始地址,.bss段是没有初始值的全局变量,由启动代码把这 部分内容全初始化为0
.word _sbss
.word _ebss

符号

含义

用途

_sidata

.data 段的初始值存储位置(在 Flash 中)

例如:int x = 5; 的值 5 存储在此处

_sdata

.data 段在 RAM 中的起始地址

初始化后将 _sidata 复制到此处

_edata

.data 段在 RAM 中的结束地址

标记数据段大小

_sbss

.bss 段在 RAM 中的起始地址

未初始化的全局变量

_ebss

.bss 段在 RAM 中的结束地址

用于清零该区域

关键作用:启动代码需要将 .data 段从 Flash 复制到 RAM,并将 .bss 段清零,才能让 C 程序正常运行。

(2)声明这是“复位入口”

.section .text.Reset_Handler // 【告诉编译器】把下面代码放进“复位专用章节” 
.weak Reset_Handler // 【柔性声明】“如果别人也写了Reset_Handler,用别人的” 
.type Reset_Handler, %function // 【标注】这是个函数(方便调试工具识别) 

(3)设置stack和clock

ldr r0, =_estack // 把“栈顶地址”(比如0x20005000)抄到速记本r0 
mov sp, r0 // 把r0的值设为“栈指针sp”——工作台搭好!
bl SystemInit // 跳去执行SystemInit函数,并记住“回来的位置”

bl 指令 跳转,并记住返回地址 SystemInit 是C语言写的函数(在 system_stm32f1xx.c 里)

为什么要去执行SystemInit函数呢?

(4)搬运“已打包的工具”(.data段)

ldr r0, =_sdata // RAM起点地址 → r0(新家地址) 
ldr r1, =_edata // RAM终点地址 → r1(新家门牌号) 
ldr r2, =_sidata // Flash起点地址 → r2(仓库地址) 
movs r3, #0 // 偏移量清零 → r3(从第0个箱子开始搬) 
b LoopCopyDataInit // 先跳去检查“还要搬吗?

CopyDataInit: // 【搬运动作】 
	ldr r4, [r2, r3] // 从 仓库(r2)+偏移(r3) 拿1箱工具 → r4 
	str r4, [r0, r3] // 把r4的工具 放到 新家(r0)+偏移(r3) 
	adds r3, r3, #4 // 偏移+4(下一箱,每箱4字节) 

LoopCopyDataInit: // 【检查循环】 
	adds r4, r0, r3 // 计算“当前搬到哪了” → r4 
	cmp r4, r1 // 比较:当前地址 < 新家终点? 
	bcc CopyDataInit // 如果“还没搬完”(小于),跳回CopyDataInit继续搬

(5)交接main函数与原地踏步防跑飞

bl __libc_init_array // (c++构造全局对象) c可以跳过
bl main // 正式交给main函数!

LoopForever: 
	b LoopForever // 原地踏步
	

.size Reset_Handler, .-Reset_Handler //计算.Reset_Handler段的长度

(6)中断向量表

.section .isr_vector,"a",%progbits 

.type g_pfnVectors, %object

  • g_pfnVectors = “全局函数指针向量表”(Global Pointer to Function Vectors)

  • %object = 告诉工具链:“这是数据块,不是函数”(调试器不会试图反汇编它)


部分

作用

挂号台类比

.isr_vector

段名(Interrupt Service Routine Vector)

“急诊挂号区”专属标签

"a"

可分配(allocatable)

这个区域要实际占用物理空间

%progbits

包含真实二进制数据

挂号台上有真实窗口编号

→ 编译后,这段数据紧贴 Flash 起始地址 0x08000000
→ 启动时,STM32 硬件将 0x08000000 映射到 0x00000000,CPU 看到的就是“正门挂号台”!

g_pfnVectors:

.word _estack

.word Reset_Handler

.word NMI_Handler

.word HardFault_Handler

.word MemManage_Handler

.word BusFault_Handler

.word UsageFault_Handler

.word 0

.word 0

.word 0

.word 0

.word SVC_Handler

.word DebugMon_Handler

.word 0

.word PendSV_Handler

.word SysTick_Handler

.word WWDG_IRQHandler /* Window Watchdog interrupt */

.word PVD_IRQHandler /* PVD through EXTI line detection interrupt */

.word TAMPER_IRQHandler /* Tamper interrupt */

.word RTC_IRQHandler /* RTC global interrupt */

.word FLASH_IRQHandler /* Flash global interrupt */

.word RCC_IRQHandler /* RCC global interrupt */

.word EXTI0_IRQHandler /* EXTI Line0 interrupt */

.word EXTI1_IRQHandler /* EXTI Line1 interrupt */

.word EXTI2_IRQHandler /* EXTI Line2 interrupt */

.word EXTI3_IRQHandler /* EXTI Line3 interrupt */

.word EXTI4_IRQHandler /* EXTI Line4 interrupt */

.word DMA1_Channel1_IRQHandler /* DMA1 Channel1 global interrupt */

.word DMA1_Channel2_IRQHandler /* DMA1 Channel2 global interrupt */

.word DMA1_Channel3_IRQHandler /* DMA1 Channel3 global interrupt */

.word DMA1_Channel4_IRQHandler /* DMA1 Channel4 global interrupt */

.word DMA1_Channel5_IRQHandler /* DMA1 Channel5 global interrupt */

.word DMA1_Channel6_IRQHandler /* DMA1 Channel6 global interrupt */

.word DMA1_Channel7_IRQHandler /* DMA1 Channel7 global interrupt */

.word ADC1_2_IRQHandler /* ADC1 and ADC2 global interrupt */

.word USB_HP_CAN_TX_IRQHandler /* USB High Priority or CAN TX interrupts */

.word USB_LP_CAN_RX0_IRQHandler /* USB Low Priority or CAN RX0 interrupts */

.word CAN_RX1_IRQHandler /* CAN RX1 interrupt */

.word CAN_SCE_IRQHandler /* CAN SCE interrupt */

.word EXTI9_5_IRQHandler /* EXTI Line[9:5] interrupts */

.word TIM1_BRK_IRQHandler /* TIM1 Break interrupt */

.word TIM1_UP_IRQHandler /* TIM1 Update interrupt */

.word TIM1_TRG_COM_IRQHandler /* TIM1 Trigger and Commutation interrupts */

.word TIM1_CC_IRQHandler /* TIM1 Capture Compare interrupt */

.word TIM2_IRQHandler /* TIM2 global interrupt */

.word TIM3_IRQHandler /* TIM3 global interrupt */

.word TIM4_IRQHandler /* TIM4 global interrupt */

.word I2C1_EV_IRQHandler /* I2C1 event interrupt */

.word I2C1_ER_IRQHandler /* I2C1 error interrupt */

.word I2C2_EV_IRQHandler /* I2C2 event interrupt */

.word I2C2_ER_IRQHandler /* I2C2 error interrupt */

.word SPI1_IRQHandler /* SPI1 global interrupt */

.word SPI2_IRQHandler /* SPI2 global interrupt */

.word USART1_IRQHandler /* USART1 global interrupt */

.word USART2_IRQHandler /* USART2 global interrupt */

.word USART3_IRQHandler /* USART3 global interrupt */

.word EXTI15_10_IRQHandler /* EXTI Line[15:10] interrupts */

.word RTCAlarm_IRQHandler /* RTC Alarms through EXTI line interrupt */

.word 0 /* Reserved */

.word TIM8_BRK_IRQHandler /* TIM8 Break interrupt */

.word TIM8_UP_IRQHandler /* TIM8 Update interrupt */

.word TIM8_TRG_COM_IRQHandler /* TIM8 Trigger and Commutation interrupts */

.word TIM8_CC_IRQHandler /* TIM8 Capture Compare interrupt */

.word ADC3_IRQHandler /* ADC3 global interrupt */

.word FSMC_IRQHandler /* FSMC global interrupt */

.word SDIO_IRQHandler /* SDIO global interrupt */

.word TIM5_IRQHandler /* TIM5 global interrupt */

.word SPI3_IRQHandler /* SPI3 global interrupt */

.word UART4_IRQHandler /* UART4 global interrupt */

.word UART5_IRQHandler /* UART5 global interrupt */

.word TIM6_IRQHandler /* TIM6 global interrupt */

.word TIM7_IRQHandler /* TIM7 global interrupt */

.word DMA2_Channel1_IRQHandler /* DMA2 Channel1 global interrupt */

.word DMA2_Channel2_IRQHandler /* DMA2 Channel2 global interrupt */

.word DMA2_Channel3_IRQHandler /* DMA2 Channel3 global interrupt */

.word DMA2_Channel4_5_IRQHandler /* DMA2 Channel4 and DMA2 Channel5 global interrupt */

.size g_pfnVectors, .-g_pfnVectors
Q:为什么有些中断写 .word 0 而不是 Default_Handler

→ ARM Cortex-M 规范强制要求
保留位必须为 0(非0值可能导致未定义行为)
→ 触发保留中断时,CPU 会因“跳转到0地址”触发总线错误,最终进入 HardFault_Handler(安全机制)

(7)弱定义中断向量表内容

.weak DMA2_Channel4_5_IRQHandler
.thumb_set DMA2_Channel4_5_IRQHandler,Default_Handler

指令

作用

为什么必须用 .thumb_set

.set

普通符号赋值

生成 ARM 指令地址(Cortex-M 只支持 Thumb)

.thumb_set

Thumb 模式专用赋值

确保地址最低位=1(0xXXXXXXX1),CPU 知道这是 Thumb 函数 → 避免总线错误!

Cortex-M 要求:所有函数地址最低位必须为 1(表示 Thumb 模式)。.thumb_set 自动处理此细节。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

.s启动文件详解
/archives/.sqi-dong-wen-jian-xiang-jie
作者
Sharkey
发布于
2026-08-25
许可协议
CC BY-NC-SA 4.0

评论