专注、坚持

Swift 拾遗 - 汇编

2020.08.10 by kingcos
Date Notes
2020-08-10 首次提交

Preface

《Swift 拾遗》是一个关于 Swift 的新文章专辑,这个系列的文章将不会涉及基本语法的层面,而是尝试从底层「拾」起之前所忽视的内容。本文涉及了各个部分关于汇编的内容。

struct & class

struct

分配在栈区

// main.swift

struct Foo {
    var a: Int
    var b: Int
}

func bar() {
    var foo1 = Foo(a: 10, b: 11) // BREAKPOINT 🔴
    var foo2 = foo1

    foo2.a = 20
    foo2.b = 22
}

bar()

我们定义一个结构体 Foo,并在函数 bar 中声明和构造后赋值于 foo1,之后将 foo1 赋值给新变量 foo2;此时改变 foo2 中的值,由于结构体是值类型,因此对于 foo2 的改变将不会影响 foo1,我们尝试从汇编底层看一下这个过程:

demo`bar():
    ; ...
    ; 函数调用前要先传递参数:
    ; 将立即数 10 移动到 edi 寄存器(即 rdi 寄存器低 32 位)
    0x100000b73 <+19>: movl   $0xa, %edi
    ; 将立即数 11 移动到 esi 寄存器(即 rsi 寄存器低 32 位)
    0x100000b78 <+24>: movl   $0xb, %esi
    ; 构造函数调用:
->  0x100000b7d <+29>: callq  0x100000b50               ; demo.Foo.init(a: Swift.Int, b: Swift.Int) -> demo.Foo at main.swift:1
    ; 经构造函数调用后,此时 10 位于 rax,11 位于 rdx(详见下方 init 内汇编)
    ; (lldb) register read rax => rax = 0x000000000000000a
    ; (lldb) register read rdx => rdx = 0x000000000000000b
    ; ---
    ; (lldb) register read rbp => rbp = 0x00007ffeefbff4a0
    ; (lldb) po withUnsafePointer(to: &foo1) { print("\($0)") } => 0x00007ffeefbff490
    ; 将 rax 寄存器内容(10)移动到 rbp - 0x10 地址(0x7FFEEFBFF490,即 foo1 的内存地址)
    0x100000b82 <+34>: movq   %rax, -0x10(%rbp)
    ; 将 rdx 寄存器内容(11)移动到 rbp - 0x8 地址(0x7FFEEFBFF498)
    0x100000b86 <+38>: movq   %rdx, -0x8(%rbp)
    ; (lldb) po withUnsafePointer(to: &foo2) { print("\($0)") } => 0x00007ffeefbff480
    ; 将 rax 寄存器内容(10)移动到 rbp - 0x20 地址(0x7FFEEFBFF480,即 foo2 的内存地址)
    0x100000b8a <+42>: movq   %rax, -0x20(%rbp)
    ; 将 rdx 寄存器内容(11)移动到 rbp - 0x18 地址(0x7FFEEFBFF488)
    0x100000b8e <+46>: movq   %rdx, -0x18(%rbp)
    ; 将立即数 20 移动到 rbp - 0x20 寄存器(foo2.a)
    0x100000b92 <+50>: movq   $0x14, -0x20(%rbp)
    ; 将立即数 22 移动到 rbp - 0x18 寄存器(foo2.b)
    0x100000b9a <+58>: movq   $0x16, -0x18(%rbp)
    0x100000ba2 <+66>: addq   $0x20, %rsp
    0x100000ba6 <+70>: popq   %rbp
    0x100000ba7 <+71>: retq

; 断点处执行 LLDB 命令 si:
demo`Foo.init(a🅱️):
->  0x100000b50 <+0>:  pushq  %rbp
    0x100000b51 <+1>:  movq   %rsp, %rbp
    ; 将参数放在函数内部的栈空间:
    ; 将 rdi 寄存器内容(10)移动到 rax 寄存器
    0x100000b54 <+4>:  movq   %rdi, %rax
    ; 将 rsi 寄存器内容(11)移动到 rdx 寄存器
    0x100000b57 <+7>:  movq   %rsi, %rdx
    0x100000b5a <+10>: popq   %rbp
    0x100000b5b <+11>: retq

综上,变量的内存地址格式为:-0x10(%rbp) 一般是栈空间的局部变量。这是因为 rbp 会根据每次函数调用而有不一样的值,局部变量的内存地址也在每次调用时会重新分配。

分配在全局区

// main.swift

struct Foo {
    var a: Int
    var b: Int
}

var foo1 = Foo(a: 10, b: 11) // BREAKPOINT 🔴
var foo2 = foo1

foo2.a = 20
foo2.b = 22

foo1foo2 在全局区时则又有些不一样:

demo`main:
    0x100000a80 <+0>:   pushq  %rbp
    0x100000a81 <+1>:   movq   %rsp, %rbp
    0x100000a84 <+4>:   subq   $0x60, %rsp
    ; 函数调用前要先传递参数:
    ; 将立即数 10 移动到 eax 寄存器(即 rax 寄存器低 32 位)
->  0x100000a88 <+8>:   movl   $0xa, %eax
    0x100000a8d <+13>:  movl   %edi, -0x4c(%rbp)
    ; 将 rax 寄存器内容(10)移动到 rdi 寄存器
    0x100000a90 <+16>:  movq   %rax, %rdi
    ; 将立即数 11 移动到 eax 寄存器(即 rax 寄存器低 32 位)
    0x100000a93 <+19>:  movl   $0xb, %eax
    0x100000a98 <+24>:  movq   %rsi, -0x58(%rbp)
    ; 将 rax 寄存器内容(11)移动到 rsi 寄存器
    0x100000a9c <+28>:  movq   %rax, %rsi
    ; 构造函数调用:
    0x100000a9f <+31>:  callq  0x100000c00               ; demo.Foo.init(a: Swift.Int, b: Swift.Int) -> demo.Foo at main.swift:3
    ; 经构造函数调用后,此时 10 位于 rax,11 位于 rdx(详见下方 init 内汇编)
    ; (lldb) register read rax => rax = 0x000000000000000a
    ; (lldb) register read rdx => rdx = 0x000000000000000b
    0x100000aa4 <+36>:  leaq   0x156d(%rip), %rcx        ; demo.foo1 : demo.Foo
    0x100000aab <+43>:  xorl   %r8d, %r8d
    0x100000aae <+46>:  movl   %r8d, %esi
    ; (lldb) po withUnsafePointer(to: &foo1) { print("\($0)") } => 0x0000000100002018
    ; 将 rax 寄存器内容(10)移动到 rip + 0x1560 地址(0x100002018,即 foo1 的内存地址)
    0x100000ab1 <+49>:  movq   %rax, 0x1560(%rip)        ; demo.foo1 : demo.Foo
    ; 将 rdx 寄存器内容(11)移动到 rip + 0x1561 地址(0x100002020)
    0x100000ab8 <+56>:  movq   %rdx, 0x1561(%rip)        ; demo.foo1 : demo.Foo + 8
    0x100000abf <+63>:  movq   %rcx, %rdi
    0x100000ac2 <+66>:  leaq   -0x18(%rbp), %rax
    0x100000ac6 <+70>:  movq   %rsi, -0x60(%rbp)
    0x100000aca <+74>:  movq   %rax, %rsi
    0x100000acd <+77>:  movl   $0x20, %edx
    0x100000ad2 <+82>:  movq   -0x60(%rbp), %rcx
    0x100000ad6 <+86>:  callq  0x100000e42               ; symbol stub for: swift_beginAccess
    ; (lldb) po withUnsafePointer(to: &foo2) { print("\($0)") } => 0x0000000100002028
    ; 将 rip + 0x1536 地址(0x100002018,即 foo1)内容(10)移动到 rax
    0x100000adb <+91>:  movq   0x1536(%rip), %rax        ; demo.foo1 : demo.Foo
    ; 将 rax 寄存器内容(10)移动到 rip + 0x153f 地址(00x100002028,即 foo2 的内存地址)
    0x100000ae2 <+98>:  movq   %rax, 0x153f(%rip)        ; demo.foo2 : demo.Foo
    ; 将 rip + 0x1530 地址(0x100002020)内容(11)移动到 rax
    0x100000ae9 <+105>: movq   0x1530(%rip), %rax        ; demo.foo1 : demo.Foo + 8
    ; 将 rax 寄存器内容(11)移动到 rip + 0x1539 地址(0x100002030)
    0x100000af0 <+112>: movq   %rax, 0x1539(%rip)        ; demo.foo2 : demo.Foo + 8
    0x100000af7 <+119>: leaq   -0x18(%rbp), %rdi
    0x100000afb <+123>: callq  0x100000e48               ; symbol stub for: swift_endAccess
    0x100000b00 <+128>: leaq   0x1521(%rip), %rax        ; demo.foo2 : demo.Foo
    0x100000b07 <+135>: xorl   %r8d, %r8d
    0x100000b0a <+138>: movl   %r8d, %ecx
    0x100000b0d <+141>: movq   %rax, %rdi
    0x100000b10 <+144>: leaq   -0x30(%rbp), %rsi
    0x100000b14 <+148>: movl   $0x21, %edx
    0x100000b19 <+153>: callq  0x100000e42               ; symbol stub for: swift_beginAccess
    ; 将立即数 20 移动到 rip + 0x14ff 寄存器(0x100002028,foo2.a)
    0x100000b1e <+158>: movq   $0x14, 0x14ff(%rip)       ; demo.foo1 : demo.Foo + 12
    0x100000b29 <+169>: leaq   -0x30(%rbp), %rdi
    0x100000b2d <+173>: callq  0x100000e48               ; symbol stub for: swift_endAccess
    0x100000b32 <+178>: leaq   0x14ef(%rip), %rax        ; demo.foo2 : demo.Foo
    0x100000b39 <+185>: xorl   %r8d, %r8d
    0x100000b3c <+188>: movl   %r8d, %ecx
    0x100000b3f <+191>: movq   %rax, %rdi
    0x100000b42 <+194>: leaq   -0x48(%rbp), %rsi
    0x100000b46 <+198>: movl   $0x21, %edx
    0x100000b4b <+203>: callq  0x100000e42               ; symbol stub for: swift_beginAccess
     ; 将立即数 22 移动到 rip + 0x14d5 寄存器(0x100002030,foo2.b)
    0x100000b50 <+208>: movq   $0x16, 0x14d5(%rip)       ; demo.foo2 : demo.Foo + 4
    0x100000b5b <+219>: leaq   -0x48(%rbp), %rdi
    0x100000b5f <+223>: callq  0x100000e48               ; symbol stub for: swift_endAccess
    0x100000b64 <+228>: xorl   %eax, %eax
    0x100000b66 <+230>: addq   $0x60, %rsp
    0x100000b6a <+234>: popq   %rbp
    0x100000b6b <+235>: retq

; 将断点加在 `0x100000a9f <+31>: callq 0x100000c00` 一行,并执行 `si`,进入函数内部;
; 因为 `init` 函数是结构体本身的函数,与结构体变量所在的内存区域无关,因此这部分的汇编与之前完全相同。
demo`Foo.init(a🅱️):
->  0x100000c00 <+0>:  pushq  %rbp
    0x100000c01 <+1>:  movq   %rsp, %rbp
    ; 将参数放在函数内部的栈空间:
    ; 将 rdi 寄存器内容(10)移动到 rax 寄存器
    0x100000c04 <+4>:  movq   %rdi, %rax
    ; 将 rsi 寄存器内容(11)移动到 rdx 寄存器
    0x100000c07 <+7>:  movq   %rsi, %rdx
    0x100000c0a <+10>: popq   %rbp
    0x100000c0b <+11>: retq

与刚才在函数体内的栈区不同的是,全局区(数据段)大量使用了 rip 寄存器。rip 作为指令指针寄存器,存储着 CPU 下一条要执行的指令的地址,一旦 CPU 读取一条指令,rip 会自动指向下一条指令。而代码一旦装载,每条指令的地址就会得以确认,因此通常使用 rip 加偏移的地址来存放全局变量。

class

分配在栈区

// main.swift

class Foo {
    var a: Int
    var b: Int

    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }
}

func bar() {
    var foo1 = Foo(a: 10, b: 11)
    var foo2 = foo1 // BREAKPOINT 🔴

    foo2.a = 20
    foo2.b = 22
}

bar()
demo`bar():
    0x100001cc0 <+0>:   pushq  %rbp
    0x100001cc1 <+1>:   movq   %rsp, %rbp
    0x100001cc4 <+4>:   pushq  %r13
    0x100001cc6 <+6>:   subq   $0x38, %rsp
    0x100001cca <+10>:  movq   $0x0, -0x10(%rbp)
    0x100001cd2 <+18>:  movq   $0x0, -0x18(%rbp)
    0x100001cda <+26>:  xorl   %eax, %eax
    0x100001cdc <+28>:  movl   %eax, %edi
    0x100001cde <+30>:  callq  0x100001d90               ; type metadata accessor for demo.Foo at <compiler-generated>
    ; 函数调用前要先传递参数:
    ; 将立即数 10 移动到 edi 寄存器(即 rdi 寄存器低 32 位)
    0x100001ce3 <+35>:  movl   $0xa, %edi
    ; 将立即数 11 移动到 esi 寄存器(即 rsi 寄存器低 32 位)
    0x100001ce8 <+40>:  movl   $0xb, %esi
    0x100001ced <+45>:  movq   %rax, %r13
    0x100001cf0 <+48>:  movq   %rdx, -0x20(%rbp)
    ; 构造函数调用:
    0x100001cf4 <+52>:  callq  0x100001b50               ; demo.Foo.__allocating_init(a: Swift.Int, b: Swift.Int) -> demo.Foo at main.swift:7
    ; rax 寄存器通常放置函数返回值(详见下文),这里即 foo1 指针所指向的堆空间内存地址
    ; (lldb) register read rax => rax = 0x000000010046d080
    ; (lldb) x --size 8 --format x --count 4 0x000000010046d080
    ; 0x10046d080: 0x0000000100003150 0x0000000000000002
    ; 0x10046d090: 0x000000000000000a 0x000000000000000b
    ; 此时可以尝试读取 foo1 的内存地址(指针本身的内存地址,位于栈空间)
    ; (lldb) po withUnsafeMutablePointer(to: &foo1) { print("\($0)") } => 0x00007ffeefbff510
    ; (lldb) po withUnsafeMutablePointer(to: &foo2) { print("\($0)") } => 0x00007ffeefbff508
    0x100001cf9 <+57>:  movq   %rax, %rdi
    ; 将 rax 寄存器内容(0x000000010046d080)移动到 rbp - 0x28 地址
    0x100001cfc <+60>:  movq   %rax, -0x28(%rbp)
    0x100001d00 <+64>:  callq  0x100001df6               ; symbol stub for: swift_retain
    ; 将 rbp - 0x28 地址内容(0x000000010046d080)移动到 rcx 寄存器
    0x100001d05 <+69>:  movq   -0x28(%rbp), %rcx
    ; (lldb) register read rbp => rbp = 0x00007ffeefbff520
    ; 将 rcx 寄存器内容(0x000000010046d080)移动到 rbp - 0x10 地址(0x7FFEEFBFF510,即 foo1)
    0x100001d09 <+73>:  movq   %rcx, -0x10(%rbp)
    ; (lldb) x --size 8 --format x --count 4 0x7FFEEFBFF510
    ; 0x7ffeefbff510: 0x000000010046d080 0x0000000000000000
    ; 0x7ffeefbff520: 0x00007ffeefbff540 0x00000001000017a4
->  0x100001d0d <+77>:  movq   %rcx, %rdi
    0x100001d10 <+80>:  movq   %rax, -0x30(%rbp)
    0x100001d14 <+84>:  callq  0x100001df6               ; symbol stub for: swift_retain
    0x100001d19 <+89>:  movq   -0x28(%rbp), %rdi
    0x100001d1d <+93>:  movq   %rax, -0x38(%rbp)
    0x100001d21 <+97>:  callq  0x100001df6               ; symbol stub for: swift_retain
    ; 将 rbp - 0x28 地址内容(0x000000010046d080)移动到 rcx 寄存器
    0x100001d26 <+102>: movq   -0x28(%rbp), %rcx
    ; 将 rcx 寄存器内容(0x000000010046d080)移动到 rbp - 0x18 地址(0x7FFEEFBFF508,即 foo2)
    0x100001d2a <+106>: movq   %rcx, -0x18(%rbp)
    ; (lldb) x --size 8 --format x --count 4 0x7FFEEFBFF508
    ; 0x7ffeefbff508: 0x000000010046d080 0x000000010046d080
    ; 0x7ffeefbff518: 0x0000000000000000 0x00007ffeefbff540
    ; 因此此时,foo1 和 foo2 中存储了相同的内存地址
    0x100001d2e <+110>: movq   (%rcx), %rdx
    0x100001d31 <+113>: movq   0x68(%rdx), %rdx
    ; 将立即数 20 移动到 edi 寄存器
    0x100001d35 <+117>: movl   $0x14, %edi
    0x100001d3a <+122>: movq   %rcx, %r13
    0x100001d3d <+125>: movq   %rax, -0x40(%rbp)
    ; 见下文
    0x100001d41 <+129>: callq  *%rdx
    0x100001d43 <+131>: movq   -0x28(%rbp), %rdi
    0x100001d47 <+135>: callq  0x100001df0               ; symbol stub for: swift_release
    0x100001d4c <+140>: movq   -0x28(%rbp), %rax
    0x100001d50 <+144>: movq   (%rax), %rcx
    0x100001d53 <+147>: movq   0x80(%rcx), %rcx
    ; 将立即数 22 移动到 edi 寄存器(同理)
    0x100001d5a <+154>: movl   $0x16, %edi
    0x100001d5f <+159>: movq   %rax, %r13
    0x100001d62 <+162>: callq  *%rcx
    0x100001d64 <+164>: movq   -0x28(%rbp), %rdi
    0x100001d68 <+168>: callq  0x100001df0               ; symbol stub for: swift_release
    0x100001d6d <+173>: movq   -0x18(%rbp), %rdi
    0x100001d71 <+177>: callq  0x100001df0               ; symbol stub for: swift_release
    0x100001d76 <+182>: movq   -0x10(%rbp), %rdi
    0x100001d7a <+186>: callq  0x100001df0               ; symbol stub for: swift_release
    0x100001d7f <+191>: addq   $0x38, %rsp
    0x100001d83 <+195>: popq   %r13
    0x100001d85 <+197>: popq   %rbp
    0x100001d86 <+198>: retq

; 将断点加在 `0x100000a9f <+31>: callq 0x100000c00` 一行,并执行 `si`,进入函数内部:
demo`Foo.__allocating_init(a🅱️):
->  0x100001b50 <+0>:  pushq  %rbp
    0x100001b51 <+1>:  movq   %rsp, %rbp
    0x100001b54 <+4>:  pushq  %r13
    0x100001b56 <+6>:  subq   $0x18, %rsp
    0x100001b5a <+10>: movl   $0x20, %eax
    0x100001b5f <+15>: movl   $0x7, %edx
    ; (lldb) register read rdi => rdi = 0x000000000000000a
    ; (lldb) register read rsi => rsi = 0x000000000000000b
    ; (lldb) register read rbp => rbp = 0x00007ffeefbff4d0
    ; 将 rdi 寄存器内容(10)移动到 rbp - 0x10 地址(0x7FFEEFBFF4C0)
    0x100001b64 <+20>: movq   %rdi, -0x10(%rbp)
    0x100001b68 <+24>: movq   %r13, %rdi
    ; 将 rsi 寄存器内容(11)移动到 rbp - 0x18 地址(0x7FFEEFBFF4B8)
    0x100001b6b <+27>: movq   %rsi, -0x18(%rbp)
    0x100001b6f <+31>: movq   %rax, %rsi
    0x100001b72 <+34>: callq  0x100001dd2               ; symbol stub for: swift_allocObject
    ; 将 rbp - 0x10 地址内容(10)移动到 rdi
    0x100001b77 <+39>: movq   -0x10(%rbp), %rdi
    ; 将 rbp - 0x18 地址内容(11)移动到 rsi
    0x100001b7b <+43>: movq   -0x18(%rbp), %rsi
    0x100001b7f <+47>: movq   %rax, %r13
    ; 构造函数调用:
    0x100001b82 <+50>: callq  0x100001b90               ; demo.Foo.init(a: Swift.Int, b: Swift.Int) -> demo.Foo at main.swift:7
    0x100001b87 <+55>: addq   $0x18, %rsp
    0x100001b8b <+59>: popq   %r13
    0x100001b8d <+61>: popq   %rbp
    0x100001b8e <+62>: retq

; 将断点加在 `0x100001b82 <+50>: callq  0x100001b90` 一行,并执行 `si`,进入函数内部:
demo`Foo.init(a🅱️):
->  0x100001b90 <+0>:   pushq  %rbp
    0x100001b91 <+1>:   movq   %rsp, %rbp
    0x100001b94 <+4>:   subq   $0x80, %rsp
    ; 清零:
    0x100001b9b <+11>:  movq   $0x0, -0x8(%rbp)
    0x100001ba3 <+19>:  movq   $0x0, -0x10(%rbp)
    0x100001bab <+27>:  movq   $0x0, -0x18(%rbp)
    ; 参数局部变量化:
    ; 第一个参数 10 从 rdi 转移到 rbp - 0x8 地址
    0x100001bb3 <+35>:  movq   %rdi, -0x8(%rbp)
    ; 第二个参数 11 从 rsi 转移到 rbp - 0x10 地址
    0x100001bb7 <+39>:  movq   %rsi, -0x10(%rbp)
    ; ...

; 将断点加在 `0x100001d41 <+129>: callq  *%rdx` 一行,并执行 `si`,进入函数内部:
demo`Foo.a.setter:
->  0x1000018d0 <+0>:  pushq  %rbp
    0x1000018d1 <+1>:  movq   %rsp, %rbp
    0x1000018d4 <+4>:  subq   $0x40, %rsp
    0x1000018d8 <+8>:  movq   %r13, %rax
    0x1000018db <+11>: addq   $0x10, %rax
    0x1000018df <+15>: xorl   %ecx, %ecx
    0x1000018e1 <+17>: leaq   -0x18(%rbp), %rdx
    0x1000018e5 <+21>: movl   $0x21, %esi
    ; (lldb) register read rdi => rdi = 0x0000000000000014
    ; (lldb) register read rbp => rbp = 0x00007ffeefbff4d0
    ; 将 rdi 寄存器内容(14)移动到 rbp - 0x20 地址
    0x1000018ea <+26>: movq   %rdi, -0x20(%rbp)
    0x1000018ee <+30>: movq   %rax, %rdi
    0x1000018f1 <+33>: movq   %rsi, -0x28(%rbp)
    0x1000018f5 <+37>: movq   %rdx, %rsi
    0x1000018f8 <+40>: movq   -0x28(%rbp), %rax
    0x1000018fc <+44>: movq   %rdx, -0x30(%rbp)
    0x100001900 <+48>: movq   %rax, %rdx
    0x100001903 <+51>: movq   %r13, -0x38(%rbp)
    0x100001907 <+55>: callq  0x100001dd8               ; symbol stub for: swift_beginAccess
    ; 将 rbp - 0x38 地址(0x7FFEEFBFF498)内容(0x000000010046d080,foo1 和 foo2 指向的内存空间地址)移动到 rax 寄存器
    0x10000190c <+60>: movq   -0x38(%rbp), %rax
    ; 将 rbp - 0x20 地址内容(14)移动到 rcx 寄存器
    0x100001910 <+64>: movq   -0x20(%rbp), %rcx
    ; 将 rcx 寄存器内容(14)移动到 rax + 0x10(0x10046D090)
    0x100001914 <+68>: movq   %rcx, 0x10(%rax)
    ; (lldb) x --size 8 --format x --count 4 0x000000010046d080
    ; 0x10046d080: 0x0000000100003150 0x0000000600000002
    ; 0x10046d090: 0x0000000000000014 0x000000000000000b
    0x100001918 <+72>: movq   -0x30(%rbp), %rdi
    0x10000191c <+76>: callq  0x100001de4               ; symbol stub for: swift_endAccess
    0x100001921 <+81>: addq   $0x40, %rsp
    0x100001925 <+85>: popq   %rbp
    0x100001926 <+86>: retq

当对象初始化后将通过 rax 返回给指针变量,再通过 rax 偏移去改变堆空间中的值,因此内存地址格式为:0x10(%rax) 的变量一般是堆空间。

函数

函数参数

// main.swift

func baz(_ a: Int, _ b: Int, _ c: Int, _ d: Int, _ e: Int,
         _ f: Int, _ g: Int, _ h: Int, _ i: Int, _ j: Int) {

}

baz(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // BREAKPOINT 🔴

定义一个需要 10 个参数的函数,我们尝试从汇编底层看一下这个过程:

demo`main:
    0x100000ea0 <+0>:   pushq  %rbp
    0x100000ea1 <+1>:   movq   %rsp, %rbp
    0x100000ea4 <+4>:   subq   $0x30, %rsp
    ; 第一个参数先放在 eax(rax 的低 32 位)
->  0x100000ea8 <+8>:   movl   $0x1, %eax
    0x100000ead <+13>:  movl   %edi, -0x4(%rbp)
    ; 第一个参数从 rax 转移到 rdi
    0x100000eb0 <+16>:  movq   %rax, %rdi
    ; 第二个参数先放在 eax(rax 的低 32 位)
    0x100000eb3 <+19>:  movl   $0x2, %eax
    0x100000eb8 <+24>:  movq   %rsi, -0x10(%rbp)
    ; 第二个参数从 rax 转移到 rsi
    0x100000ebc <+28>:  movq   %rax, %rsi
    ; 第三个参数放在 edx(rdx 的低 32 位)
    0x100000ebf <+31>:  movl   $0x3, %edx
    ; 第四个参数放在 ecx(rcx 的低 32 位)
    0x100000ec4 <+36>:  movl   $0x4, %ecx
    ; 第五个参数放在 r8d(r8 的低 32 位)
    0x100000ec9 <+41>:  movl   $0x5, %r8d
    ; 第六个参数放在 r9d(r9 的低 32 位)
    0x100000ecf <+47>:  movl   $0x6, %r9d
    ; 第七个参数放在 rsp((lldb) register read rsp => rsp = 0x00007ffeefbff510)
    0x100000ed5 <+53>:  movq   $0x7, (%rsp)
    ; 第八个参数放在 rsp + 0x8 地址(0x7FFEEFBFF518)
    0x100000edd <+61>:  movq   $0x8, 0x8(%rsp)
    ; 第九个参数放在 rsp + 0x10 地址(0x7FFEEFBFF520)
    0x100000ee6 <+70>:  movq   $0x9, 0x10(%rsp)
    ; 第十个参数放在 rsp + 0x18 地址(0x7FFEEFBFF528)
    0x100000eef <+79>:  movq   $0xa, 0x18(%rsp)
    ; 函数调用
    0x100000ef8 <+88>:  callq  0x100000f10               ; demo.baz(Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int) -> () at main.swift:1
    0x100000efd <+93>:  xorl   %eax, %eax
    0x100000eff <+95>:  addq   $0x30, %rsp
    0x100000f03 <+99>:  popq   %rbp
    0x100000f04 <+100>: retq

由上我们可以得出:rdiedi)、rsiesi)、rdxedx)、rcxecx)、r8r8d)、r9r9d)寄存器将依次存放函数参数,当以上寄存器个数仍不满足函数参数个数时,将从 rsp 开始放置函数参(其实 rsp 是函数栈空间的栈底,详见下文)。将断点加在 0x100000ef8 <+88>: callq 0x100000f10 一行,并执行 si,进入函数内部:

demo`baz(_:_:_:_:_:_:_:_:_:_:):
    ; 调用帧入栈
->  0x100000f10 <+0>:   pushq  %rbp
    ; 切换栈帧到当前栈帧
    0x100000f11 <+1>:   movq   %rsp, %rbp
    ; (lldb) register read rbp => rbp = 0x00007ffeefbff500
    0x100000f14 <+4>:   pushq  %rbx
    ; (lldb) register read rsp => rsp = 0x00007ffeefbff4f8
    ; rbp + 0x28(0x7FFEEFBFF528,即第十个参数 10)转移到 rax
    0x100000f15 <+5>:   movq   0x28(%rbp), %rax
    ; rbp + 0x20(0x7FFEEFBFF520,即第九个参数 9)转移到 r10
    0x100000f19 <+9>:   movq   0x20(%rbp), %r10
    ; rbp + 0x18(0x7FFEEFBFF518,即第八个参数 8)转移到 r11
    0x100000f1d <+13>:  movq   0x18(%rbp), %r11
    ; rbp + 0x10(0x7FFEEFBFF510,即第七个参数 7)转移到 rbx
    0x100000f21 <+17>:  movq   0x10(%rbp), %rbx
    ; 清零:
    0x100000f25 <+21>:  movq   $0x0, -0x10(%rbp)
    0x100000f2d <+29>:  movq   $0x0, -0x18(%rbp)
    0x100000f35 <+37>:  movq   $0x0, -0x20(%rbp)
    0x100000f3d <+45>:  movq   $0x0, -0x28(%rbp)
    0x100000f45 <+53>:  movq   $0x0, -0x30(%rbp)
    0x100000f4d <+61>:  movq   $0x0, -0x38(%rbp)
    0x100000f55 <+69>:  movq   $0x0, -0x40(%rbp)
    0x100000f5d <+77>:  movq   $0x0, -0x48(%rbp)
    0x100000f65 <+85>:  movq   $0x0, -0x50(%rbp)
    0x100000f6d <+93>:  movq   $0x0, -0x58(%rbp)
    ; 参数局部变量化:
    ; 第一个参数从 rdi 转移到 rbp - 0x10 地址(0x7FFEEFBFF4F0,高地址)
    0x100000f75 <+101>: movq   %rdi, -0x10(%rbp)
    ; 第二个参数从 rsi 转移到 rbp - 0x18 地址(0x7FFEEFBFF4E8)
    0x100000f79 <+105>: movq   %rsi, -0x18(%rbp)
    ; 第三个参数从 rdx 转移到 rbp - 0x20 地址(0x7FFEEFBFF4E0)
    0x100000f7d <+109>: movq   %rdx, -0x20(%rbp)
    ; 第四个参数从 rcx 转移到 rbp - 0x28 地址(0x7FFEEFBFF4D8)
    0x100000f81 <+113>: movq   %rcx, -0x28(%rbp)
    ; 第五个参数从 r8 转移到 rbp - 0x30 地址(0x7FFEEFBFF4D0)
    0x100000f85 <+117>: movq   %r8, -0x30(%rbp)
    ; 第六个参数从 r9 转移到 rbp - 0x38 地址(0x7FFEEFBFF4C8)
    0x100000f89 <+121>: movq   %r9, -0x38(%rbp)
    ; 第七个参数从 rbx 转移到 rbp - 0x40 地址(0x7FFEEFBFF4C0)
    0x100000f8d <+125>: movq   %rbx, -0x40(%rbp)
    ; 第八个参数从 r11 转移到 rbp - 0x48 地址(0x7FFEEFBFF4B8)
    0x100000f91 <+129>: movq   %r11, -0x48(%rbp)
    ; 第九个参数从 r10 转移到 rbp - 0x50 地址(0x7FFEEFBFF4B0)
    0x100000f95 <+133>: movq   %r10, -0x50(%rbp)
    ; 第十个参数从 rax 转移到 rbp - 0x58 地址(0x7FFEEFBFF4A8,低地址)
    0x100000f99 <+137>: movq   %rax, -0x58(%rbp)
    0x100000f9d <+141>: popq   %rbx
    0x100000f9e <+142>: popq   %rbp
    0x100000f9f <+143>: retq

rbp(lldb) register read rbp => rbp = 0x00007ffeefbff500,高地址,栈底)与 rsp(lldb) register read rsp => rsp = 0x00007ffeefbff4f8,低地址,栈顶)寄存器之间为函数的栈空间,即栈帧。函数参数局部变量化时,将依次从高地址排列至低地址,即入栈。

函数返回值

// main.swift

func baz() -> Int {
    return 10
}

_ = baz()

定义一个需要返回值的函数,我们尝试从汇编底层看一下这个过程:

demo`baz():
->  0x100000fa0 <+0>:  pushq  %rbp
    0x100000fa1 <+1>:  movq   %rsp, %rbp
    ; 将立即数 10 移动到 eax 寄存器(即 rax 寄存器低 32 位)
    0x100000fa4 <+4>:  movl   $0xa, %eax
    0x100000fa9 <+9>:  popq   %rbp
    0x100000faa <+10>: retq

此时返回值将移动到 rax 寄存器供函数外部使用。通常来说,一个函数只有一个返回值,但 Swift 中支持元组类型,可以将多个元素同时返回,那么此时返回值存在哪里呢?

// main.swift

func baz() -> (Int, Int, Int, Int, Int) {
    return (10, 11, 12, 13, 14)
}

var t = baz()
var a = t.0
var b = t.1
var c = t.2
var d = t.3
var e = t.4

转换为汇编代码:

demo`baz():
->  0x100000f50 <+0>:  pushq  %rbp
    0x100000f51 <+1>:  movq   %rsp, %rbp
    ; 返回值元组第一个元素放在 rax
    0x100000f54 <+4>:  movq   $0xa, (%rax)
    ; 返回值元组第二个元素放在 rax + 0x8
    0x100000f5b <+11>: movq   $0xb, 0x8(%rax)
    ; 返回值元组第三个元素放在 rax + 0x10
    0x100000f63 <+19>: movq   $0xc, 0x10(%rax)
    ; 返回值元组第四个元素放在 rax + 0x18
    0x100000f6b <+27>: movq   $0xd, 0x18(%rax)
    ; 返回值元组第五个元素放在 rax + 0x20
    0x100000f73 <+35>: movq   $0xe, 0x20(%rax)
    0x100000f7b <+43>: popq   %rbp
    0x100000f7c <+44>: retq

demo`main:
    0x100000dd0 <+0>:   pushq  %rbp
    0x100000dd1 <+1>:   movq   %rsp, %rbp
    0x100000dd4 <+4>:   subq   $0xb0, %rsp
    0x100000ddb <+11>:  leaq   -0x28(%rbp), %rax
    0x100000ddf <+15>:  movl   %edi, -0xa4(%rbp)
    0x100000de5 <+21>:  movq   %rsi, -0xb0(%rbp)
->  0x100000dec <+28>:  callq  0x100000f50               ; demo.baz() -> (Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int) at main.swift:3
    ; (lldb) register read rax => rax = 0x00007ffeefbff518
    ; (lldb) x --size 8 --format x --count 6 0x00007ffeefbff518
    ; 0x7ffeefbff518: 0x000000000000000a 0x000000000000000b
    ; 0x7ffeefbff528: 0x000000000000000c 0x000000000000000d
    ; 0x7ffeefbff538: 0x000000000000000e 0x00007ffeefbff550
    0x100000df1 <+33>:  leaq   0x1220(%rip), %rax        ; demo.t : (Swift.Int, Swift.Int, Swift.Int, Swift.Int, Swift.Int)
    0x100000df8 <+40>:  xorl   %ecx, %ecx
    ; (lldb) register read rbp => rbp = 0x00007ffeefbff540
    ; 将 rbp - 0x28 地址(0x7FFEEFBFF518)内容(10)移动到 rdx
    0x100000dfa <+42>:  movq   -0x28(%rbp), %rdx
    ; 将 rbp - 0x20 地址(0x7FFEEFBFF518)内容(11)移动到 rsi
    0x100000dfe <+46>:  movq   -0x20(%rbp), %rsi
    ; 将 rbp - 0x18 地址(0x7FFEEFBFF518)内容(12)移动到 r8
    0x100000e02 <+50>:  movq   -0x18(%rbp), %r8
    ; 将 rbp - 0x10 地址(0x7FFEEFBFF518)内容(13)移动到 r9
    0x100000e06 <+54>:  movq   -0x10(%rbp), %r9
    ; 将 rbp - 0x8 地址(0x7FFEEFBFF518)内容(14)移动到 r10
    0x100000e0a <+58>:  movq   -0x8(%rbp), %r10
    ; ...

我们可以看出,当元组作为返回值时,将从 rax 开始偏移依次存放。

Reference