7

Swift 拾遗 - 汇编

 3 years ago
source link: https://kingcos.me/posts/2020/swift_with_asm/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
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:b:):
->  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:b:):
->  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:b:):
->  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:b:):
->  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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK