Swift 5.0 Method Internals Unearthed

DZone 's Guide to

Swift 5.0 Method Internals Unearthed

Follow along and learn how methods work at a low-level in the Swift language.

· Web Dev Zone ·
Free Resource

So far, we've been looking at a simple program to try to see how methods are defined internally in compiled Swift 5.0 programs. In our simple example, essentially a hello world example printing from a small class, we have been able to find the class definition itself but we haven't been able to see where the methods are defined. We traced through the compiled program and found this:

0000000100002120 struct __objc_data {
  0x80, // flags
  16, // instance start
  32, // instance size
  0x0, // ivar layout
  aTtc9swiftcmd7p, // name
  0x0, // base methods
  0x0, // base protocols
  __objc_class__TtC9swift_cmd7Printer_ivars, // ivars
  0x0, // weak ivar layout
  0x0 // base properties

But it doesn't have any associated method definitions. In Objective-C, there's a list of method descriptors that contain pointers to functions, so we expected to find the same thing here. We didn't.

So how are these methods stored?

There's a couple of key hints. From the main() function, we have this section of disassembly:

mov rcx, qword [rbp+var_28]
mov rsi, qword [rcx]
mov rdi, rax
mov qword [rbp+var_30], rsi
mov rsi, rdx
mov r13, rcx
mov rax, qword [rbp+var_30]
mov qword [rbp+var_38], rdx
call qword [rax+0x80]

If you trace through this, you have these assignments:

rcx <- [rbp + var_20]
rsi <- rcx
[rbp + var_30] <- rsi
rax <- [rbp + var_30]

What's interesting here is the use of offsets from the base pointer on the stack. We're not pushing and popping here. So how are these function addresses getting there? Time to look at LLDB.

To start up LLDB, I do a couple of things. First, I use an lldb-run file in my local directory with the following contents:

$ cat lldb-run
br s -n main

Usually I'll tweak those as I move through the program, learning what exactly it's doing. So let's start up LLDB, use this command:

$ lldb -s lldb-run swift-cmd 

Then, when in main() check out the disassembly:

(lldb) di
swift-cmd, main
-> 0 0x100001840 <+0>: push rbp
 1 0x100001841 <+1>: mov rbp, rsp
 2 0x100001844 <+4>: push r13
 3 0x100001846 <+6>: sub rsp, 0x38
 4 0x10000184a <+10>: xor eax, eax
 5 0x10000184c <+12>: mov ecx, eax
 6 0x10000184e <+14>: mov dword ptr [rbp - 0xc], edi
 7 0x100001851 <+17>: mov rdi, rcx
 8 0x100001854 <+20>: mov qword ptr [rbp - 0x18], rsi
*9 0x100001858 <+24>: call 0x1000018d0 ; type metadata accessor for swift_cmd.Printer
 10 0x10000185d <+29>: mov r13, rax
 11 0x100001860 <+32>: mov qword ptr [rbp - 0x20], rdx
*12 0x100001864 <+36>: call 0x100001a60 ; swift_cmd.Printer.__allocating_init() -> swift_cmd.Printer
 13 0x100001869 <+41>: mov r8d, 0xc
 14 0x10000186f <+47>: mov esi, r8d
 15 0x100001872 <+50>: mov qword ptr [rip + 0x9af], rax swift_cmd.printer : swift_cmd.Printer ; 0x100002228 swift-cmd.__DATA.__common
 16 0x100001879 <+57>: mov rax, qword ptr [rip + 0x9a8] swift_cmd.printer : swift_cmd.Printer ; 0x100002228 swift-cmd.__DATA.__common
 17 0x100001880 <+64>: lea rdi, [rip + 0x599] "Hello World!" ; 0x100001e20 swift-cmd.__TEXT.__cstring
 18 0x100001887 <+71>: mov edx, 0x1
 19 0x10000188c <+76>: mov qword ptr [rbp - 0x28], rax
*20 0x100001890 <+80>: call 0x100001d66 ; (__TEXT.__stubs) Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
 21 0x100001895 <+85>: mov rcx, qword ptr [rbp - 0x28]
 22 0x100001899 <+89>: mov rsi, qword ptr [rcx]
 23 0x10000189c <+92>: mov rdi, rax
 24 0x10000189f <+95>: mov qword ptr [rbp - 0x30], rsi
 25 0x1000018a3 <+99>: mov rsi, rdx
 26 0x1000018a6 <+102>: mov r13, rcx
 27 0x1000018a9 <+105>: mov rax, qword ptr [rbp - 0x30]
 28 0x1000018ad <+109>: mov qword ptr [rbp - 0x38], rdx
*29 0x1000018b1 <+113>: call qword ptr [rax + 0x80]
 30 0x1000018b7 <+119>: mov rdi, qword ptr [rbp - 0x38]
*31 0x1000018bb <+123>: call 0x100001d84 ; (__TEXT.__stubs) swift_bridgeObjectRelease
 32 0x1000018c0 <+128>: xor eax, eax
 33 0x1000018c2 <+130>: add rsp, 0x38
 34 0x1000018c6 <+134>: pop r13
 35 0x1000018c8 <+136>: pop rbp
*36 0x1000018c9 <+137>: ret

We want to set some additional breakpoints so we can understand what's going on. First, let's set them at the call on offset 113:

mov qword [rbp+var_38], rdx
call qword [rax+0x80]
mov rdi, qword [rbp+var_38]

We want to stop at the MOV opcode prior to the call. If we do, and we examine the contents of the RAX register, we see that they point to the top of the class definition structure at address 0x1000021a0. If we add 0x80 to this address, we have 0x100002220. The contents in this pointer are 0x100001c40, the address of the _$s9swift_cmd7PrinterC11printString7messageySS_tFfunction.

There's a block of function pointers in the program that are easy to miss. If we look at the address 0x100002220 in the disassembled program, we can see the address 0x100001c40, the pointer to the _$s9swift_cmd7PrinterC11printString7messageySS_tF function.

To wrap this up, the function pointers are stored next to the class definition structure, in what seems to be a table of other class data and function pointers. This is a bit more clear in IDA, which shows this at the bottom of the table:

__data:00000001000021F8 dq offset _$s9swift_cmd7PrinterC12str_to_printSSvg
__data:0000000100002200 dq offset _$s9swift_cmd7PrinterC12str_to_printSSvs
__data:0000000100002208 dq offset _$s9swift_cmd7PrinterC12str_to_printSSvM
__data:0000000100002210 dq offset _$s9swift_cmd7PrinterCACycfC
__data:0000000100002218 dq offset _$s9swift_cmd7PrinterC8printMsgyyF
__data:0000000100002220 dq offset _$s9swift_cmd7PrinterC11printString7messageySS_tF

It'll be interesting to see if this is an optimization or an extension of the Objective-C class definition structures that's currently undocumented. But for now, I think we've looked enough at Swift method implementations — there's plenty of Swift primitives that don't have Objective-C analogs, or are equally as core to the language, like Protocols.

swift internals ,web dev ,swift tutorial ,swift methods ,obective-c

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}