ARM
RISC(精简指令集计算)处理器,因此具有简化的指令集(100条指令或更少)和比CISC更多的通用寄存器
ARM指令集可以分为以下六种
- 跳转指令
- 数据处理指令
- 程序状态寄存器传输指令
- Load/Store指令
- 协处理器指令
- 异常中断指令
基础知识
数据类型可以是signed and unsigned words, halfwords, or bytes.
-h(unsigned) -sh(signed) 表示 halfwords 2byte
-b -sb 表示 byte
其余表示word 4byte
则对应的加载和存储指令就是
ldr ldrh ldrsh ldrb ldrsb
str strh strsh strb strsb
并且支持双端序,实际中默认都是使用小端序。
关于arm寄存器,除了基于 ARMv6-M 和 ARMv7-M 的处理器外,有30个通用寄存器。前 16 个寄存器可在用户级模式下访问。
r0-r10 通用寄存器,r7保留syscall,r11帧指针(FP)栈底指针,r12内部过程调用,r13(栈顶),r14(LR),r15(PC)EIP
在x86中的寄存器的对应关系

PC总是在当前执行的指令之前获取两条指令
当前程序状态寄存器CPSR

如果设置了当前程序状态寄存器中的 T 位,我们就处于Tumble模式。
指令集
ARM处理器有两种主要状态,它们可以运行(我们在这里不计算Jazelle),ARM和Thumb。
如前所述,有不同的拇指版本。不同的命名只是为了将它们彼此区分开来(处理器本身将始终将其称为 Thumb)。
- Thumb-1(16位指令):用于ARMv6和更早的架构。
- Thumb-2(16 位和 32 位指令):通过添加更多指令并允许它们为 16 位或 32 位宽(ARMv6T2、ARMv7)来扩展 Thumb-1。
- ThumbEE:包括一些针对动态生成的代码(在执行前不久或执行期间在设备上编译的代码)的更改和添加。
ARM和Thumb之间的区别:
- 条件执行:ARM 状态中的所有指令都支持条件执行。某些 ARM 处理器版本允许使用 IT 指令在 Thumb 中进行条件执行。条件执行会导致更高的代码密度,因为它减少了要执行的指令数量并减少了昂贵的分支指令的数量。
- 32 位 ARM 和 Thumb 指令:32 位 Thumb 指令具有 .w 后缀。
- 枪管换档器是另一个独特的 ARM 模式功能。它可用于将多个指令缩小为一条。例如,您可以使用左移 1 -> Mov R1、R0、LSL #1 将乘法包含在 MOV 指令中,而不是使用两个指令进行乘法(将寄存器乘以 2 并使用 MOV 将结果存储到另一个寄存器中);R1 = R0 * 2(相当于位移指令可以和操作数2合并)
所以操作数2也可以称为灵活操作数
MOV R0, R1, LSL #1
MOVLE R0, #5
ADD R0, R1, #2
ADD R0, R1, R2
内存指令
汇编器默认把文字池放在每一个代码节的末尾处。代码节的末尾的确定或者是由汇编源文件尾部的。文本池是同一部分中的内存区域(因为文本池是代码的一部分),用于存储常量、字符串或偏移量
arm使用加载存储模型进行内存访问,也就是说,只有LDR和STR指令能够访问内存。数据必须在操作之前从内存移到寄存器中。所以,如果需要增加特定的内存地址处的值,需要“加载,递增,存储”三个类型的指令。
LDR R2, [R0]
将R0地址处的值加载到R2
STR R2, [R1]
将 R2 中的值存储到 R1 中地址
实际上的指令是:
#取地址保存到寄存器
ldr r0, adr_var1
ldr r1, adr_var2
#进行实际存储操作
ldr r2, [r0]
str r2, [r1]
相对寻址
ldr r0, [pc, #12]
缩放寄存器作为偏移
LDR Ra, [Rb,Rc, <shifter>]
STR Ra, [Rb,Rc, <shifter>]
此偏移形式的一个示例用法是循环遍历数组
str r2, [r1, r2, LSL#2]

取r2的值,向左(LSL)位移2位(#2),然后与r1寄存器地址相加,取偏移后的[R1],将r2的值送入[r1]地址中去

用于PC相对寻址的LDR
LDR可以将数据从存储加载到寄存器中,还可以引用word池中的数据
.section .text
.global _start
_start:
ldr r0, =jump
ldr r1, =0x68DB00AD
jump:
ldr r2, =511
bkpt
ldr r0, =jump
将函数标签为jump的地址存放入r0
ldr r1, =0x68DB00AD
将0x68DB00AD
放入r1寄存器中
jump函数中ldr r2, =511
则是将511放入r2寄存器
因为arm一次性只能加载一个8位值,即1byte,所以需要这个语法来进行32位常量的移动。这个机制由于arm的处理即时值的机制。
arm机立即数机制
每个立即数都是由一个8位的常循环右移偶数位得到。
v = n ror 2*r

所以存在有效立即数和无效立即数,为了能够一次性加载完32位地址,可以用下面两个方法绕过限制
- 使用ADD,将两个有效立即数相加
- 使用负载结构
LDR r1 ,=511
LDM STM
对多个值的加载
如果未另行指定,LDM 和 STM 将按字的步长(32 位 = 4 字节)进行操作。(WORD)
IA(之后增加)、-IB(增加之前)、-DA(之后减少)、-DB(之前减少)
例如:LDMIB 指令首先将源地址增加 4 个字节(一个字值),然后执行第一次加载。
条件EQual condition

例如
MOV R0, #2
CMP R0, #3
ADDLT RO, RO, #1
CMP R0, #3
ADDLT RO, RO ,#1
2-3=-1,cmp将negative位置设为1,没有发生溢出,V为0,满足ADDLT的N≠V,则R0=R0+1=#3
再将r0和#3比较,得到标志Z=0,N=0,未发生溢出,V=0,则不进行ADD,
Thumb版本
IT指令的结构是“IF-then-(Else)”,语法是两个字母T和E的结构:
- 它指的是 If-then(下一个指令是有条件的)
- ITT 指的是 If-then-then(接下来的 2 条指令是有条件的)
- ITE 指的是 If-then-Else(接下来的 2 条指令是有条件的)
- ITTE指的是If-then-then-Else(接下来的3条指令是有条件的)
- ITTEE指的是If-then-then-else-Else(接下来的4条指令是有条件的)

注意到IF的条件和then的指令条件必须是一致的,else的条件必须是if条件的逆
跳转指令
简单跳转:B
分支链接:BL,在LR中保存(PC+4),并跳转(有点类似jmp)
分支交换:BX和分支链路交换:BLX 会转换到Thumb模式(需要寄存器作为操作数)
堆栈及函数
前面提到的LDM和STM用于多个数据的存取,在arm中是实现堆栈存取的一种方法


在x86中使用的完全降序的栈结构,在这里如何实现的
MOV R0, #2
PUSH {R0} ; 此时SP-=4,R0的值入栈,存在sp所在的地址,也就是SP地址所指向的内存地址,内存地址存储了2
MOV R0, #3 ; 此时R0的值被破坏
POP {R0} ;将SP处的值出栈,SP所指向的的地址的存储的值2,保存到R0中,SP+=4
而FP帧指针设置为堆栈帧的底部,在FP的下面是返回地址LR
在进入函数前,首先需要进入准备工作,将LR和R11(即EBP和返回地址)存储到stack上,并让局部参数入栈
PUSH {R11, LR}
ADD R11,SP,#0 ;将sp初始化到栈顶(底)
sub sp,sp,16
函数的结果是通过R0返回的,在长度超过32的情况下,可以将r1和r0结合返回
函数的最后,将程序状态恢复。
SUB SP,R11,#0 ;相当mov esp,ebp
pop {r11,pc}
叶与非叶函数
叶函数是一种不从自身调用/分支到另一个函数的函数。非叶函数是一种函数,除了它自己的逻辑之外,它还调用/分支到另一个函数。
非叶函数在函数的准备工作时,需要将返回地址入栈
push {r11,lr}
而叶函数的准备工作,不需要将准备工作入栈
push {r11}
因为非叶函数在进入更深的函数调用时,返回地址LR会被修改,所以LR的值是需要被保留的,而叶函数不需要进行更深的函数调用,返回地址LR不会被修改,所以没有必要保留。
同样的,在函数的结束工作,非叶函数需要
pop {r11, pc}
而叶函数需要
pop {r11}
在使用BL跳转指令到叶函数的时候,标签将会被替换成内存地址,LR同时会被保存(隐式的)
而离开叶函数使用的BX会将LR作为参数,
BX 指令可以在分支操作期间在 ARM/Thumb 模式之间进行扩展交换。在这种情况下,它是通过检查 LR 寄存器的最后一位来完成的:如果该位设置为 1,CPU 会将模式更改为(或保持)为 thumb,如果设置为 0,则模式将更改为(或保留)为 ARM。这是一个很好的设计功能,允许从不同的模式调用函数。
接下来用两个函数进行详细解释:
;非叶函数
MAIN:
PUSH {R11, LR} ;将返回地址和栈底地址入栈
ADD R11, SP , #0 ;将栈顶指针初始化
SUB, SP, SP, #16 ;开辟栈空间
MOV R0, #1 ;寄存器作为参数
MOV R1, #2
BL MAX ;max标签将会转换为函数的内存地址,同时将LR入栈
SUB SP, R11, #0 ;将栈顶指针恢复到栈底
POP {R11 ,PC} ;结束栈的工作
;叶函数
MAX:
PUSH {R11} ;将栈底入栈
ADD R11, SP, #0 ;将栈顶指针初始化
SUB SP , SP, #12 ;开辟栈空间
CMP R0, R1 ;比较大小
MOVLT R0, R1 ;1-2=-1,标志位N=1,V=0,所以此时r0=r1=2
ADD SP, R11, #0 ;恢复栈顶到栈底
POP {R11} ;恢复栈底,结束栈的工作
BX LR ;跳转到BL所保存的LR
Comments | NOTHING