arm汇编

(汇编)指令是CPU机器指令的助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。
(汇编)伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码。

  ARM官方的ARM汇编风格:指令一般用大写、Windows中IDE开发环境(如ADS、MDK等)常用。如: LDR R0, [R1]

  GNU风格的ARM汇编:指令一般用小写字母、linux中常用。如:ldr r0, [r1]

  ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。
  ldr(load register)指令将内存内容加载入通用寄存器。
  str(store register)指令将寄存器内容存入内存空间中。
  ldr/str组合用来实现 ARM CPU和内存数据交换

  寄存器寻址 mov r1, r2
  立即寻址mov r0, #0xFF00
  寄存器移位寻址mov r0, r1, lsl #3
  寄存器间接寻址ldr r1, [r2]
  基址变址寻址ldr r1, [r2, #4]
  多寄存器寻址ldmia r1!, {r2-r7, r12}
  堆栈寻址stmfd sp!, {r2-r7, lr}

  相对寻址 beq flag

  同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:
  B(byte)功能不变,操作长度变为8位
  H(half word)功能不变,长度变为16位
  S(signed)功能不变,操作数变为有符号
  如 ldr ldrb ldrh ldrsb ldrsh
  S(S标志)功能不变,影响CPSR标志位
  如 mov和movsmovs r0, #0

?
1
2
3
4
5
6
7
  mov r0, r1 @ 相当于C语言中的r0 = r1;
  moveq r0, r1  @ 如果eq后缀成立,则直接执行mov r0, r1;如果eq不成立则本句代码直接作废,相当于没有
  @ 类似于C语言中 if (eq){r0 = r1;}
  条件后缀执行注意2点:
1、条件后缀是否成立,不是取决于本句代码,而是取决于这句代码之前的代码运行后的结果。
2、条件后缀决定了本句代码是否被执行,而不会影响上一句和下一句代码是否被执行。

  

GT greater than
LT less than

数据传输指令 mov mvn
算术指令add sub rsb adc sbc rsc
逻辑指令and orr eor bic
比较指令cmp cmn tst teq
乘法指令mvl mla umull umlal smull smlal
前导零计数clz

mrs & msr

mrs用来读psr,msr用来写psr
CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr。

  b & bl & bx

b 直接跳转(就没打开算返回)
bl branch and link,跳转前把返回地址放入lr中,以便返回,以便用于函数调用
bx跳转同时切换到ARM模式,一般用于异常处理的跳转。

bne 指令 检测到Z!=0 时 执行跳转

beq 指令 检测到Z   =0 时 执行跳转

ldr/str & ldm/stm & swp

单个字/半字/字节访问 ldr/str
多字批量访问 ldm/stm
swp r1, r2, [r0]
swp r1, r1, [r0]

合法立即数与非法立即数

ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数。因此立即数有合法和非法之分。
合法立即数:经过任意位数的移位后非零部分可以用8位表示的即为合法立即数

  swi(software interrupt)

  软中断指令用来实现操作系统中系统调用

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
mov(move) mov r1, r0 @两个寄存器之间数据传递
mov r1, #0xff   @ 将立即数赋值给寄存器
mvn和mov用法一样,区别是mov是原封不动的传递,而mvn是按位取反后传递
按位取反的含义:
譬如r1 = 0x000000ff,然后mov r0, r1 后,r0 = 0xff  但是我mvn r0, r1后,r0=0xffffff00
and 逻辑与
orr 逻辑或
eor 裸机异或
bic 位清除指令
bic r0,r1,#0x1f @ 将r1中的数的bit0到bit4清零后赋值给r0  0x1f = 0x0000001f=0x0000```11111
比较指令:
cmp cmp r0, r1 等价于 sub r2, r0, r1 (r2 = r0 - r1)
cmn cmn r0, r1 等价于 add r0, r1
tst tst r0, #0xf    @测试r0的bit0~bit3是否全为0
teq
比较指令用来比较2个寄存器中的数
注意:比较指令不用后加s后缀就可以影响cpsr中的标志位。
cpsr和spsr的区别和联系:cpsr是程序状态寄存器,整个SoC中只有1个;而spsr有5个,分别在5种异常模式下,作用是当从普通模式进入异常模式时,用来保存之前普通模式下的cpsr的,以在返回普通模式时恢复原来的cpsr。
合法立即数: 0x000000ff   0x00ff0000 0xf000000f
非法立即数: 0x000001ff

  

mcr & mrc

mrc用于读取CP15中的寄存器
mcr用于写入CP15中的寄存器

  SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。
  ARM设计上支持多达16个协处理器,但是一般SoC只实现其中的CP15.(cp:coprocessor)
  协处理器和MMU、cache、TLB等处理有关,功能上和操作系统的虚拟地址映射、cache管理等有关。

mcr{<cond>} p15, <opcode_1>, <Rd>, <Crn>, <Crm>, {<opcode_2>}
opcode_1:对于cp15永远为0
Rd:ARM的普通寄存器
Crn:cp15的寄存器,合法值是c0~c15
Crm:cp15的寄存器,一般均设为c0
opcode_2:一般省略或为0

举例(来自于uboot)

mrc p15, 0, r0, c1, c0, 0
orrr0, r0, #1
mcr p15, 0, r0, c1, c0, 0

  dr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ldm
  ldm(load register mutiple)
  stm(store register mutiple)

  举例(uboot start.S 537行)
  stmiasp, {r0 - r12}
  将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址······直到r12内容放入(0x3001030),指令完成。
  一个访存周期同时完成13个寄存器的读写

8种后缀
  ia(increase after)先传输,再地址+4
  ib(increase before)先地址+4,再传输
  da(decrease after)先传输,再地址-4
  db(decrease before)先地址-4,再传输
  fd(full decrease)满递减堆栈
  ed(empty decrease)空递减堆栈
  fa(·······) 满递增堆栈
  ea(·······)空递增堆栈
四种栈
  空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出
  满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针
  增栈:栈指针移动时向地址增加的方向移动的栈
  减栈:栈指针移动时向地址减小的方向移动的栈

ldmia r0, {r2 - r3}
ldmiar0!, {r2 - r3}

感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。

ldmfd sp!, {r0 - r6, pc}
ldmfdsp!, {r0 - r6, pc}^

^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。

总结

  批量读取或写入内存时要用ldm/stm指令
  各种后缀以理解为主,不需记忆,最常见的是stmia和stmfd
  谨记:操作栈时使用相同的后缀就不会出错,不管是满栈还是空栈、增栈还是减栈

伪指令不是指令,伪指令和指令的根本区别是经过编译后会不会生成机器码。
伪指令的意义在于指导编译过程。
伪指令是和具体的编译器相关的,我们使用gnu工具链,因此学习gnu环境下的汇编伪指令。

gnu汇编中的一些符号

@ 用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//类似
# 做注释,一般放在行首,表示这一行都是注释而不是代码。
:以冒号结尾的是标号
. 点号在gnu汇编中表示当前指令的地址
# 立即数前面要加#或$,表示这是个立即数

常用gnu伪指令

.global _start@ 给_start外部链接属性
.section .text@ 指定当前段为代码段
.ascii .byte .short .long .word
.quad .float .string @ 定义数据
.align 4@ 以16字节对齐
.balignl 16 0xabcdefgh @ 16字节对齐填充

偶尔

.end@标识文件结束
.include@ 头文件包含
.arm / .code32@声明以下为arm指令
.thumb / .code16@声明以下为thubm指令

最重要的几个伪指令

ldr大范围的地址加载指令
adr小范围的地址加载指令
adrl中等范围的地址加载指令
nop空操作

ARM中有一个ldr指令,还有一个ldr伪指令
一般都使用ldr伪指令而不用ldr指令

adr与ldr

adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理;
adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址在哪里
ldr加载的地址和链接时给定的地址有关,由链接脚本决定。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int a;
while(1);
flag:
b flag
b .
IRQ_STACK_START:
.word   0x0badc0de
等价于 unsigned int IRQ_STACK_START = 0x0badc0de;
.align 4    @ 16字节对齐
.align 2    @ 4字节对齐
.balignl 16, 0xdeadbeef @ 对齐 + 填充
b表示位填充;align表示要对齐;l表示long,以4字节为单位填充;16表示16字节对齐;0xdeadbeef是用来填充的原料。
0x00000008: .balignl 16, 0xdeadbeef
0x0000000c  0xdeadbeef
0x00000010: 下一条指令
ldr指令:  ldr r0, #0xff
伪指令:    ldr r0, =0xfffl @涉及到合法/非法立即数,涉及到ARM文字池
adr和ldr的差别:ldr加载的地址在链接时确定,而adr加载的地址在运行时确定;所以我们可以通过adr和ldr加载的地址比较来判断当前程序是否在链接时指定的地址运行。

  

(0)

相关推荐