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
当 foo1
和 foo2
在全局区时则又有些不一样:
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
由上我们可以得出:rdi
(edi
)、rsi
(esi
)、rdx
(edx
)、rcx
(ecx
)、r8
(r8d
)、r9
(r9d
)寄存器将依次存放函数参数,当以上寄存器个数仍不满足函数参数个数时,将从 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
开始偏移依次存放。