在 操作系统开发:(1) 启动文件与链接脚本 初步了解启动文件和汇编脚本的作用,以及 cpu 上电后的动作并实现最基础的控制,这里进行详解从硬件复位到程序执行的完整过程

1. 链接脚本

1.1 链接脚本的作用

  • 内存映射:将程序逻辑段(代码/数据)映射到物理内存(FLASH/RAM)
  • 地址分配:精确控制各段在内存中的位置和布局
  • 符号定义:生成关键地址符号供启动文件和C代码使用
  • 优化控制:决定哪些段保留/丢弃,影响最终固件大小

1.2 概念

概念

说明

VMA (Virtual Memory Address)

程序运行时地址

.data 段的 VMA 在 RAM (0x2000xxxx)

LMA (Load Memory Address)

存储介质中的地址

.data 段的 LMA 在 FLASH (0x0800xxxx)

位置计数器 .

当前输出段的 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) 时:

  1. malloc 调用 _sbrk(increment) 请求内存
  2. _sbrk 通过end/_ebss 符号(链接脚本定义)从 end/_ebss(.bss 结束处)开始向上分配空间
  3. 堆指针(heap_end)随之增长

但是在本项目中的源码并未使用 malloc ,而是自定义实现的 port_pvMalloc,其内存分配来源于 heap.c 中自定义的 static uint8_t g_u8Heap[ cfgTOTAL_HEAP_SIZE ],实际上位于 .bss 段。

2. 启动文件

2.1 启动文件的作用

功能

主要作用

指令/符号

备注

指令集与架构声明

定义汇编语法、目标CPU、浮点支持和指令集类型

.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb

.fpu softvfp仅为汇编器提示,实际FPU由C代码启用;Thumb-2混合指令集优化代码密度

全局符号声明

暴露关键符号供链接器和调试器识别

.global g_pfnVectors
.global Reset_Handler
.global Default_Handler

g_pfnVectors为中断向量表入口,Reset_Handler为程序入口点

地址嵌入

将链接脚本定义的段边界地址嵌入代码段,供初始化使用

.word _sidata
.word _sdata
.word _edata
.word _sbss
.word _ebss

这些地址在.text段中,供Reset_Handler高效访问,避免运行时符号解析

中断向量表

定义硬件中断跳转入口,位于Flash起始地址0x08000000

.section .isr_vector,"a",%progbits
.word _estack
.word Reset_Handler
.word port_vSVCHandler
.word port_vPendSVHandler
.word port_vSysTickHandler

• 第0项为MSP初始值,第1项为复位入口
• 缺失项将导致未定义中断触发后系统崩溃

复位处理程序 

系统上电后第一条执行代码(Reset _Handler) ,完成初始化并跳转main()

ldr r0, =_estack
mov sp, r0
bl SystemInit
.data段复制循环
.bss段清零循环
bl main

• 顺序:栈指针设置 → 时钟配置→.data 段循环复制 →.bss 段清零→ 跳入主函数 main()

默认中断处理

未实现中断的统一兜底处理,防止系统跑飞

.section .text.Default_Handler
b Infinite_Loop

死循环便于调试器捕获异常中断源,避免 HardFault 连锁反应

内核异常弱定义

为未实现的内核异常提供安全回退

.weak NMI_Handler
.thumb_set NMI_Handler, Default_Handler
(其他异常同理)

弱定义允许C代码覆盖;未覆盖时自动回退到Default_Handler

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启动流程与地址重映射。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐