[quote='797124022, Nickkk, /thread/760029?answerId=797124022#797124022, /profile/Nickkk']
I found out that there is another base address here 0x0000000100000000
(which almost looks like 0x0) that I also have to subtract.
[/quote]
Right, yeah, I should’ve mentioned that. A Mach-O executable starts with a 4 GiB zero page segment, so the load address of the __TEXT
segment is 0x0000000100000000. Consider:
% otool -l Test760029.app/Contents/MacOS/Test760029
Test760029.app/Contents/MacOS/Test760029 (architecture x86_64):
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
…
Load command 1
cmd LC_SEGMENT_64
cmdsize 1192
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000003000
…
This segment is not present for other Mach-O images, so they have a load address of 0.
I’m glad you were able to figure that out on your own!
[quote='797124022, Nickkk, /thread/760029?answerId=797124022#797124022, /profile/Nickkk']
I get this relatively short and rather obscure output:
[/quote]
Right. There’s a lot to unpack here, and it’s hard to explain the details without having your code, so lemme show you an example based on this code:
import Cocoa
class ViewController: NSViewController {
@IBAction func testQQQ1Action(_ sender: Any) {
print("QQQ1", sender)
}
@IBAction func testQQQ2Action(_ sender: Any) {
print("QQQ2", sender)
}
}
I’ve pasted the disassembly in below; you’ll probably want to copy that into a different window so you can view it side-by-side with this explanation.
The first thing to note is that the compiler is really frikkin’ clever when you turn on optimisations. I have two Objective-C methods, -testQQQ1Action:
and -testQQQ2Action:
, that share a bunch of code. The compiler has noticed that and merged the implementations. So, the disassembly starts out with two short function stubs that put the string value into a register and jump into the actual implementation.
Setting a breakpoint at one of those stubs, I see that the registers are set up the way you’d expect:
(lldb) settings set target.language objc
(lldb) po $x0
<Test760029.ViewController: 0x60000278c780>
(lldb) p (char *)$x1
(char *) 0x00000001000032db "testQQQ1Action:"
(lldb) po $x2
<NSButton: 0x129f10290>
Note The target language defaults to Swift, which can’t cope with register variables like $x0
, so I force it to Objective-C. That’s generally a good idea when debugging at the assembly language level.
The first two are self
and SEL
, and the second one is sender
.
Now these stubs set up an extra register, w3
, or the 32-bit variant of x3
, to hold either 'QQQ1'
or 'QQQ2'
. So, at the entry point to the merged function we have:
x0
is self
x1
is SEL
x2
is sender
x3
is four ASCII bytes of the string to print
With that in mind, let’s look at this function.
The first thing to note is the name:
-
It starts with merged
. I’ve not seen that before but the meaning seems pretty obvious. It’s the result of the merger of two similar routines, in my test testQQQ1(_:)
and ``testQQQ2(_😃 `.
-
It has @objc
, indicating that it follows Objective-C calling conventions.
-
Finally, there’s the Swift signature, which LLDB has helpfully demangled for us.
Next let’s walk through the instructions:
-
+0 through +16 are the function prologue, setting up the stack frame.
-
+20 through +28 save the parameters into preserved registers. We now have:
x21
is self
x20
is sender
x19
is four ASCII bytes of the string to print
-
+32 through +48 are retaining sender
and self
respectively. AFAICT swift_unknownObjectRetain
is a routine that detects whether the object is Swift or Objective-C and retains the value accordingly. Honestly, I’m not sure why that’s necessary here. I suspect it’s because it allows this routine to be called directly from Swift.
-
+52 through +68 are converting the incoming sender
value from AnyObject
to AnyValue
. This Any
value now holds the retain count, so +68 balances the retain at +36.
-
+72 through +96 is Swift allocating the storage for the array that’s gonna be passed to print(…)
. Note that the object pointer gets stored in x20
, which replacing the sender
object that was previously there. This is fine because we’ve released our reference to that object at +68. The real sender
value is an Any
, which is stored on the stack at at +60 via the address set up at +52.
-
+100 through +164 is the rest of the call to print(…)
, populating the array and the various other parameters. The only thing I want to highlight is +128, which stores x19
, the ASCII bytes of the string, into the string value.
-
+168 through +172 balances the retain at +44.
-
+176 through +180 balances the allocation at +92
-
+184 through +188 release the reference to sender
held in our Any
box. Note how its argument, in x0
, is set up from the stack pointer.
-
+192 through +204 are the function epilogue, tearing down the stack frame.
So, in this case the thunk that I described in my earlier post isn’t a separate thing. The Swift compiler has inlined the Swift method into the Objective-C thunk. However, the thunk is actually a real thing because, in the non-optimised build, where the compiler inlines a lot less, the Swift method and the Objective-C thunk get their own stack frames:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001053c9420 Test760029.debug.dylib`ViewController.testQQQ1Action(sender=0x00000001277145e0) at ViewController.swift:6:15
frame #1: 0x00000001053c95e8 Test760029.debug.dylib`@objc ViewController.testQQQ1Action(_:) at <compiler-generated>:0
frame #2: 0x0000000198e6a0e8 AppKit`-[NSApplication(NSResponder) sendAction:to:from:] + 460
…
Anyway, I’m not sure this answers all of your questions but it should give you something to chew on.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
(lldb) disas -s 0x100002aec -e 0x100002bd4
Test760029`@objc Test760029.ViewController.testQQQ1Action(Any) -> ():
0x100002aec <+0>: mov w3, #0x5151
0x100002af0 <+4>: movk w3, #0x3151, lsl #16
0x100002af4 <+8>: b 0x100002b04 ; merged @objc Test760029.ViewController.testQQQ1Action(Any) -> ()
Test760029`@objc Test760029.ViewController.testQQQ2Action(Any) -> ():
0x100002af8 <+0>: mov w3, #0x5151
0x100002afc <+4>: movk w3, #0x3251, lsl #16
0x100002b00 <+8>: b 0x100002b04 ; merged @objc Test760029.ViewController.testQQQ1Action(Any) -> ()
Test760029`merged @objc Test760029.ViewController.testQQQ1Action(Any) -> ():
0x100002b04 <+0>: sub sp, sp, #0x50
0x100002b08 <+4>: stp x22, x21, [sp, #0x20]
0x100002b0c <+8>: stp x20, x19, [sp, #0x30]
0x100002b10 <+12>: stp x29, x30, [sp, #0x40]
0x100002b14 <+16>: add x29, sp, #0x40
0x100002b18 <+20>: mov x19, x3
0x100002b1c <+24>: mov x20, x2
0x100002b20 <+28>: mov x21, x0
0x100002b24 <+32>: mov x0, x2
0x100002b28 <+36>: bl 0x100003014 ; symbol stub for: swift_unknownObjectRetain
0x100002b2c <+40>: mov x0, x21
0x100002b30 <+44>: bl 0x100002fc0 ; symbol stub for: objc_retain
0x100002b34 <+48>: mov x21, x0
0x100002b38 <+52>: mov x8, sp
0x100002b3c <+56>: mov x0, x20
0x100002b40 <+60>: bl 0x100002f60 ; symbol stub for: Swift._bridgeAnyObjectToAny(Swift.Optional<Swift.AnyObject>) -> Any
0x100002b44 <+64>: mov x0, x20
0x100002b48 <+68>: bl 0x100003008 ; symbol stub for: swift_unknownObjectRelease
0x100002b4c <+72>: adrp x0, 6
0x100002b50 <+76>: add x0, x0, #0xbd8
0x100002b54 <+80>: bl 0x100002d18 ; __swift_instantiateConcreteTypeFromMangledName at <compiler-generated>
0x100002b58 <+84>: mov w1, #0x60
0x100002b5c <+88>: mov w2, #0x7
0x100002b60 <+92>: bl 0x100002fcc ; symbol stub for: swift_allocObject
0x100002b64 <+96>: mov x20, x0
0x100002b68 <+100>: adrp x8, 1
0x100002b6c <+104>: ldr q0, [x8, #0x90]
0x100002b70 <+108>: adrp x8, 2
0x100002b74 <+112>: ldr x8, [x8, #0x28]
0x100002b78 <+116>: str q0, [x0, #0x10]
0x100002b7c <+120>: str x8, [x0, #0x38]
0x100002b80 <+124>: mov x8, #-0x1c00000000000000
0x100002b84 <+128>: stp x19, x8, [x0, #0x20]
0x100002b88 <+132>: add x1, x0, #0x40
0x100002b8c <+136>: mov x0, sp
0x100002b90 <+140>: bl 0x100002d58 ; outlined init with copy of Any at <compiler-generated>
0x100002b94 <+144>: mov x0, x20
0x100002b98 <+148>: mov w1, #0x20
0x100002b9c <+152>: mov x2, #-0x1f00000000000000
0x100002ba0 <+156>: mov w3, #0xa
0x100002ba4 <+160>: mov x4, #-0x1f00000000000000
0x100002ba8 <+164>: bl 0x100002f84 ; symbol stub for: Swift.print(_: Any..., separator: Swift.String, terminator: Swift.String) -> ()
0x100002bac <+168>: mov x0, x21
0x100002bb0 <+172>: bl 0x100002fb4 ; symbol stub for: objc_release
0x100002bb4 <+176>: mov x0, x20
0x100002bb8 <+180>: bl 0x100002fd8 ; symbol stub for: swift_bridgeObjectRelease
0x100002bbc <+184>: mov x0, sp
0x100002bc0 <+188>: bl 0x100002d94 ; __swift_destroy_boxed_opaque_existential_0 at <compiler-generated>
0x100002bc4 <+192>: ldp x29, x30, [sp, #0x40]
0x100002bc8 <+196>: ldp x20, x19, [sp, #0x30]
0x100002bcc <+200>: ldp x22, x21, [sp, #0x20]
0x100002bd0 <+204>: add sp, sp, #0x50