arm64汇编基础

iOS汇编

  • 真机:arm64汇编
  • 模拟器:x86汇编

将c语言的代码转化成汇编:

xcrun --sdk iphoneos clang -S -arch arm64 main.c -o main.s

1. 寄存器

lldb查看当前arm64iOS真机)的所有寄存器:

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的时候可以使用低位32bitw0 ~ 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位,对应不同的功能状态:

cpsr
1.3 程序计数器寄存器pc
  • pc寄存器
    • 记录CPU当前指令是哪一条指令
    • 存储着当前CPU正在执行的指令的地址
1.4 链接状态寄存器lr
  • lr寄存器(也是x30寄存器)
    • 是链接寄存器(link register
    • 存储着当前函数的返回地址,即函数下一条指令的地址
1.5 堆栈指针寄存器sp fp
  • spStack Pointer
  • fpFrame 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指令后边可以跟的条件域:

  1. EQ: equal,等于
  2. NE: not equal,不等于
  3. GT: great than,大于
  4. GE: great equal,大于等于
  5. LT: less than,小于
  6. LE: less equal,小于等于

条件指令对应的状态寄存器cpsr的2进制标志位:

condtions

汇编实现:

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相关的)

    • ldrldur
    • ldpppair(一对)的简称)
  • store,往内存中写入数据(带s相关的)

    • strstur
    • stp
    • 零寄存器,里面存储的值是0
      • wzr32bitWord Zero Register
      • xzr64bit
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 xzr0寄存器

寄存器里面存储的值是0,是专门用来存储0的寄存器

  • wzr32bit
  • xzr64bit
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) 

之前的跳转指令bbl的区别就是:

  • b指令仅仅只是跳转
  • bl可以跳转回函数的返回地址,以便继续执行下面的指令,我们来看一下其本质

4. blret指令配合使用的本质

bl跳转指令执行的操作:

  1. 将下一条指令的地址存储到lrx30)寄存器中,lr寄存器存储着下一条指令的地址
  2. 跳转到标记处开始执行代码

ret返回指令执行的操作:

  1. lrx30)指令的地址赋值给pc指令,此时lr寄存器正好存储着下一条指令的地址,所以pc指令现在是下一条指令的地址
  2. 继续执行pc指令,即使函数返回的地址

所以blret指令配合才能达到函数返回的目的

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程序一样,通过修改汇编代码达到破解的目的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容