前段时间写了一篇博文简单的说明了地址无关编译中的ropi和rwpi技术,简单的用一个我目前正在使用的os进行说明(基于rt-thread),部分细节没有讲清楚,因此用此篇文章来进一步介绍地址无关编译,争取能够分析透彻。
个人比较喜欢技术分享,认为技术不应该有壁垒,应该是开放的、互相进步的。本来计划6月份参与rt-thread的一个测评计划的,但由于自身一些不可描述的原因只能放弃了,所以拿出这篇文章(这篇文章之前在论坛中也出现过,当时只是一个链接,最近rt论坛推出了文章功能,所以顺便来尝尝鲜)。
由于单篇篇幅限制10000字符,所以无奈本篇中简要列举一下完整的文章结构以及部分简要摘录。
二、单片机的分散加载机制
1.分散加载
三、地址无关编译
1.ropi介绍
2.rwpi介绍
分散加载实际上规定了单片机(或者机器,后面不再赘述)在正式上 电执行前后,如何准备好运行时的环境,这个环境指主要指的是变量地址如何分配。
可能说变量地址如何分配不太恰当,但是我实在是找不到一个名词来描述这个场景:从flash或者说是rom中把变量搬移到ram空间。
之所以说“搬移”,是基于以下几点考虑:
以下尽可能详细的介绍一下这种“搬移过程”。另外说明一下,我不太喜欢贴图片,所以我之后大部分会以代码的形式来展示示例。
以下都是以KEIL为例!
D:\MDK-ARM V5.25\Keil_v5\ARM\ARM\ARMCC\bin
和D:\MDK-ARM V5.25\Keil_v5\ARM\ARM\ARMCLANG\bin
,这两个为两个不同的编译链下的工具,对于Cortex-M来说,二者没有区别,详细大家可以百度:“armcc和armclang的区别。”)。如果你完全按照我的这个操作流程,你会得到一个与.axf文件同名的.S文件。
裸机下的上电过程,裸机我就不单独摆程序了,这里只是简单叙述一下流程:
__main()
函数(这个不是我们见到的main()
函数,这个是编译器内置函数,用于调用一些初始化操作函数,然后再调用我们的main函数,交回使用权)。__main()
函数中,一般会有两个函数:__scatterload()
和__rt_entry()
,一个用于分散加载,一个用于系统库的初始化(如__rt_lib_init()
,这个函数内实现了动态加载过程)。__rt_entry()
调用main()
函数,跳转到我们的c语言世界。$Sub$$main
和$Super$$main
,这哥俩用于在main()
函数之前调用一些操作,毕竟之前的那些操作都是编译器来处理,我们干预不了,只能干瞪眼($Sub$$main
和$Super$$main
,这个在rt-thread的源码中有使用示例,这里不赘述)。!!!main
__main
0x000000c0: f000f802 .... BL __scatterload ; 0xc8
0x000000c4: f000f883 .... BL __rt_entry ; 0x1ce
!!!scatter
__scatterload
__scatterload_rt2
__scatterload_rt2_thumb_only
0x000000c8: a00a .. ADR r0,{pc}+0x2c ; 0xf4
0x000000ca: e8900c00 .... LDM r0,{r10,r11}
0x000000ce: 4482 .D ADD r10,r10,r0
0x000000d0: 4483 .D ADD r11,r11,r0
0x000000d2: f1aa0701 .... SUB r7,r10,#1
__scatterload_null
0x000000d6: 45da .E CMP r10,r11
0x000000d8: d101 .. BNE 0xde ; __scatterload_null + 8
0x000000da: f000f878 ..x. BL __rt_entry ; 0x1ce
0x000000de: f2af0e09 .... ADR lr,{pc}-7 ; 0xd7
0x000000e2: e8ba000f .... LDM r10!,{r0-r3}
0x000000e6: f0130f01 .... TST r3,#1
0x000000ea: bf18 .. IT NE
0x000000ec: 1afb .. SUBNE r3,r7,r3
0x000000ee: f0430301 C... ORR r3,r3,#1
0x000000f2: 4718 .G BX r3
$d
0x000000f4: 00037f6c l... DCD 229228
0x000000f8: 00037f8c .... DCD 229260
;;中间的代码省略...
Region$$Table$$Base
0x00038060: 00000055 U... DCD 85
0x00038064: 00000002 .... DCD 2
0x00038068: 00001b38 8... DCD 6968
0x0003806c: 00037f63 c... DCD 229219
0x00038070: 00000819 .... DCD 2073
0x00038074: 00001b3a :... DCD 6970
0x00038078: 0000f5d0 .... DCD 62928
0x0003807c: 00037eeb .~.. DCD 229099
上述代码直接为从__main()
开始的过程,即跳转到__main()
函数中,先执行__scatterload()
函数,这里就是分散加载的过程(实际的过程可能比这个要繁琐些,繁琐在哪,后面会介绍到)。
以下是分析:
__main()
函数处(实际上在汇编世界里,这就是一个标号,函数和变量没有太大区别,无非都是再地址空间里的一些有特殊含义的二进制数据)。0x000000c0: f000f802 .... BL __scatterload ; 0xc8
,大家只需要看到BL 0xc8
就可以,其他的信息,都是些杂七杂八的信息(前面说了生成的.axf中要带有调试信息,所以这里会看到BL __scatterload ; 0xc8
字样,__scatterload()
实际上就是从.axf文件中的符号表中提取出来的)。__scatterload()
函数(下面的代码和上一节的是一摸一样的)。!!!main
__main
0x000000c0: f000f802 .... BL __scatterload ; 0xc8
0x000000c4: f000f883 .... BL __rt_entry ; 0x1ce
!!!scatter
__scatterload
__scatterload_rt2
__scatterload_rt2_thumb_only
0x000000c8: a00a .. ADR r0,{pc}+0x2c ; 0xf4
0x000000ca: e8900c00 .... LDM r0,{r10,r11}
0x000000ce: 4482 .D ADD r10,r10,r0
0x000000d0: 4483 .D ADD r11,r11,r0
0x000000d2: f1aa0701 .... SUB r7,r10,#1
__scatterload_null
0x000000d6: 45da .E CMP r10,r11
0x000000d8: d101 .. BNE 0xde ; __scatterload_null + 8
0x000000da: f000f878 ..x. BL __rt_entry ; 0x1ce
0x000000de: f2af0e09 .... ADR lr,{pc}-7 ; 0xd7
0x000000e2: e8ba000f .... LDM r10!,{r0-r3}
0x000000e6: f0130f01 .... TST r3,#1
0x000000ea: bf18 .. IT NE
0x000000ec: 1afb .. SUBNE r3,r7,r3
0x000000ee: f0430301 C... ORR r3,r3,#1
0x000000f2: 4718 .G BX r3
$d
0x000000f4: 00037f6c l... DCD 229228
0x000000f8: 00037f8c .... DCD 229260
;;中间的代码省略...
Region$$Table$$Base
0x00038060: 00000055 U... DCD 85
0x00038064: 00000002 .... DCD 2
0x00038068: 00001b38 8... DCD 6968
0x0003806c: 00037f63 c... DCD 229219
0x00038070: 00000819 .... DCD 2073
0x00038074: 00001b3a :... DCD 6970
0x00038078: 0000f5d0 .... DCD 62928
0x0003807c: 00037eeb .~.. DCD 229099
很清楚的看到0x000000c8
到0x000000d2
这块代码,实际上是获取了Region$$Table$$Base
这个数据块的起始地址(r10是始地址,r11是末地址+1,r7是始地址-1。只不过用了重定位技术,在链接时才知道Region$$Table$$Base
的地址,然后回来修改$d
处的值,所以这块代码使用了相对地址偏移,简单来说就是编译的时候$d
这里填充的0,链接时才知道了真正的值)。
然后顺序执行到__scatterload_null()
处,__scatterload_nul()
l本质是一个循环,反复的利用Region$$Table$$Base
中的数据来执行。
篇幅有限,附链接
单片机分散加载机制、地址无关编译的分析