操作系统开发:(9) 从硬件复位到程序执行:如何编写符合硬件动作的启动文件与链接脚本
摘要:本文详细讲解了操作系统开发中启动文件与链接脚本的核心作用。链接脚本负责内存映射(FLASH/RAM)、精确地址分配、符号定义和优化控制,通过VMA/LMA概念实现程序段布局。启动文件则完成CPU上电后的初始化工作,包括设置栈指针、复制.data段、清零.bss段并跳转main函数。二者协同工作确保程序正确加载运行,其中链接脚本定义的内存布局(如中断向量表必须位于0x08000000)与启动文
在 操作系统开发:(1) 启动文件与链接脚本 初步了解启动文件和汇编脚本的作用,以及 cpu 上电后的动作并实现最基础的控制,这里进行详解从硬件复位到程序执行的完整过程。
1. 链接脚本
1.1 链接脚本的作用
- 内存映射:将程序逻辑段(代码/数据)映射到物理内存(FLASH/RAM)
- 地址分配:精确控制各段在内存中的位置和布局
- 符号定义:生成关键地址符号供启动文件和C代码使用
- 优化控制:决定哪些段保留/丢弃,影响最终固件大小
1.2 概念
|
概念 |
说明 |
例 |
|---|---|---|
|
VMA (Virtual Memory Address) |
程序运行时地址 |
|
|
LMA (Load Memory Address) |
存储介质中的地址 |
|
|
位置计数器 |
当前输出段的 VMA |
用于动态计算段边界和对齐 |
|
AT> 指令 |
显式指定 LMA |
.data > RAM AT > FLASH |
1.3 源码
STM32G431RBTX_FLASH.ld
ENTRY(Reset_Handler)
_estack = ORIGIN(RAM) + LENGTH(RAM);
_Min_Heap_Size = 0x200;
_Min_Stack_Size = 0x400;
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
}
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} >FLASH
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .;
} >FLASH
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. = ALIGN(4);
} >FLASH
.ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
. = ALIGN(4);
} >FLASH
.preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
} >FLASH
.init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
} >FLASH
.fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4);
} >FLASH
_sidata = LOADADDR(.data);
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
*(.RamFunc)
*(.RamFunc*)
. = ALIGN(4);
_edata = .;
} >RAM AT> FLASH
. = ALIGN(4);
.bss :
{
_sbss = .;
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = _ebss;
} >RAM
/* User_heap_stack section, used to check that there is enough "RAM" Ram type memory left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
/* Remove information from the compiler libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
根据该源码可得其内存布局如下:
FLASH (0x08000000 – 0x0801FFFF, 128KB)
┌──────────────────────────────────────┐
│ .isr_vector (中断向量表) │ ← 0x08000000
├──────────────────────────────────────┤
│ .text (代码) │
├──────────────────────────────────────┤
│ .rodata (只读数据) │
├──────────────────────────────────────┤
│ .ARM.* / .init_array 等 │
├──────────────────────────────────────┤← _sidata
│ .data 副本 (初始化数据镜像) │
└──────────────────────────────────────┘
RAM (0x20000000 – 0x20007FFF, 32KB)
┌──────────────────────────────────────┐
│ .data (运行时初始化数据) │ ← 0x20000000 (_sdata)
├──────────────────────────────────────┤← _sbss
│ .bss (未初始化数据) │
├──────────────────────────────────────┤← _ebss
│ 堆 (Heap) 向上/高地址增长 (512B) │
├──────────────────────────────────────┤
│ 未使用区域(堆栈安全缓冲) │
├──────────────────────────────────────┤
│ 栈 (Stack) 向下/低地址增长 (1024B) │
└──────────────────────────────────────┘ ← 0x20008000 (_estack,栈顶)
1.4 精简版
linker.ld
/* ========================================================================
* 1. 程序入口点
* ======================================================================== */
ENTRY(Reset_Handler)
/* ========================================================================
* 2. 定义变量栈顶 _estack - 从RAM顶部开始向下生长
* 计算: 0x20000000 + 32KB = 0x20008000
* ======================================================================== */
_estack = ORIGIN(RAM) + LENGTH(RAM);
/* ========================================================================
* 3. 物理内存定义 - STM32G431RBT6固定配置
* ======================================================================== */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* 程序存储区 */
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K /* 数据运行区 */
}
/* ========================================================================
* 4. 内存段布局, 定义段映射规则
* 每个 { ... } 块是一个输出段
* 书写顺序决定了不同段在内存中的物理排列顺序
* VMA(Virtual Memory Address):程序运行时该段所在的地址
* LMA(Load Memory Address):该段在存储介质中的地址, AT 用于显示指定 LMA
* . 是当前位置计数器(location counter)
* 它的值是 当前输出段的 VMA, 即程序运行时该位置的地址
* ======================================================================== */
SECTIONS
{
/* --------------------------------------------------------------
* 4.1 中断向量表 - 位于 FLASH 起始地址: 0x08000000
* Cortex-M4硬件要求: 前8字节 = [栈顶, 复位地址]
* -------------------------------------------------------------- */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* 保留向量表不被优化,
KEEP(...): 强制保留括号内的段, 即使在链接优化时也不会被丢弃 */
KEEP(*(.vectors)) /* 兼容不同命名 */
. = ALIGN(4);
} > FLASH /* VMA 和 LMA 相同, 都在 FLASH 起始地址 */
/* --------------------------------------------------------------
* 4.2 代码段 - 所有函数机器码(main/port/tasks等代码)
* -------------------------------------------------------------- */
.text :
{
. = ALIGN(4);
*(.text*) /* 所有代码段 */
*(.glue_7) /* Thumb/ARM胶合代码(保留兼容性)*/
*(.glue_7t)
*(.eh_frame) /* 异常帧 */
. = ALIGN(4);
_etext = .; /* 定义变量 _etext 为代码段 VMA 结束地址(供启动文件使用)*/
} > FLASH /* VMA 和 LMA 相同, 都在 FLASH .isr_vector 段后面 */
/* --------------------------------------------------------------
* 4.3 只读数据 - 常量、字符串字面量等
* -------------------------------------------------------------- */
.rodata :
{
. = ALIGN(4);
*(.rodata*)
*(.rodata.*)
. = ALIGN(4);
} > FLASH /* VMA 和 LMA 相同, 都在 FLASH .text 段后面 */
/* 定义 _sidata 为.data 段初始值在 FLASH 中的起始地址(LMA)*/
_sidata = LOADADDR(.data);
/* --------------------------------------------------------------
* 4.4 已初始化数据 - 如: int x = 5;
* 运行时在RAM,初始值从Flash复制
* -------------------------------------------------------------- */
.data :
{
. = ALIGN(4);
_sdata = .; /* 定义 .data 段在 RAM 中起始地址 */
*(.data*)
*(.data.*)
. = ALIGN(4);
_edata = .; /* 定义 .data 段在 RAM 中结束地址 */
} > RAM AT > FLASH /* LMA=Flash, VMA=RAM */
. = ALIGN(4);
/* --------------------------------------------------------------
* 4.5 未初始化数据 - 如: int y;
* 启动时由启动文件清零 .bss 段
* -------------------------------------------------------------- */
.bss :
{
_sbss = .;
*(.bss*)
*(.bss.*)
*(COMMON) /* 传统未初始化全局变量 */
. = ALIGN(4);
_ebss = .;
} > RAM /* LMA=VMA=RAM */
/* --------------------------------------------------------------
* ABI属性段 - 必须保留,否则链接器警告
* 包含Thumb指令集等ABI信息
* -------------------------------------------------------------- */
.ARM.attributes 0 : { *(.ARM.attributes) }
/* --------------------------------------------------------------
* 丢弃无用段
* -------------------------------------------------------------- */
/DISCARD/ :
{
*(.ARM.exidx*) /* C++异常索引 */
*(.ARM.extab*) /* C++异常表 */
*(.init) /* C库初始化(直接跳main)*/
*(.fini)
*(.preinit_array*) /* 全局构造函数(C++)*/
*(.init_array*) /* 全局构造函数 */
*(.fini_array*) /* 全局析构函数 */
*(.note.gnu.build-id) /* 构建ID(调试用,生产可移除)*/
}
}
其内存布局如下:
FLASH (0x08000000 – 0x0801FFFF, 128KB)
┌──────────────────────────────────────┐
│ .isr_vector (中断向量表) │ ← 0x08000000
├──────────────────────────────────────┤
│ .text (代码) │
├──────────────────────────────────────┤
│ .rodata (只读数据) │
├──────────────────────────────────────┤← _sidata
│ .data 副本 (初始化数据镜像) │
└──────────────────────────────────────┘
RAM (0x20000000 – 0x20007FFF)
┌──────────────────────────────┐
│ .data (运行时初始化数据) │ ← 0x20000000 (_sdata)
├──────────────────────────────┤← _sbss
│ .bss (未初始化数据) │
├──────────────────────────────┤← _ebss
│ 未使用区域(安全缓冲) │
├──────────────────────────────┤
│ 栈 (Stack) 向下/低地址增长 |
└──────────────────────────────┘ ← 0x20008000 (_estack)
可以发现,相比源码少了一些段,其中最重要的是少了堆 ._user_heap_stack 这个符号的定义,注意堆不是段。段要求编译时被写入,堆和栈为运行时,故只是供其他代码运行时使用的符号。
堆管理由 C 库(如 newlib)在 main() 之前通过 _sbrk 等函数实现,当程序调用 malloc(100) 时:
- malloc 调用 _sbrk(increment) 请求内存
- _sbrk 通过end/_ebss 符号(链接脚本定义)从 end/_ebss(.bss 结束处)开始向上分配空间
- 堆指针(heap_end)随之增长
但是在本项目中的源码并未使用 malloc ,而是自定义实现的 port_pvMalloc,其内存分配来源于 heap.c 中自定义的 static uint8_t g_u8Heap[ cfgTOTAL_HEAP_SIZE ],实际上位于 .bss 段。
2. 启动文件
2.1 启动文件的作用
|
功能 |
主要作用 |
指令/符号 |
备注 |
|---|---|---|---|
|
指令集与架构声明 |
定义汇编语法、目标CPU、浮点支持和指令集类型 |
|
|
|
全局符号声明 |
暴露关键符号供链接器和调试器识别 |
|
|
|
地址嵌入 |
将链接脚本定义的段边界地址嵌入代码段,供初始化使用 |
|
这些地址在 |
|
中断向量表 |
定义硬件中断跳转入口,位于Flash起始地址0x08000000 |
|
• 第0项为MSP初始值,第1项为复位入口 |
|
复位处理程序 |
系统上电后第一条执行代码(Reset _Handler) ,完成初始化并跳转main() |
|
• 顺序:栈指针设置 → 时钟配置→.data 段循环复制 →.bss 段清零→ 跳入主函数 main() |
|
默认中断处理 |
未实现中断的统一兜底处理,防止系统跑飞 |
|
死循环便于调试器捕获异常中断源,避免 HardFault 连锁反应 |
|
内核异常弱定义 |
为未实现的内核异常提供安全回退 |
|
弱定义允许C代码覆盖;未覆盖时自动回退到 |
2.2 源码
startup_stm32g431rbtx.s
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
.global g_pfnVectors
.global Default_Handler
/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss
.equ BootRAM, 0xF1E0F85F
/**
* @brief This is the code that gets called when the processor first
* starts execution following a reset event. Only the absolutely
* necessary set is performed, after which the application
* supplied main() routine is called.
* @param None
* @retval : None
*/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr r0, =_estack
mov sp, r0 /* set stack pointer */
/* Call the clock system initialization function.*/
bl SystemInit
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
CopyDataInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
LoopCopyDataInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyDataInit
/* Zero fill the bss segment. */
ldr r2, =_sbss
ldr r4, =_ebss
movs r3, #0
b LoopFillZerobss
FillZerobss:
str r3, [r2]
adds r2, r2, #4
LoopFillZerobss:
cmp r2, r4
bcc FillZerobss
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
LoopForever:
b LoopForever
.size Reset_Handler, .-Reset_Handler
/**
* @brief This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
*
* @param None
* @retval : None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex-M4. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
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
.word PVD_PVM_IRQHandler
.word RTC_TAMP_LSECSS_IRQHandler
.word RTC_WKUP_IRQHandler
.word FLASH_IRQHandler
.word RCC_IRQHandler
.word EXTI0_IRQHandler
.word EXTI1_IRQHandler
.word EXTI2_IRQHandler
.word EXTI3_IRQHandler
.word EXTI4_IRQHandler
.word DMA1_Channel1_IRQHandler
.word DMA1_Channel2_IRQHandler
.word DMA1_Channel3_IRQHandler
.word DMA1_Channel4_IRQHandler
.word DMA1_Channel5_IRQHandler
.word DMA1_Channel6_IRQHandler
.word 0
.word ADC1_2_IRQHandler
.word USB_HP_IRQHandler
.word USB_LP_IRQHandler
.word FDCAN1_IT0_IRQHandler
.word FDCAN1_IT1_IRQHandler
.word EXTI9_5_IRQHandler
.word TIM1_BRK_TIM15_IRQHandler
.word TIM1_UP_TIM16_IRQHandler
.word TIM1_TRG_COM_TIM17_IRQHandler
.word TIM1_CC_IRQHandler
.word TIM2_IRQHandler
.word TIM3_IRQHandler
.word TIM4_IRQHandler
.word I2C1_EV_IRQHandler
.word I2C1_ER_IRQHandler
.word I2C2_EV_IRQHandler
.word I2C2_ER_IRQHandler
.word SPI1_IRQHandler
.word SPI2_IRQHandler
.word USART1_IRQHandler
.word USART2_IRQHandler
.word USART3_IRQHandler
.word EXTI15_10_IRQHandler
.word RTC_Alarm_IRQHandler
.word USBWakeUp_IRQHandler
.word TIM8_BRK_IRQHandler
.word TIM8_UP_IRQHandler
.word TIM8_TRG_COM_IRQHandler
.word TIM8_CC_IRQHandler
.word 0
.word 0
.word LPTIM1_IRQHandler
.word 0
.word SPI3_IRQHandler
.word UART4_IRQHandler
.word 0
.word TIM6_DAC_IRQHandler
.word TIM7_IRQHandler
.word DMA2_Channel1_IRQHandler
.word DMA2_Channel2_IRQHandler
.word DMA2_Channel3_IRQHandler
.word DMA2_Channel4_IRQHandler
.word DMA2_Channel5_IRQHandler
.word 0
.word 0
.word UCPD1_IRQHandler
.word COMP1_2_3_IRQHandler
.word COMP4_IRQHandler
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word CRS_IRQHandler
.word SAI1_IRQHandler
.word 0
.word 0
.word 0
.word 0
.word FPU_IRQHandler
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word RNG_IRQHandler
.word LPUART1_IRQHandler
.word I2C3_EV_IRQHandler
.word I2C3_ER_IRQHandler
.word DMAMUX_OVR_IRQHandler
.word 0
.word 0
.word DMA2_Channel6_IRQHandler
.word 0
.word 0
.word CORDIC_IRQHandler
.word FMAC_IRQHandler
.size g_pfnVectors, .-g_pfnVectors
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler
.weak SVC_Handler
.thumb_set SVC_Handler,Default_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler,Default_Handler
.weak PendSV_Handler
.thumb_set PendSV_Handler,Default_Handler
.weak SysTick_Handler
.thumb_set SysTick_Handler,Default_Handler
.weak WWDG_IRQHandler
.thumb_set WWDG_IRQHandler,Default_Handler
.weak PVD_PVM_IRQHandler
.thumb_set PVD_PVM_IRQHandler,Default_Handler
.weak RTC_TAMP_LSECSS_IRQHandler
.thumb_set RTC_TAMP_LSECSS_IRQHandler,Default_Handler
.weak RTC_WKUP_IRQHandler
.thumb_set RTC_WKUP_IRQHandler,Default_Handler
.weak FLASH_IRQHandler
.thumb_set FLASH_IRQHandler,Default_Handler
.weak RCC_IRQHandler
.thumb_set RCC_IRQHandler,Default_Handler
.weak EXTI0_IRQHandler
.thumb_set EXTI0_IRQHandler,Default_Handler
.weak EXTI1_IRQHandler
.thumb_set EXTI1_IRQHandler,Default_Handler
.weak EXTI2_IRQHandler
.thumb_set EXTI2_IRQHandler,Default_Handler
.weak EXTI3_IRQHandler
.thumb_set EXTI3_IRQHandler,Default_Handler
.weak EXTI4_IRQHandler
.thumb_set EXTI4_IRQHandler,Default_Handler
.weak DMA1_Channel1_IRQHandler
.thumb_set DMA1_Channel1_IRQHandler,Default_Handler
.weak DMA1_Channel2_IRQHandler
.thumb_set DMA1_Channel2_IRQHandler,Default_Handler
.weak DMA1_Channel3_IRQHandler
.thumb_set DMA1_Channel3_IRQHandler,Default_Handler
.weak DMA1_Channel4_IRQHandler
.thumb_set DMA1_Channel4_IRQHandler,Default_Handler
.weak DMA1_Channel5_IRQHandler
.thumb_set DMA1_Channel5_IRQHandler,Default_Handler
.weak DMA1_Channel6_IRQHandler
.thumb_set DMA1_Channel6_IRQHandler,Default_Handler
.weak ADC1_2_IRQHandler
.thumb_set ADC1_2_IRQHandler,Default_Handler
.weak USB_HP_IRQHandler
.thumb_set USB_HP_IRQHandler,Default_Handler
.weak USB_LP_IRQHandler
.thumb_set USB_LP_IRQHandler,Default_Handler
.weak FDCAN1_IT0_IRQHandler
.thumb_set FDCAN1_IT0_IRQHandler,Default_Handler
.weak FDCAN1_IT1_IRQHandler
.thumb_set FDCAN1_IT1_IRQHandler,Default_Handler
.weak EXTI9_5_IRQHandler
.thumb_set EXTI9_5_IRQHandler,Default_Handler
.weak TIM1_BRK_TIM15_IRQHandler
.thumb_set TIM1_BRK_TIM15_IRQHandler,Default_Handler
.weak TIM1_UP_TIM16_IRQHandler
.thumb_set TIM1_UP_TIM16_IRQHandler,Default_Handler
.weak TIM1_TRG_COM_TIM17_IRQHandler
.thumb_set TIM1_TRG_COM_TIM17_IRQHandler,Default_Handler
.weak TIM1_CC_IRQHandler
.thumb_set TIM1_CC_IRQHandler,Default_Handler
.weak TIM2_IRQHandler
.thumb_set TIM2_IRQHandler,Default_Handler
.weak TIM3_IRQHandler
.thumb_set TIM3_IRQHandler,Default_Handler
.weak TIM4_IRQHandler
.thumb_set TIM4_IRQHandler,Default_Handler
.weak I2C1_EV_IRQHandler
.thumb_set I2C1_EV_IRQHandler,Default_Handler
.weak I2C1_ER_IRQHandler
.thumb_set I2C1_ER_IRQHandler,Default_Handler
.weak I2C2_EV_IRQHandler
.thumb_set I2C2_EV_IRQHandler,Default_Handler
.weak I2C2_ER_IRQHandler
.thumb_set I2C2_ER_IRQHandler,Default_Handler
.weak SPI1_IRQHandler
.thumb_set SPI1_IRQHandler,Default_Handler
.weak SPI2_IRQHandler
.thumb_set SPI2_IRQHandler,Default_Handler
.weak USART1_IRQHandler
.thumb_set USART1_IRQHandler,Default_Handler
.weak USART2_IRQHandler
.thumb_set USART2_IRQHandler,Default_Handler
.weak USART3_IRQHandler
.thumb_set USART3_IRQHandler,Default_Handler
.weak EXTI15_10_IRQHandler
.thumb_set EXTI15_10_IRQHandler,Default_Handler
.weak RTC_Alarm_IRQHandler
.thumb_set RTC_Alarm_IRQHandler,Default_Handler
.weak USBWakeUp_IRQHandler
.thumb_set USBWakeUp_IRQHandler,Default_Handler
.weak TIM8_BRK_IRQHandler
.thumb_set TIM8_BRK_IRQHandler,Default_Handler
.weak TIM8_UP_IRQHandler
.thumb_set TIM8_UP_IRQHandler,Default_Handler
.weak TIM8_TRG_COM_IRQHandler
.thumb_set TIM8_TRG_COM_IRQHandler,Default_Handler
.weak TIM8_CC_IRQHandler
.thumb_set TIM8_CC_IRQHandler,Default_Handler
.weak LPTIM1_IRQHandler
.thumb_set LPTIM1_IRQHandler,Default_Handler
.weak SPI3_IRQHandler
.thumb_set SPI3_IRQHandler,Default_Handler
.weak UART4_IRQHandler
.thumb_set UART4_IRQHandler,Default_Handler
.weak TIM6_DAC_IRQHandler
.thumb_set TIM6_DAC_IRQHandler,Default_Handler
.weak TIM7_IRQHandler
.thumb_set TIM7_IRQHandler,Default_Handler
.weak DMA2_Channel1_IRQHandler
.thumb_set DMA2_Channel1_IRQHandler,Default_Handler
.weak DMA2_Channel2_IRQHandler
.thumb_set DMA2_Channel2_IRQHandler,Default_Handler
.weak DMA2_Channel3_IRQHandler
.thumb_set DMA2_Channel3_IRQHandler,Default_Handler
.weak DMA2_Channel4_IRQHandler
.thumb_set DMA2_Channel4_IRQHandler,Default_Handler
.weak DMA2_Channel5_IRQHandler
.thumb_set DMA2_Channel5_IRQHandler,Default_Handler
.weak UCPD1_IRQHandler
.thumb_set UCPD1_IRQHandler,Default_Handler
.weak COMP1_2_3_IRQHandler
.thumb_set COMP1_2_3_IRQHandler,Default_Handler
.weak COMP4_IRQHandler
.thumb_set COMP4_IRQHandler,Default_Handler
.weak CRS_IRQHandler
.thumb_set CRS_IRQHandler,Default_Handler
.weak SAI1_IRQHandler
.thumb_set SAI1_IRQHandler,Default_Handler
.weak FPU_IRQHandler
.thumb_set FPU_IRQHandler,Default_Handler
.weak RNG_IRQHandler
.thumb_set RNG_IRQHandler,Default_Handler
.weak LPUART1_IRQHandler
.thumb_set LPUART1_IRQHandler,Default_Handler
.weak I2C3_EV_IRQHandler
.thumb_set I2C3_EV_IRQHandler,Default_Handler
.weak I2C3_ER_IRQHandler
.thumb_set I2C3_ER_IRQHandler,Default_Handler
.weak DMAMUX_OVR_IRQHandler
.thumb_set DMAMUX_OVR_IRQHandler,Default_Handler
.weak DMA2_Channel6_IRQHandler
.thumb_set DMA2_Channel6_IRQHandler,Default_Handler
.weak CORDIC_IRQHandler
.thumb_set CORDIC_IRQHandler,Default_Handler
.weak FMAC_IRQHandler
.thumb_set FMAC_IRQHandler,Default_Handler
2.3 精简版
startup.s
注意使用时删除注释
/* ========================================================================
* 1. 指令集和CPU架构声明
* ======================================================================== */
.syntax unified /* 使用统一ARM/Thumb汇编语法 */
.cpu cortex-m4 /* 目标CPU: Cortex-M4 (STM32G4内核) */
.fpu softvfp /* 软浮点(你的port.c已启用硬件FPU,此处仅为汇编器提示)*/
.thumb /* 生成Thumb-2指令集(16/32位混合,节省空间)*/
/* ========================================================================
* 2. 全局符号声明
* ======================================================================== */
.global g_pfnVectors /* 中断向量表起始地址(必须全局可见)*/
.global Reset_Handler /* 复位处理函数(链接脚本ENTRY指定的入口)*/
.global Default_Handler /* 默认中断处理(所有未实现中断的兜底)*/
/* ========================================================================
* 3. 地址嵌入
* .word :在当前地址,放一个 4 字节的数值,这个数值就是符号 _sidata 所代表的地址值
* 可以让启动代码高效读取这些地址
* ======================================================================== */
.word _sidata
.word _sdata
.word _edata
.word _sbss
.word _ebss
/* ========================================================================
* 4. 中断向量表 - 必须位于Flash起始地址 0x08000000
* Cortex-M4硬件行为:
* 上电后自动从 0x08000000 读取 4字节 → 加载到 MSP (主栈指针)
* 然后从 0x08000004 读取 4字节 → 跳转执行 (即 Reset_Handler)
* ======================================================================== */
/* ----------------------------------------------------------------
* 4.1 创建一个名为 .isr_vector 的段 "a"=allocatable, %progbits=程序数据
* a = allocatable(可分配)→ 该段会被放入最终的可执行文件
* %progbits=程序数据 → 表示该段包含程序实际数据(会占用 ROM/FLASH 空间)
* 进入 .isr_vector 段
* ---------------------------------------------------------------- */
.section .isr_vector,"a",%progbits
/* ----------------------------------------------------------------
* 4.2 type声明符号类型
* g_pfnVectors符号名(全局函数指针向量表),定义了向量表的起始地址
* %object类型为 数据对象(非函数)
* ---------------------------------------------------------------- */
.type g_pfnVectors, %object
/* 4.3 后续的 .word ... 指令会从这个地址开始连续存放数据 */
g_pfnVectors:
.word _estack /* 0: 初始栈顶地址(链接脚本定义)*/
.word Reset_Handler /* 1: 复位后第一条执行的代码 */
.word NMI_Handler /* 2: 不可屏蔽中断 */
.word HardFault_Handler /* 3: 硬件故障(最严重错误)*/
.word MemManage_Handler /* 4: 内存管理故障 */
.word BusFault_Handler /* 5: 总线故障 */
.word UsageFault_Handler /* 6: 用法故障(如未对齐访问)*/
.word 0 /* 7: 保留(必须为0)*/
.word 0 /* 8: 保留 */
.word 0 /* 9: 保留 */
.word 0 /* 10: 保留 */
.word port_vSVCHandler /* 11: SVC调用 - 调度器启动第一个任务 */
.word DebugMon_Handler /* 12: 调试监控(调试器使用)*/
.word 0 /* 13: 保留 */
.word port_vPendSVHandler /* 14: PendSV - 任务上下文切换核心 */
.word port_vSysTickHandler /* 15: SysTick - 时钟节拍源(驱动调度)*/
/* --------------------------------------------------------------------
* 外设中断向量(共91个)- 用Default_Handler
* 注意: 向量表必须完整,不能截断
* Cortex-M 内核在发生中断时,会根据中断号直接索引向量表。
* 例如:RCC 中断号 = 21 → CPU 自动读取 向量表基址 + 21 * 4 处的地址作为跳转目标。
* 如果向量表被截断(比如只定义到第 30 项),那么第 31 项之后的地址就是随机值
* 结果:一旦触发未定义的中断(比如 DMA 错误、看门狗超时),CPU 会跳转到无效地址 → HardFault 或系统死机。
* 这里不使用任何外设中断,忽略
* -------------------------------------------------------------------- */
/* 4.4 定义符号 g_pfnVectors 的大小(调试用)*/
.size g_pfnVectors, .-g_pfnVectors
/* ========================================================================
* 5. 复位处理程序 - 系统上电/复位后第一条执行的代码
* 执行顺序:
* 1. 设置主栈指针 MSP
* 2. 调用时钟初始化 (SystemInit)
* 3. 复制 .data 段 (Flash → RAM)
* 4. 清零 .bss 段
* 5. 直接跳转 main() - 不调用 __libc_init_array
* ======================================================================== */
.section .text.Reset_Handler,"ax",%progbits /* "ax"=可执行+已分配 */
.weak Reset_Handler /* 弱定义(允许C代码覆盖)*/
.type Reset_Handler, %function
Reset_Handler:
/* --------------------------------------------------------------
* 5.1: 设置主栈指针 MSP
* Cortex-M4上电后MSP未初始化,必须首先设置
* _estack 由链接脚本定义 = 0x20008000 (RAM顶部)
* -------------------------------------------------------------- */
ldr r0, =_estack /* r0 = 0x20008000 (链接脚本定义的栈顶) */
mov sp, r0 /* sp = r0 (设置MSP为主栈指针) */
/* --------------------------------------------------------------
* 5.2: 调用时钟初始化
* 你的port.c提供弱定义SystemInit(),可为空实现
* 后续可在此添加HSI→PLL配置(当前用默认16MHz HSI足够)
* -------------------------------------------------------------- */
bl SystemInit /* 跳转到SystemInit() */
/* --------------------------------------------------------------
* 5.3: 初始化 .data 段
* 将已初始化全局变量从Flash复制到RAM
* 例如: int x = 5; → "5"存储在Flash,运行时需在RAM可修改
* -------------------------------------------------------------- */
ldr r0, =_sdata /* r0 = .data段在RAM的起始地址 */
ldr r1, =_edata /* r1 = .data段在RAM的结束地址 */
ldr r2, =_sidata /* r2 = .data初始值在Flash中的地址 */
movs r3, #0 /* r3 = 偏移量计数器(字节) */
b LoopCopyDataInit /* 跳转到循环开始 */
CopyDataInit:
ldr r4, [r2, r3] /* 从Flash加载4字节: r4 = *(r2 + r3) */
str r4, [r0, r3] /* 存储到RAM: *(r0 + r3) = r4 */
adds r3, r3, #4 /* r3 += 4 (处理下一个字) */
LoopCopyDataInit:
adds r4, r0, r3 /* r4 = r0 + r3 (当前RAM地址) */
cmp r4, r1 /* 比较: 当前地址 >= 结束地址? */
bcc CopyDataInit /* 如果小于,继续循环 (bcc = branch if carry clear) */
/* --------------------------------------------------------------
* 5.4: 清零 .bss 段
* C标准要求未初始化全局变量必须为0
* 例如: int y; → y必须=0
* -------------------------------------------------------------- */
ldr r2, =_sbss /* r2 = .bss段起始地址 */
ldr r4, =_ebss /* r4 = .bss段结束地址 */
movs r3, #0 /* r3 = 0 (清零值) */
b LoopFillZerobss /* 跳转到循环开始 */
FillZerobss:
str r3, [r2] /* *(r2) = 0 */
adds r2, r2, #4 /* r2 += 4 (下一个字地址) */
LoopFillZerobss:
cmp r2, r4 /* 比较: 当前地址 >= 结束地址? */
bcc FillZerobss /* 如果小于,继续循环 */
/* --------------------------------------------------------------
* 5.5: 跳转到C语言main函数
* 任务入口
* 注意: main()不应返回(任务调度器永不退出)
* -------------------------------------------------------------- */
bl main
/* --------------------------------------------------------------
* 安全兜底: 如果main()意外返回,进入死循环
* 便于调试器捕获异常状态
* -------------------------------------------------------------- */
LoopForever:
b LoopForever
.size Reset_Handler, .-Reset_Handler
/* ========================================================================
* 6. 默认中断处理程序 - 任何未处理的中断都会进入死循环
* 切换到(或创建)一个名为 .text.Default_Handler 的段,ax - 可执行+已分配
* 设计目的:
* 1. 防止未实现中断导致系统跑飞
* 2. 死循环便于调试器定位问题(程序停在此处)
* 3. 双任务演示中,意外触发中断=配置错误,应立即停止
* ======================================================================== */
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop /* 无限循环 - 保持系统状态供调试 */
.size Default_Handler, .-Default_Handler
/* ========================================================================
* 7. 未定义的内核中断
* 作用:
* - 允许C代码用同名函数覆盖(强符号优先)
* - 如果未实现,自动回退到Default_Handler
* ======================================================================== */
.weak NMI_Handler
.thumb_set NMI_Handler, Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler, Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler, Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler, Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler, Default_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler, Default_Handler
/* ========================================================================
* 8. 为port处理函数提供弱回退
* 虽然port.c已实现,保留弱定义确保链接安全
* ======================================================================== */
.weak port_vSVCHandler
.thumb_set port_vSVCHandler, Default_Handler
.weak port_vPendSVHandler
.thumb_set port_vPendSVHandler, Default_Handler
.weak port_vSysTickHandler
.thumb_set port_vSysTickHandler, Default_Handler
3. 启动文件与链接脚本的关系
3.1 Cortex-M4 在上电后的硬件行为
在 linker .ld 中,规定了不同的段在内存中具体地址的分配,并定义了一些符号。
首先,Cortex-M4 在上电后的硬件行为:
- 上电后自动从 0x08000000 读取 4字节 → 加载到 MSP (主栈指针)
- 然后从 0x08000004 读取 4字节 → 跳转执行 (即 Reset_Handler)
3.2 linker.ld 的作用
linker.ld 中:
/* ========================================================================
* 2. 定义变量栈顶 _estack - 从RAM顶部开始向下生长
* 计算: 0x20000000 + 32KB = 0x20008000
* ======================================================================== */
_estack = ORIGIN(RAM) + LENGTH(RAM);
/* ========================================================================
* 3. 物理内存定义 - STM32G431RBT6固定配置
* ======================================================================== */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K /* 程序存储区 */
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K /* 数据运行区 */
}
/* ========================================================================
* 4. 内存段布局, 定义段映射规则
* 每个 { ... } 块是一个输出段
* 书写顺序决定了不同段在内存中的物理排列顺序
* VMA(Virtual Memory Address):程序运行时该段所在的地址
* LMA(Load Memory Address):该段在存储介质中的地址, AT 用于显示指定 LMA
* . 是当前位置计数器(location counter)
* 它的值是 当前输出段的 VMA, 即程序运行时该位置的地址
* ======================================================================== */
SECTIONS
{
/* --------------------------------------------------------------
* 4.1 中断向量表 - 位于 FLASH 起始地址: 0x08000000
* Cortex-M4硬件要求: 前8字节 = [栈顶, 复位地址]
* -------------------------------------------------------------- */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* 保留向量表不被优化,
KEEP(...): 强制保留括号内的段, 即使在链接优化时也不会被丢弃 */
KEEP(*(.vectors)) /* 兼容不同命名 */
. = ALIGN(4);
} > FLASH /* VMA 和 LMA 相同, 都在 FLASH 起始地址 */
规定了 FLASH 的起始地址为 0x08000000,.isr_vector 段在 SECTIONS 开始处且 > FLASH 表明其 LMA 和 VMA 都在 0x08000000
3.3 startup.s 的作用
startup.s 中:
/* ========================================================================
* 4. 中断向量表 - 必须位于Flash起始地址 0x08000000
* Cortex-M4硬件行为:
* 上电后自动从 0x08000000 读取 4字节 → 加载到 MSP (主栈指针)
* 然后从 0x08000004 读取 4字节 → 跳转执行 (即 Reset_Handler)
* ======================================================================== */
/* ----------------------------------------------------------------
* 4.1 创建一个名为 .isr_vector 的段 "a"=allocatable, %progbits=程序数据
* a = allocatable(可分配)→ 该段会被放入最终的可执行文件
* %progbits=程序数据 → 表示该段包含程序实际数据(会占用 ROM/FLASH 空间)
* 进入 .isr_vector 段
* ---------------------------------------------------------------- */
.section .isr_vector,"a",%progbits
/* ----------------------------------------------------------------
* 4.2 type声明符号类型
* g_pfnVectors符号名(全局函数指针向量表),定义了向量表的起始地址
* %object类型为 数据对象(非函数)
* ---------------------------------------------------------------- */
.type g_pfnVectors, %object
/* 4.3 后续的 .word ... 指令会从这个地址开始连续存放数据 */
g_pfnVectors:
.word _estack /* 0: 初始栈顶地址(链接脚本定义)*/
.word Reset_Handler /* 1: 复位后第一条执行的代码 */
- .section .isr_vector:创建了一个叫 .isr_vector 的段,于是链接器会根据链接脚本的规定执行动作,即将所有编译单元中叫做 *(.isr_vector) 的段合并在一起放在规定好的 LMA 和 VMA 中,但是所有编译单元中只有这个启动文件创建了这一个段,所以只有这一个 .isr_vector 段被放在了 0x08000000,无其他同名段合并
- .word _estack:.word 会让汇编器在当前地址放一个 4 字节的数据,值 = 链接脚本计算的0x20008000,启动文件将这个值放在了.isr_vector 段首处,当前地址正好为 0x08000000 ,于是硬件上电后的第一个动作 ( 0x08000000 读取 4字节 → 加载到 MSP ) 被正确的执行了
- .word Reset_Handler:在当前地址放一个 4 字节的数据,值 = Reset_Handler的地址,启动文件将这个值放在了.isr_vector 段首处第二个字节,当前地址正好为 0x08000004 ,于是硬件上电后的第二个动作(从 0x08000004 读取 4字节 → 跳转执行)被正确的执行了
后面的异常向量表同理,也就是说 ARMv7-M 系统级程序员模型规定了硬件上电后的动作(上电时从哪个地址取数据并执行对应的命令,发生异常时又去哪个地址),我们就需要让汇编脚本和启动文件符合硬件动作,这样才能让异常发生时硬件找到正确的异常处理函数并执行。

详见 操作系统开发:(4) 系统级程序员模型、模式、特权、栈、异常与函数序言(实现 portable.h )、操作系统开发:(5) 异常模型、伪造栈帧、SVC 与 PendSV异常处理(实现 port.c)与 操作系统开发:(0) CPU启动流程与地址重映射。
更多推荐



所有评论(0)