一、基础概念
1.1 指令分类
- 指令
能够最终生成机器指令的代码。 - 伪指令(也叫伪操作)
用于协助整个编译过程的代码;最终不会形成机器指令。以.开头的语句是伪指令。 - 宏指令
就是用前两种汇编指令写的宏。
1.2 指令大小写
汇编不区分大小写,只有字符型数据或者字符串区分大小写。所以汇编中的指令和寄存器可以是大写也可以是小写。例如:如下两行指令是等价的。
MOV Xo, X1 ; 大写的汇编指令
mov x0, x1 ; 小写风格的汇编指令
Arm风格的汇编指令一般采用大写,GUN风格的汇编代码采用小写字母作为指令(iOS Arm64采用小写的指令)。
1.3 汇编指令注释
关于汇编如何添加注释?汇编语言的注释是以分号";"开头的,分号之后的内容都属于注释。一般而言,汇编语言的注释出现在以下3个地方:
- 程序的最前面,注释内容一般是该程序的说明,解释程序的主要功能,程序的版本号,程序的修改日志,程序的编制人等等
- 子程序的前面,一般说明该子程序或函数完成的功能,输入参数,输出参数,影响的标志位等等。
- 指令行的后面,解释该行指令语句的功能。
1.4 指令寻址方式
指令寻址方式,其实质就是指令找到所要操作的内存地址、寄存器的方式(寻址方式是指处理器根据指令中给出的地址信息来寻找物理地址的方式)——大部分汇编指令都是操作内存或者寄存器。
- 寄存器寻址
mov x0, x1
- 立即数寻址
也称为立即数寻址,这种寻址方式指令中就已经给出了操作数。也就是在执行指令的过程中,处理器取得指令的同时也取得了操作数,因此称为立即数寻址。例如:
ADD R0, #1 @R0+1->R0
ADD R0, R0, #0x3F @R0+0x3F->R0
在上面两条指令中,源操作数就是立即数,要求以“#”开始,对于十六进制的立即数,要求在“#”后面加“0x”或“&”。
- 寄存器位移寻址
mov x0, lsl #3 ;
- 寄存器间接寻址
mov x0, [x1] ; 其实质就是对x1寄存器的值所代表的内存地址进行操作
- 基址变址寻址
基址变址寻址是把基址寄存器的内容与指令中给出的地址偏移量相加,从而得到一个操作数的有效地址。该方式常用于访问基地址附近的某些存储单元,一般有以下几种方式:
LDR R0, [R1, #4] ;将寄存器R1的值加上4作为操作数的有效地址,取得操作数后存入R0中。
LDR R0, [R1, #4]! ; 将寄存器R1的值加上4作为操作数的有效地址,取得操作数后存入R0中,然后寄存器R1的值加上4个字节。
LDR R0, [R1], #4 ; 将寄存器R1的值作为操作数的有效地址,取得操作数后存入R0中,然后寄存器R1的值加上4个字节。
LDR R0, [R1, R2] ; 将寄存器R1和R2的值相加作为操作数的有效地址,取得操作数后存入R0中。
- 多寄存器寻址
LDMIA R0, {R1,R2,R3,R4,} @ [R0]->R1,[R0+4]->R2,[R0+8]->R3,[R0+12]->R4
- 堆栈寻址
堆栈是一种数据结构,按先进后出的方式工作,使用一个称为堆栈指针的专用寄存器指示当前的操作,堆栈指针总是指向堆栈顶端。当堆栈指针指向最后压入的数据时,称为满堆栈;当堆栈指针指向下一个将要压入的位置时,称为空堆栈。
根据堆栈的生成方式,可分为递增堆栈和递减堆栈。当堆栈由低地址向高地址生成时,称为递增堆栈,反之称为递减堆栈。排列组合后可得到4中类型的堆栈工作方式,ARM微处理器支持全部4种类型的堆栈工作方式。
满递增堆栈:堆栈指针指向最后压入的数据,由低地址向高地址生成。
满递减堆栈:堆栈指针指向最后压入的数据,由高地址向低地址生成。
空递增堆栈:堆栈指针指向下一个将要压入数据的空位置,由低地址向高地址生成。
空递减堆栈:堆栈指针指向下一个将要压入数据的空位置,由高地址向低地址生成。
stmfd sp!, {r2 - 47, lr}
- 相对选址
与基址变址寻址类似,相对寻址以程序计数器PC的当前值作为基地址,指令中的地址标号作为偏移量,将两者相加后得到操作数的有效地址。以下程序完成子程序的调用和返回,跳转指令BL采用了相对寻址方式:
BL NEXT @ 跳转到子程序NEXT处执行指令
......
NEXT
......
MV PC, LR @ 从子程序返回
二、常见指令
2.1 算术指令
- add
add X0, X1, X2 ; 把寄存器X1、X2的值相加后赋值给寄存器X0。即X0 = X1+X2
- sub
sub X0, X1, X2 ; 把寄存器x1、x2的值相减后赋值给寄存器x0。即x0 = X1-X2
- mul
mul X0, X0, X8 ; 把寄存器x0、x8的值相乘后赋值给寄存器x0。即x0 = X0*X8
- sdiv
sdiv X0, X0, X1 ; 即X0 = X0 / X1;有符号除法运算指令。
- udiv
UDIV X0, X0, X1 ; 即X0 = X0 / X1;无符号除法运算指令。
- cmp
cmp X28, X0 ; X28与X0相减,不存储结果只更新CPSR中的标志位。 (CPSR即为current program status register)
2.2 跳转指令
跳转指令分为条件跳转和无条件跳转。条件跳转即若条件为真则跳转。无条件跳转是直接跳转到某个位置执行指令。
- b
b label ; 跳转到label标签处开始执行,这是无条件跳转
- bl
b label ; 跳转到label标签处开始执行,这是无条件跳转(与b相比,执行bl前返回地址会被保存到X30(LR)寄存器。bl执行完后会把lr保存的地址赋值给pc寄存器。这样就可以回到bl跳转前的位置继续向下执行)
- ret
; 子程序返回指令。返回地址默认保存在x30(lr)寄存器,这是无条件跳转
条件跳转?????
2.3 逻辑指令
- and
AND X0, X1, X2 ; X1和X2寄存器的数据进行逻辑与运算结果保存到X0中。即:X0 = X1 & X2
- orr
ORR X0, X1, X2 ; X1和X2寄存器的数据进行逻辑或运算结果保存到X0中。即:X0 = X1 | X2
- eor
EOR X0, X1, X2 ; X1和X2寄存器的数据进行逻辑抑或运算结果保存到X0中。即:X0 = X1 ^ X2
2.4 数据传输指令
- MOV
MOV X19, X1 ; 把寄存器X1 中的数据存储到寄存器X19中。即 X1 = X19
其他常用数据传输指令还有:MOVZ、MOVN、MOVK。
2.5 地址偏移指令
- ADR
ADR Xn, label ; 即Xn = PC + label(小范围的地址读取指令。ADR 指令将基于PC 相对偏移的地址值读取到寄存器中)
- ADRP
; 以页为单位的大范围的地址读取指令,这里的P就是page的意思。
2.6 存储系统操作指令
加载(load)、存储(store)指令经常成对出现的;无论是加载还是存储相关的指令,指令后面都只能写寄存器,不能把寄存器放到指令行的最后面。
LDR、LDUR、LDP都是和加载相关的指令。即把内存中的数据读取到寄存器中。LD即为load的缩写,R即为register的缩写,P即为pair的缩写,即同时操作两个寄存器。
LDR & LDUR :从内存中读取8/4字节数据到一个64/32位寄存器中,即从源寄存器中读取数据写入目的寄存器。LDUR中的“U”是unscaled的缩写,代表不需要按字节对齐。
LDUR和LDR指令的区别:可以简单理解为地址偏移量为负数则使用LDUR,偏移量为正数则使用LDR。例如:
LDR X1, [sp, #0x28] ; 从SP+ 0x28处开始读取8字节数据到X1寄存器中。
LDUR X30, [X29, #-0x10] ; 从X29 - 0x10出开始读取8字节数据到X30(LR)寄存器中。
LDP:从内存中读取数据放到两个寄存器中。例如:
LDP w0, w1, [x0, #0x10] ; 读取x0+0x10内存的字数据,然后四个字节赋值给w0, 另外四个字节赋值给w1
STR、STUR、STP都是和存储相关的指令。即把寄存器中的数据写入内存中。ST即为store的缩写,R即为register的缩写,P即为pair的缩写,即同时操作两个寄存器。
STR & STUR:将寄存器中的数据写入内存中,即把源寄存器的内容写入目的寄存器。STR&STUR的区别和LDR&LDUR的区别类似。如下:
STR X0, [sp, #0x28] ; 将X0寄存器中的数据写入SP + 0x28的位置。
STUR X0, [sp, #-0x10] ; 将X0寄存器中的数据写入SP - 0x10的位置。
STP:将两个寄存器的数据写入内存中。例如:
STP X1, X2, [X0, #0x28]; 将X1和X2的数据 写入 X0+#0x28的位置
参考文章
1、iOS逆向之ARM64汇编基础
2、第七章 ARM 反汇编基础(七)(AArch64 汇编指令集)
3、ARM汇编基本指令
4、ARM学习路线02-ARM指令集、寻址方式
网友评论