从两句汇编认识运行时地址与链接地址
首先看两行汇编代码:
1: adr r0, _start
2: ldr r1, =_start
同样是加载一个标号的地址值,adr和ldr有什么区别呢?注意这里的ldr不是命令ldr,而是伪指令ldr,若想区分它们请参看我的一篇博文《adr adrl ldr mov总结整理》。
要区分它们,就需要引入4个概念:
1、运行时地址起始位置:它芯片公司指定的一开始运行代码的位置。这个位置和芯片本身有关,不可改动。对于2440来说一般就是片内SRAM的首地址0x0;对于210来说就是片内SRAM中的地址0xD0020010。
2、链接地址起始位置:它是由程序员指定的,或者说是有链接脚本设定。是可以变动的。但是这个位置在程序链接之后,就会确定下来。
3、运行时地址:就在从运行时地址起始位置(包括起始位置)往后排都是运行时地址。
4、链接地址:就是从链接地址起始位置(包括起始位置)往后排都是链接地址。
说明了以上4点内容之后,我需要铺垫一些前提内容,adr r0, _start ; ldr r1, =_start
这两句代码是从朱老师的一个实验程序里直接截取出来,这实验的目的是演示重定位。之后这段代码我会贴到文章的最后。因为开发板是210的板子所以运行时地址是从0xd0020010开始的,链接地址设置为0xd0024000开始的。
整个程序编译之后,在进行反编译,我们查找adr r0, _start ; ldr r1, =_start 对应的反汇编内容:
1、adr r0, _start 对应的是:
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
2、ldr r1, =_start对应的是:
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
同样是加载_start的地址,反汇编之后却是截然不同的命令。首先我们需要会看反汇编,最左边的是链接地址,第二个是机器码,第三个是反汇编得到的内容,最右边分号之后的是反汇编编译器额外帮我们注释了一些内容方便我们阅读。
我们发现反汇编之后,有一个地方很不同,就是pc指针。ldr r1, =_start对于的反汇编pc指针被放到的了[]里面,而另一条反汇编没有。我们知道对于汇编而言,放到[]里面代表是取得寄存器的值并且将寄存器的值当作地址,来访问地址中存储的值。
而对于pc而言,当你直接读取pc的值时访问的是运行时地址,而当你读取[pc]的值时访问的是链接地址。
反观adr r0, _start 和ldr r1, =_start它们都是伪指令,意思也分别是读取运行时地址和读取链接地址。和反汇编意义吻合。
我们现在来验证,我们前面分析的是否正确。首先_start作为程序的最开始,所以_start如果对应运行时地址,那么读取的_start的值应该是运行时地址起始位置及0xd0020010。
观察反汇编及对应的汇编
1、adr r0, _start
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
由于此时该句代码的链接地址是d002401c链接地址的起始位置设定的是0xd0024000,偏移量是0x1c,根据这个便宜量可以算出该句代码的运行时地址为0xd0020010 +0x1c = d002002C,前面提到pc的值对应的就是运行时地址所以pc = d002002C。
d002002C - (36 十进制)+ 8 (流水线)= D002 0010 ;正好得到了_start的运行时地址完全没错。
再看链接地址是否算错,首先_start作为程序的最开始,所以_start如果对应链接地址,那么读取的_start的值应该是链接地址起始位置及之前设定的0xd0024000。
2、ldr r1, =_start
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070 <run_on_dram+0x10>
根据偏移量,这句的运行时地址是d0020030,如果说是运行时地址 + 偏移量(72十进制),得到的是D002 0078,再加8(流水线)等于D002 0080,显然不对。
明显这里的[pc]的值得到的是当前语句对应的链接地址,d0024020 + 偏移量(72十进制)+ 8 才等于D002 4070(这个值也正好是注释里的值)大家是不是奇怪,为啥值不是0xd0024000?是不是算错了?其实不是,你到D002 4070这个链接地址看看就会发现这里存放的值正好就是D002 4070。
代码如下:d0024070: d0024000 andle r4, r2, r0
这里符合ldr r1, [pc, #72]这句指令的本意,他访问的就是这个值代表的地址中的值。(这种跳转的方法其实就是为了应对非法立即数,导致在一个机器码里放不下命令和数据的情况)
代码如下:
.text .global _start _start: bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启 bl memsetup @ 设置存储控制器 bl copy_steppingstone_to_sdram @ 复制代码到SDRAM中 ldr pc, =on_sdram @ 跳到SDRAM中继续执行 on_sdram: ldr sp, =0x34000000 @ 设置堆栈 bl main halt_loop: b halt_loop
增改:2016-06-07
总结:
首先说明,之前的提出的结论:
1、运行时地址:就在从运行时地址起始位置(包括起始位置)往后排都是运行时地址。
2、链接地址:就是从链接地址起始位置(包括起始位置)往后排都是链接地址。
3、对于pc而言,当你直接读取pc的值时访问的是运行时地址,而当你读取[pc]的值时访问的是链接地址。
4、adr r0, _start ;加载的是运行时地址; ldr r1, =_start加载的是链接地址。
整个过程是这样的:
1、程序在运行之前先编译链接,链接完了之后,每句程序都对应一个链接地址(就像你反汇编看到的那样)。而链接地址的起始地址其实位置往往是DDR的起始位置。
2、但是一开始程序是在SRAM上运行的,运行地址的起始位置往往就是SRAM的起始位置。那么一开始运行的代码它的
链接地址和运行地址是不同的,但是代码本身不知道这件事,而程序员明白,所以这段代码只能是位置无关码,主要负责一些必要的初始化和搬运(重定位)。直到
搬运完成,pc的值还是运行时地址的值。也就是说pc不等于[pc].
3、通过绝对跳转修改PC的值为当前链接地址的值:
ldr pc, =on_sdram @ 跳到SDRAM中继续执行,及让pc = [pc]
如果用相对跳转就是当前运行时地址加上一个偏移量,而在那个地方可能并没有内存和程序。
如: bl on_sdram @通过PC + 偏移量完成,此时PC运行是运行时地址。这样就无法到DDR上运行程序了。
4、此后,可以认为运行时地址和链接地址相等(pc = [pc]),或者说不需要运行时地址和链接地址这两个概念了。除非你还想重定位程序。
作者:宋桓公
出处:http://www.cnblogs.com/douzi2/
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。