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