寄存器
寄存器的数量取决于ARM版本,
- ARM32有30个通用寄存器(基于ARMv6-M和基于ARMv7-M的处理器除外),前16个寄存器可在用户级模式下访问,其他寄存器可在特权软件执行中使用
- 其中,r0-15寄存器可在任何特权模式下访问。这16个寄存器可以分为两组:通用寄存器(R0-R11)和专用寄存器(R12-R15)
- ARM32下寄存器与X86架构下寄存器大致的对照表(R7在处理系统调用时用来存储系统调用号)
- ARM64位参数调用规则遵循AAPCS64
字节序
大端模式(MSB)以及小端模式(LSB)均有指令集
在ARM架构下有两种指令集,ARM和Thumb,ARM状态下的指令为32位,Thumb状态下的指令为16位,但是现在引入了增强的Thumb指令集(Thumbv2),该指令集允许32位Thumb指令甚至条件执行
由于四字节或二字节对齐,指令地址的最低位永远是0,所以用地址最低位来区分ARM和Thumb指令,如果使用分支指令BX(分支和交换)或BLX(分支,链接和交换)跳转的时候跳转到0xNNNNNNN1,则进入Thumb模式;如果跳转的时候跳转到0xNNNNNNN0,则进入ARM模式,并修改程序状态寄存器中的T位置来区别两种模式
ARM和Thumb之间的区别:
- 条件执行:ARM状态下的所有指令均支持条件执行。某些ARM处理器版本允许使用“it”指令在Thumb中有条件执行。
- 32位ARM和Thumb指令:32位Thumb指令带有.w后缀。
- 桶式移位器(barrel shifter)是ARM模式的另一个独特功能。它可以用于将多个指令缩小为一个。例如,您可以使用左移,而不是使用两条指令,将寄存器乘以2并使用mov将结果存储到另一个寄存器中:mov r1, r0, lsl #1 ; r1 = r0 * 2
ARM指令一般采用以下模式
指令 {S} {条件码} {目标寄存器},Operand1,Operand2
(并非所有指令都使用模板中提供的所有字段)
其中Operand2被称为灵活操作数,可以用一些表达式来充当Operand2
比如以下的寄存器间操作
主要的指令有
条件码有
例如:ADDHI R0, R0, #1 ;若R0 > R1, 则R0 = R0 + 1
ARM使用加载存储模型进行内存访问,通过ldr、str这类指令 加载内存中的数据到寄存器、保存寄存器数据到内存
(带符号的数据类型可以同时包含正值和负值,因此范围较小;无符号数据类型可以容纳较大的正值(包括“零”),但不能容纳负值,因此范围更广)
而批量存取数据使用ldm、stm
如
stmdb sp!,{r0,r1} ;push {ro,r1}
ldria sp!,{r4,r5} ;pop {r4,r5}
;后面的"db"与"ia"在后面介绍
(“!”代表存取结束后移动指针,如果不用”!”,则只是简单地在内存中存取数据)
ARM32与ARM64常用指令对应关系
(在编写ARM shellcode时,我们需要摆脱NULL字节,并使用16位Thumb指令而不是32位ARM指令来减少使用它们的机会)
堆栈结构
四种栈中
ARM32和ARM64都是满递减堆栈(FD)
- 堆栈指针指向最后压入栈的有效数据项,每次存入时需要先移动栈指针再存入;取出时先取出,然后再移动栈指针
- 随着数据的入栈,SP指针从高地址->低地址移动
出入栈一般使用LDMIA(Increase/After)/STMDB(Decrease/Before),可以理解成increase pointer after lord and decrease pointer before store,也就是满递减堆栈的操作LDMFD/STMFD,但事实上在汇编的过程中都转化为了pop/push
函数调用
在ARM ATPCS中规定,函数调用时前四个参数用寄存器R0-R3来传递,剩下还有参数的话则与x86下一样保存在栈中
在main函数调用func1时(func1是一个非叶子函数,arm架构下也有叶子函数与非叶子函数之分,与上面mips下概念一样),程序的栈帧如下所示
可以看到,在调用函数时,返回地址往上就是第五个参数
子程序返回32位的整数,使用R0返回;返回64位整数时,使用R0返回低位,R1返回高位
但是并不是所有函数调用都需要先执行push {fp, ip, lr, pc},如果在子函数调用过程中不会去改变这些值(也就是在调用叶子函数的时候),就不需要压栈,而是直接sub sp,sp,#N分配栈空间
需要注意的是在ARM汇编中对字符串的引用
在引用字符串的时候,PC 指针作为基址寄存器寻址
0xF31EF494 LDR R3, =(a_so - 0xF31EF4A0) ;R3=0x00002B78
0xF31EF498 ADD R3, PC, R3 ; " \x03\x01" ;R3=0xF31F2018
0xF31EF49C LDR R3, [R3] ; " \x03\x01" ;
0xF31EF4A0 CMP R3, #0 ;
0xF31EF4A4 BNE loc_F31EF
在执行到ADD R3, PC, R3时,PC寄存器的值为0xF31EF49C,但是由于流水线效应已经对0xF31EF4A0的指令取指了,所以在实际执行ADD指令运算的时候是将0xF31EF4A0作为PC的值计算的,所以结果就是0xF31EF4A0+0x2B78,也就是0xF31F2018