MCU - 解剖复位后的软件启动过程,找出第一条指令

文摘   科技   2024-03-13 07:34   广东  







链接脚本ld概述,及程序存储机制

链接脚本指定变量、函数的存储地址







1. MCU复位后的软件启动过程    

1.1 概述  


下图是Cortex-M3内核的MCU启动过程:








1.2 STM32的启动模式 


根据BOOT0、BOOT1引脚,有3种启动方式:

  • 主闪存存储器:也就是通常使用的FLASH,从0x0800 0000启动

  • 系统存储器

    • IC封测时,厂商在该处固化了BootLoader(厂商使用后门应该可以修改)

    • 从该处启动后,可采用【ISP】方式烧录代码

  • SRAM:从此处启动的应用较少



注:前两种模式,都把其基地址映射到0x0000 0000。






  

1.3 核心的汇编启动文件  


  • 不同MCU的启动方式基本大同小异,复位后都是从汇编的启动文件开始

  • MDK环境的启动文件过于简略:

    • 大部分准备工作都交给C库处理,在【__main】函数中

    • 无法窥视MCU具体的启动流程



基于此,这里使用【STM32CubeIDE】中使用的启动文件,详细描述启动过程。







1.4 STM32复位后执行的第一条指令  


这里需要准备的文件:

  • .s汇编启动文件

  • .list文件,汇编代码,以FLASH地址为序号,且包含C代码

  • .map文件,查看SRAM、FLASH的地址分配

  • .ld链接脚本




1.4.1 复位后的启动机制 


这里截取【Cortex ™ -M4 Devices Generic User Guide】文档中的描述:

developer.arm.com/documentation/dui0553/latest/



即复位后,由于FLASH映射到0x0000 0000:    

  • CPU首先从【0x0000 0000】也就是【0x0800 0000】获取栈顶

  • 接着加载【0x0000 0004】也就是【0x0800 0004】处保存的【复位向量】至PC指针中

  • 跳转到【复位中断服务函数】,执行复位中断服务函数中的第一条指令







1.4.2 从例程看第一条执行的指令  


1)链接脚本中定义【栈顶地址_estack】:




2)接着在链接脚本的【SECTIONS】命令的开头,把FLASH的起始处分配给中断向量表【isr_vector】:




3)接着在汇编启动文件中,定义全局的中断向量表【g_pfnVectors】,【_estack】【复位中断服务函数】依次放在开头:




4)编译项目,查看【.list文件】:

  • 【isr_vector】放到了FLASH起始处【0x0800 0000】

  • 【Reset_Handler】起始地址为【0x0800 19CC】

  • 未找到【isr_vector】存储的数据,无法观察上电取第一条指令的过程

  • 转而从【HEX文件】下手    




5)查看【HEX文件】:

  • 【0x0800 0000】处存储【0x2002 0000】,是【_estack】栈顶地址

  • 【0x0800 0004】存储【0x0800 19CD】,是【Reset_Handler】的地址,其bit0置1,符合要求





6)复位中断处理函数的第一条指令,即设置栈指针【sp = _estack】








1.5 复位中断处理函数  


整体的执行内容:







1.5.1 数据从LMA加载到VMA  


搬运函数、搬运变量初始值都是一样操作,只是VMA、LMA的值不一样:








1.5.2 .bss清零  


沿用相同的指令,这里不做具体讲解,建议查ARM指令集(也可整段代码发给ChatGPT,由它讲解)。








1.5.3 调用SystemInit  


字面意思,一般是芯片原厂提供的,对IC进行初步的初始化。





    

1.5.4 调用__libc_init_array  



执行C库的初始化,目前未理解。查看【.list文件】:

  • 部分寄存器压入栈

  • 两次加载函数列表至r4 r5,并逐一执行函数

  • 中间跳转执行了【_init】函数

  • 最后出栈,退出【__libc_init_array】








1.5.5 跳转到main  


在复位中断服务函数的末尾,终于跳到我们写的【main】中。




-- END --






碎片聚合
求真务实
 最新文章