arm64 汇编准备
寄存器
通用寄存器
31
个R0 ~ R30
,每个寄存器可以存取一个 64 位大小的数。 当使用 x0 - x30
访问时,是一个 64位的数;当使用 w0 - w30
访问时,是一个 32 位的数,访问的是寄存器的 低 32
位,如图:
向量寄存器
(也可以说是 浮点型寄存器)每个寄存器的大小是 128
位的。 分别可以用Bn Hn Sn Dn Qn
的方式来访问不同的位数;如图:
注:word 是
32
位,也就是 4 Byte大小。
- Bn:一个 Byte的大小,即
8
位 - Hn:half word,即
16
位 - Sn:single word,即
32
位 - Dn:double word,即
64
位 - Qn:quad word,即
128
位
特殊寄存器
- sp: (Stack Pointer),栈顶寄存器,用于保存栈顶地址;
- fp(x29): (Frame Pointer)为栈基址寄存,用于保存栈底地址;
- lr(x30): (Link Register) ,保存调用跳转指令
bl
指令的下一条指令的内存地址; - zr(x31): (Zero Register),
xzr/wzr
分别代表 64/32 位,其作用就是 0,写进去代表丢弃结果,读出来是0
; - pc:保存将要执行的指令的地址(有操作系统决定其值,不能改写)。
状态寄存器 CPSR
CPSR (Current Program Status Register)和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义;而 CPSR 寄存器是按位起作用的,即,每一位都有专门的含义,记录特定的信息;如下图
注: CPSR 寄存器是 32 位的。
CPSR
的 低8位(包括I
、F
、T
和M[4:0]
)称为控制位,程序无法修改,除非 CPU 运行于 特权模式 下,程序才能修改控制位。N
、Z
、C
、V
均为条件码标志位;其内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行。N(Negative)标志:CPSR 的第
31
位是 N,符号标志位;记录相关指令执行后其结果是否为负数,**如果为负数,则N = 1
;如果是非负数,则N = 0
**。Z(Zero)标志:
CPSR
的第30
位是 Z,0标志位;记录相关指令执行后,其结果是否为0,**如果结果为0,则Z = 1
;如果结果不为0,则Z = 0
**。C(Carry)标志:CPSR 的第
29
位是C,进位标志位;- 加法运算:当运算结果产生了 进位 时(无符号数溢出),
C = 1
,否则C = 0
; - 减法运算(包括
CMP
): 当运算时产生了 借位 时(无符号数溢出),C = 0
,否则C = 1
。
- 加法运算:当运算结果产生了 进位 时(无符号数溢出),
V(Overflow)标志:CPSR 的第 28` 位是 V,溢出标志位;在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
条件码列表
操作码 | 条件码助记符 | 标志 | 含义 |
---|---|---|---|
0000 | EQ | Z=1 | 相等 |
0001 | NE(Not Equal) | Z=0 | 不相等 |
0010 | CS/HS(Carry Set/High or Same) | C=1 | 无符号数大于或等于 |
0011 | CC/LO(Carry Clear/LOwer) | C=0 | 无符号数小于 |
0100 | MI(MInus) | N=1 | 负数 |
0101 | PL(PLus) | N=0 | 正数或零 |
0110 | VS(oVerflow set) | V=1 | 溢出 |
0111 | VC(oVerflow clear) | V=0 | 没有溢出 |
1000 | HI(High) | C=1,Z=0 | 无符号数大于 |
1001 | LS(Lower or Same) | C=0,Z=1 | 无符号数小于或等于 |
1010 | GE(Greater or Equal) | N=V | 有符号数大于或等于 |
1011 | LT(Less Than) | N!=V | 有符号数小于 |
1100 | GT(Greater Than) | Z=0,N=V | 有符号数大于 |
1101 | LE(Less or Equal) | Z=1,N!=V | 有符号数小于或等于 |
1110 | AL | 任何 | 无条件执行(默认) |
1111 | NV | 任何 | 从不执行 |
指令读取
在 arm64
架构中,每个指令读取都是 64 位,即 8
字节 空间。
arm64 约定(一般来说)
x0 ~ x7
分别会存放方法的前 8 个参数;如果参数个数超过了8个,多余的参数会存在栈上,新方法会通过栈来读取。- 方法的返回值一般都在 x0 上;如果方法返回值是一个较大的数据结构时,结果会存在 x8 执行的地址上。
常见汇编指令
mov: 将某一寄存器的值复制到另一寄存器(只能用于寄存器与寄存器或者寄存器与常量之间传值,不能用于内存地址),如:
1
mov x1, x0 ; 将寄存器 x0 的值复制到寄存器 x1 中
add: 将某一寄存器的值和另一寄存器的值 相加 并将结果保存在另一寄存器中,如:
1
2
3add x0, x0, #1 ; 将寄存器 x0 的值和常量 1 相加后保存在寄存器 x0 中
add x0, x1, x2 ; 将寄存器 x1 和 x2 的值相加后保存到寄存器 x0 中
add x0, x1, [x2] ; 将寄存器 x1 的值加上寄存器 x2 的值作为地址,再取该内存地址的内容放入寄存器 x0 中sub: 将某一寄存器的值和另一寄存器的值 相减 并将结果保存在另一寄存器中,如:
1
sub x0, x1, x2 ; 将寄存器 x1 和 x2 的值相减后保存到寄存器 x0 中
mul:将某一寄存器的值和另一个寄存器的值 相乘 并将结果保存在另一寄存器中,如:
1
mul x0, x1, x2 ; 将寄存器 x1 和 x2 的值相乘后结果保存到寄存器 x0 中
sdiv:(有符号数,对应 udiv: 无符号数)将某一寄存器的值和另一个寄存器的值 相除 并将结果保存在另一寄存器中,如:
1
sdiv x0, x1, x2 ; 将寄存器 x1 和 x2 的值相除后结果保存到寄存器 x0 中
and: 将某一寄存器的值和另一寄存器的值 按位与 并将结果保存到另一寄存器中,如:
1
and x0, x0, #0xf ; 将寄存器 x0 的值和常量 0xf 按位与后保存到寄存器 x0 中
orr: 将某一寄存器的值和另一寄存器的值 按位或 并将结果保存到另一寄存器中,如:
1
orr x0, x0, #9 ; 将寄存器 x0 的值和常量 9 按位或后保存到寄存器 x0 中
eor: 将某一寄存器的值和另一寄存器的值 按位异或 并将结果保存到另一寄存器中,如:
1
eor x0, x0, #0xf ; 将寄存器 x0 的值和常量 0xf 按位异或后保存到寄存器 x0 中
str: (store register) 将寄存器中的值写入到内存中,如:
1
str w9, [sp, #0x8] ; 将寄存器 w9 中的值保存到栈内存 [sp + 0x8] 处
strb: (store register byte) 将寄存器中的值写入到内存中(只存储一个字节),如:
1
strb w8, [sp, #7] ; 将寄存器 w8 中的低 1 字节的值保存到栈内存 [sp + 7] 处
ldr: (load register) 将内存中的值读取到寄存器中,如:
1
2
3
4
5ldr x0, [x1] ; 将寄存器 x1 的值作为地址,取该内存地址的值放入寄存器 x0 中
ldr w8, [sp, #0x8] ; 将栈内存 [sp + 0x8] 处的值读取到 w8 寄存器中
ldr x0, [x1, #4]! ; 将寄存器 x1 的值加上 4 作为内存地址, 取该内存地址的值放入寄存器 x0 中, 然后将寄存器 x1 的值加上 4 放入寄存器 x1 中
ldr x0, [x1], #4 ; 将寄存器 x1 的值作为内存地址,取内该存地址的值放入寄存器 x0 中, 再将寄存器 x1 的值加上 4 放入寄存器 x1 中
ldr x0, [x1, x2] ; 将寄存器 x1 和寄存器 x2 的值相加作为地址,取该内存地址的值放入寄存器 x0 中ldrsb: (load register byte) 将内存中的值(只读取一个字节)读取到寄存器中,如:
1
ldrsb w8, [sp, #7] ; 将栈内存 [sp + 7] 出的 低 1 字节的值读取到寄存器 w8 中
stur:同
str
将寄存器中的值写入到内存中(一般用于负
地址运算中),如:1
stur w10, [x29, #-0x4] ; 将寄存器 w10 中的值保存到栈内存 [x29 - 0x04] 处
ldur: 同
ldr
将内存中的值读取到寄存器中(一般用于负
地址运算中),如:1
ldur w8, [x29, #-0x4] ; 将栈内存 [x29 - 0x04] 处的值读取到 w8 寄存器中
stp: 入栈指令(
str
的变种指令,可以同时操作两个寄存器),如:1
stp x29, x30, [sp, #0x10] ; 将 x29, x30 的值存入 sp 偏移 16 个字节的位置
ldp: 出栈指令(
ldr
的变种指令,可以同时操作两个寄存器),如:1
ldp x29, x30, [sp, #0x10] ; 将 sp 偏移 16 个字节的值取出来,存入寄存器 x29 和寄存器 x30
scvtf: (Signed Convert To Float)带符号 定点数 转换为 浮点数,如:
1
scvtf d1, w0 ; 将寄存器 w0 的值(顶点数,转化成 浮点数) 保存到 向量寄存器/浮点寄存器 d1 中
fcvtzs:(Float Convert To Zero Signed)浮点数 转化为 定点数 (舍入为0),如:
1
fcvtzs w0, s0 ; 将向量寄存器 s0 的值(浮点数,转换成 定点数)保存到寄存器 w0 中
cbz: 和 0 比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令),如:
1
cbz x8, loc_1800b4530 ; 将寄存器 x8 的值和 0 比较,如果结果为 “0” 则跳转到 ‘loc_1800b4530’ 标签处开始指令
cbnz: 和非 0 比较(Compare),如果结果非零(Non Zero)就转移(只能跳到后面的指令),如:
1
cbnz x9, loc_1800b4530 ; 将寄存器 x9 的值和 0 比较,如果结果为 “非 0” 则跳转到 ‘loc_1800b4530’ 标签处开始指令
cmp: 比较指令,相当于
subs
,影响程序状态寄存器 CPSR ;cset:比较指令,满足条件,则并置
1
,否则置0
,如:1
2cmp w8, #2 ; 将寄存器 w8 的值和常量 2 进行比较
cset w8, gt ; 如果是大于(grater than),则将寄存器 w8 的值设置为 1,否则设置为 0LSL: 逻辑左移
LSR: 逻辑右移
ASR: 算术右移
ROR: 循环右移
adrp: 用来定位数据段中的数据用, 因为 aslr 会导致代码及数据的地址随机化, 用 adrp 来根据 pc 做辅助定位
b: (branch)跳转到某地址(无返回), 不会改变 lr (x30) 寄存器的值;一般是本方法内的跳转,如
while
循环,if else
等 ,如:1
b LBB0_1 ; 直接跳转到标签 ‘LLB0_1’ 处开始执行
bl: 跳转到某地址(有返回),先将下一指令地址(即函数返回地址)保存到寄存器 lr (x30)中,再进行跳转 ;一般用于不同方法直接的调用 ,如:
1
bl 0x100cfa754 ; 先将下一指令地址(‘0x100cfa754’ 函数调用后的返回地址)保存到寄存器 ‘lr’ 中,然后再调用 ‘0x100cfa754’ 函数
blr: 跳转到
某寄存器
(的值)指向的地址(有返回),先将下一指令地址(即函数返回地址)保存到寄存器 lr (x30)中,再进行跳转 ;如:1
blr x20 ; 先将下一指令地址(‘x20’指向的函数调用后的返回地址)保存到寄存器 ‘lr’ 中,然后再调用 ‘x20’ 指向的函数
br: 跳转到某寄存器(的值)指向的地址(无返回), 不会改变 lr (x30) 寄存器的值。
brk: 可以理解为跳转指令特殊的一种。
ret: 子程序(函数调用)返回指令,返回地址已默认保存在寄存器 lr (x30) 中
函数调用
每个函数调用,都会有 入栈 和 出栈 操作。
例子: PushAndPop.c
源代码
1 | #include <stdio.h> |
汇编代码
通过 Xcode “
Product——>Perform Action——>Assemble PushAndPop.c
“ 查看其对应的汇编代码:也可以通过
clang
编译成汇编代码:1
2
3
4
5// 注意,以下代码将默认生成pc版的汇编指令
clang -S PushAndPop.c
// arm64汇编需要如下命令,指定架构和系统头文件所在的目录,请务必将isysroot的sdk版本修改为对应的 xcode 中存在的版本!
clang -S -arch arm64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.1.sdk PushAndPop.c
去除一大堆 不相干东西 得到对应汇编代码,如下:
1 | sub sp, sp, #32 ; 更新栈顶寄存器的值,(可以看出:申请 32 字节占空间作为新用) |
对与上面的汇编代码,分配了
32
自己空间,其中16
字节是用作 入栈操作,剩下的16
字节是用于存储临时变量的。疑问:例子函数命名是没有临时变量,为什么还会需要申请占空间?
解释:虽然该函数没有临时变量,但是调用
printf
函数后,编译器自动会加上 该函数返回值 的处理,由于 arm64 规定了整数型返回值放在x0
寄存器里,因此会隐藏有一个局部变量int return_value;
的声明在,该临时变量占用4
字节空间;又因为 arm64 下对于使用sp
作为地址基址寻址的时候,必须要16byte-alignment
(对齐),所以申请了16
字节空间作为临时变量使用。具体参见 这里。