Chinaunix首页 | 论坛 | 博客
  • 博客访问: 18775
  • 博文数量: 11
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 50
  • 用 户 组: 普通用户
  • 注册时间: 2015-01-28 15:24
个人简介

为了美好生活,加油!

文章分类
文章存档

2015年(11)

我的朋友

分类: 嵌入式

2015-01-29 20:19:30

在资料中,ADR的定义为:小范围的地址读取伪指令,ADR指令将基于PC相对偏移的地址值读取到寄存器中,在编译源程序时ADR伪指令被编译器替换成一 条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,刚产生错误。

在如上的定义中,有两个关键信息:⑴将基于PC相对偏移的地址值读取到寄存器中;⑵被编译器替换成一条合适的指令。ADR指令只能将地址值读取到寄存器中,而不能是其它的立即数,并用只能用一条指令。

如果在汇编程序中使用ADR R1,ResetHandel语句,其中ResetHandel是汇编程序中的一个标签,此条伪指令的作用是把ResetHandel标签所在的指令地址 读取到寄存器R0中。当汇编器对此条伪指令进行编译的时候,将会编译成机器码:0xE28F100C,转换成二进制就是1110 0010 1000 1111 0001 0000 0000 1100,下面对这个机器码进行分析:

根据上面的分析,可以看到,编译器在编译的时候把ADR伪指令编译成一个ADD R1,PC,Immediate指令,其中Immediate是一个立即数,数值是ResetHandel语句和此条伪指令之间的差值,由编译器自动算 出。由于立即数寻址的约束,这个Immediate存在一定的约束,所以会出现定义中所说的不能用一条指令实现。

LDR说的定义为:大范围地址读取伪指令,LDR伪指令用于加载32们的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译 器替换成一条合适的指令。若加载的常数未超出MOV或者MVN的范围,刚使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入字池,并使用一 条程序相对偏移的LDR指令从文字池读出常量。

与ARM指令的LDR相比,伪指令的LDR的参数有"="号。

在如上的定义中,有三个关键信息:⑴用于加载32们的立即数或一个地址值到指定寄存器;⑵被编译器替换成一条合适的指令;⑶优先使用MOV或MVN指令代替该指令。

如果使用MOV指令,那就使用立即数寻址的方式,但是立即数寻址存在一个范围白约束,所以不是所以的常数都可以使用立即数寻址白方式。当不能使用立 即数寻址方式时,就把常量放入文字池,使用一条程序相对于PC寻址的LDR指令把文字池的内容读取到寄存器中。立即数和地址值的操作方式是一样的。

如果有伪指令:LDR R0,=0x123456。那编译器在编译该伪指令的时候将会编译成机器码:1110 0101 1001 1111 0000 0000 0001 0100,下面对这个机器码进行分析:

由上面分析可知,伪指令LDR R0,=0x123456其实就是被编译成LDR R0,[PC,Immediate]。其中立即数0x123456被储存在一个文字池中,他的地址和指令LDR R0,[PC,Immediate]的地址之前差了Immediate。因此指令LDR R0,[PC,Immediate]就可以把立即数0x123456读取到R0中了


ldr     r0, _start
        adr     r0, _start
        ldr     r0, =_start
        nop
        mov     pc, lr

_start:
        nop
        
编译的时候设置 RO 为 0x0c008000

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

0c008000 <_start-0x14>:
c008000:       e59f000c        ldr     r0, [pc, #12]   ; c008014 <_start>
c008004:       e28f0008        add     r0, pc, #8      ; 0x8
c008008:       e59f0008        ldr     r0, [pc, #8]    ; c008018 <_start+0x4>
c00800c:       e1a00000        nop                     (mov r0,r0)
c008010:       e1a0f00e        mov     pc, lr

0c008014 <_start>:
c008014:       e1a00000        nop                     (mov r0,r0)
c008018:       0c008014        stceq   0, cr8, [r0], -#80

分析:

ldr     r0, _start

从内存地址 _start 的地方把值读入。执行这个后,r0 = 0xe1a00000

adr     r0, _start

取 得 _start 的地址到 r0,但是请看反编译的结果,它是与位置无关的。其实取得的时相对的位置例如这段代码在 0x0c008000 运行,那么 adr r0, _start 得到 r0 = 0x0c008014;如果当前实际在地址 0 运行,就是 0x00000014 了。(自己注:为何相对地址指令有那么聪明,会自动减掉当前运行的实际地址呢?其实是相对地址指令借助了PC寄存器,不管你相对地址指令后的标号的连接地址在编译时被确定为多少,都要把这个已经确定的连接地址减去PC寄存器的值,这样,如果在Nor Flash中运行,那PC寄存器肯定是0x00000000开始的,那就用“连接地址-0x00000000”);如果在SDRAM中运行,那PC寄存器肯定是从0x30000000开始的,那就用“连接地址-0x3000000”,但绝对地址指令就不顾PC寄存器当前值了,直接操作标号表示的连接地址,而标号的连接地址全部都是在编译时就被强制确定的,但由于ARM设计了非常经典的相对地址和绝对地址,保证了uboot能在Nor Flash和SDRAM中自由翱翔,因此uboot的start.s文件中的一串代码还能让uboot智能地判断自己在Nor Flash还是在SDRAM中运行,就好像uboot自己长了眼睛一样,说实话在看到这串代码之前我也从来没有意识到汇编代码能写得那么“聪明”呢,其本质就是借助了经典的相对地址指令和绝对地址指令来实现的。这样解释应该比较清楚了!

ldr     r0, =_start

这 个取得标号 _start 的绝对地址。这个绝对地址是在 link 的时候确定的。看上去这只是一个指令,但是它要占用 2 个 32bit 的空间,一条是指令,另一条是 _start 的数据(因为在编译的时候不能确定 _start 的值,而且也不能用 mov 指令来给 r0 赋一个 32bit 的常量,所以需要多出一个空间存放 _start 的真正数据,在这里就是 0x0c008014)。
因此可以看出,这个是绝对的寻址,不管这段代码在什么地方运行,它的结果都是 r0 = 0x0c008014


一、先谈LDR (大范围)

LDR 既可以做伪指令,又可以做load/store指令,关于怎么区分,不是重点,一般来讲,LDR伪指令是写在程序前部的。而且这些工作也是编译器做的,而不是我们,重要的是谈 LDR 伪指令的功能(load/store 方式已有很多例子)。

LDR 伪指令格式: LDR {cond} register, = {exprl | label_expr}

1.当expr表示的地址没有超过mov或mvn指令中地址的取值范围时,编译器用合适的mov指令或mvn指令代替该LDR伪指令。    

2. 当expr表示的地址超过了mov或mvn指令中地址的取值范围时,编译器将该常数放在缓冲区中,同时用一条基于PC的

LDR指令读取该常数。

至于如何区分这个地址是否超过这个长度,笔者也不太清楚,在CSDN社区有一篇文章讲的是 MOV . MVN 是寄存器与寄存器内部的传输,立即数与寄存器的传输,但是这个立即数的长度不能超过8位。 我认同他的前者,对他的后者保留意见。 我们先来看个例子吧:

COUNT EQU 0X4000

LDR R0, = COUNT    ;R0 = 0X4000

//** LDR R0, = 0X4000    ; R0 = 0X4000 **//

MOV R1, #88

STR R1, [R0]

上述的操作可以完成 COUNT 变量的赋值。

二、谈 ADR (小范围)

ADR 指令将基于PC的地址值或者给予寄存器的地址值读取到寄存器中。在汇编编译器处理源程序时,ADR伪指令被编译器替换成一条合适的指令。 通常是一条ADD 指令或者SUB指令来实现该伪指令的功能。

格式: ADR  , 

看一个网上谈的比较多的例子。

ldr     r0, _start
     adr     r0, _start
     ldr     r0, =_start
_start:
        b   _start
        
编译的时候设置 RO 为 0x30000000(运行地址,runaddress),下面是反汇编的结果:

   0x00000000: e59f0004 ldr r0, [pc, #4] ; 0xc
   0x00000004: e28f0000 add r0, pc, #0 ; 0x0
   0x00000008: e59f0000 ldr r0, [pc, #0] ; 0x10
   0x0000000c: eafffffe b 0xc
   0x00000010: 3000000c andcc r0, r0, ip

--------------------------------------

1.ldr r0, _start:

简单的说就是把 _start地址存放的值读出来。

汇编程序计算出当前位置执行到_start(这里是一个标号,相对程序的位置表达式)位置pc所要增加的数值#4,然后由当前pc(其实是当前地址+2个指令字节长)加上偏移量#4,得到_start所在内存地址,然后将内存地址的值去出来放在r0中。只要此指令和标号_start的相对位置不变,R0的值相同 0xeafffffe


2.adr r0, _start

简单的说就是把 _start地址读出来,而且这个地址是相对当前pc的,所以和当前程序运行地址相关,如果在0x30000000运行,r0 = pc(0x30000004 + 0x08) + #0 = 0x3000000C;如果在0x00000000运行, r0 = pc(0x00000004 + 0x08) + #0 = 0x0000000C,所以在不同的位置运行,r0所得到的结果是不一样的,唯一确定的相对偏移量。

3. ldr, r0, =_start

这是一条伪指令,取得得是_start的绝对地址,不管在身地方运行,r0 = 0x3000000C


ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令。
比如想把数据从内存中某处读取到寄存器中,只能使用ldr
比如:
ldr r0, 0x12345678
就是把0x12345678这个地址中的值存放到r0中。
而mov不能干这个活,mov只能在寄存器之间移动数据,或者把立即数移动到寄存器中,这个和x86这种CISC架构的芯片区别最大的地方。
x86中没有ldr这种指令,因为x86的mov指令可以将数据从内存中移动到寄存器中。

另外还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。ldr伪指令可以在立即数前加上=,以表示把一个地址写到某寄存器中,比如:
ldr r0, =0x12345678
这样,就把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的。只不过mov指令限制了立即数的长度为8位,也就是不能超过512。而ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。

阅读(353) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~