赞
踩
现在iOS设备几乎已经都是ARM64架构,此外,Mac M1芯片的电脑也是基于ARM64架构,本文对ARM64汇编做一个简单的介绍。本文后面给出了一个汇编案例,通过汇编窥探代码底层的实现逻辑。
ARM64汇编中有34个寄存器,其中包含31个通用寄存器(x0-x30),sp,pc和cpsr。
Xcode可以通过register read
指令查看所有寄存器的存储值:
(lldb) register read General Purpose Registers: x0 = 0x0000000000000008 x1 = 0x000000016fdfdc70 x2 = 0x000000016fdfdc80 x3 = 0x000000016fdfdd80 x4 = 0x0000000000000000 x5 = 0x0000000000000000 x6 = 0x0000000000000000 x7 = 0x0000000000000000 x8 = 0x0000000000000008 x9 = 0x0000000000000002 x10 = 0x0000000000000000 x11 = 0x0000000000000002 x12 = 0x0000000000000002 x13 = 0x0000000000000000 x14 = 0x0000000000000001 x15 = 0x0000000000000044 x16 = 0x000000030006f120 x17 = 0x6ae100016fdfa280 x18 = 0x0000000000000000 x19 = 0x00000001000d4060 x20 = 0x0000000100002ea0 x21 = 0x0000000100080070 x22 = 0x0000000000000000 x23 = 0x0000000000000000 x24 = 0x0000000000000000 x25 = 0x0000000000000000 x26 = 0x0000000000000000 x27 = 0x0000000000000000 x28 = 0x0000000000000000 fp = 0x000000016fdfdaf0 lr = 0x000000010000315c sp = 0x000000016fdfdae0 pc = 0x0000000100002f4c cpsr = 0x60001000
通用寄存器x0-x30有64bit, 如果用不到64位,可以用低位的32位(w0-w30), 也就是说x0和w0本质上是一个寄存器,只是利用的位数不一样而已。
说明:有的博客说的是“只有
x0-x7
用来存放参数,如果函数参数超过了8个,则在压入函数栈中”,我目前测试的情况是至少15个参数都是可以通过寄存器来传值,更多的参数就没有测试了。
x0
通常用来来存放返回值,如果返回的数据比较复杂,会放在x8
的这个执行地址上。其他寄存器都是存储数据,是个统一的整体;状态寄存器有点特殊,它的每一位都有特殊的含义,记录特定的信息。
最常见的是NZCV标志位,分别代表运算过程中产生的不同状态,可以决定运算结果或者代码执行逻辑。
xzr/wzr
分别表示64/32位,其做用就是0,写进去表明丢弃结果,读出来是0。
加载/存储指令⽤于在寄存器和存储器之间传送数据,加载指令⽤于将存储器中的数据传送到寄存器,存储 指令则完成相反的操作。
ldr
指令ldr x8, [sp] // 将存储地址为sp的数据读取到x8寄存器中
ldr x9, [sp, #0x8] // 将存储地址为sp+0x8的数据读取到x9寄存器中
ldrb
指令(只操作一个字节)ldrb w8, [sp, #0x8] // 将存储器地址为sp+0x8的1个字节数据读⼊寄存器w8,并将w8的⾼24位清零。
ldrh
指令(只操作两个字节)ldrh w8, [sp, #0x8] // 将存储器地址为sp+0x8的2个字节数据读⼊寄存器w8,并将w8的⾼16位清零。
ldur
指令 (u和负地址运算相关)ldurb w0, [x29, #-0x8] // 将存储地址为x29-0x8的数据读取到w0寄存器中
stp
指令(str 的变种指令,能够同时操做两个寄存器)stp x29, x30, [sp, #0x10] // 将 x29, x30 的值存⼊sp+0x10存储地址
str
指令str x0, [sp] // 将x0寄存器中的值存储到sp的存储地址
str x0, [sp, #0x10] // 将x0寄存器中的值存储到sp+0x10的存储地址
strb
指令(只操作一个字节)strb x0, [sp, #0x10] // 将x0寄存器中的低8位的字节的数据存储到sp+0x10的存储地址
strh
指令(只操作两个字节)strh x0, [sp, #0x10] // 将x0寄存器中的低16位的字节的数据存储到sp+0x10的存储地址
stur
指令 (u和负地址运算相关)stur w0, [x29, #-0x8] // 将x0寄存器中的数据存储到x29-0x8的存储地址
ldp
指令(ldr 的变种指令,能够同时操做两个寄存器)ldp x29, x30, [sp, #0x10] // 将sp+0x10的值取出来,存⼊寄存器 x29 和寄存器 x30
mov
指令(不用于内存地址)mov w8, #0x1 //将立即数赋值给w8寄存器
mov x0, x8 //将给x0寄存器赋值给给x8寄存器
add
指令 (加)add sp, sp, #0x20 // 将寄存器sp的值和立即数0x20相加后保存在寄存器sp中
add x0, x1, x2 // 将寄存器 x1 和 x2 的值相加后保存到寄存器 x0 中
add x0, x1, [x2] // 将寄存器x1的值加上寄存器x2 的值做为地址,再取该内存地址的内容放⼊寄存器x0中
sub
指令 (减)sub sp, sp, #0x20 // 将寄存器sp的值和立即数0x20相减后保存在寄存器sp中
sub x0, x1, x2 // 将寄存器 x1 和 x2 的值相减后保存到寄存器 x0 中
mul
指令 (乘)mul x0, x1, x2 // 将寄存器 x1 和 x2 的值相乘后结果保存到寄存器 x0 中
sdiv
指令 (除,无符号除是udiv)sdiv x0, x1, x2 // 将寄存器 x1 和 x2 的值相除后结果保存到寄存器 x0 中
and
指令 (位与)and x0, x1, x2 // 将寄存器 x1 和 x2 的值按位与后保存到寄存器 x0 中
orr
指令 (位或)orr x0, x1, x2 // 将寄存器 x1 和 x2 的值按位或后保存到寄存器 x0 中
eor
指令 (位异或)eor x0, x1, x2 // 将寄存器 x1 和 x2 的值按位异或后保存到寄存器 x0 中
cbnz
指令 (和⾮ 0 ⽐较)cbnz x0, 0x100002f70 // 如果非0,跳转到0x100002f70指令执行
cbz
指令 (和0 ⽐较)cbz x0, 0x100002f70 // 如果为0,跳转到0x100002f70指令执行
b
指令 (直接跳转)b 0x100002fd0
bl
指令 (跳转前记录下一条指令地址)bl 0x100002f54
blr
指令 (跳转到某寄存器 (的值)指向的地址)blr x10
ret
指令 (函数返回)ret
enum EnumTest: Int {
case n1, n2, n3, n4
}
var e = EnumTest.init(rawValue: 1)
下面的代码是Swift
枚举类型init?(rawValue:)
方法的汇编代码,通过汇编就可以窥探该方法的实现逻辑。
0x100002f54 <+0>: sub sp, sp, #0x20 // sp = sp - 32,将栈顶指针向下移动 32 字节 100002f58 <+4>: str x0, [sp, #0x8] // 将x0参数存储到sp+0x8的存储位置 (将参数保存到局部变量) 100002f5c <+8>: str xzr, [sp, #0x10] // 将sp+0x10的存储位置的数据清零 100002f60 <+12>: str x0, [sp, #0x10] // 将x0的数据又保存到sp+0x10的存储位置 100002f64 <+16>: cbnz x0, 0x100002f70 // 判断x0是否是0,如果是0执行0x100002f68指令,否则执行0x100002f70指令(分支1) 100002f68 <+20>: strb wzr, [sp, #0x1f] // 将0保存到sp+0x1f的存储位置 100002f6c <+24>: b 100002fd0 // 直接跳转到0x100002fd0处理结果 100002f70 <+28>: ldr x9, [sp, #0x8] // 将保存的局部变量参数读取出来,放在x9寄存器上 100002f74 <+32>: mov w8, #0x1 // 将0x1存储到x8寄存器上 100002f78 <+36>: subs x8, x8, x9 // 用0x1-x9寄存器的值放到x8寄存器上 100002f7c <+40>: b.ne 0x100002f8c // 如果等于0执行0x100002f80指令,否则执行0x100002f8c指令(分支2) 100002f80 <+44>: mov w8, #0x1 // 将1赋值给w8寄存器 100002f84 <+48>: strb w8, [sp, #0x1f] // 将w8寄存器中的1保存到sp+0x1f的存储位置 100002f88 <+52>: b 100002fd0 // 直接跳转到0x100002fd0处理结果 100002f8c <+56>: ldr x9, [sp, #0x8] 100002f90 <+60>: mov w8, #0x2 100002f94 <+64>: subs x8, x8, x9 100002f98 <+68>: b.ne 0x100002fa8 // (分支3) 100002f9c <+72>: mov w8, #0x2 100002fa0 <+76>: strb w8, [sp, #0x1f] 100002fa4 <+80>: b 100002fd0 100002fa8 <+84>: ldr x9, [sp, #0x8] 100002fac <+88>: mov w8, #0x3 100002fb0 <+92>: subs x8, x8, x9 100002fb4 <+96>: b.ne 0x100002fc4 // (分支4) 100002fb8 <+100>: mov w8, #0x3 100002fbc <+104>: strb w8, [sp, #0x1f] 100002fc0 <+108>: b 100002fd0 // (分支5) 100002fc4 <+112>: mov w8, #0x4 100002fc8 <+116>: str w8, [sp, #0x4] 100002fcc <+120>: b 100002fd8 100002fd0 <+124>: ldrb w8, [sp, #0x1f] // 将结果取出来放在w8寄存器中 100002fd4 <+128>: str w8, [sp, #0x4] // 将w8寄存器中的结果又放入sp+0x4的存储地址中 100002fd8 <+132>: ldr w0, [sp, #0x4] // sp+0x4的存储地址中的值赋值给w0 (相当于将结果放在了x0寄存器上) 100002fdc <+136>: add sp, sp, #0x20 // 恢复栈顶位置 100002fe0 <+140>: ret // 函数执行完后返回
其实编译器帮我们实现的逻辑代码就是如下所示,因为下面的代码的汇编和上面的汇编代码是一致的:
init?(rawValue: Int) {
switch rawValue {
case 0: self = .n1
case 1: self = .n2
case 2: self = .n3
case 3: self = .n4
default: return nil
}
}
原来原始值的枚举类型EnumTest
.n1
, .n2
, .n3
, .n4
, 内存真实存储的值是 0,1,2,3
, 而且nil
的存储值竟然是4
。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。