二者之间的区别主要体现在以下几个方面:
1. 架构
Arm32位是ARMV7架构;
ARM64位采用ARMv8架构。
2. 指令编码长度
A32模式(ARM instruction sets),指令固定的编码长度为32bit;
T32模式(Thumb instruction sets),指令可以编码成16bit长,也可编码成32bit长;
A64模式(AArch64 instruction sets),指令固定的编码长度为32bit
3.当前指令的地址(PC)
由于ARM指令的三级流水线执行(ARM处理器使用流水线来增加处理器指令流的速度,这样可使几个操作同时进行,并使处理与存储器系统之间的操作更加流畅连续),程序计数器R15(PC)总是指向“正在取指”的指令,如下图,而不是指向“正在执行”的指令或者正在“译码”的指令。
在ARM32状态下,当前执行指令的地址通常是pc-8,而在Thumb状态下通常是pc-4。
在AARCH64状态下,当前执行指令的地址通常是pc。
Program counter
The current Program Counter (PC) cannot be referred to by number as if part of the general register file and therefore cannot be used as the source or destination of arithmetic instructions, or as the base, index or transfer register of load and store instructions.
The only instructions that read the PC are those whose function it is to compute a PC-relative address (ADR, ADRP, literal load, and direct branches), and the branch-and-link instructions that store a return address in the link register (BL and BLR). The only way to modify the program counter is using branch, exception generation and exception return instructions.
Where the PC is read by an instruction to compute a PC-relative address, then its value is the address of that instruction. Unlike A32 and T32, there is no implied offset of 4 or 8 bytes.
在ARM64下,PC的值只能间接读取或改变:
- 64位可读取PC值的情况有:计算相对地址,如adr,adrp,文字池加载以及直接分支;子程序返回地址,比如bl,blr
- 可修改pc的方式为:使用控制流指令,如条件跳转、无条件跳转、异常生成和异常返回指令。
4. 参数的传递和访问
arm32下,前4个参数是通过r0~r3传递,大于4个的参数通过栈来传递(即第4个参数需要通过sp访问,第5个参数需要通过sp + 4 访问,第n个参数需要通过sp + 4*(n-4)访问)。
arm64下,前8个参数是通过x0~x7传递,大于8个的参数通过栈来传递(第8个参数需要通过sp访问,第9个参数需要通过sp + 8 访问,第n个参数需要通过sp + 8*(n-8)访问)。
5. 寄存器和寻址
(1)寻址
ARM微处理器采用的是精简指令集,指令间的组合灵活。
ARM微处理器支持九种寻址方式,分别是:立即寻址、寄存器寻址、寄存器间接寻址、寄存器移位寻址、基址寻址、多寄存器寻址、堆栈寻址、块拷贝寻址、相对寻址。
- 立即寻址
指令的地址字段指出的不是操作数的地址,而是操作数本身。注意:立即数只能作为源操作数,不能作为目的操作数。MOV R0 0x10;
- 寄存器寻址
当操作数不放在内存中,而是放在CPU的通用寄存器中时,可采用寄存器寻址方式。MOV R0 R1;
- 寄存器间接寻址
寄存器间接寻址方式与寄存器寻址方式的区别在于:指令格式中的寄存器内容不是操作数,而是操作数的地址,该地址指明的操作数在内存中。MOV R0 R1; //此时R1中存放的是操作数的地址
- 寄存器移位寻址(ARM指令集特有的寻址方式)
在操作前对源寄存器操作数进行移位操作。支持以下5种移位操作:LSL: 逻辑左移,移位后寄存器空出的低位补0。 LSR: 逻辑右移,移位后寄存器空出的高位补0。 ASR: 算术右移,移位过程中符号位保持不变,如果源操作数为正数,则移位后寄存器空出的高位补0;否则补1。 ROR: 循环右移,移位后移出的低位填入移位空出的高位。 RRX: 带扩展的循环右移,操作数右移一位,移位后寄存器空出的高位用C标志的值填充。
MOV R0, R1, LSL #2 //R0=R1*4(逻辑左移2位)
- 基址寻址
基址寻址是将基址寄存器与偏移量相加,形成操作数的有效地址,所需的操作数保存在有效地址所指向的存储单元中。基址寻址多用于查表、数据访问等操作。LDR R0, [R1, #-4] //R0=[R1-4]
- 多寄存器寻址
多寄存器寻址一条指令最多可以完成16个通用寄存器值的传送。比如LDMIA和LDMIB指令,LDM是数据加载指令,指令的后缀IA表示每次执行完加载操作后寄存器的值自增1个字;指令的后缀IB表示每次执行加载操作前寄存器的值自增1个字;还有两条指令后缀DA和DB,分别表示在指令操作后和操作前寄存器的值自减1个字。ARM32指令集中,字表示一个32位的数字,注意:该条指令的源寄存器与目的寄存器位置。LDMIA R0, {R1, R2, R3, R4} //R1=[R0], R2=[R0+4], R3=[R0+8], R4=[R0+12]
- 堆栈寻址(ARM32位指令集特有的寻址方式)
注意:这里列出的指令是ARM32支持的批量压栈和出栈指令,thumb指令模式下的出栈和压栈指令是POP/PUSH。在ARM64下对于栈操作限制严格,这两种栈操作指令都不再支持,只有LDP/STP这一对双寄存器栈操作指令。
堆栈寻址是ARM指令集特有的一种寻址方式,堆栈寻址需要使用特定的指令来完成。堆栈寻址的指令有LDMFA/STMFA、LDMEA/STMEA、LDMFD/STMFD、LDMED/STMED。LDM和STM为指令前缀,表示多寄存器寻址。FA(Full Ascending stack)、FD(Full Descending stack)、EA、ED为指令后缀,其中:FA表示满递增堆栈,堆栈向高地址生长,堆栈指针指向下一个要放入的空地址;FD表示满递减堆栈,堆栈向低地址生长,堆栈指针指向最后一个入栈的有效数据数据项; EA表示空递增堆栈,堆栈向高地址生长;ED空递减堆栈,堆栈向低地址生长。STMFD SP!, {R1-R7, LR} //将R1-R7, LR入栈,多用于保护子程序现场 LDMFD SP!, {R1-R7, LR} //将数据出栈,放入R1-R7, LR寄存器。多用于恢复子程序现场
- 块拷贝寻址
块拷贝寻址可实现连续地址数据从存储器的某一位置拷贝到另一位置。块拷贝寻址的指令有LDMIA/STMIA、LDMDA/STMDA、LDMIB/STMIB、LDMDB/STMDB。指令前缀和指令后缀前面已经介绍了。STMIA R0!, {R1-R3} //从R0寄存器指向的存储单元中读取3个字到R1-R3寄存器 LDMIA R0!, {R1-R3} //存储R1-R3寄存器的内容到R0寄存器指向的存储单元
- 相对寻址
相对寻址以程序计数器PC的当前值为基地址,指令中的地址标作为偏移量,将两者相加之后得到操作数的有效地址。例如——BL NEXT .... NEXT: ....
(2)ARM32寄存器
ARM32微处理器有两种工作状态:ARM32状态与Thumb状态。处理器可以在两种状态之间随意切换,当处理器处于ARM状态时,会执行32位对齐的ARM指令;当处于Thumb状态时,会执行16位对齐的Thumb指令。ARM32指令集与Thumb指令集切换方法,是在BX和BLX指令跳转时,判断目标地址最低位是否为1。
如果为1,跳转时将CPSR寄存器标志T置位,并将目标地址处的代码解释位Thumb代码,处理器切换到Thumb状态;
如果为0,跳转时将CPSR寄存器标志T复位,并将目标地址处的代码解释位ARM32代码,处理器切换到ARM32状态。
Thumb状态下对寄存器的命名与ARM32有部分差异,它们的关系如下图所示。
(3)ARM64寄存器
ARM64位参数调用规则遵循AAPCS64,规定堆栈为满递减堆栈。对于ARM64架构的CPU, 以 X 开头的就是64位的寄存器, 以 W 开头的就是32位的寄存器, 其中32位寄存器就是64位寄存器的低32位部分。
- X0~X7:用于传递子程序参数和结果,使用时不需要保存,多余参数采用堆栈传递,64位返回结果采用X0表示,128位返回结果采用X1:X0表示。
- X8:用于保存子程序返回地址, 尽量不要使用 。
- X9~X15:临时寄存器,使用时不需要保存。
- X16~X17:子程序内部调用寄存器,使用时不需要保存,尽量不要使用。
- X18:平台寄存器,它的使用与平台相关,尽量不要使用。
- X19~X28:临时寄存器,使用时必须保存。
- X29:帧指针寄存器FP(栈底指针),用于连接栈帧,使用时需要保存。
- X30:链接寄存器LR
- X31:堆栈指针寄存器SP或零寄存器ZXR
6. 栈操作
ARM32中的LDM、STM、PUSH、POP指令,在ARM64中并不存在。取而代之的是LDP、STP指令,例如,用IDA Pro逆向的某个ARM64 SO库函数的开头和结尾:
STP X24, X23, [SP,#var_40]!
STP X22, X21, [SP,#0x40+var_30]
STP X20, X19, [SP,#0x40+var_20]
STP X29, X30, [SP,#0x40+var_10]
ADD X29, SP, #0x40+var_10
....
SUB SP, X29, #0x30
LDP X29, X30, [SP,#0x150+var_120]
LDP X20, X19, [SP,#0x150+var_130]
LDP X22, X21, [SP,#0x150+var_140]
LDP X24, X23, [SP+0x150+var_150],#0x40
RET
7. 软中断进行系统调用
关于软中断
软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和”信号”有些类似。软中断是实现系统API函数调用的手段。
软中断调用时将返回地址和CPU状态寄存器内容压栈,修改特权级,根据中断号查找中断向量表,找到ISR中断服务例程地址,跳转执行。函数调用和软中断调用的区别是,软中断多了修改特权级和查找中断向量表的功能,其他部分完全一样。
有了软中断,就可以实现应用程序的动态加载。就像WINDOWS/Linux那样,应用程序和系统程序分别开发,不在一起编译连接,应用程序通过软中断调用系统提供的功能。
(1)ARM32
在使用软中断进行系统调时,系统调用号通过R7寄存器传递,用SWI指令产生软中断,实现从用户模式到管理模式的切换。例如,调用exit(0)的汇编代码如下:
MOV R0, #0 //参数0
MOv R7, #1 //系统功能号1为 exit
SWI #0 //执行 exit(0)
(2)ARM64
在使用软中断进行系统调时,系统调用号通过x8寄存器传递,用svc指令产生软中断,实现从用户模式到管理模式的切换。例如:
mov x0, 123 // exit code
mov x8, 93 // sys_exit() is at index 93 in kernel functions table
svc #0 // generate kernel call sys_exit(123);