咱们知道ARM CPU中有一条被广泛运用的指令LDR,它主要是用来从存储器(切当地说是地址空间)中装载数据到通用寄存器。但不论是ARMASM仍是GNU ARM AS,都供给了一条与之同名的伪指令LDR,而在实践中运用该伪指令的状况也较多,那他们有什么不同呢?下面我谈谈我的了解。
因为我运用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 翻译成intel的汇编句子便是: 例如: ldr r1, _rWTCON _rWTCON: |