ARM指令集简介

指令和伪指令概念

指令
指令指的是CPU机器指令的助记符,是由CPU的指令集提供的,经过编译之后,会以二进制机器码的形式由CPU读取执行

伪指令
伪指令本质上不是指令,和CPU的机器指令没有任何关系,只是和指令一起写在代码中而已,是由编译器环境提供的,其目的是用于指导编译过程,伪指令经过编译后不会生成二进制机器码,仅仅在编译阶段有效果

指令编程风格
ARM官方风格
官方风格指令一般使用大写,例如:LDR R0,[R1],Windows中常使用这种风格

GUN Linux风格
指令一般使用小写字母,例如:ldr r0,[r1],Linux环境中常用这种风格

ARM汇编特点

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

8种寻址方式
寄存器寻址:move r1,r2:把r2的值赋值到r1寄存器中

立即寻址:move r0,#0xFF00:把立即数0xFF00赋值给r0寄存器

寄存器移位寻址:move r0,r1,lsl #3:把r1左移三位(*8)之后的值赋值给r0寄存器

寄存器间接寻址:ldr r1,[r2]:寄存器有中括号,表示内存地址对应的数据,所以这里r2表示一个内存地址,[]表示取r2指针对应的数据,这句代码的意思是把r2对应的内存中的数据赋值给r1

基址变址寻址:ldr r1,[r2,#4]:将指针r2的值(内存地址)+4之后指向的数据赋值给r1

多寄存器寻址:ldmia r1!,{r2 - r7,r12}:这种情况下,r1是一个指针,里边存放的内存地址,然后以r1里边的内存地址为基地址,向后以此加1得到{}里的寄存器数量个内存地址,然后将刚才得到的这些内存地址指向的变量的值赋值给{}里的对应位置的寄存器,类似从内存中读取数组,然后把数组的元素依次赋值给这些寄存器

堆栈寻址:stmfd sp!,{r2 - r7,lr}:和多寄存器类似,区别是将栈SP中连续访问{}数量个字节,然后依次赋值给{}里的寄存器

相对寻址:beq flag::flag:标号用于标记标号后面那句指令的地址,常用来表示入口点,函数名就是一个标号,C语言中的goto就可以跳转到一个标号,在ARM汇编中用指令b flag:就可以跳转到flag:对应的标号处执行,和beq flag:是一样的,其原理是相对于PC程序位置寄存器做一个偏移

指令后缀

ARM中的指令可以带后缀,从而丰富该指令的功能,这种形式叫做指令族,常用的后缀有:
B(byte):功能不变,操作长度变为8位(依赖CPU位数,以下相同)
H(Halfword):功能不变,操作长度变为16位
S(signed):功能不变,操作数变为有符号数
S(S标识):影响CPSR里的NZCV标识位,

举例:
ldr指令族:ldrb,ldrh,ldrsb ldrsh,从内存中加载指定长度的数据
mov指令族:movs r0,#0,结果是0,赋值会影响CPSR的NZCV标识,将Z位置为1
条件执行后缀
条件执行后缀用于限制该执行执行的,只有在符合条件之后才能够执行该指令

举例:moveq r0,r1,如果eq成立,执行mov r0,r1,不成立则该条不执行,和C语言中的条件判断类似
条件后缀成立与否,不是取决于本条指令,而是取决于之前指令运行后的结果
条件后缀决定了本条指令是否执行,不会影响之前和之后指令
条件后缀和CPSR的NZCV位相关,例如,如果上一句代码执行的结果将Z置为1,下一句带有eq条件后缀的语句就会被执行

多级指令流水线

多级流水线用于增加处理器处理指令的速度,
允许CPU同时异步的执行多条指令,而非上一条指令全部执行完毕之后才会执行下一条指令
多级可以简单那理解为把一条指令分为多个步骤来异步执行,例如:
CPU把一条指令分为[取址,解码,执行]3个步骤,则为3级指令流水线
第一条指令进行取值操作
第一条指令取值完毕,进入解码操作,第二条指令紧随其后就开始执行取值操作
第一条指令解码完毕,进入执行操作,第二条指令紧接着进入解码操作,同时第三条指令进入取值操作
第一条指令执行完毕,第二条指令进入执行操作,第三条指令进入解码操作,第四条指令进入取值操作,依次类推
可见,多级流水线可以提高同时执行指令的数量,从而加速指令执行
需要注意的是,PC指向的是正在取值的指令,而非正在执行的指令,之间的差值就是流水线级数和单字节长度的乘积,在中断返回到PC的时候需要注意这个问题
ARM指令
数据处理指令
数据传输指令
mov:move,在两个寄存器之间或者立即数和寄存器之间传递数据,将后一个寄存器上的值或者立即数赋值给前一个寄存器
例如:mov r1,r0
mov r1,#0xFF:将立即数0xFF赋值给寄存器r1
mvn:和mov用法一致,区别是mvn会把后一个寄存器的值或者立即数按位取反后赋值给前一个寄存器
例如:mvn r0,#0xFF,则r0的值为0xffffff00(32位数据)
算术运算指令
add:加法运算
sub:减法运算
rsb:反减运算
adc: 带进位的加法运算
sbc: 带进位的减法运算
rsc:带进位的反减指令
逻辑指令
and:与操作
orr:或操作
eor:异或操作
bic:位清除操作
比较指令
cmp:比较大小
cmn:取反比较
tst:按位与运算
teq:按位异或运算
乘法指令
mvl: mla: umull: umlal: smull: smlal:
前导0计数
clz:统计一个数的二进制位前面有几个0
CPSR访问指令
mrs
用于读取CPSR和SPSR

msr
用于写CPSR和SPSR

CPSR和SPSR
CPSR是程序状态寄存器,整个Soc只有一个
SPSR在五种异常模式下各有一个,用于从普通模式进入异常模式的时候,保存普通模式下的CPSR,在返回普通模式时可以恢复原来的CPSR
跳转分支指令
b指令: 无条件直接跳转,没打算返回
bl指令:跳转前把返回地址放入lr中,以便返回,常用在函数中
bx指令:跳转同时切换到ARM模式,用于异常处理的跳转
内存访问指令
ldr:加载指定内存地址的数据到寄存器,按照字节访问
str:加载指定寄存器数据到内存地址中,按照字节访问
ldm:和ldr功能一样,一次多字节多寄存器访问
stm:和str功能一样,一次多字节多寄存器访问
swp:内存和寄存器互换指令,一边读一边写,例如:
swp r1,r2,[r0]:读取指针r0的数据到r1中,同时把r2的数据赋值给r0指针指向的变量
软中断指令
swi(software interrupt),在软件层模拟产生一个中断,这个中断会传送给CPU,常用于实现系统调用

立即数
非法与合法
ARM指令都是32为,除了指令标记和操作标记外,只能附带少位数的立即数,所以有非法与合法之分

非法立即数:
合法立即数:经过任意位数的移位后,非0部分可以用8位表示就是合法立即数

协处理器与指令

协处理器
协处理器属于Soc中另外一颗核心,用于协助主CPU实现某些功能,被主CPU调用来执行任务,协处理器和MMU,Cache,TLB有功能和管理上的联系
ARM设计可以支持多达16个协处理器,但是一般只实现其中的CP15

协处理器指令
mrc:读取CP15中的寄存器
mcr:向CP15中的寄存器写数据
指令用法: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
ldm,stm和栈
ldm,stm
ldr与str只能访问4个字节,当数据较大的时候,就会明显的降低效率,这时就需要使用到ldm和stm,ldm与stm是大量的从寄存器与内存交换数据的方式,常用于在内存和寄存器之间大量读取和写入数据:

stmia sp {r0 - r12}:stm表示进行批量数据操作,ia的意思是将r0存入SP的内存地址处,然后SP内存地址+4(32位),将r1存入该地址,内存地址再+4,存入r2,依次存到r12,
这就是一个寄存器和内存交换大量数据的示例,在一个周期内完成了多个内存地址和多个寄存器的操作。
除了ia后缀,还有多个不同功能的后缀:
ia:increase after,后增加,表示每个操作的时候,先传输数据,后增加内存地址,
ib:increase before,先增加,表示在每个操作的时候,先增加内存地址,再进行数据传输
da:decrease after:和ia一样,差别在于减少地址
db:decrease before:和ib一样,差别在于减少地址
fd:full decrease:满递减堆栈,查看栈的描述
ed:empty decrease:空递减堆栈
fa:满递增堆栈
ea:空递增堆栈
操作栈时使用相同的后缀就不会出错
堆栈(栈)
空栈:栈指针指向空位,每次可以直接存入,然后栈指针(SP)递增或者递减1格,取的时候要递增或者递减1格才能取出
满栈:SP指向栈最后1格数据,存入的时候需要先移动1格才能存入,取的时候可以直接取出
增栈:SP向地址增加的方向移动
减栈:SP向地址减少的方向移动
组合起来就可以成为空增/减栈,满增/减栈。四种形式

汇编中的符号

!号的作用
在汇编中常见”!”符号,究竟是用来干嘛的呢?
例如:

ldmia r0,{r2 - r3}:数据传输完毕之后指针r0的内存地址值还是第一次操作之前的值
ldmia r0!,{r2 - r3}:数据传输完毕之后指针r0的内存地址值是最后一次操作后的值
^号的作用
那么^号又是什么作用呢?
例如:

ldmfd sp!,{r0 - r6,pc}
ldmfd sp!,{r0 - r6,pc}^
作用:当目标寄存器中有pc时,会同时将SPSR写入到CPSR,一般用于从异常返回。

伪指令
伪指令的作用用于指导编译过程,伪指令并不是指令,编译后并不会生成二进制机器码,伪指令是和具体的编译环境有关的,我们使用的GNU编译工具链,所以需要使用GNU下的汇编伪指令。

GNU汇编符号
[@,#,//,/~/]:注释,和C语言的//是一样的
::冒号,在汇编中以冒号结尾的是标号,标号标记标号后面的指令的地址,
.:点号,代表当前指令的地址,例如:[b .]指令会进入死循环
#:#或者$号后面跟着数值,代表一个立即数(不区分进制)
伪指令
.global _start:给_start外部可链接属性,可以在外部文件中访问_start
.section .text:指定当前段为代码段
.ascii .byte .short .long .word .quad .float .string:定义各种类型的数据
.align 4:以16字节对齐
.balignl 16 0x3C:b表示填充,align表示对齐,l表示long,以4字节为单位填充,16表示以16字节对齐,0x3C是用来填充的原料
.equ:宏定义
.end:表示一个文件的结束
.include:用于包含头文件
.arm / .code32:声明以下的代码是arm指令
.thumb / .code16:声明以下的代码是thumb指令
比较重要的伪指令
nop:空操作,什么也不执行
ldr:大范围地址加载指令,把内存地址加载到寄存器中,和ldr指令是有区别的
ldr指令附带立即数要考虑合法性问题,只能带合法立即数,带的立即数前缀#号
ldr伪指令,不用考虑立即数合法问题,带的立即数前面是一个=号,借助了编译器环境文字池的帮助,帮忙加载非法立即数
ldr可以加载的地址比较广,加载的地址和链接时给的地址有关,由连接脚本决定,在链接时确定,编译时会被mov或者以文字池方式处理
adr:小范围地址加载指令,可加载的范围比较小
adr:总是以P C为基准来表示地址,则该地址为相对地址,要在执行的时候才能确定,因此该指令和运行地址有关,可以用于检测程序当前运行地址在哪里,编译时会被sub或者add替代
adrl:中等范围地址加载指令

猜你喜欢

转载自blog.csdn.net/qq_38131812/article/details/84706625