从HelloWord学习ARM64汇编 - jiaxw32/iNote GitHub Wiki

前言

SwiftDart 等高级语言广泛流行的时代,作为一名 iOS 开发者还有没有必要学习汇编这门低级语言,很多人可能都有这样的困惑。的确在日常的业务开发中,我们极少直接使用汇编来编写代码,利用高级语言就能完成大多数工作,但并不意味着学习汇编没有意义。上层的语言层出不穷,底层的原理却很少变化,了解汇编有助于理解高级语言的内部实现原理,加深对高级语言的认知,写出更高效的代码。另外在一些特殊的场景,也还是会用到汇编,例如使用汇编实现的 ptrace 反调试不会被人轻易绕过,相对更加安全;基于汇编实现的 objc_msgsend hook 更加高效。因此掌握一些汇编知识还是非常有必要的,对于逆向开发的人员,汇编的重要性更是不言而喻。

ARM 架构是当今非常流行的架构,我们日常使用的手机是 ARM 架构的。苹果最新的 MacBook 搭配的 M1 芯片使用 ARM 架构,性能显著提升。 iPhone 5S 之前的设备,如iPhone 5C、iPhone 5、iPhone 4S 等使用的 32 位架构,iPhone 5S 及之后的设备都采用了 64 位架构,目前我们使用的大多是 64 位的设备,因此本文主要介绍 ARM64 汇编。正如学习其他高级语言一样,我们也将从最简单的 HelloWorld 程序开始,开启汇编学习之旅。

初识汇编

首先使用 Xcode 新建一个命令行工具项目,项目名称命名为 HelloWorld,项目模版自动为我们生成了代码,如下所示:

#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    return 0;
}

上述代码很简单,相信稍微有些编程基础的人都可以读懂。那这段代码转化后的汇编代码又如何呢?在 Xcode 菜单栏,选择 Debug -> Debug Workflow -> Always show Disassembly,然后在 main 入口函数添加断点,运行程序,即可查看相应的汇编代码。

HelloWorld`main:
->  0x100003f38 <+0>:  sub    sp, sp, #0x30             ; =0x30 
    0x100003f3c <+4>:  stp    x29, x30, [sp, #0x20]
    0x100003f40 <+8>:  add    x29, sp, #0x20            ; =0x20 
    0x100003f44 <+12>: mov    w8, #0x0
    0x100003f48 <+16>: stur   wzr, [x29, #-0x4]
    0x100003f4c <+20>: stur   w0, [x29, #-0x8]
    0x100003f50 <+24>: str    x1, [sp, #0x10]
    0x100003f54 <+28>: adrp   x0, 0
    0x100003f58 <+32>: add    x0, x0, #0xfa8            ; =0xfa8 
    0x100003f5c <+36>: str    w8, [sp, #0xc]
    0x100003f60 <+40>: bl     0x100003f78               ; symbol stub for: printf
    0x100003f64 <+44>: ldr    w8, [sp, #0xc]
    0x100003f68 <+48>: mov    x0, x8
    0x100003f6c <+52>: ldp    x29, x30, [sp, #0x20]
    0x100003f70 <+56>: add    sp, sp, #0x30             ; =0x30 
    0x100003f74 <+60>: ret    

最简单的 HelloWorld 程序,包括函数声明总共 4 行代码,生成的汇编代码却有 16 行之多。初学汇编看到这些指令犹如天书,会有一丝畏惧,还没入门就想放弃。其实汇编并没有你想象中的那么难,在读懂上述代码之前,有必要介绍汇编中两个重要的概念:寄存器和栈。

(这里好些人可能会好奇,Xcode 创建的命令行应用程序怎么会是 ARM 架构的,因为我用的是 M1)

寄存器

寄存器是 CPU 内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果以及一些 CPU 运行需要的信息。寄存器位于存储器层次结构的最顶端,存贮容量非常有限,但读写速度极快。寄存器依靠名称区分数据,每一个寄存器都有特定的名称。如果你想对寄存器有进一步的了解,建议 Google 搜索,本文不再做进一步介绍。

通用(整数)寄存器

ARM64 架构提供了 31 个通用(整数)寄存器,命名为 R0~R30。当寄存器以特定指令形式使用时,必须进一步限定寄存器的大小,以指示操作数据大小(32位或64位)。

通用寄存器的限定名称如下,其中 n 是为寄存器编号,取值范围为 0~30。

大小(bits) 名称
32 位 Wn
64 位 Xn

Xn 与 Wn 实际上指向的是同一个寄存器,代表两种不同表现形式。

register

用于数据处理时,具体选择使用 X 寄存器还是 W 寄存器,取决于操作数的大小。X 寄存器用于 64 位运算,W 寄存器用于 32 位运算。例如 32 位整数加法运算,使用 W 寄存器。当写入 W 寄存器时,64 位寄存器的前 32 位被清零。

ADD W0, W1, W2 ; 等价于 W0 = W1 + W2

64 位整数加法运算,使用 X 寄存器

ADD X0, X1, X2 ; 等价于 X0 = X1 + X2

其中,R29 和 R30 寄存器,有特殊的用途,分别用作帧寄存器(FP, frame pointer)和链接寄存器(LR, link register)。

专用寄存器 SP 与 ZR

第 31 号寄存器命名并不是 R31,而是根据使用的上下文不同具有不同的命名。当 31 号寄存器用来处理栈相关的指令时,被用作栈指针,命名如下:

大小(bits) 名称
32位 WSP
64位 SP

SP 寄存器用用来保存指向栈顶的指针,栈指针必须 16 字节对齐。

当31 号寄存器用来处理其他指令时,被作为零寄存器(ZR),从其中读取数据时,返回 0,向其中写入数据时会被丢弃,命名如下:

大小(bits) 名称
32位 WZR
64位 XZR

PC 寄存器

PC 寄存器是程序计数器,可以理解为指令指针,指向当前执行的指令的地址。PC 寄存器不允许被直接修改,修改程序计数器必须使用流程控制指令。

其他寄存器

除了整数寄存器,ARM64 架构中还提供了浮点寄存器用于处理浮点运算和向量运算,另外还有当前程序状态寄存器(CPSR)、系统寄存器。由于本文暂不涉及到这些寄存器,不再做进一步介绍,有兴趣的同学请查阅相关文档。

由于寄存器的存储容量相当有限,函数执行过程中的临时变量、指针需要存储到栈上。汇编中栈由高地址向低地址增长,栈顶地址由 SP 寄存器定位,入栈使用栈顶的地址减小,出栈使栈顶地址增大。

register

需要强调的是,在栈上进行数据读写时,是向高地址增长的,举个例子:

LDR W0 [SP]

W0 寄存器为 32 位,可以存储 4 字节数据,SP 寄存器指向栈顶地址,[SP] 为汇编中的取地址运算,该指令的作用为"加载栈顶 4 字节数据到 W0 寄存器"。由于在栈上读写数据是向高地址增长的,所有这里读取的是栈上 SP~SP+4 地址数据。

详解 HelloWorld 汇编

在了解了寄存器与栈的概念后,接下来我们可以逐行解决 HelloWorld 指令了。

寄存器状态

(lldb) re read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x000000016fdff440
        x2 = 0x000000016fdff450
        x3 = 0x000000016fdff548
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0xb43148f04b510087
        x9 = 0xb43148f04b510087
       x10 = 0x0000000100000508  HelloWorld`_mh_execute_header + 1288
       x11 = 0x0000000080000028
       x12 = 0x0000000080000028
       x13 = 0x0000000000000000
       x14 = 0x0000000000000881
       x15 = 0x000000000000000c
       x16 = 0x0000000100003f38  HelloWorld`main at main.c:10
       x17 = 0x0000000100091810  dyld`vtable for ImageLoaderMachOCompressed + 16
       x18 = 0x0000000000000000
       x19 = 0x0000000000000000
       x20 = 0x0000000000000000
       x21 = 0x0000000000000000
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x000000016fdff430
        fp = 0x000000016fdff420
        lr = 0x00000001815b4f64  libdyld.dylib`start + 4
        sp = 0x000000016fdff410
        pc = 0x0000000100003f38  HelloWorld`main at main.c:10
      cpsr = 0x60000000

读取寄存器值

(lldb) re read w0
      w0 = 0x00000001
(lldb) re read x29 x30
      fp = 0x000000016fdff420
      lr = 0x00000001815b4f64  libdyld.dylib`start + 4

指令详解

sub sp, sp, #0x30

(lldb) re read sp
      sp = 0x000000016fdff3e0
(lldb) 

stp x29, x30, [sp, #0x20]

(lldb) re read sp
      sp = 0x000000016fdff3e0
(lldb) memory read --format x --size 8 --count 2 $sp+0x20
0x16fdff400: 0x000000016fdff420 0x00000001815b4f64

add x29, sp, #0x20

(lldb) re read x29
      fp = 0x000000016fdff400
(lldb) re read fp
      fp = 0x000000016fdff400

mov w8, #0x0

(lldb) re read w8
      w8 = 0x4b510087

(lldb) re read w8
      w8 = 0x00000000
(lldb) 

stur wzr, [x29, #-0x4]

(lldb) memory read --format x --size 4 --count 1 $x29-0x4
0x16fdff3fc: 0x00000000

stur w0, [x29, #-0x8]

(lldb) re read w0
      w0 = 0x00000001
(lldb) memory read --format x --size 4 --count 1 $x29-0x8
0x16fdff3f8: 0x00000001

str x1, [sp, #0x10]

(lldb) re read x1
      x1 = 0x000000016fdff440
(lldb) memory read --format x --size 8 --count 1 $sp+0x10
0x16fdff3f0: 0x000000016fdff440

adrp x0, 0

(lldb) re read x0
      x0 = 0x0000000000000001
(lldb) re read x0
      x0 = 0x0000000100003000  HelloWorld`_mh_execute_header + 12288
(lldb) 

add x0, x0, #0xfa8

(lldb) re read x0
      x0 = 0x0000000100003fa8  "Hello, World!\n"
(lldb) 

str w8, [sp, #0xc]

(lldb) re read w8
      w8 = 0x00000000
(lldb) memory read --format x --size 4 --count 1 $sp+0xc
0x16fdff3ec: 0x00000000

bl 0x100003f78

(lldb) re read lr
      lr = 0x00000001815b4f64  libdyld.dylib`start + 4
(lldb) re read pc
      pc = 0x0000000100003f60  HelloWorld`main + 40 at main.c:12:5
HelloWorld`printf:
->  0x100003f78 <+0>: nop    
    0x100003f7c <+4>: ldr    x16, #0x4084              ; (void *)0x0000000100003f9c
    0x100003f80 <+8>: br     x16
(lldb) re read lr
      lr = 0x0000000100003f64  HelloWorld`main + 44 at main.c:12:5
(lldb) re read pc
      pc = 0x0000000100003f78  HelloWorld`symbol stub for: printf
(lldb) re read sp
      sp = 0x000000016fdff3e0
(lldb) re read x16
     x16 = 0x0000000100003f9c  
(lldb) re read
General Purpose Registers:
        x0 = 0x0000000100003fa8  "Hello, World!\n"
        x1 = 0x000000016fdff440
        x2 = 0x000000016fdff450
        x3 = 0x000000016fdff548
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x0000000000000000
        x9 = 0xa46ce1b402c600c5
       x10 = 0x0000000100000508  HelloWorld`_mh_execute_header + 1288
       x11 = 0x0000000080000028
       x12 = 0x0000000080000028
       x13 = 0x0000000000000000
       x14 = 0x0000000000000881
       x15 = 0x000000000000000c
       x16 = 0x0000000100003f38  HelloWorld`main at main.c:10
       x17 = 0x0000000100091810  dyld`vtable for ImageLoaderMachOCompressed + 16
       x18 = 0x0000000000000000
       x19 = 0x0000000000000000
       x20 = 0x0000000000000000
       x21 = 0x0000000000000000
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x000000016fdff430
        fp = 0x000000016fdff400
        lr = 0x0000000100003f64  HelloWorld`main + 44 at main.c:12:5
        sp = 0x000000016fdff3e0
        pc = 0x0000000100003f78  HelloWorld`symbol stub for: printf
      cpsr = 0x60000000

(lldb) 

ldr w8, [sp, #0xc]

(lldb) re read w8
      w8 = 0x00000000
(lldb) re read x30
      lr = 0xfb77800100003f64 (0x0000000100003f64) HelloWorld`main + 44 at main.c:12:5

mov x0, x8

(lldb) re read w8
      w8 = 0x00000000
(lldb) re read x0
      x0 = 0x0000000000000000
(lldb) 

ldp x29, x30, [sp, #0x20]

(lldb) re read x29 x30
      fp = 0x000000016fdff400
      lr = 0x6048000100003f64 (0x0000000100003f64) HelloWorld`main + 44 at main.c:12:5

(lldb) re read x29 x30
      fp = 0x000000016fdff420
      lr = 0x00000001815b4f64  libdyld.dylib`start + 4
(lldb) 

add sp, sp, #0x30

(lldb) re read sp
      sp = 0x000000016fdff3e0
(lldb) re read sp
      sp = 0x000000016fdff410
(lldb) 

0x100003f74 <+60>: ret

(lldb) re read lr
      lr = 0x00000001815b4f64  libdyld.dylib`start + 4
libdyld.dylib`start:
    0x1815b4f60 <+0>: nop    
->  0x1815b4f64 <+4>: bl     0x1815d45c0               ; symbol stub for: exit
    0x1815b4f68 <+8>: brk    #0x3
⚠️ **GitHub.com Fallback** ⚠️