美文网首页
arm64汇编基础

arm64汇编基础

作者: CoderJRHuo | 来源:发表于2020-03-30 18:06 被阅读0次

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

    相关文章

      网友评论

          本文标题:arm64汇编基础

          本文链接:https://www.haomeiwen.com/subject/cxxdactx.html