1、ARM汇编指令集
1.1、两个概念:指令与伪指令
(汇编) 指令是CPU机器指令的助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。
(汇编)伪指令本质上不是指令 (只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码。
1.2、两种不同风格的ARM指令
ARM官方的ARM汇编风格::指令一般用大写、Windows中IDE开发环境 (如ADS、MDK等) 常用。如:LDR RO,[R1]。
GNU风格的ARM汇编::指令一般用小写字母、linux中常用。如::ldr ro,[r1]。
1.3、ARM汇编特点
1.3.1、LDR/STR架构
ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。
ldr (load register) 指令将内存内容加载入通用寄存器。
str (store register) 指令将寄存器内容存入内存空间中。
ldr/str组合用来实现 ARM CPU和内存数据交换。
1.3.2、8种寻址方式
寄存器寻址 mov r1, r2
立即寻址 mov r0,#OxFF00
寄存器移位寻址 mov r0, r1, Isl #3
寄存器间接寻址 ldr r1, [r2]
基址变址寻址 ldr r1, [r2, #4]
多寄存器寻址 ldmia r1!, {r2-r7, r12}
堆栈寻址 stmfd sp!, {r2-r7, lr}
相对寻址 flag:beq flag
1.3.3、指令后缀
同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:
B (byte) 功能不变,操作长度变为8位
H (half word)功能不变,长度变为16位
S(signed)功能不变,操作数变为有符号
例如:ldr ldrb ldrh ldrsb ldrsh
S(S标志)功能不变,影响CPSR标志位。
例如:mov和movs
1.3.4、条件执行后缀
1.3.5、多级指令流水线
为增加处理器指令流的速度,ARM使用多级流水线,下图为3级流水线工作原理示意图。(S5PV210使用13级流水线,ARM11为8级)
- 允许多个操作同时处理,而非顺序执行。
PC指向正被取指的指令,而非正在执行的指令。
1.4数据传输和跳转指令集
1.4.1、数据处理指令
数据传输指令 mov mvn
对比:mov是原封不动的传递,而mvn是按位取反后传递
算术指令 add sub rsb adc sbc rsc
逻辑指令 and orr eor bic
比较指令 cmp cmn tst teq
对比:比较指令用来比较两个寄存器中的数;比较指令不用后加s后缀就可以影像CPSR中的标志位。
乘法指令 mvl mla umull umlal smull smla
前导零计数 clz
1.4.2、CPSR访问指令
mrs&msr
mrs用来读psr,msr用来写。
CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr。
1.4.3、跳转(分支)指令
b & bl & bx
b 直接跳转 (就没打开算返回)
bl(branch and link),跳转前把返回地址放入lr中,以便返回,以便用于函数调用
bx跳转同时切换到ARM模式,一般用于异常处理的跳转
1.4.4、访存指令
ldr/str & ldm/stm & swp
单个字/半字/字节访问 ldr/str
多字批量访问 Idm/stm
swp r1, r2, [r0]
swp r1, r1, [r0]
1.4.5、立即数
在数字之前要加#,机器才知道这是个数字
合法立即数与非法立即数
ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数。因此立即数有合法和非法之分。
合法立即数:经过任意位数的移位后非零部分可以用8位表示的即为合法立即数。
1.4.6、软中断指令
swi(software interrupt)
软中断指令用来实现操作系统中系统调用
1.5、协处理器和协处理器指令
1.5.1、什么是协处理器
SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。
ARM设计上支持多达16个协处理器,但是一般SoC只实现其中的CP15(cp:coprocessor)。
协处理器和MMU、cache、TLB等处理有关功能上和操作系统的虚拟地址映射、cache管理等有关。
1.5.2、协处理器cp15操作指令
mcr & mrc
mrc用于读取CP15中的寄存器
mcr用于写入CP15中的寄存器
1.5.3、mrc & mcr的使用方法
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
1.5.4、协处理器学习要点
不必深究,将uboot中和kernel中起始代码中的一般操作搞明白即可。
只看一般用法,不详细区分参数细节,否则会陷入很多复杂未知中。
关键在于理解,而不在于记住。
1.6、ldm/stm与栈的处理
1.6.1、为什么需要多寄存器访问指令
ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ldm。
ldm(load register mutiple)
stm(store register mutiple)
1.6.2、八种后缀
ia (increase after) 先传输,再地址+4
ib (decrease before) 先地址+4,再传输
da (decrease after) 先传输,再地址-4
db (decrease before) 先地址-4,再传输
fd (full decrease) 满递减堆栈
ed (empty decrease) 空递减堆栈
fa (......) 满递增堆栈
ea (......) 空递增堆栈
1.6.3、四种栈
空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出。
满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针。
增栈:栈指针移动时向地址增加的方向移动的栈。
减栈:栈指针移动时向地址减小的方向移动的栈。
1.6.4、!的作用
ldmia r0, {r2-r3}
ldmia ro!, {r2-r3}
感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。
1.6.5、^的作用
ldmfd sp!, {r0-r6,pc}
ldmfd sp!, {r0-r6,pc}^
在目标寄存器中有pc时,会同时将spsr写入cpsr,一般用于异常模式返回。
1.6.6、总结
批量读取或写入内存时要用ldm/stm指令。
各种后缀以理解为主,不需记忆,最常见的是stmia和stmfd。
谨记:操作栈时使用相同的后缀就不会出错,不管是满栈还是空栈、增栈还是减栈。
1.7、伪指令
1.7.1、伪指令的意义
伪指令不是指令,伪指令和指令的根本区别是经过编译后会不会生成机器码。
伪指令的意义在于指导编译过程。
伪指令是和具体的编译器相关的,我们使用gnu工具链,因此学习gnu环境下的汇编伪指令。
1.7.2、gnu汇编中的一些符号
@ 用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//类似。
# 做注释,一般放在行首,表示这一行都是注释而不是代码。
: 以冒号结尾的是标号。
. 点号在gnu汇编中表示当前指令的地址。
# 立即数前面要加#或$,表示这是个立即数。
1.7.3、常用伪指令
.global _start @ 给_start声明外部链接属性
.section .text @ 指定当前段为代码段
.ascii .byte .short .long .word .quad .float .string @ 定义变量数据
.align 4 @ 以16字节对齐
.balignl 16 0xabcdefgh @ 16字节对齐填充
@b表示位填充;align表示要对齐;l表示long,以4字节为单位填充;16表示16字节对齐;0xabcdefgh是用来填充的原料。
.equ @ 类似于C中宏定义
1.7.4、偶尔会用到的gpu伪指令
.end @标识文件结束
.include @头文件包含
.arm/.code32 @声明以下为arm指令
.thumb/.code16 @声明以下为thubm指令
1.7.5、最重要的几个伪指令
ldr 大范围的地址加载指令
adr 小范围的地址加载指令
adrl 中等范围的地址加载指令
nop 空操作
ARM中有一个ldr指令,还有一个ldr伪指令,一般都是用ldr伪指令而不是ldr指令。
ldr r0, #0xff @ldr指令
ldr r0, =0xff @伪指令
1.7.6、adr与ldr
adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理。
adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址在哪里。
ldr加载的地址和链接时给定的地址有关,由链接脚本决定。