文章目录
固件起始地址存储了主栈指针和向量表内容
《ARM Cortex-M3与Cortex-M4权威指南》中的4.8章节复位和复位流程中有下面这段的描述:
在复位后以及处理器开始执行程序前,Cortex-M处理器会从存储器中读出头两个字,如图4.30所示。向量表位于存储器的开头部分,它的头两个字为主栈指针(MSP)的初始值,以及代表复位处理起始地址的复位向量(参考本书图4.26和4.5.3节)。处理器读出这两个字后,就会将这些数值赋给MSP和程序计数器(PC)
当异常时间产生且被处理器内核接受后,响应的异常处理就会执行。要确定异常处理的起始地址,处理器利用了一种向量表机制。向量表为系统存储器内的字数据数组,每个元素都代表一个异常类型的起始地址,如图4.26所示。
《Cortex-M3处理器技术参考手册》中5.9复位章节中的表5-7显示了复位的行为:
基于上述描述我们可以知道固件的起始地址存储了主栈指针的初始值,后续存储了向量表的内容。
从上面的map文件中我们可以看出程序的栈顶地址就是0x200069b0,从下面的固件中我们可以看出起始地址存放的就是栈顶指针的值。
…
基于map文件找出后续程序地址对应的数据内容,和上面向量表对应的内容也完全一致
细心的你可以能已经发现了,这里固件中存储的向量表的地址和map文件中对应的函数地址不完全一致,差了1,这是因为向量表的LSB位必须置1以表示Thumb状态(Cortex-M处理器不支持ARM状态)
《ARM Cortex-M3与Cortex-M4权威指南》7.5章节:
启动代码使用的向量表还包含主栈指针(MSP)的初始值,这种设计是很有必要的,因为NMI等异常可能会紧接着复位产生,而此时还没有进行任何初始化操作。
总之,芯片上电后处理器会将第一个数据赋值给SP,第二个数据加载到PC寄存器中,这是由处理器设计决定的。
启动文件分析
启动文件主要做了以下工作:
- 初始化栈指针 MSP= _initial_sp
- 初始化PC指针 PC=Reset_Handler
- 初始化中断向量表
- 配置系统时钟
- 调用C库函数__main初始化用户堆栈,从而最终调用main函数
复位函数是系统上电后第一个执行的程序。
Code,RO Data,RW Data, ZI Data
map文件中镜像组件大小信息:
Code : 代码段
RO Data : 只读数据段
RW Data : 读写数据段,定的时候被初始化的变量
ZI Data:未初始化的数据段
Cortex-M芯片启动详细过程
下图为芯片启动后程序的基本执行流程图:
下图为芯片启动后程序执行的详细流程:
Rest_Handler函数是芯片上电后第一个执行的程序。然后会调用C库函数__main()通过分散加载复制函数将加载域中的RW Data复制到执行域,通过分散加载零初始化函数在RAM中开辟出一段空间并初始化为0(该函数实际初始化为0的数据空间包括ZI Data段、Heap段和Stack段)然后初始化用户堆栈和C库并跳转到用户main函数中。
Regin$$Table
程序在编译链接的时候会生成此分散加载表,在程序执行的过程中会通过C库函数根据该表的信息将flash中的RW Data复制到SRAM中,并在SRAM中开辟出一段空间并初始化为0用于ZI Data、Heap、Stack数据段。
map文件中的Regin$$Table的位置如下:
对应固件中的地址位置的数据如下:
值 | 描述 |
---|---|
08030500 | RW Data在加载域中的起始位置 |
20000000 | RW Data复制到执行域中的起始位置 |
00000478 | 需要复制RW Data数据的长度 |
080257A | 分散加载复制函数的入口地址 |
080305E8 | RW Data在加载域中的结束位置 |
20000478 | ZI Data在执行域中的起始位置 |
00006538 | ZI 数据段的大小(这里包含了ZI Data Heap Stack的总大小) |
0802F73A | 分散加载ZI段初始化函数的入口地址 |
这里 0x6538 + 0x478 = 0x69b0, 0x20000000 + 0x69b0 = 0x200069b0刚好对应于栈顶位置
进一步固件分析
从上一章节中我们可以知道RW Data的大小为0x478字节,如果所有的这些数据全部直接从固件中复制的话固件的结束位置应为0x08030500+0x478=0x08030978的位置,但实际并不是这样,下图展示了固件实际的结束位置:0x080305E8
根据MAP文件中的固件大小信息部分我们可以看到,RW Data的大小为232(0xE8)字节,这刚好和固件的大小相对应。
上面内容说明了RW Data段的数据也不是简单的直接从flash空间直接复制到RAM空间的。实际0xE8的空间存储了RW Data变量的初始值信息,如果初始值一样占用的是同一个初始值空间。在程序运行时在RAM中为RW Data段开辟出0x478的空间,并将初始值赋值给对应的变量。这样的设计可以减小生成固件的大小。
升华
- 当你深入向下发掘时,科学的神秘面纱便会一层一层褪去,渐渐露出真容。
- 芯片严格遵循着人们创造它的规则在运行,其中有着非常严谨的逻辑。