arm64汇编基础
iOS汇编
- 真机:
arm64
汇编 - 模拟器:
x86
汇编
将c语言的代码转化成汇编:
xcrun --sdk iphoneos clang -S -arch arm64 main.c -o main.s
1. 寄存器
lldb
查看当前arm64
(iOS
真机)的所有寄存器:
register read
输出:
General Purpose Registers:
x0 = 0x1100000000000022
x1 = 0x00000002815a1040
x2 = 0x00000002824bdb20
x3 = 0x00000002815a1040
x4 = 0x0000000280ab1c80
x5 = 0x0000000000000001
x6 = 0x0000000281ba03c0
x7 = 0x00000000000004b0
x8 = 0x0000200000000000
x9 = 0x000141a1d420434d (0x00000001d420434d) (void *)0x01d42045c8000000
x10 = 0x0000000000000000
x11 = 0x0000000128853ff8
x12 = 0x00000000000000bf
x13 = 0x0000000000000000
x14 = 0x0000000000000001
x15 = 0x0000000000000000
x16 = 0x000000018b560130 libobjc.A.dylib`objc_release
x17 = 0x00000001c3bfeac0
x18 = 0x0000000000000000
x19 = 0x0000000127d0d9e0
x20 = 0x00000002824bdb60
x21 = 0x00000002815a1040
x22 = 0x00000001c3bfeac0
x23 = 0x0000000127d02200
x24 = 0x00000002824bdb20
x25 = 0x0000000127d02200
x26 = 0x00000002815a1040
x27 = 0x000000018fbecb7c UIKitCore`__UILogCategoryNewNode.llvm.6688940174485051596
x28 = 0x00000001d420bd08 UIKitCore`UIApp
fp = 0x000000016dc614a0
lr = 0x00000001021a200c ARM64`-[ViewController touchesBegan:withEvent:] + 64 at ViewController.m:23:5
sp = 0x000000016dc61470
pc = 0x00000001021a200c ARM64`-[ViewController touchesBegan:withEvent:] + 64 at ViewController.m:23:5
cpsr = 0x00000000
1.1 通用寄存器x0 ~ x28
64bit: x0 ~ x28
-
32bit: w0 ~ w28
(属于x0 ~ x28
的低位32bit
)
当将64bit
拆分成32bit
的时候可以使用低位32bit
的w0 ~ w28
来操作
其中:
-
x0 ~ x7
寄存器通常用来存放函数的参数,更多的参数使用堆栈来传递 -
x0
寄存器通常用来存放函数的返回值
读取某一个寄存器存储的值:
register read x0
给某一个通用寄存器写入值:
register write x0 0x1100000000000022
register write x1 5
1.2 程序状态寄存器cpsr
spsr
-
cpsr(current program status register)
寄存器: -
spsr(saved program status register)
寄存器:程序异常状态下使用
cpsr
寄存器有32位,对应不同的功能状态:
1.3 程序计数器寄存器pc
-
pc
寄存器- 记录CPU当前指令是哪一条指令
- 存储着当前CPU正在执行的指令的地址
1.4 链接状态寄存器lr
-
lr
寄存器(也是x30
寄存器)- 是链接寄存器(
link register
) - 存储着当前函数的返回地址,即函数下一条指令的地址
- 是链接寄存器(
1.5 堆栈指针寄存器sp
fp
-
sp
(Stack Pointer
) -
fp
(Frame Pointer
),也就是x29
寄存器
2. 指令
lldb
指令:si
是单步运行汇编指令,可以使用si
指令来进入函数内部汇编
汇编实现test
函数:
.text ; 指定代码在text段
.global _test ; test函数对外可见
; test函数实现
_test:
...
...
ret ; 返回
2.1 mov
指令
汇编实现:
mov x0, #0x8 ; 将0x8赋值给通用寄存器x0
mov x1, x0 ; 将通用寄存器x0的值赋值给通用寄存器x1
查看运行时汇编:
ARM64`test:
0x102fe2024 <+0>: mov x0, #0x8
0x102fe2028 <+4>: mov x1, x0
-> 0x102fe202c <+8>: ret
lldb
调试:
(lldb) si
(lldb) register read x0
x0 = 0x0000000104806038
(lldb) si
(lldb) register read x0
x0 = 0x0000000000000008
(lldb) register read x1
x1 = 0x0000000104806000
(lldb) si
(lldb) register read x1
x1 = 0x0000000000000008
(lldb)
2.2 add
sub
指令
汇编实现:
mov x0, #0x2
mov x1, #0x1
add x2, x0, x1 ; x0 + x1 赋值给 x2
sub x3, x0, x1 ; x0 - x1 赋值给 x3
查看运行时汇编:
ARM64`test:
0x1002ae01c <+0>: mov x0, #0x2
0x1002ae020 <+4>: mov x1, #0x1
0x1002ae024 <+8>: add x2, x0, x1
0x1002ae028 <+12>: sub x3, x0, x1
-> 0x1002ae02c <+16>: ret
lldb
调试:
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) register read x2
x2 = 0x0000000000000003
(lldb) register read x3
x3 = 0x0000000000000001
(lldb)
用汇编实现一个加法add
函数和减法sub
函数:
.text ; 存放在text段
.global _add, _sub ; 函数对外可见
; x0通常用来存放函数返回值,x0 ~ x7通常用来存放函数参数
; add函数
_add:
add x0, x0, x1 ; x0 + x1 赋值给 x0
ret
; sub函数
_sub:
sub x0, x0, x1 ; x0 - x1 赋值给 x0
ret
2.3 cmp
指令
将两个寄存器相减,将比较结果放到状态寄存器cpsr
里面,减法的结果会影响到状态寄存器cpsr
的2进制标志位
比较3和1的大小:
汇编实现:
mov x0, #0x3
mov x1, #0x1
cmp x0, x1 ; 做比较,相当于将 x0 - x1 的结果值给 cpsr
查看运行时汇编:
ARM64`test:
0x10092a020 <+0>: mov x0, #0x3
0x10092a024 <+4>: mov x1, #0x1
0x10092a028 <+8>: cmp x0, x1
-> 0x10092a02c <+12>: ret
lldb
调试:
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) register read cpsr // 读取cpsr的值
cpsr = 0x20000000 // 对应cpsr寄存器的2进制的29位是1
(lldb)
比较1和1的大小:
汇编实现:
mov x0, #0x1
mov x1, #0x1
cmp x0, x1 ; 做比较,相当于 x0 - x1 的结果值给 cpsr
查看运行时汇编:
ARM64`test:
0x102656020 <+0>: mov x0, #0x1
0x102656024 <+4>: mov x1, #0x1
0x102656028 <+8>: cmp x0, x1
->
lldb
调试:
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) register read cpsr
cpsr = 0x60000000 // 对应cpsr寄存器的2进制的30位是1
(lldb)
比较1和3的大小:
汇编实现:
mov x0, #0x1
mov x1, #0x3
cmp x0, x1 ; 做比较,相当于将 x0 - x1 的结果值给 cpsr
查看运行时汇编:
ARM64`test:
0x1008be020 <+0>: mov x0, #0x1
0x1008be024 <+4>: mov x1, #0x3
0x1008be028 <+8>: cmp x0, x1
-> 0x1008be02c <+12>: ret
lldb
调试:
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) register read cpsr
cpsr = 0x80000000 // 对应cpsr寄存器的2进制的31位是1
(lldb)
2.4 b
指令
跳转指令
汇编实现:
b mycode ; 跳转到下面的 mycode
mov x0, #0x5 ; 这条指令不会执行
mycode:
mov x1, #0x6
查看运行时汇编:
ARM64`test:
-> 0x1004f2020 <+0>: b 0x1004f2028 ; mycode
0x1004f2024 <+4>: mov x0, #0x5
跳转到mycode
:
ARM64`mycode:
0x1004f2028 <+0>: mov x1, #0x6
-> 0x1004f202c <+4>: ret
lldb
调试:
(lldb) si
(lldb) si
(lldb) si
(lldb) register read x1
x1 = 0x0000000000000006
(lldb
2.5 带条件跳转b
指令
一般和cmp
指令配合使用
b
指令后边可以跟的条件域:
-
EQ
:equal
,等于 -
NE
:not equal
,不等于 -
GT
:great than
,大于 -
GE
:great equal
,大于等于 -
LT
:less than
,小于 -
LE
:less equal
,小于等于
条件指令对应的状态寄存器cpsr
的2进制标志位:
汇编实现:
mov x0, #0x1
mov x1, #0x3
cmp x0, x1 ; 将x0 - x1的结果存放到cpsr寄存器
beq mycode ; eq会从读取cpsr寄存器的2进制标志位Z,相当于只有在x0 == x1的时候才会跳转到mycode
ret
mov x0, #0x5
mycode:
mov x1, #0x6
查看运行时汇编:
ARM64`test:
0x104dc6010 <+0>: mov x0, #0x1
0x104dc6014 <+4>: mov x1, #0x3
0x104dc6018 <+8>: cmp x0, x1
0x104dc601c <+12>: b.eq 0x104dc6028 ; mycode
-> 0x104dc6020 <+16>: ret
0x104dc6024 <+20>: mov x0, #0x5
lldb
调试:
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) register read cpsr
cpsr = 0x80000000 // 对应cpsr寄存器的2进制Z位(30位)为0
(lldb) si
(lldb)
2.6 bl
指令
带返回的跳转指令
汇编实现:
; 内部私有函数,配合bl指令
privatecode:
mov x0, #0x1
mov x2, #0x2
add x2, x0, x1
ret
bl privatecode ; 只有使用bl指令,调用privatecode之后才能返回到下一行指令,使用b指令返回不到下一行指令
mov x3, #0x2
mov x4, #0x1
查看运行时汇编:
bl
指令跳转到privatecode
:
ARM64`test:
-> 0x1043c2020 <+0>: bl 0x1043c2010 ; privatecode
0x1043c2024 <+4>: mov x3, #0x2
0x1043c2028 <+8>: mov x4, #0x1
0x1043c202c <+12>: ret
来到privatecode
:
ARM64`privatecode:
-> 0x1043c2010 <+0>: mov x0, #0x1
0x1043c2014 <+4>: mov x2, #0x2
0x1043c2018 <+8>: add x2, x0, x1
0x1043c201c <+12>: ret
privatecode
执行ret
之后再回到之前的函数执行下一行:
ARM64`test:
0x1043c2020 <+0>: bl 0x1043c2010 ; privatecode
-> 0x1043c2024 <+4>: mov x3, #0x2
0x1043c2028 <+8>: mov x4, #0x1
0x1043c202c <+12>: ret
3. 内存相关指令
-
load
、从内存中读取数据(带l
相关的)-
ldr
、ldur
-
ldp
(p
是pair
(一对)的简称)
-
-
store
,往内存中写入数据(带s
相关的)-
str
、stur
stp
- 零寄存器,里面存储的值是0
-
wzr
(32bit
,Word Zero Register
) -
xzr
(64bit
)
-
-
3.1 ldr
指令
外部调用:
int a = 8;
test();
汇编实现:
ldr x0, [x1] ; 寻址:x1的地址,取8字节(x0对应8字节),赋值给x0
查看运行时汇编:
ARM64-Memory`test:
0x100122054 <+0>: ldr x0, [x1]
-> 0x100122058 <+4>: ret
lldb
调试:
(lldb) p &a
(int *) $0 = 0x000000016fce3854
(lldb) si
(lldb) register read x1
x1 = 0x0000000101002000
(lldb) register write x1 0x000000016fce3854 // 将a的内存地址写入x1寄存器
// 查看a所在的内存地址0x16fce3854,也是x1寄存器的地址,之后会读取8字节赋值给x0
(lldb) x 0x000000016fce3854
0x16fce3854: 08 00 00 00 00 00 00 00 00 00 00 00 b8 38 ce 6f .............8.o
0x16fce3864: 01 00 00 00 01 00 00 00 00 00 00 00 90 38 ce 6f .............8.o
(lldb)
// x0之前的地址
(lldb) register read x0
x0 = 0x0000000101002038
// 通过ldr指令,会从x1内存地址中读取8个字节存入x0寄存器,正好对应上面的8字节
(lldb) si
(lldb) register read x0
x0 = 0x0000000000000008
汇编实现:
ldr x2, [x1, #0x3] ; 寻址:x1的地址 + 0x3,读取8字节x2(x2对应8字节)赋值给x2
3.2 ldur
指令
汇编实现:
; ldur指令用于负数
ldur x2, [x1, #-0x3]! ; 寻址:x1的地址 - 0x3,并且改变x1的地址(x1 - 0x3),然后取8字节赋值给x2(x2对应8字节)
查看运行时汇编:
ARM64-Memory`test`:
0x10428e054 <+0>: ldr x2, [x1, #0x3]!
-> 0x10428e058 <+4>: ret
lldb
调试:
(lldb) p &a
(int *) $0 = 0x000000016bb77854
(lldb) si
(lldb) register write x1 0x000000016bb77854
(lldb) si ; 执行ldr指令
(lldb) register read x1 ; x1的内存是+0x3以后的内存
x1 = 0x000000016bb77857
(lldb) register read x2 ; 寻址:x1 + 0x3,读取前8字节(x2)赋值给x2
x2 = 0x0000000000000000
; 可以看到前8字节正好是x2的地址
(lldb) x 0x000000016bb77857
0x16bb77857: 00 00 00 00 00 00 00 00 00 b8 78 b7 6b 01 00 00 ..........x.k...
0x16bb77867: 00 01 00 00 00 00 00 00 00 90 78 b7 6b 01 00 00 ..........x.k...
(lldb)
3.3 ldp
指令
汇编实现:
ldp w0, w1, [x2, #0x10] ; 寻址:x2的内存地址 + 0x10,分别各取4字节分别放赋值给一对寄存器w0和w1(w0和w1对应4字节)
查看运行时汇编:
ARM64-Memory`test:
0x100552054 <+0>: ldp w0, w1, [x2, #0x10]
-> 0x100552058 <+4>: ret
lldb
调试:
(lldb) p &a
(int *) $0 = 0x000000016f8b3854
(lldb) si
(lldb) register write x2 0x000000016f8b3854
(lldb) x 0x000000016f8b3854+0x10
0x16f8b3864: 01 00 00 00 01 00 00 00 00 00 00 00 90 38 8b 6f .............8.o
0x16f8b3874: 01 00 00 00 60 03 62 8b 01 00 00 00 60 03 62 8b ....`.b.....`.b.
(lldb) si
(lldb) register read w0
w0 = 0x00000001 // 取了前面4字节
(lldb) register read w1
w1 = 0x00000001 // 继续取4字节
(lldb)
3.4 str
stur
stp
指令
对应上面的ldr
ldur
ldp
指令
ldr
ldur
ldp
指令是从寄存器读取内存地址
str
stur
stp
指令是往寄存器写入内存地址
汇编实现:
str w0, [x1] ; 寻址:x1的地址,取4字节w0的值,写入8字节到x1的内存(x1的地址不变,存储的内容会被覆盖)
查看运行时汇编:
ARM64-Memory`test:
0x10269a054 <+0>: str w0, [x1]
-> 0x10269a058 <+4>: ret
lldb
调试:
lldb) p &a
(int *) $0 = 0x000000016d76b854
(lldb) si
(lldb) register write w0 5 // w0的值为5
(lldb) register write x1 0x000000016d76b854 // x1的内存地址 = a的内存地址
(lldb) si // 取4字节w0的值 5 写入8字节到x1(a的内存),x1的内存地址(a的内存地址)不变,存储的内容变为5
(lldb) c
Process 25740 resuming
(lldb) po a // 得到a的值也变成了5
5
(lldb)
同样也可以进行地址+ / -
操作:
str w0, [x1, #0x4] ; 取4字节寄存器w0的值写入8字节的[x1寄存器的内存地址 + 0x4]之后的地址
3.5 wzr
xzr
0寄存器
寄存器里面存储的值是0,是专门用来存储0的寄存器
-
wzr
是32bit
-
xzr
是64bit
3.6 pc
lr
寄存器
直接查看运行时汇编:
0x10278e274 <+52>: adrp x0, 7
0x10278e278 <+56>: add x0, x0, #0x388 ; =0x388
0x10278e27c <+60>: ldr x0, [x0]
-> 0x10278e280 <+64>: bl 0x10278e5c8 ; symbol stub for: objc_opt_class
可以发现pc
寄存器存储着当前执行指令的内存地址0x10278e280
:
register read
General Purpose Registers:
x0 = 0x0000000102795418 (void *)0x00000001027953f0: AppDelegate
x1 = 0x0000000103007003
x2 = 0x4c45524f545541a1
x3 = 0x000000016d677980
x4 = 0x0000000000000000
x5 = 0x0000000000000000
x6 = 0x0000000000000000
x7 = 0x0000000000000000
x8 = 0x0000000000000008
x9 = 0x00000000a1a1a1a1
x10 = 0x0000000000000d21
x11 = 0x00000001d6ec0537
x12 = 0x00000001d6ec0537
x13 = 0x0000000000000008
x14 = 0x0000000000000028
x15 = 0x00000000000000d0
x16 = 0x000000018b561428 libobjc.A.dylib`objc_autoreleasePoolPush
x17 = 0x0000000102795490 _dyld_private
x18 = 0x0000000000000000
x19 = 0x0000000000000000
x20 = 0x0000000000000000
x21 = 0x0000000000000000
x22 = 0x0000000000000000
x23 = 0x0000000000000000
x24 = 0x0000000000000000
x25 = 0x0000000000000000
x26 = 0x0000000000000000
x27 = 0x0000000000000000
x28 = 0x000000016d6778a8
fp = 0x000000016d677870
lr = 0x000000010278e274 ARM64-Memory`main + 52 at main.m:19:9
sp = 0x000000016d677840
pc = 0x000000010278e280 ARM64-Memory`main + 64 at main.m:21:50
cpsr = 0x80000000
lr
链接寄存器也是x30
寄存器:
(lldb) register read x30
lr = 0x000000010278e274 ARM64-Memory`main + 52 at main.m:19:9
lr
寄存器存储着当前函数的返回地址,即函数下一条指令的地址:
bl
跳转指令即将进入函数test
:
-> 0x1044ca270 <+48>: bl 0x1044ca054 ; test
0x1044ca274 <+52>: adrp x0, 7
0x1044ca278 <+56>: add x0, x0, #0x388 ; =0x388
0x1044ca27c <+60>: ldr x0, [x0]
进入test
函数:
ARM64-Memory`test:
-> 0x1044ca054 <+0>: ldr x2, [x1, #0x3]!
0x1044ca058 <+4>: ret
此时查看lr
寄存器地址,正好是上面的地址0x00000001044ca274
,即test
函数的下一条指令内存地址:
(lldb) si
(lldb) register read lr
lr = 0x00000001044ca274 ARM64-Memory`main + 52 at main.m:19:9
(lldb) si
(lldb) si
(lldb)
之前的跳转指令b
和bl
的区别就是:
-
b
指令仅仅只是跳转 - 而
bl
可以跳转回函数的返回地址,以便继续执行下面的指令,我们来看一下其本质
4. bl
和ret
指令配合使用的本质
bl
跳转指令执行的操作:
- 将下一条指令的地址存储到
lr
(x30
)寄存器中,lr
寄存器存储着下一条指令的地址 - 跳转到标记处开始执行代码
ret
返回指令执行的操作:
- 将
lr
(x30
)指令的地址赋值给pc
指令,此时lr
寄存器正好存储着下一条指令的地址,所以pc
指令现在是下一条指令的地址 - 继续执行
pc
指令,即使函数返回的地址
所以bl
和ret
指令配合才能达到函数返回的目的
5. 堆栈平衡
5.1 函数类型
- 叶子函数:函数里面没有再调用其他函数了
- 非叶子函数:函数里面有调用其他函数
5.2 叶子函数的栈寄存器sp
平衡
实现一个c语言函数CTest.c
:
void haha(void) {
int a = 2;
int b = 3;
}
查看运行时汇编:
ARM64-Memory`haha:
; 切换sp寄存器的指向:sp地址 - 0x10,申请栈空间,存放变量a和b
-> 0x104ebe218 <+0>: sub sp, sp, #0x10 ; =0x10
; 将变量a的值2写入栈空间
0x104ebe21c <+4>: orr w8, wzr, #0x2
0x104ebe220 <+8>: str w8, [sp, #0xc]
; 将变量b的值3写入栈空间
0x104ebe224 <+12>: orr w8, wzr, #0x3
0x104ebe228 <+16>: str w8, [sp, #0x8]
; 恢复sp寄存器的指向:sp地址 + 0x10,指向原来的地址
0x104ebe22c <+20>: add sp, sp, #0x10 ; =0x10
; 返回
0x104ebe230 <+24>: ret
调用函数的时候sp
寄存器存放某一个地址0x000000016af47840
(lldb) si
(lldb) register read sp ; sp起始地址
sp = 0x000000016af47840
(lldb) si
(lldb) register read sp ; sp - 0x10,切换sp指向,用来存放a和b
sp = 0x000000016af47830
(lldb) si
(lldb) si ; w8的值是2,str指令将w8的值2写入a的栈内存:sp + 0xc
(lldb) p &a
(int *) $0 = 0x000000016af4783c
(lldb) si
(lldb) si ; w8的值是3,str指令将w8的值3写入b的栈内存:sp + 0x8
(lldb) p &b
(int *) $1 = 0x000000016af47838
(lldb) si
(lldb) register read sp
sp = 0x000000016af47840
(lldb)
5.3 非叶子函数的栈寄存器sp
平衡
实现一个c语言函数CTest.c
:
void haha(void) {
int a = 2;
int b = 3;
}
void hehe() {
int a = 4;
int b = 5;
haha();
}
查看运行时汇编:
ARM64-Memory`hehe:
; 切换sp指针:sp指针 - 0x20,申请栈空间
-> 0x100306208 <+0>: sub sp, sp, #0x20 ; =0x20
; 先在当前函数栈空间记录之前的sp和lr寄存器的值:
; stp指令,寻址:[sp + #0x10],取8字节将原来的x29寄存器(fp寄存器)的值写入当前函数栈内存
; 再取8字节将原来x30寄存器(lr)寄存器的值写入当前栈内存地址
; lr寄存器的值是函数的返回地址
0x10030620c <+4>: stp x29, x30, [sp, #0x10]
; 改变fp的指向:[sp + 0x10] ,此时fp指向当前栈内存地址,指向栈底
; 此时,lr和sp都有存储地址
0x100306210 <+8>: add x29, sp, #0x10 ; =0x10
; 开始使用存储函数局部变量的值:
; 存储a的值4,内存地址:fp - 0x4
0x100306214 <+12>: orr w8, wzr, #0x4
0x100306218 <+16>: stur w8, [x29, #-0x4]
; 存储b的值5,内存地址:sp + 0x8
0x10030621c <+20>: mov w8, #0x5
0x100306220 <+24>: str w8, [sp, #0x8]
; 跳转到haha函数
0x100306224 <+28>: bl 0x1003061ec ; haha at CTest.c:11
; 恢复fp,lr ,因为开始存储到了当前函数栈
; 从[sp + #0x10]读取8字节的内容赋值给fp,
; 再读取8字节的内容赋值给lr
0x100306228 <+32>: ldp x29, x30, [sp, #0x10]
; 恢复sp寄存器指向:sp指针 + 0x20
0x10030622c <+36>: add sp, sp, #0x20 ; =0x20
; 返回
0x100306230 <+40>: ret
lldb
调试:
lldb) si
; 改变sp指向:sp - 0x20,申请当前函数栈空间,使其指向栈顶
(lldb) register read sp
sp = 0x000000016d2e7840
(lldb) si
(lldb) register read sp
sp = 0x000000016d2e7820
; 这是fp和lr最开始的地址
(lldb) register read fp
fp = 0x000000016d2e7870
(lldb) register read lr
lr = 0x0000000102b1e260 ARM64-Memory`main + 44 at main.m:26:13
; 先将fp和lr的地址在当前栈空间存储,起始地址:sp + 0x10,分别取8字节存储
(lldb) si
(lldb) x 0x000000016d2e7830
0x16d2e7830: 70 78 2e 6d 01 00 00 00 60 e2 b1 02 01 00 00 00 px.m....`.......
0x16d2e7840: 00 00 00 00 00 00 00 00 38 80 80 03 01 00 00 00 ........8.......
; 改变fp指向,使其指向栈底
(lldb) si
(lldb) register read fp
fp = 0x000000016d2e7830
; 存储临时变量a的值4,地址:fp - 0x4
(lldb) si
(lldb) si
(lldb) p &a
(int *) $0 = 0x000000016d2e782c
; 存储临时变量b的值5,地址:sp + 0x8
(lldb) si
(lldb) si
(lldb) p &b
(int *) $1 = 0x000000016d2e7828
; bl跳转到haha函数
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) si
(lldb) si
; 从当前函数栈读取之前存储的fp和lr,起始地址:sp + 0x10,分别取8字节,恢复其指向
(lldb) si
(lldb) register read fp
fp = 0x000000016d2e7870
(lldb) register read lr
lr = 0x0000000102b1e260 ARM64-Memory`main + 44 at main.m:26:13
(lldb) si
(lldb) register read sp
sp = 0x000000016d2e7840
(lldb)
-
sp
也叫栈顶指针 -
fp
也叫栈底指针
6. 简单应用
6.1 lldb
打印寄存器对应的函数
根据runtime
方法调用的本质objc_msgSend(调用者,方法选择器,参数1,参2数,......)
,使用lldb
可以打印函数的参数:
// 打印方法调用者
po $x0
// 打印方法名,以字符串的形式打印内存
x/s $x1
// 打印参数
po $x2
...
...
6.2 破解Mac
命令行程序
通过修改汇编代码来破解程序:
使用Hopper
来分析汇编代码:
; while循环
loc_100000ed6:
; cmp指令比较
0000000100000ed6 cmp dword [rbp+var_14], 0x1e240 ; CODE XREF=_main+89
; 跳转到loc_100000f0e执行
0000000100000edd je loc_100000f0e
; while循环的代码,可以删除这段汇编代码达到破解的目的
0000000100000ee3 lea rdi, qword [aXe8xafxb7xe8xb] ; argument "format" for method imp___stubs__printf, "\\xE8\\xAF\\xB7\\xE8\\xBE\\x93\\xE5\\x85\\xA5\\xE5\\xAF\\x86\\xE7\\xA0\\x81:"
0000000100000eea mov al, 0x0
0000000100000eec call imp___stubs__printf ; printf
0000000100000ef1 lea rdi, qword [aD] ; argument "format" for method imp___stubs__scanf, "%d"
0000000100000ef8 lea rsi, qword [rbp+var_14]
0000000100000efc mov dword [rbp+var_24], eax
0000000100000eff mov al, 0x0
0000000100000f01 call imp___stubs__scanf ; scanf
0000000100000f06 mov dword [rbp+var_28], eax
0000000100000f09 jmp loc_100000ed6
; 结尾处有返回到了loc_100000ed6
loc_100000f0e:
0000000100000f0e lea rdi, qword [aXe5xafx86xe7xa] ; argument "format" for method imp___stubs__printf, "\\xE5\\xAF\\x86\\xE7\\xA0\\x81\\xE6\\xAD\\xA3\\xE7\\xA1\\xAE\\xEF\\xBC\\x8C\\xE6\\xAC\\xA2\\xE8\\xBF\\x8E\\xE4\\xBD\\xBF\\xE7\\x94\\xA8!\\n", CODE XREF=_main+45
0000000100000f15 mov al, 0x0
0000000100000f17 call imp___stubs__printf ; printf
0000000100000f1c mov rdi, qword [rbp+var_20] ; argument "pool" for method imp___stubs__objc_autoreleasePoolPop
0000000100000f20 mov dword [rbp+var_2C], eax
0000000100000f23 call imp___stubs__objc_autoreleasePoolPop ; objc_autoreleasePoolPop
0000000100000f28 xor eax, eax
0000000100000f2a add rsp, 0x30
0000000100000f2e pop rbp
0000000100000f2f ret
; endp
6.3 破解iOS程序
原理同破解Mac
程序一样,通过修改汇编代码达到破解的目的。