我斑愿称你为最强(STM32
启动文件分析)
1)奥利给,肝了:
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
重头开始看起:汇编程序和其他程序一样,并不是一上来就是代码段,可能会有一些定义,声明之类的东西。
EQU
是一个伪指令(伪指令是没有对应的机器码的),就是说明Stack_Size
等于0x400
,就是通知编译器,这个符号的值为0x400
。这里补充一点伪指令的定义:伪指令没有对应的机器代码,并不像指令语句那样由CPU来执行,是由汇编器在汇编源程序时执行的。
主要是完成变量定义,存储器分配,指示段定义,段分配,段结束等。
AREA
代表的是The
AREAdirective instructs the assembler to assemble a new code or data section.
AREA STACK, NOINIT, READWRITE, ALIGN=3
AREA
就是告诉汇编器汇编一个代码段或数据段。后面从参数用于告诉汇编器要汇编段的信息。
STACK
说明段的名字,就是栈;NOINIT
不初始化,READWRITE
可读可写,ALIGN
说明是8(2^3)字节对齐。这里主要说明一下字节对齐的概念,8字节对齐的意思就是说该段所在的起始地址的低三位为0。
SPACE
也是个伪指令,前面AREA
说明要汇编一个栈段,那么后面要说明该栈段的大小。SPACE
告诉汇编器栈段的大小为Stack_Size
即0x400
,Stack_Mem
是什么意思呢,他是一个可选项(就是可写,可不写)他与指令中的标号是一个意思,代表的是汇编地址;__initial_sp
代表的是栈的结束地址,就是栈顶地址。如果想看一下
__initial_sp
到底是什么东西呢,涉及到一个.map
文件,里面有所有符号的定义
要想讲清楚
.map
文件中的内容,涉及到 的还是挺多的,要了解可执行目标文件的格式、编译等内容,还是有点篇幅,这里暂时不讲,不理解的话没关系,暂时只需要清楚上面的内容即可。可以发现
__initial_sp
的值就是0x20000410
,这里正好对应着程序在运行时SRAM
区的栈顶地址。这里面需要了解的就是程序在存储时是存储在flash
中,运行时会将RW
数据区(有初值的全局变量)移到SRAM
中,并添加ZI
数据区。(ZI
数据区包含有初始化为0 的全局变量(类似于bss
段),以及堆,以及栈段),如何看各个段的大小呢?
![](https://gitee.com/koala-middle/figure-lib/raw/master/images/pwN7CAx9VW1LdGY.png)
在使用
fromelf
工具查看.axf
文件(可执行文件)时,就可以看到段的大小,可以发现RW
数据区与ZI
数据区的bss
段所占的空间为16字节(0x10
),所以前面STACK
的值为0x20000010
在启动文件中明明有堆空间的创建(程序紧接着栈后面),而实际的却没有堆空间,这是怎么回事呢?这一点与
armlink
中的编译器优化相关。编译器优化看样子是很高深的概念,其实就是在不影响正常程序功能的情况下,让程序更好,就是说可能会让程序占据更小的空间,比如这里的堆,因为没有使用动态内存分配,所以有和没有是一样的,为了减少SRAM
空间的消耗,就不给他分配空间。这对资源少的嵌入式设备来说,是有一定帮助的。
接下来就是汇编一个名叫
RESET
的只读数据区AREA RESET, DATA, READONLY
。
主要说明就是EXPORT
The
EXPORTdirective declares a symbol that can be used by the linker to resolve symbol references in separate object and library files.
GLOBALis a synonym for
EXPORT.
EXPORT指令就是声明一个标号,什么样的标号,被连接器使用来解析符号在不同目标与库文件的引用。
Use
EXPORTto give code in other files access to symbols in the current file.
根据上面这句话的意思就是说,在当前文件中,对其他文件的的 符号使用标号,就可以访问该标号。
三个标号的具体值是多少呢?
(这里有我自己想无意中思考的问题,就是在任何代码文件中,并没有指定这些标号的值,到底这些标号是如何被赋予这些特定的值的呢?后面学习了程序的可执行文件,推测大概是这样的,当编译器在生成可执行文件映像的过程中,会将映像与程序的存储的物理地址联系起来,这个在物理地址编译之前是给了的,最后根据映像在物理地址的位置,上面这些标号的值就建立起来了。这里只是记录个人的想法,可略过,继续下面的内容)
这里很奇怪,这里向量表的结束地址为
0x08000130
,但是在cortex-M3
编程手册中的(PM0056
)中向量表的结束地址会大一点,如下图所示:
暂时不是特别清楚,为什么会这样,但是并不影响阅读与理解,先放这里。
DCD
命令主要说明的就是分配内存空间,那这时会不会有人像我一样,前面不是有一个叫SPACE
的吗?不也是分配内存空间,两者有什么区别呢?
DCD
开辟的空间支持4字节对齐,而且可以初始化,就是给开辟的空间赋值DCD __initial_sp
.
SPACE
开辟的空间是不支持字节对齐,如果想让字节对齐,后面必须跟一条语句。下面的英文是官方的解释。而且用这条语句开辟的空间是初始化为0的。这就是与DCD
指令的区别
Use the
ALIGNdirective to align any code following a
SPACEor
FILLdirective
整个向量表除了第一项存放的栈顶地址,其余各项存放的是函数名。**函数名就是该函数(代码块)所在内存的起始地址。**以其中一项为例说明:
这个
Reset_Handler
的值就为0x08000145
,本质上就是地址。
接下来就是代码段了
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
上面就是复位的异常处理函数,当
STM32
上电时,PC
指针(R15
)被赋予的初值为复位向量的值,也就是复位异常处理函数。堆栈指针MSP
被赋值为向量表的第一项(刚上电时是特权级),也就__initial_sp
。然后程序就来执行这里的代码。
PEOC
与ENDP
是固定用法,代表的是过程的开始与结束。有点汇编基础的比较好理解。
这里
EXPORT
后面加了一个[WEAK]
,代表什么意思呢?官方的解释是这样的。
如果能在其他源文件中找到一个相同的符号,那么那个在其他源文件的符号的优先级将高于这个。也就是说你如果自定义了复位函数,那么系统会执行你自定义的复位函数。
IMPORT
说明是什么含义呢?说明后面的标号不是本文件定义的,引用其他文件定义的标号。连接器链接时会帮你找到它真正的位置。
这里先埋下一个伏笔,就是说通过反汇编代码去深入的学习
__main
函数。因为直接跳到__main
函数会发现没有定义。记住非常关键点就是0x8000131
。正好在向量表的下面。
SystemInit
就是正常的c语言函数。
LDR
指令,官方解释:Load addresses to a register using LDR Rd, =label
,就是加载地址到寄存器中。简单。
BLX
指令, 官方解释:
![](https://gitee.com/koala-middle/figure-lib/raw/master/images/X2mUqtBPNTf5yeC.png)
总结就是,保存下条指令的地址到
LR
中,跳转到分支去执行,还会切换处理器的状态,但是Cortex-M3
只有Thumb
态,所以这里并不会切换。所谓Thumb
态,还有一种ARM
态指的是处理器识别不同 的指令集,像ARM9
之类的处理器,存在两种状态的。
BX
指令与BLX
指令类似(BL
与BLX
类似),就是不保存返回地址。
这里有没有发现一个奇怪的地方,就是
SystemInit
的值是0x08000abd
,(来自工程的.map
文件)但是反汇编出来的,真正的地址是0x08000abc
,差了1,为什么会差了1呢?又验证了其他几个函数名,发现都是相差1,我曾经看过这么一段话,就是在更新PC
(R15
)寄存器时,会将PC
指针的LSB
置1,来表示Thumb
状态,对于高级编程语言(包括C和C++),编译器会自动将跳转目标的LSB
置位。正好对应到这里的相差1。
这里
SystemInit
只有一句话,就是跳转至lr
中的地址,实际的C语言是:
void SystemInit (void)
{