由于我使用GNU工具链,所以以下的内容都以GNU AS的ARM语法为准。
LDR伪指令的语法形式如下:
LDR
这个常量表达式
范例demo.s:
.equ STACK_SIZE, 0x00001000
.text
ldr sp, = STACK_BASE
ldr sl, = STACK_BASE - STACK_SIZE
ldr pc, = entry
这是一个合法的汇编文件,它把堆栈基址设为0x0c002000,栈限设为0x0c001000,然后跳到entry所标识的C程序中执行。
下面我们假设符号“entry”的地址为0x0c000000。
我们如果把上面代码写成:
mov sp, #0x0c002000
mov sl, #0x0c001000
mov pc, #0x0c000000
汇编器会报错:
demo.s: Assembler messages:
demo.s:2: Error: invalid constant -- `mov sp,#0x0c002000'
demo.s:3: Error: invalid constant -- `mov sl,#0x0c001000'
说起这个错误的原因可就话长了,简而言之是因为RISC有一个重要的概念就是所有指令等长。在ARM指令集中,所有指令长度为4字节(Thumb指令是2字节)。那问题就来了,4字节是不可能同时存的下指令控制码和32位立即数的,那么我要把一个32位立即数(比如一个32位地址值)传送给寄存器该怎么办?
RISC CPU提供一个通用的方法就是把地址值作为数据而不是代码,从存储器中相应的位置读入到寄存器中,待会我们会看到这样的例子。
此外ARM还提供另一种方案。由于传送类指令的指令控制码部分(cond, opcode, S, Rd, Rn域)占去了20个字节,那能提供给立即数的就只剩12个位了。
ARM并未使用这12个位来直接存一个12位立即数,而是使用了类似有效数字一样的概念,只存8个字节的有效位和一个4位的位偏移量(偏移单位为2)。这个东西在ARM被叫做术语immed_8,有兴趣的人可以找资料了解一下,到处都有介绍。
可以看出ARM的这个方法能直接使用的立即数是相当有限的,像0xfffffff0这样的数显然无法支持。别着急,ARM的传送类指令中还有一个MVN指令可以解决该问题。显然0x0000000f是一个有效立即数,MVN会先将其取反再传送,这样有效立即数的范围又扩充了一倍。
可就算如此仍有大量的32位立即数是无效的,比如上面那个例子中的0x0c002000和0x0c001000。面对这种问题一是使用RISC的通用方法,二是分次载入。
比如可以这样载入0x0c002000:
mov sp, #0x0c000000
add sp, sp, #0x00002000
mov sp, #0x0c000000
orr sp, sp, #0x00002000
感觉很狡猾是吧,呵呵。但是要注意它和方法一的一大区别:需要多条指令。那么在一些对指令数目有限制的场合就无法使用它,比如异常向量表处要做长跳转(超过±32MB)的话就只能用方法一;还有就是在同步事务中该操作不是原子的,因此可能需要加锁。
扯了这么多再回到LDR伪指令上来。显然上面的内容是复杂繁琐的,如果然程序员在写程序的时候还要考虑某个数是不是immed_8一定超级麻烦,因此为了减轻程序员的负担才引入了LDR伪指令。
你一定很好奇第一段代码demo.s被GNU AS变成了什么,好,让我们在Linux环境下执行下面的命令:
arm-elf-as -o demo.o demo.s
arm-elf-objdump -D demo.o
结果:
Disassembly of section .text:
00000000 <.text>:
0: e59fd004 ldr sp, [pc, #4] ; c <.text+0xc>
4: e59fa004 ldr sl, [pc, #4] ; 10 <.text+0x10>
8: e59ff004 ldr pc, [pc, #4] ; 14 <.text+0x14>
c: 0c002000 stceq 0, cr2, [r0]
10: 0c001000 stceq 0, cr1, [r0]
14: 00000000 andeq r0, r0, r0
Disassembly of section .data:
由于entry还没连上目标地址,objdump反汇编会认为是0,我们先不管它。另外两条LDR伪指令变成了实际的LDR指令!但目标很奇怪,都是[pc, #4]。那好我们看看[pc, #4]是什么。
我们知道pc中存放的是当前指令的下下条指令的位置,也就是. + 8。那么上面的第一条指令ldr sp, [pc, #4]中的pc就是0x8,pc + 4就是0xc,而[0xc]的内容正是0x0c002000;同理,第二条ldr指令也是如此。显然这里LDR伪指令采用的是RISC通用的方法。
另外要说的是,如果LDR的是一个immed_8或者immed_8的反码数,则会直接被解释成mov或mvn指令。如ldr pc, = 0x0c000000会被解释成mov pc, 0x0c000000。
最后一点补充,我发现arm-elf-gcc通常都用累加法。如C语句中的i = 0x100ffc04;会变成类似于以下的语句:
mov r0, #0x10000004
add r0, r0, #0x000ff000
add r0, r0, #0x00000c00
...
原因不详。
添加的内容:
1 指令LDR
应用举例:
u LDR R0, [R1, #4] ;将内存单元R1+4中的字读取到R0寄存器
其中,R1为基址,#4为偏移地址,R0为目标地址。注意,此时不更新R1。
u LDR R0, [R1, #-4] ;将内存单元R1-4中的字读取到R0寄存器
u LDR R0, [R1, #4]! ;将内存单元R1+4中的字读取到R0寄存器。同时更新R1,R1=R1+4。
u LDR R0, [R1], #4 ;将地址为R1的内存单元数据读取到R0寄存器,然后R1=R1+4。
2 伪指令LDR
ARM中的伪指令不是真正的ARM指令或者Thumb指令,这些伪指令在汇编编译器对源程序进行汇编处理时,被替换为相应的ARM或者Thumb指令(序列)。
LDR伪指令将一个32位的常数或者一个地址值读取到寄存器中。
语法格式:
LDR{cond} register, =[expr | label-expr]
其中,register为目标寄存器
expr为32位的常量。编译器将根据expr的取值情况,如下处理LDR伪指令:
u 当expr所表示的地址值没有超过MOV或MVN指令中的地址取值范围时,编译器用合适的MOV或者MVN指令代替LDR伪指令。
应用举例:
将0xFF0读取到R1中
LDR R1, =0xFF0
汇编后得到:
MOV R1, 0xFF0
u 当expr表示的地址值超过了MOV或者MVN指令中的地址的取值范围(第二操作数的取值范围)时,编译器将该常数放在数据缓冲区中,同时用一条基于PC的LDR指令读取该常数。
LDR R1,=0xFFF
汇编后得到:
LDR R1, [PC, OFFSET_TO_LPOOL]
…
LPOOL DCD 0xFFF
关于label-expr的介绍我不是很理解。不理解其中关于“连接重定位伪操作”。(P144)
声明:本文为我在学习杜春雷编著的《ARM体系结构与编程》时做的总结笔记,文中摘录了书中的很多内容。
补充2:
原文地址:http://hi.baidu.com/andylgh/blog/item/17dbdc1f7d102a62f624e4dc
说说这个.word的作用。 word expression就是在当前位置放一个word型的值,这个值就是expression 例如: ldr r1, _rWTCON |
上一篇:GNU ARM汇编入门
下一篇:S3C2440 clock 工作原理
推荐阅读最新更新时间:2024-03-16 14:51
设计资源 培训 开发板 精华推荐
- WEBENCH轻松注册,尽享精彩好礼!
- 逛东芝电子在线展会,上百份礼品等您拿!
- 有奖直播【如何在几分钟之内完成高效可靠的USB PD电源设计——PI Expert™分步教程】(9:30开始入场)
- Littelfuse【智能家居的电路保护方案】在线研讨会 11月22日10点 强势登场!预报名、参与研讨会赢好礼!
- 沁恒CH579M-R1开发板免费测评,丰富外设等你体验
- 阅1SP0350V SCALE-2单通道即插即用型门极驱动器,参与PI答题享好礼!
- 直播已结束|安世新一代高效的氮化镓(GaN)电源设计方案
- TI MCU 常见问题(FAQ)大搜集
- 了解 PI 全新 PowiGaN 开关电源 IC ,答题赢好礼!
- Littelfuse 高效 • 可靠 • 精准的功率控制和电路保护方案在工业、交通、通讯、医疗及新能源中的应用