嵌入式Linux学习:浅淡加载地址和运行地址区别&汇编指令ldr和adr的区别

时间:2021-01-06 18:48:25

本文引用了http://blog.csdn.net/shanzhizi/article/details/38387421?_t=t博文的一些说明

也引用了http://blog.sina.com.cn/s/blog_4b5210840100c80i.html的例子和图片


笔者简单的分享了在学习uboot过程中的一些心得,如果有问题,欢迎及时提出;

------------------------------------------------正文开始-------------------------------------------

加载地址与运行地址

Ø  运行地址就是可执行文件被“连接”的地址;

Ø  加载地址就是可执行文件被“存储”的地址

        两者不同之处就如同字面的理解

        运行地址就是程序员期望程序能够运行在该区域内,且完全正常运行;比如我们要执行一段main函数,那这个main函数就是一段.text代码段,那运行地址就是我们希望这段代码最终能够在哪里被运行(在SDRAM的起始处,还是偏移0x1000处)。加载地址就是程序员期望程序被保存在flash的哪个区域,当我们编写好main函数以后,我们希望这段代码能够保存在flash里,然后在重新上电时能够正确的被其他boot程序识别到(怎么识别?靠的就是加载地址,因为所有的指令也好,数据也好,都是以二进制保存在FLASH中的,无法区分。那只有前期标记好哪里到哪里保存的是什么,才能准确的识别),并将之加载(复制)到上述链接地址。

简单总结一下:,

Ø  加载地址就是程序保存在flash中的地址

Ø  运行地址就是程序应该被运行在SDRAM(或SRAM)中的地址


n  注:因为断电易失,所以程序不可能一开始就保存在SDRAM中,即只能保存在如NOR或者NAND这类FLSAH的器件中 。而且即使FLASH可以XIP(即在FLASH中直接运行程序),但是速度也很慢,而片上的RAM又不够大,所以最终只能将FLASH的程序加载到SDRAM中在运行。


所以重新上电后,一般需要分两个阶段:

1.     阶段一也分两种情况,即NOR启动还是NAND启动。这两种启动方式(都是FLASH)如下:


a)       如果是NAND启动,则在上电完成后,CPU会主动将NAND的前4KB(S3C2440是4KB,其他的ARM也类似,只是大小不一样)加载到片上的SRAM(从0x0000开始到0x1000区域,称之为Steppingstone),然后CPU的指针指向0x0000,开始依次执行里面的指令;

b)       如果是NOR启动,这在上电完成后,将存储控制器的地址空间的0x0000_0000到0x0800_0000这一段映射到片外的NOR FLASH,然后CPU的指针指向0x0000(对应的就是NOR的0x00处)开始依次执行,也就是上述的XIP,即在FLASH中直接运行程序!


该阶段不管从哪里开始启动,一般完成以下功能:硬件初始化(包括看门狗,关闭MMU,关闭中断等)、代码从FLASH搬移到SDRAM、运行环境初始化(堆栈初始化),最后执行一跳转跳指令,进入SDRAM执行后续代码;


2.     第二阶段就是完成更加复杂的功能,可能是uboot的后续程序之类的,以后再展开;

实例巩固

下面的以一个简单的实例来介绍下这两个地址的不同,可以点击一下链接

http://blog.csdn.net/shanzhizi/article/details/38387421?_t=t

head.s源代码如下:

嵌入式Linux学习:浅淡加载地址和运行地址区别&汇编指令ldr和adr的区别

Makefile如下:

嵌入式Linux学习:浅淡加载地址和运行地址区别&汇编指令ldr和adr的区别

先看Makefile文件的第4行,这命令的意思:将head.s编译后的obj文件head.o和leds.c编译后的obj文件leds.o两个文件连接成一个输出文件sdram_elf,特别需要注意的是这里的-Ttext  0x30000000,这个0x30000000就是连接地址,也就是运行地址,那这里的加载地址(或者说存储地址呢?这里缺省了,意味着就从NAND FLASH的0x00000处开始保存,然后默认是先将代码段.text放到最前头,也就是上面head.s中的_start实际保存在FLASH的0x0000)。也即是说FLASH的0x0000依次往后存储了我们的代码,但是我们又希望我们的代码能够运行在0x30000000处(对了,0x30000000是我们SDRAM的起始),也就是运行在SDRAM中去。


通过前面的叙述,应该已经知道了,在上电后,CPU会将NAND的前4K的数据自动加载到片上的Steppingstone处(从0x0000开始到0x1000区域),所以上电完成后我们的代码就被CPU从FLASH中复制到了片上的SRAM中去,然后从0x0000处开始运行。


等等,我们上面不是说要将代码运行在SDRAM(因为我们的链接地址是0x30000000),这会儿怎么运行到SRAM中了呢?这里涉及到两个问题:

1.     原本希望运行到SDRAM中的代码能不能运行在SRAM中?

2.     能不能通过某些代码,最终让程序由SRAM转到SDRAM中呢?


先回答第1个问题:某些汇编指令可以运行,另外那些不可以;

怎么解释只有部分汇编指令可以运行呢?我们看看最上面的head.s的源码中标红字的部分:

第一条:bl disable_watch_dog

第二条:ldrpc, =on_sdram

其中B、BL、MOV等指令都是位置无关码,而LDRPC,=LABEL等类似的代码都是位置有关码


比如说BL指令,它执行时被实际翻译成add pc,pc, #offset,也就是说这条指令实际上就是利用当前的pc值加上某一个offset的结果在保存进pc实现转跳,所以说这段代码不管是运行在SRAM还是SDRAM中,都是能够正常运行的,所以称之为位置无关码,因为这种转跳是相对的!


后者ldr pc,=on_sdram(伪指令)就没有那么好糊弄了,他在执行时被翻译成ldrpc,[pc,#offset],也就是说利用当前的pc值加上某一个offset作为地址,将这个地址上数据直接赋值给pc,从而实现转跳,像这个例子中,pc+offset的地址上保存的数据就是0x30000010(为什么不是0x30000000?因为0x30000000只是SDRAM的起始地址,前面一堆代码已经占了那部分空间),所以这个时候程序就要转跳到SDRAM中去运行了;


那么,此时SDRAM中有数据了吗?不是说上电完成后只会讲NAND的前4kB的数据一直到SRAM中嘛?SDRAM中哪来的数据呢?再看head.s,原来是用户自己完成了这部分代码的移植,也就是从SRAM中移植到SDRAM!这也就是回答了第二个问题!


这里总结一下这两个问题:

Ø  加载地址和运行地址确实能够按照预期的功能进行工作,但是必须辅助以人工的数据移植(从SRAM移植到SDRAM或者从NAND直接移植到SDRAM)

Ø  为了正常运行很大的程序,如uboot,上电后一般要完成两个阶段,就是上面说的,这里在重复一下就是突出分阶段的意义!那是因为阶段一一般执行的基本都是位置无关码,所以可以直接运行在SRAM或者FLASH,而阶段二会执行更复杂的程序,所以需要运行在SDRAM中,而这个准备工作就是在阶段一完成的。


adr和ldr


在uboot的start.s中大量应用到了adr和ldr,有必要在这里进行下区分:

参考以下链接:

http://blog.sina.com.cn/s/blog_4b5210840100c80i.html

        ldr     r0, _start
        adr     r0, _start
        ldr     r0, =_start
_start:
        b  _start
       
连接地址改为0x30000000,下面是反汇编的结果:

   0x30000000: e59f0004  ldr r0, [pc, #4] ;
   0x30000004: e28f0000 add r0, pc, #0 ;
   0x30000008: e59f0000 ldr r0, [pc, #0] ;
   0x3000000c: eafffffe b 
   0x30000010: 3000000c andcc r0, r0, ip

 

这里先简单介绍下pc值,pc值就是当前正在被执行的(32位)机器码的下两条机器码的实际地址;上面标粗的就是正在被执行的机器码(e59f0004,它不一定如反汇编所示的处在0x30000000,毕竟上电后前4k代码(nand加载)是保存在0x0000到0x1000里的,那么此时这个机器码的实际地址就是0x0000,它的下两条机器码的地址(PC)就是0x0008(一条机器码4字节,即1word),如果此时已经完成了SDRAM的复制,那么这条机器码的所处地址(因为我们的连接地址就是0x30000000,所以我们自然希望这条机器码被运行在0x30000000)也有可能就是0x30000000,那么它的下两条机器码的地址(PC)就是0x30000008;当然,也可以重定位,比如说此时的该机器码实际运行在0x60000000,那么pc=0x60000008;所以正在运行的机器码的pc值是由它本身所处的位置决定的!

 

e59f0004 的地址

下两条机器码地址,即PC值

运行在SRAM

0x0000

0x0008

运行在SDRAM

0x30000000

0x30000008

其他

0x60000000

0x60000008

 

1.     ldr r0,_start的作用:

可以看到,反汇编后转化为ldr r0,[pc, #4],就是将pc值加上立即数4作为地址,将这个地址里的数赋值给r0;可以看到,不管这条代码被运行在哪里,结果r0都是等于0xeafffffe ; 


2.      adr r0,_start的作用:

首先adr本身实际上被执行为add r0, pc, #0,当运行在SRAM时;r0=0x000C,当运行在SDRAM时,r0=0x3000000C,所以运行在哪里,这条代码产生的结果是不一样的!


3.     ldr r0,=_start

此时被实际执行为ldr r0,[pc, #0],所以不管运行在哪里,r0输出的结果都是0x3000000C!


总结:

Ldr r0,lable

实际上是将lable这个标号的数据赋值给r0,和运行在哪里没有关系

Ldr r0,=lable

实际上是将lable的标号(地址,连接时确定)赋值给r0,和运行在哪里没有关系

Adr r0,lable

实际上是将lable当前的运行地址赋值给r0,和运行在哪里有关!