RT-Thread STM32 系统分析(1)- 启动文件
前言
基本信息
名称 | 描述说明 |
---|---|
RT-Thread Studio 软件版本 | 版本: 1.1.3 |
RT-Thread 系统版本 | 4.0.2 |
STM32CubeIDE 软件版本 | 1.4.0 |
STM32芯片型号 | STM32F013VG |
前言说明
本着自我提高和方便更多人学习的目标,我决定从目前最新的STM32 RT-thread 4.0.2 系统版本开始,进行详细的内核分析和学习,其中的解释有不足之处希望读者能够多多指导和交流,由于文章编写水平有限,查阅资料过于琐碎其中如果有引用没有注明出处的望请见谅,感谢很多前辈的辛勤无私的付出,谢谢!
启动文件对比
使用了STM32CubeIDE 和 RT-Thread Studio 建立了相同型号芯片的工程,对比其中启动文件内容差异如下:
左侧是RT-Thread 系统启动文件 右侧是STM32裸机启动文件
可以看出RT-Thread 系统将初始的入口函数定义为entry,转到entry函数的定义处看到如下代码:
说明:这是一段条件编译代码,其中灰色部分不参与编译,这段代码是针对不同的编译器进行的优化。默认使用gcc编译器。
宏定义名称 | 对应编译器名称 |
---|---|
__CC_ARM | ARM RealView (armcc) 编译器 |
__CLANG_ARM | CLANG 编译器 |
ICCARM | IAR EWARM 编译器 |
GNUC | gcc编译器 |
因此entry函数之前的代码均为启动文件相关的逻辑代码。
上电启动方式
在给单片机上电前,先通过给BOOT0和BOOT1连接高低电平来设置程序启动的初始位置。STM32程序启动的位置有三种:
BOOT0 | BOOT1 | 启动模式 | 说明 |
---|---|---|---|
0 | 无关 | 用户闪存存储器启动(Main Flash memory) | 是STM32内置的Flash,一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。 |
1 | 0 | 从系统存储器启动(System memory ) | 从系统存储器启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。即我们常说的串口下载方式(ISP),不建议使用这种,速度比较慢。STM32 中自带的BootLoader就是在这种启动方式中,如果出现程序硬件错误的话可以切换BOOT0/1到该模式下重新烧写Flash即可恢复正常 |
1 | 0 | 从内嵌SRAM启动(Embedded Memory ) | 内置SRAM,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。 |
在这三种方式里面用的最多的是第一种,下面以第一种启动方式为例进行介绍:
在重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存。然后进入启动文件,具体的启动文件为startup_stm32f103xg.S。
首先启动文件中有一个Reset_Handler,这个汇编函数的作用有两个:
1.将FLASH中的程序代码拷贝到SRAM中
2.将系统堆栈初始化(清0) 然后进入main,从而进入c的世界。
启动过程相关文件分析
启动文件包括两部分:startup_stm32f103xg.S文件和link.lds 文件,其中与芯片相关的的详细参数都在link.lds 文件中,这个文件是GUN 脚本连接器使用的连接脚本。
startup_stm32f103xg.S文件内容与注释
打开RT-thread系统的 startup_stm32f103xg.S 文件,可以看到启动文件全文内容:
/**
*************** (C) COPYRIGHT 2017 STMicroelectronics ************************
* @file startup_stm32f103xb.s
* @author MCD Application Team
* @brief STM32F103xB Devices vector table for Atollic toolchain.
* This module performs: 本模块执行:
* - Set the initial SP -设置初始SP
* - Set the initial PC == Reset_Handler, -设置初始PC==Reset_Handler,
* - Set the vector table entries with the exceptions ISR address -设置带有异常ISR address的向量表条目
* - Configure the clock system -配置时钟系统
* - Branches to main in the C library (which eventually
* calls main()).-在C库中分支到main(它最终调用main())。
* After Reset the Cortex-M3 processor is in Thread mode,
* priority is Privileged, and the Stack is set to Main. 重置后,Cortex-M3处理器处于线程模式,优先级为特权,堆栈设置为Main。
******************************************************************************
* @attention 注意
*
* <h2><center>© Copyright (c) 2017 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
.syntax unified /*.syntax unified 是一个指示,说明下面的指令是ARM和THUMB通用格式的(指示使用了统一汇编语言语法)*/
.cpu cortex-m3 /**/
.fpu softvfp /**/
.thumb /*16位Thumb指令及架构*/
.global g_pfnVectors /*声明一个外部文件 可调用 标签*/
.global Default_Handler /*声明一个外部文件 可调用 标签 Default_Handler(默认的中断处理函数)*/
/* start address for the initialization values of the .data section..data节的初始化值的起始地址。
defined in linker script 在链接器脚本中定义*/
.word _sidata /*在连接器脚本link.lds 文件中定义*/
/* start address for the .data section. defined in linker script .data节的起始地址。在链接器脚本中定义*/
.word _sdata /*在连接器脚本link.lds 文件中定义*/
/* end address for the .data section. defined in linker script .data节的结束地址。在链接器脚本中定义*/
.word _edata /*在连接器脚本link.lds 文件中定义*/
/* start address for the .bss section. defined in linker script .bss节的起始地址。在链接器脚本中定义 */
.word _sbss /*在连接器脚本link.lds 文件中定义*/
/* end address for the .bss section. defined in linker script .bss节的结束地址。在链接器脚本中定义*/
.word _ebss /*在连接器脚本link.lds 文件中定义*/
.equ BootRAM, 0xF1E0F85F /*RAM 模式引导起始地址*/
/**
* @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. 这是处理器在重置事件后首次开始执行时调用的代码。
* 只执行绝对必要的set,然后调用应用程序提供的main()例程。
* @param None
* @retval : None
*/
.section .text.Reset_Handler //定义文本段(代码段)输入段描述符:text 和Reset_Handler
.weak Reset_Handler //弱定义名称 Reset_Handler 启动处理函数
.type Reset_Handler, %function /*将Reset_Handler 定义为函数*/
Reset_Handler:
/* Copy the data segment initializers from flash to SRAM 将数据段初始化器从闪存复制到SRAM*/
movs r1, #0 /*将r1的值设置为0,并更新APSR标志位*/
b LoopCopyDataInit /*程序转移到LoopCopyDataInit处 */
/*arm采用risc架构,cpu不能直接读取内存,所以需要将内存内容加载到寄存器
ldr 指令将内存内容加载入通用寄存器
str 指令将寄存器内容存储到内存*/
CopyDataInit: /*从FLASH中拷贝地址在sdata和edata之间的代码到SRAM中 */
ldr r3, =_sidata /*从存储器中将_sidata加载到寄存器r3中 */
ldr r3, [r3, r1] /*从地址r3+r1处读取一个字(32bit)到r3中*/
str r3, [r0, r1] /*把寄存器r3的值存储到存储器中地址为r0+r1地址处 */
adds r1, r1, #4 /*r1 = r1 + 4*/
LoopCopyDataInit:
ldr r0, =_sdata /*从存储器中将data段的起始地址,加载到寄存器r0中*/
ldr r3, =_edata /*从存储器中将data段的结束地址,加载到寄存器r3中*/
adds r2, r0, r1 /*将寄存器R0和R1的内容相加,其结果存放在寄存器R2中,并更新APSR标志位*/
cmp r2, r3 /*比较r2和r3,让计算r2 - r3,若小于0,标志位为0,反之为1,并更新APSR标志位 */
bcc CopyDataInit /*如果标志位为0(无借位)即r2<r3,则跳转到 CopyDataInit处,BCC 是指当APSR寄存器条件标志位为0时跳转,这是循环执行判断*/
ldr r2, =_sbss /*从存储器中将_sbss加载到寄存器r2中*/
b LoopFillZerobss /*无条件跳转到 LoopFillZerobss*/
/* Zero fill the bss segment.未初始化的内存段以0值填充 */
FillZerobss:
movs r3, #0 /*将立即数0存入寄存器r3*/
str r3, [r2], #4 /*将寄存器r3的值存储到地址为r2寄存器值得地址处后,r2 = r2 + 4*/
LoopFillZerobss:
ldr r3, = _ebss /*从存储器中将_ebss加载到寄存器r3中*/
cmp r2, r3 /*比较r2,r3,然后更新标志位 */
bcc FillZerobss /*如果标志位为0(无借位),则跳转到FillZerobss处*/
/* Call the clock system intitialization function.*/
bl SystemInit /*初始化系统时钟*/
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entry point.*/
bl entry /*转移并连接。用于呼叫一个子程序,返回地址被存储在 LR 中,转移到 entry函数起始处*/
bx lr /*启动异常返回序列,执行动作(如前一步bl entry不成功则执行) */
.size Reset_Handler, .-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 /*a:可分配,x可执行,progbits:属于type类型*/
Default_Handler: /*默认错误处理函数*/
Infinite_Loop:
b Infinite_Loop /*无条件跳转到Infinite_Loop,无限循环*/
.size Default_Handler, .-Default_Handler
/******************************************************************************
* Cortex M3 的最小向量表。请注意,必须对其进行适当的构造,以确保它最终位于物理地址
* The minimal vector table for a Cortex M3. 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 /*输入段描述符:isr_vector,定义中断向量表的数据段 */
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors: /*这一块程序的意思就是说以g_pfnVectors为初始地址,然后依次以字为单位写入相应的数据*/
.word _estack /*;在当前位置放置一个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_IRQHandler
.word TAMPER_IRQHandler
.word RTC_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 DMA1_Channel7_IRQHandler
.word ADC1_2_IRQHandler
.word USB_HP_CAN1_TX_IRQHandler
.word USB_LP_CAN1_RX0_IRQHandler
.word CAN1_RX1_IRQHandler
.word CAN1_SCE_IRQHandler
.word EXTI9_5_IRQHandler
.word TIM1_BRK_TIM9_IRQHandler
.word TIM1_UP_TIM10_IRQHandler
.word TIM1_TRG_COM_TIM11_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_TIM12_IRQHandler
.word TIM8_UP_TIM13_IRQHandler
.word TIM8_TRG_COM_TIM14_IRQHandler
.word TIM8_CC_IRQHandler
.word ADC3_IRQHandler
.word FSMC_IRQHandler
.word SDIO_IRQHandler
.word TIM5_IRQHandler
.word SPI3_IRQHandler
.word UART4_IRQHandler
.word UART5_IRQHandler
.word TIM6_IRQHandler
.word TIM7_IRQHandler
.word DMA2_Channel1_IRQHandler
.word DMA2_Channel2_IRQHandler
.word DMA2_Channel3_IRQHandler
.word DMA2_Channel4_5_IRQHandler
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word 0
.word BootRAM /* @0x1E0. This is for boot in RAM mode for
STM32F10x XL-Density devices. @0x1E0。 这个是为stm32f10系列中容量系列设备的boot中的RAM模式准备的*/
/*******************************************************************************
*
* 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 //弱声明 定义一个 NMI_Handler 函数
.thumb_set NMI_Handler,Default_Handler /*如果这个不重写这个弱别名,那么默认执行Default_Handler,反之这执行重写过的NMI_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_IRQHandler
.thumb_set PVD_IRQHandler,Default_Handler
.weak TAMPER_IRQHandler
.thumb_set TAMPER_IRQHandler,Default_Handler
.weak RTC_IRQHandler
.thumb_set RTC_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 DMA1_Channel7_IRQHandler
.thumb_set DMA1_Channel7_IRQHandler,Default_Handler
.weak ADC1_2_IRQHandler
.thumb_set ADC1_2_IRQHandler,Default_Handler
.weak USB_HP_CAN1_TX_IRQHandler
.thumb_set USB_HP_CAN1_TX_IRQHandler,Default_Handler
.weak USB_LP_CAN1_RX0_IRQHandler
.thumb_set USB_LP_CAN1_RX0_IRQHandler,Default_Handler
.weak CAN1_RX1_IRQHandler
.thumb_set CAN1_RX1_IRQHandler,Default_Handler
.weak CAN1_SCE_IRQHandler
.thumb_set CAN1_SCE_IRQHandler,Default_Handler
.weak EXTI9_5_IRQHandler
.thumb_set EXTI9_5_IRQHandler,Default_Handler
.weak TIM1_BRK_TIM9_IRQHandler
.thumb_set TIM1_BRK_TIM9_IRQHandler,Default_Handler
.weak TIM1_UP_TIM10_IRQHandler
.thumb_set TIM1_UP_TIM10_IRQHandler,Default_Handler
.weak TIM1_TRG_COM_TIM11_IRQHandler
.thumb_set TIM1_TRG_COM_TIM11_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_TIM12_IRQHandler
.thumb_set TIM8_BRK_TIM12_IRQHandler,Default_Handler
.weak TIM8_UP_TIM13_IRQHandler
.thumb_set TIM8_UP_TIM13_IRQHandler,Default_Handler
.weak TIM8_TRG_COM_TIM14_IRQHandler
.thumb_set TIM8_TRG_COM_TIM14_IRQHandler,Default_Handler
.weak TIM8_CC_IRQHandler
.thumb_set TIM8_CC_IRQHandler,Default_Handler
.weak ADC3_IRQHandler
.thumb_set ADC3_IRQHandler,Default_Handler
.weak FSMC_IRQHandler
.thumb_set FSMC_IRQHandler,Default_Handler
.weak SDIO_IRQHandler
.thumb_set SDIO_IRQHandler,Default_Handler
.weak TIM5_IRQHandler
.thumb_set TIM5_IRQHandler,Default_Handler
.weak SPI3_IRQHandler
.thumb_set SPI3_IRQHandler,Default_Handler
.weak UART4_IRQHandler
.thumb_set UART4_IRQHandler,Default_Handler
.weak UART5_IRQHandler
.thumb_set UART5_IRQHandler,Default_Handler
.weak TIM6_IRQHandler
.thumb_set TIM6_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_5_IRQHandler
.thumb_set DMA2_Channel4_5_IRQHandler,Default_Handler
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
link.lds 文件内容与注释
/*
* linker script for STM32F103VG with GNU ld
GNU 脚本连接器的STM32F103VG的链接脚本
*/
/* Program Entry, set to mark it as "used" and avoid gc 设置入口程序,*/
MEMORY /*重新分配所有可用的内存块和FLASH块*/
{
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024k /* 1024K flash */
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 96k /* 96K sram */
}
ENTRY(Reset_Handler) /* 运行程序第一个被执行的程序 */
_system_stack_size = 0x400; /*定义系统站大小,工程中栈大小是1024字节,即局部变量不能大于1024字节 */
SECTIONS /*输出段SECTIONS用于将所有.o文件输出为统一的文件*/
{
.text : /*可执行代码部分*/
{
. = ALIGN(4); /*指示编译器将接下来的代码进行4字节对齐编译,也就是在分配地址时,以4的整数倍分配。*/
_stext = .; /*_stext 地址被定义为 4字节对齐后的地址计数器的值*/
KEEP(*(.isr_vector)) /* Startup code 启动码,所有isr_vector的section都连接到这个地址*/
. = ALIGN(4);
*(.text) /* remaining code 剩余代码*/
*(.text.*) /* remaining code 剩余代码*/
*(.rodata) /* read-only data (constants) 只读数据(常量)*/
*(.rodata*)
*(.glue_7)
*(.glue_7t)
*(.gnu.linkonce.t*)
/* section information for finsh shell */ /*finsh shell 片段信息*/
. = ALIGN(4);
__fsymtab_start = .; /*__fsymtab_start 赋值为 当前地址计数器值,在components文件夹下shell.c文件内容使用*/
KEEP(*(FSymTab)) /*程序暴露出的函数,函数定义在SECTION("FSymTab")*/
__fsymtab_end = .; /*__fsymtab_end 赋值为 当前地址计数器值,在components文件夹下shell.c文件内容使用*/
. = ALIGN(4);
__vsymtab_start = .; /*__vsymtab_start 赋值为 当前地址计数器值,在components文件夹下shell.c文件内容使用*/
KEEP(*(VSymTab)) /*程序暴露出的变量,变量定义在SECTION("VSymTab")*/
__vsymtab_end = .; /*__vsymtab_end 赋值为 当前地址计数器值,在components文件夹下shell.c文件内容使用*/
/* section information for utest */ /*utest 片段信息*/
. = ALIGN(4);
__rt_utest_tc_tab_start = .; /*utest 测试框架使用变量,在utest.c文件中*/
KEEP(*(UtestTcTab)) /*测试框架 utest 将所有的测试用例导出到了 UtestTcTab 代码段,在 GCC 编译时,需要在链接脚本中显式地设置 UtestTcTab 段。*/
__rt_utest_tc_tab_end = .; /*utest 测试框架使用变量,在utest.c文件中*/
/* section information for at server */ /*AT组件 片段信息*/
. = ALIGN(4);
__rtatcmdtab_start = .; /*AT组件 使用变量,在at_server.c文件中*/
KEEP(*(RtAtCmdTab)) /*gcc 工具链,需在链接脚本中添加 AT 服务器命令表对应的 section*/
__rtatcmdtab_end = .; /*AT组件 使用变量,在at_server.c文件中*/
. = ALIGN(4);
/* section information for initial. */ /*自动初始化组件 section 信息*/
. = ALIGN(4);
__rt_init_start = .;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;
. = ALIGN(4);
PROVIDE(__ctors_start__ = .); /*C++全局对象构造函数存储段 起始地址*/
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE(__ctors_end__ = .); /*C++全局对象构造函数存储段 结束地址*/
. = ALIGN(4);
_etext = .; /*text数据段结束地址*/
} > ROM = 0
/* .ARM.exidx is sorted, so has to go in its own output section. */ /*.ARM.exidx是排序的,所以必须在它自己的输出部分中。*/
__exidx_start = .; /*__exidx_start 分配了 C++ 异常的起始地址, ,当异常产生的时候,就会被分配到指定的段地址中.*/
.ARM.exidx : /*C++ 异常段地址*/
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
/* This is used by the startup in order to initialize the .data secion 启动程序使用它来初始化.data secon*/
_sidata = .;
} > ROM
__exidx_end = .; /*__exidx_end 分配了 C++ 异常的结束地址*/
/* .data section which is used for initialized data */ /*.data节,用于初始化数据*/
.data : AT (_sidata) /*指定.data数据段的存储地址为当前RAM地址*/
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */ /*启动程序使用它来初始化.data secon*/
_sdata = . ; /*data数据段起始地址*/
*(.data) /*所有data数据段都连接到此地址*/
*(.data.*)
*(.gnu.linkonce.d*)
PROVIDE(__dtors_start__ = .); /**/
KEEP(*(SORT(.dtors.*)))
KEEP(*(.dtors))
PROVIDE(__dtors_end__ = .);
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */ /*启动程序使用它来初始化.data secon*/
_edata = . ; /*data数据段结束地址*/
} >RAM
.stack :
{
. = ALIGN(4);
_sstack = .; /*设置stack 起始地址*/
. = . + _system_stack_size; /**/
. = ALIGN(4);
_estack = .; /*设置stack 结束地址*/
} >RAM
__bss_start = .; /*bss栈段,起始地址*/
.bss : /*bss栈段,未初始化段*/
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_ebss = . ;
*(.bss.init)
} > RAM
__bss_end = .;/*bss栈段,结束地址*/
_end = .;
/* Stabs debugging sections. 插入调试部分 */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
* Symbols in the DWARF debugging sections are relative to the beginning
* of the section so we begin them at 0. DWARF调试部分中的符号相对于该部分的开头,因此我们从0开始它们。*/
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
}
启动文件代码逻辑流程图
流程描述:
1.根据link.lds的内容
ENTRY(Reset_Handler) /* 运行程序第一个被执行的程序 */
上电第一个被执行的程序就是 Reset_Handler函数,芯片启动必须的操作均在Reset_Handler函数中。
2. 已经初始化的data数据会根据编译器分配的地址依次复制到内存中
3. 未初始化的数据bss段,也会被复制到依次内存中,默认值为0。
4. 前面操作完成后需要配置系统时钟分配
5. 最后执行跳转到RT-thread系统初始化。至此启动结束。
6. startup_stm32f103xg.S文件中的其它内容为中断向量表定义和中断默认入口函数定义。编译器编译的时候直接编译到目标文件中。
Cortex-M3 内核部分知识点介绍
APSR标志位
cmp 指令
CMP 比较两个数并更新标志位
CMP 指令。 CMP 指令在内部做两个数的减法,并根据差来设置标志位,但是不把差写回。 CMP 可有如下的形式:
CMP R0, R1 ; 计算 R0-R1 的差, 并且根据结果更新标志位
CMP R0, 0x12 ; 计算 R0-0x12 的差, 并且根据结果更新标志位
汇编伪指令详细解释
GNU ARM汇编伪指令关键词详细解释
GNU ARM汇编伪指令均以.开始。常用汇编伪指如下所示:
伪指令 | 说明 | 示例 |
---|---|---|
.global | 用来定义一个全局的符号 | .global Default_Handler //声明一个外部文件 可调用 标签 Default_Handler(默认的中断处理函数) |
.section | 作用是定义内存段,该命令后只跟一个参数,即它声明的段的类型 | .section .text #定义文本段(代码段).section .data #定义数据段 .section .bss #定义 bss 段 |
.weak | 在该标号被引用时,如果加上了这个关键字,当存在同名标号时,该标号是不会被优先引用的 | .weak Reset_Handler //弱定义名称 Reset_Handler |
.type | 用来指定一个符号的类型是函数类型(%function)或者是对象类型(%object), 对象类型一般是数据, 格式如下: .type 符号, 类型描述 | .type Reset_Handler, %function //将Reset_Handler 定义为函数 |
.word | 插入一个32-bit的数据队列。(与armasm中的DCD功能相同)可以使用.word把标识符作为常量使用 | .word expression就是在当前位置放一个word型的值,这个值就是expression |
.equ | .equ (symbol name , value) 此指令设置符号的值(类似于armasm中的equal) | .equ BootRAM, 0xF1E0F85F //将BootRAM的值设置为0xF1E0F85F |
.size | .size name,expression:将符号name所占空间设为expression。 | .size Reset_Handler, .-Reset_Handler (将Reset_Handler占用空间设置为 自身所占空间大小) |
.thumb_set | 伪操作的作用类似于.set, 可以用来给一个标志起一个别名, 比.set功能增加的一点是可以把一个标志标记为thumb函数的入口, 这点功能等同于.thumb_func | .thumb_set NMI_Handler,Default_Handler |
.ascii “<string>” | 插入字符串到目标文件,但不以NULL结束 | .ascii “JNZ” @插入字节0x4A 0x4E 0x5A |
.asciz “<string>” | 与.ascii类似,但以NULL结束 | .asciz “JNZ” @插入字节0x4A 0x4E 0x5A 0x00 |
.balign <power_of_2>{,<fill_value>{,<max_padding>} } | 使下面的位置对齐 | byte 0x55 @插入字节: 0x55.balign 4,0 @插入3个对齐字节:0x00 0x00 0x00 .word 0xAA55EE11 @插入字节:0x11 0xEE 0x55 0xAA (LSB order) |
.byte <byte1> {,<byte2>} … | 插入字节到目标文件 | .byte 64, ‘A’ @ inserts the bytes 0x40 0x41.byte 0x42 @ inserts the byte 0x42 .byte 0b1000011, 0104 @ inserts the bytes 0x43 0x44 |
.code <number_of_bits> | 设置指令位数,16位Thumb或32位ARM | .code 16 @设置以下汇编为Thumb指令 |
.else | 与 .if 和 .endif 配合使用 | |
.end | 标志汇编文件结束,汇编器不再读后面的内容 | |
.endif | 结束条件编译代码块–请参见.if、.ifdef、.ifndef(类似于armasm中的ENDIF) | |
.endm | 标志宏定义结束,与.macro配合使用 | |
.endr | 结束一个loop,与.rept 和 .irp 配合使用 | |
.err | 汇编时如遇到.err,则停止编译 | |
.exitm | 中途退出宏定义 | |
.hword <short1> {,<short2>} | 插入16位(半字)值到目标文件 | |
.if <logical_expression> | 与 .endif 配合使用 | |
.ifdef <symbol> | 与 .endif 配合使用 | |
.ifndef <symbol> | 与 .endif 配合使用 | |
.include “<filename>” | 与C的#include类似 | |
.irp <param> {,<val_1>} {,<val_2>} … | 对值列表中的每个值重复一段代码。使用.endr指令标记块的结尾。在重复代码块中,使用<param>替换值列表中的关联值 | |
.macro <name> {<arg_1} {,<arg_2>} … {,<arg_N>} | 使用N个参数定义名<name>的汇编程序宏。宏定义必须以.endm结尾。若要在早期退出宏,请使用.exitm。这些指令类似于军备竞赛中的宏指令、修正指令和MEXIT指令。必须在伪宏参数前面加上 \. | |
.rept | 重复执行代码块number_of_times次,与C中for类似,以endr结束 | |
<register_name> .req <register_name> | 寄存器重新命名,此指令命名寄存器。它与armasm中的RN指令类似,只是您必须在右侧提供名称而不是数字 | 例:acc .req r0 |
.section <section_name> {,”<flags>”} | 启动新的代码或数据节。GNU中的部分称为.text(代码部分)、.data(初始化数据部分)和.bss(未初始化数据部分)。这些部分有默认标志,链接器理解默认名称(类似于armasm指令区域的指令)。 | 以下是允许的。ELF格式文件的节标志: <Flag> Meaning a allowable section w writable section x executable section |
.space <number_of_bytes> {,<fill_byte>} | 保留给定的字节数。如果指定的话,字节将填充零或<fill\u byte>(类似于armasm中的空格)。 |
ARM特殊字符和语法
区别于gas汇编的通用伪操作,下面是ARM特有的伪操作 :.reg ,.unreq ,.code ,.thumb ,.thumb_func ,.thumb_set, .ltorg ,.pool
伪指令 | 说明 |
---|---|
.thumb | 汇编使用Thumb指令 |
.arm | 汇编使用ARM指令 |
.code16 | 汇编使用Thumb指令 |
.code32 | 汇编使用ARM指令 |
.force_thumb | 强制使用thumb模式,即使不支持 |
.thumb_func | 将入口点标记为thumb编码(强制bx进入) |
.ltorg | Start a new literal pool(文字池:嵌入在代码中的用以存放常数的区域) |
#或$ | 直接操作数前缀 |
; | 语句分离符号 |
# | 整行注释符号 |
@ | 代码行中的注释符号 |
数据定义伪指令 | 说明 |
---|---|
.byte | 单字节定义,如:.byte 1,2,0b01,0x34,072,‘s’ |
short | 定义双字节数据,如:.short 0x1234,60000 |
.long | 定义4字节数据,如:.long 0x12345678,23876565 |
.quad | 定义8字节,如:.quad 0x1234567890abcd |
.float | 定义浮点数,如:.float 0f-31415\95028841971.693993751E-40 |
string/.asciz/.ascii | 定义多个字符串,如:.string “abcd”, “efgh”, “hello!” .asciz “qwer”, “sun”, “world!” .ascii “welcome\0” 注意:ascii伪操作定义的字符串需要自行添加结尾字符’\0’ |
.equ/.set | 赋值语句, 格式如下:.equ(.set) 变量名,表达式例如:.equ abc 3 @让abc=3 |
伪指令 | 说明 |
---|---|
.align | 用来指定数据的对齐方式,格式如下:.align [absexpr1, absexpr2] 以某种对齐方式,在未使用的存储区域填充值. 第一个值表示对齐方式,4, 8,16或 32. 第二个表达式值表示填充的值。 |
.end | 表明源文件的结束。 |
.include | 可以将指定的文件在使用.include 的地方展开,一般是头文件,例如:.include “myarmasm.h” |
.incbin | 可以将原封不动的一个二进制文件编译到当前文件中,使用方法如下:.incbin “file”[,skip[,count]] skip表明是从文件开始跳过skip个字节开始读取文件,count是读取的字数. |
if 伪操作
根据一个表达式的值来决定是否要编译下面的代码, 用.endif伪操作来表示条件判断的结束, 中间可以使用.else来决定.if的条件不满足的情况下应该编译哪一部分代码。
if 伪操作 | 说明 |
---|---|
.ifdef symbol | 判断symbol是否定义 |
.ifc string1,string2 | 字符串string1和string2是否相等,字符串可以用单引号括起来 |
.ifeq expression_r | 判断expression_r的值是否为0 |
.ifeqs string1,string2 | 判断string1和string2是否相等,字符 串必须用双引号括起来 |
.ifge expression_r | 判断expression_r的值是否大于等于0 |
.ifgt absolute expression_r | 判断expression_r的值是否大于0 |
.ifle expression_r | 判断expression_r的值是否小于等于0 |
.iflt absolute expression_r | 判断expression_r的值是否小于0 |
.ifnc string1,string2 | 判断string1和string2是否不相等, 其用法跟.ifc恰好相反。 |
.ifndef symbol, .ifnotdef symbol | 判断是否没有定义symbol, 跟.ifdef恰好相反 |
.ifne expression_r | 如果expression_r的值不是0, 那么编译器将编译下面的代码 |
.ifnes string1,string2 | 如果字符串string1和string2不相 等, 那么编译器将编译下面的代码. |
链接脚本.lds文件知识点
SECTIONS命令
SECTIONS 是一个功能很强大的命令. 这里这们会描述一个很简单的使用. 让我们假设你的程序只有代码节(.text),初始化过的数据节(data), 和未初始化过的数据节(.bss). 另外, 让我们进一步假设在你的输入文件中只有这些节. 对于这个例子, 我们说代码应当被载入到地址’0x10000’处, 而数据应当从0x8000000处开始. 下面是一个实现 这个功能的脚本:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
你使用关键字’SECTIONS’写了这个SECTIONS命令, 后面跟有一串放在花括号中的符号赋值和输出节描述的内容.
- 上例中, 在’SECTIONS’命令中的第一行是对一个特殊的符号 ‘.’ 赋值, 这是一个定位计数器 . 如果你没有以其它的方式指定输出节的地址(其他方式在后面会描述), 那地址值就会被设为定位计数器的现有值. 定位计数器然后被加上输出节的尺寸。在’SECTIONS’命令的开始处, 定位计数器拥有值’0’.
- 第二行定义一个输出节,’.text :’ 冒号是语法需要,现在可以被忽略.节名后面的花括号中,列出所有应当被放入到这个输出节中的输入节的名字.
- ’ * ‘是一个通配符,匹配任何文件名. 表达式 ’ *(.text) ’ 意思是所有的输入文件中的’.text’输入节.
- 因为当输出节’.text’定义的时候, 定位计数器的值是’0x10000’,连接器会把输出文件中的’.text’节的地址设为’0x10000’. 余下的内容定义了输出文件中的’.data’节和’.bss’节. 连接器会把’.data’输出节放到地址’0x8000000’处.连接器放好’.data’输出节之后, 定位计数器的值是’0x8000000’加上’.data’输出节的长度. 得到的结果是连接器会把’.bss’输出节放到紧接’.data’节后面的位置.
- 连接器会通过在必要时增加定位计数器的值来保证每一个输出节具有它所需的对齐. 在这个例子中, 为’.text’ 和’.data’节指定的地址会满足对齐约束, 但是连接器可能会需要在’.data’和’.bss’节之间创建一个小的缺口.
ENTRY命令
设置入口点,在运行一个程序时第一个被执行到的指令称为"入口点". 你可以使用’ENTRY’连接脚本命令来设置入口点.参数是一个符号名:
ENTRY(SYMBOL)
有多种不同的方法来设置入口点.连接器会通过按顺序尝试以下的方法来设置入口点, 如果成功了,就会停止.
MEMORY 命令
连接器在缺省状态下被配置为允许分配所有可用的内存块。你可以使用‘MEMORY’命令重新配置这个设置。MEMORY 命令描述目标平台上内存块的位置与长度。你可以用它来描述哪些内存区域可以被连接器使用,哪些内存区域是要避免使用的。然后你就可以把节分配到特定的内存区域中。连接器会基于内存区域设置节的地址,对于太满的区域,会提示警告信息。连接器不会为了适应可用的区域而搅乱节。
一个连接脚本最多可以包含一次MEMORY命令。但是,你可以在命令中随心所欲定义任意多的内存块,语法如下:
MEMORY
{
NAME [(ATTR)] : ORIGIN = ORIGIN, LENGTH = LEN
...
}
NAME是用在连接脚本中引用内存区域的名字。出了连接脚本,区域名就没有任何实际意义。区域名存储在一个单独的名字空间中,它不会和符号名,文件名,节名产生冲突,每一块内存区域必须有一个唯一的名字。
ATTR字符串是一个可选的属性列表,它指出是否为一个没有在连接脚本中进行显式映射地输入段使用一个特定的内存区域。如果你没有为某些输入段指定一个输出段,连接器会创建一个跟输入段同名的输出段。如果你定义了区域属性,连接器会使用它们来为它创建的输出段选择内存区域。
ATTR字符串必须包含下面字符中的一个,且必须只包含一个:
字符 | 说明 |
---|---|
`R’ | 只读节。 |
`W’ | 可读写节。 |
`X’ | 可执行节。 |
`A’ | 可分配节 |
`I’ | 已初始化节。 |
`L’ | 同‘I’ |
`!’ | 对前一个属性值取反。 |
如果一个未映射节匹配了上面除 ! 之外的一个属性,它就会被放入该内存区域。’!'属性对该测试取反,所以只有当它不匹配上面列出的行何属性时,一个未映射节才会被放入到内存区域。
ORIGIN是一个关于内存区域地始地址的表达式。在内存分配执行之前,这个表达式必须被求值产生一个常数,这意味着你不可以使用任何节相关的符号。关键字ORIGIN可以被缩写为org或o(但是,不可以写为,比如‘ORG’)
LEN是一个关于内存区域长充(以字节为单位)的表达式。就像ORIGIN表达式,这个表达式在分配执行前也必须被求得为一个常数值。关键字’LENGTH’可以被简写为len或l。
在下面的例子中,我们指定两个可用于分配的内存区域:一个从0开始,有256kb长度,另一个从0x4000000开始,有4mb长度。连接器会把那些没有进行显式映射且是只读或可执行的节放到’rom’内存区域。并会把另外的没有被显式映射地节放入到’ram’内存区域。
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}
一旦你定义了一个内存区域,你也可以指示连接器把指定的输出段放入到这个内存区域中,这可以通过使用
‘>REGION’输出段属性。比如,如果你有一个名为’mem’的内存区域,你可以在输出段定义中使用’>mem’。如果没有为输出段指定地址,连接器就会把地址设置为内存区域中的下一个可用的地址。如果总共的映射到一
个内存区域的输出段对于区域来说太大了,连接器会提示一条错误信息。
地址计数器‘.’(location counter)
该符号只能用于SECTIONS命令内部,初始值为‘0’,可以对该符号进行赋值,也可以使用该符号进行计算或赋值给其他符号。它会自动根据SECTIONS命令内部所描述的输出段的大小来计算当前的地址。
section指令
section
.section name [,“flags”,@type,flag_specific_arguments]:切换当前节,即将下面的代码或数据汇编到name节中。其中flag可以是a(节是可分配的),w(节是可写的),x(节是可执行的);
type可以是
@progbits(节中包含数据)
@nobits(节中不含数据,只是占位空间)
@note(节中包含注释信息,不是程序).
总结
上述很多内容与汇编指令有关,其中含有context-M3内核相关知识,很多都是自己的理解,一定会有不少漏洞,希望看到这篇文章的读者能够帮我指出问题多多提点,共同进步,谢谢!