LDR 与ADR解析
LDR与STR 是ARM汇编中的一对指令,用于RAM与寄存器之间的数据交换,常见形式为
LDR R1,[R0] 将RAM中地址空间为R0中的值载入到R1
STR R1, [R0] 将R1中的值存储到RAM地址为R0的空间
这对指令相对与其他指令的不同在于,LDR指令的源操作数在后面,STR的源操作数在前面,不一致。
这是最常见的间接寻址方式,对于ARM体系来说,LDR还是一条伪指令,我们知道,不能直接编译成机器码
的指令就是伪指令,通常伪指令还需要编译器解释成多步,再进行汇编。
先看u-boot中LDR伪指令的用法:
163 ldr r0, =pWTCON
164 mov r1, #0x0
165 str r1, [r0]
166
167 /*
168 * mask all IRQs by setting all bits in the INTMR - default
169 */
170 mov r1, #0xffffffff
171 ldr r0, =INTMSK
172 str r1, [r0]
其反汇编代码为:
59 33f00064: e3a00453 mov r0, #1392508928 ; 0x53000000
60 33f00068: e3a01000 mov r1, #0 ; 0x0
61 33f0006c: e5801000 str r1, [r0]
62 33f00070: e3e01000 mvn r1, #0 ; 0x0
63 33f00074: e59f040c ldr r0, [pc, #1036] ; 33f00488 <fiq+0x48>
64 33f00078: e5801000 str r1, [r0]
注意63行这里:
ldr r0, =INTMSK 被汇编解释为了ldr r0, [pc, #1036] ; 33f00488 <fiq+0x48>
这条只看指令从意思来看,是以PC(R15)作为基址寄存器偏移1036(0x40c)地址处空间的值复制给R0,
我们知道,ARM指令集为32位,低12为用来存储数据(高8为是数据,低4为偏移量),这对数据有限制,必须能通过偏移得到
的数据才能使用mov指令加载。而ldr伪指令能解决任何数据的加载。
根据上面的反汇编代码来看,fiq标号地址为:0x33f00440+0x48 = 0x33f00488(这里是绝对地址),而基于arm9的5级流水线取址,
在当前执行指令时,已经预取了两条指令,即PC = 当前地址(0x33f00074)+ 0x8(预取两条指令)+ 0x40c(偏移)= 0x33f00488.
370 33f00488: 4a000008 .word 0x4a000008
371 33f0048c: 000003ff .word 0x000003ff
0x33f00488处定义了一个字类型,值为INTMSK,这样在加载INTMSK时,通过基于PC的间接寻址,得到值,这是在编译时确定的。
这是LDR伪指令的一种用法,在ARM汇编中非常常见,用于普通值加载基本形式:
LDR R1,=0xffcff 或者LDR R1,=DEFINE ‘不能用#0xffcff’
另一种用法:
151 ldr r0, =SMRDATA
152 ldr r1, _TEXT_BASE
153 sub r0, r0, r1
这里的SMRDATA和_TEXT_BASE不是宏定义,而是标签,上面已经知道,如果是宏定义,编译器会自动分配一个缓冲字来存这个宏定义,但有时候
需要一段缓冲区来初始化某个控制器,比如这里的RAM,也或者我们要加载的值不是确定的,需要在链接时确定,那就必须预先声明,明确的告诉
编译器,需要把哪段区域作为缓冲池。
关键字.ltorg,正是起到这个作用,告诉编译器,从这里开始是用户定义的一个文字缓冲池,SMRDATA是缓冲池标号。
165 .ltorg
166 /* the literal pools origin */
167
168 SMRDATA:
.ltorg会被汇编成:
58760 33f37308: 33f3730c .word 0x33f3730c
58762 33f3730c <SMRDATA>:
58763 33f3730c: 2211d120 .word 0x2211d120
在看上面151行的反汇编代码:
58750 33f372e0: e59f0020 ldr r0, [pc, #32] ; 33f37308 <lowlevel_init+0x28>
58751 33f372e4: e51f1010 ldr r1, [pc, #-16] ; 33f372dc <_TEXT_BASE>
58752 33f372e8: e0400001 sub r0, r0, r1
这种方式和直接常量的载入是一样的,只是需要手动定义一个缓冲池,pc + 0x32 = 0x33f37308(lowlevel_init_ 0x28)定义到缓冲区,缓冲区的
内容为0x33f3730c,标签SMRDATA为0x33f3730c,由此ldr r0, =SMRDATA 即把SMRDATA标签地址(非标签内容)装入R0。
上面描述了LDR伪指令的两种用法,还一种用法贯穿u-boot中,即上面的152行:
152 ldr r1, _TEXT_BASE
反汇编:
58751 33f372e4: e51f1010 ldr r1, [pc, #-16] ; 33f372dc <_TEXT_BASE>
注意看与前面一种的区别
39 33f00040 <_TEXT_BASE>:
40 33f00040: 33f00000 .word 0x33f00000
58746 33f372dc <_TEXT_BASE>:
58747 33f372dc: 33f00000 .word 0x33f00000
看反汇编代码来说是没有什么区别的,都是以PC做偏移取内容。
上面的反汇编代码中贴出了两个_TEXT_BASE字,只是为了对比。ldr r1, _TEXT_BASE执行后r1的值始终为0x33f00000,
而不是33f00040 或者33f372dc,这也说明是直接取的标签处内容。当然这也是间接寻址的一种,也是以PC做为基址寄存器。
这里引申出来的两个_TEXT_BASE,也说明了使用lDR伪指令时,寻址空间有限制(4K)。
解析了LDR的几种用法,这里进行总结,同时也引出代码位置无关的限制。
以coder角度:
1.进行常量加载时,使用ldr r0, =0xfffff或者ldr r0, = DEFINE_REG即可,不用关心具体加载过程,常量所存储位置不用关心。
2.需要使用大的文字池时,只需声明一个文字池.ltorg并定义文字池标签POOLS_LABLE,使用ldr r0, =POOLS_LABLE,即可得到文字池首地址。也不用关心具体存储位置
3.定义跳转参数或者链接时才确定的参数也或者使用的全局参数,用户需要自己定义所在位置,常见形式:
_lable :
.word __label //函数名
_lable :
.word CONDIF_... //常量定义
_lable :
.word __end - __start //链接参数等
如果是全局使用还需要加上
.globl lable
这样在定义位置处就是整个程序链接生成地址。
载入内容:
1.ldr r0 , =POOLS_LABLE 载入内容为地址
2.另外两种用法载入内容由编写代码决定(值或者地址)
现在可以来理解代码位置无关了,在arm-linux编译U-BOOT时,通常会使用-pie参数进行链接,目的是生成位置无关可执行文件。
-pie position independent exeutable
位置无关代码分为:位置无关代码和位置无关数据,而数据无关一般不用考虑,只需要考虑代码位置无关。
而上面解析的ldr指令,严格意义上来讲是与代码有关的,比如:
ldr pc,_Lable 这是绝对地址跳转。为什么这里要用'严格意义'来修饰,虽然代码编译链接完以后,所有的标签都是绝对地址,但使用ldr的
大多时候都是用来间接载入值,是一些常量的访问,是位置无关的。
而另一些ARM指令才是真正位置无关的:
B/BL Lable
ADR/ADRL r0,_lable
B/BL跳转指令是基于当前PC的偏移量而跳转
ADR伪指令的反汇编:
242 stack_setup:
243
244 adr r0, _start
86 33f000b8: e24f00c0 sub r0, pc, #192 ; 0xc0
被解释成了当前PC的偏移,不管哪个地址运行,都不会影响结果,这才是真是代码位置无关。通常这个指令用于读取当前
某个标号的地址,与该标号的绝对地址(链接)地址比较,判断程序的运行位置,u-boot正式这种方式来确定是否需要搬运代码。
B/BL/ADR指令的跳转范围都只有4K,所以局限很大,通常在大范围跳转时,可使用ldr指令,首先预先在某个位置(与该ldr指令不超过4K)设置声明定义
一个字标号_lable,内容为跳转的函数名,然后执行ldr pc,_lable,即可实现大范围的跳转,这里也是绝对地址的跳转,可实现从NOR跳入指定RAM地址。
由ADR/LDR看U-boot结构
最新推荐文章于 2022-09-17 23:39:33 发布