找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3252|回复: 0
收起左侧

MDK STM32 Mian函数运行前的分析

[复制链接]
ID:391224 发表于 2019-2-1 12:02 | 显示全部楼层 |阅读模式
一、 启动文件的介绍

在 MDK 的启动文件 startup_stm32f10x_md_vl 中,该文件分别定义了栈段、 堆段、存放中断向量表的数据段、还有一个代码段
大小为 0x400 的栈段定义如图 1-1:

图 1-1

大小为 0x200 的堆段如图 1-2:

图 1-2

由其定义属性可知,栈和堆都未初始化,该过程由后面的_user_initial_stackheap
来完成。
存放中断向量的数据段,如图 1-3 所示:

图 1-3
10 个系统异常过程段和在同一地址的外部中断过程段,下面我们就详细介绍 上电复位的代码段,如图 1-4 所示:

二、Reset_Handler 段分析

1. _systeminit 函数分析
STM32 上电启动,首先从 0x0000 0000 处初始化 sp 的值,然后从 0x0000 0004
处取得复位中断处理的地址 0x0800 1F6D,程序跳转

图 2-1

但是 Reset_Handler 的地址为 0x0800 1F6C,这是因为 Cortex-M3 使用的是 thumb-2
指令集,其最低位必须为 1;如果为 0,则会出现异常,如图 2-1 所示。 查看反汇编窗口,如图 2-2 所示:

图 2-2

可以知道先取得 SystemInit 函数的地址,该地址是多少,我们可以跳转到
0x08001F94 看看,如下所示,该地址保存了值为 0x0800045D 的数,如图 2-3:

图 2-3

然后我们跳转到 0x0800045D,发现该处正是我们需要的 SystemInit 函数入口地 址,如图 2-4:

图 2-4

该函数首先保存跳转前的有关状态,然后根据使用的芯片,进行相应的初始化操 作,函数最后重新映射了中断向量的存放地址,如图 2-5:


查看反汇编窗口,如图 2-6:

图 2-6



根据分析可知,该段代码是把 0x0800 0000 存放到地址为 0xE000 ED08 处, 查找 Cortex_M3 手册如图 2-7 所示,该地址是向量表偏移寄存器,也就是说,这 条语句把中断向量表重新映射到地址为 0x0800 0000。



2. _main 函数分析
图 2-7



进行完相应的初始化,函数跳转到_main 函数,_main 函数的入口地址从
0x08001F98 取得,通过查找,发现_main 函数的地址为 0x08000121,跳转到
0x0800120,证明此处就是我们要找的_main 函数入口,如图 2-8

图 2-8

_main 函数到底进行了哪些操作呢,下面我们就一一逐步分析过去。
? _scatterload 函数分析

首先是一条跳转指令,跳转到_scatterload 函数,该函数的第一条指令是把地 址 0x08000154 赋值给 r0(但是此处 PC+4 取得值应该是0x0800012C,为什么会 是 0x08000154 呢?根据 ARMv7-M Architecture Reference Manual,ADR 的二进 制码,相差 0x28),第二条指令是分别把 0x08000154、0x08000158 存放的 0x0000
1ECC、0x0000 1EEC 给 r10、r11,如图 2-9。经过该函数的第三、第四条指令, 我们可以得到r10=0x08002020,r11=0x08002040,r7=0x0800201F,


图 2-9
查看.map 文件,知道 0x08002020 称为 Region$$Table$$Base,0x08002040 称为
Region$$Table$$Limit。
? _scatterload_null 函数分析

如图 2-10 所示,程序继续执行到_scatterload_null 函数,首先比较 r10、r11 是否相等,如果不等则跳转到 0x0800013E。很明显不等,程序跳转,第一条指 令是把 0x08000137f 赋值给 lr,其实就是保存_scatterload_null 的入口地址;第二 条指令是把 r10=0x08002020 为起始,0x08002030 为终止的地址内容分别赋值给 r0-r3,最后我们得到 r0=0x08002040, r1=0x20000000, r2=0x00000034, r3=0x0800
015C,r10=0x08002030;第三条指令是判断 r3 是否为 1,很明显不为 1,IT 指令 等效于 if-then 模式,最后几条指令可以得到 r3=0x0800015D,程序跳转




? MAP 文件分析
图 2-10




0x0800 015D 地址是函数_scatterload_copy 的入口,该函数到底 copy 了什么 值呢?在此之前我们先要熟悉一下.map 文件
.map 文件是值包括了映像文件信息图和其它信息的一个映射文件,该文件包 含了:
(1) 从映像文件中删除的输入段中未使用段的统计信息,对应参数-remove; (2) 域符号映射和全局、局部符号及生成符号映射统计信息,对应参数
-symbol;
(3) 映射文件的信息图,对应参数-map,该信息中包含映像文件中的每个加载 域、运行域和输入段的大小和地址,如 2-11 图、2-12 图所示:

图 2-11



图 2-12

(4) 映像文件的每个输入文件或库的RO、RW、ZI 等统计信息,对应参数-info sizes,示例如图 2-13:

图 2-13

(5) 文件对象类和库总的 RO、RW、ZI等下大小,对应参数-info totals,示例 如图 2-14:

图 2-14

由此,根据.map 文件我们得到 RW=0x34, ZI=0x0644, Code+RO=0x2040, RW+ZI=0x0698

现在我们回到_scatterload_copy 中,看看到底 copy 了什么,其代码如图 2-15
所示:

图 2-15


? _scatterload_copy 函数分析

经过_scatter_null 函数我们得到 r2=0x00000034,这正是需要初始化全局变量 的大小,由此我们猜测 copy 的值是全局变量。
第一条指令是得 r2=0x0000 0024,然后判断标志位 C 是否为 1,如果是 1,则 执行下面两条语句:
LDM              r0! , (r3-r6);
STM              r1! , (r3-r6);
r0=0x0800 2040, r1=0x2000 0000,而 0x20000000 正好是 SRAM 的起始地址,所以 上面两条语句是把以 0x8002040 为起始的地址复制到以0x2000 0000 为起始的地 址,该循环类似我们的 for(r0=0x0800 2040, r1=0x2000 0000;r2=r2-0x10;r2>0), 直到 r2 为负数
LSLS              r2,r2,#29
ITT              CS              如果 r2 除以 16 是 8 的整数,则复制该 8 字节
LDM              r0!,{r4-r5} STM              r1!,{r4-r5}


ITT              MI
LDR              r4,[r0,#0x00] STR                            r4,[r1,#0x00]
如果 r2 除以 16 是 4 的整数,则复制 4 或者 12 字节


这样_scatterload_copy 完成了需要初始化全局变量 RW 的装载过程,最后函 数返回到_scatterload_null。
? _scatterload_zeroinit 函数分析

然后判断 r10 是否与 r11 相等,很明显不等,r3=0x0800 0178,函数跳转,进入 到_scatterload_zeroinit 函数,此时
r0=0x0800 2074,r1=0x20000034, r2=0x0000 0664
_scatterload_zeroinit 函数代码如图 2-16:

图 2-16
通过_scatterload_copy 我们可以猜测_scatterload_zeroinit 是一个清零过程,但 是对什么需要清零呢?当然是 ZI 段,由 r2=0x00000664 这正是 ZI 的大小,所以 该过程是以 0x20000034 为起始地址,大小为 r2=0x00000664 的清零过程,具体 分析和_scatterload_copy 类似,不再重复。


程序最后返回到_scatterload,接着跳转到_rt_entry,如图 2-17




2. _rt_entry 函数分析
图 2-17



_rt_entry  的第一条指令又是一条跳转指令,程序再次跳转到
_user_setup_stackheap,如图 2-18

图 2-18

_user_setup_stackheap 函数的第一条指令是保存函数的返回地址,此处为
什么没有用 PUSH ?因为此时堆栈还没有初始化好。第二条指令是跳转到
_user_libspace 进行一些微库的初始化工作,后面的几条语句是建立一个大小为
90 字节的临时栈,然后程序跳转到_user_inital_stackheap 进行用户栈的初始化, 这也就是启动文件 startup_stm32f10x_md_vl 中初始化堆栈段的那些语句,如图
2-19

图 2-19


经 过 该 函 数 处 理 得 : r0=0x2000              0098,r1=0x2000              0698,r2=0x2000
0298,r3=0x2000 0298。最后用户栈顶被设置成 0x2000 0698,完成了堆栈的初始 化工作,程序返回到 rt_entry_main


三、 总结

最终函数终于跳转到我们的 main 函数执行我们写的代码。 总结启动文件的整个过程,分为如下:
(1) 系统初始化,包括对中断向量表的重新映射;
(2) 加载 RW 段;
(3) ZI 段清零;
(4) 初始化用户堆栈;
(5) 初始化微库(具体干什么我也不知道,屏蔽此处函数好像也能正常运行);
(6) 调用 main 函数。

MDK main函数运行前的分析.pdf

177.99 KB, 下载次数: 15, 下载积分: 黑币 -5

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表