Dear forum,
I was experimenting a little bit with regular terminal apps in Swift and noticed that same code built using swift build
generated different result than one from Xcode. Also, I’ve encountered weird crash which raised even more questions in my head but I’m not sure where to look for answers. Here goes my story:
I’ve created an app using:
swift package init --type executable
code is pretty simple:
import Foundation
//print(Thread.current.debugDescription)
RunLoop.current.run()
print("End of life...")
expected result is that it starts and runs forever without any prompt until I hit cmd+c. Easy.
BUT if I compile this using swift build
and run it… crashes:
2020-08-27 17:51:36.508 ShellExperiments[2951:67203] +[NSRunLoop currentRunLoop]: unrecognized selector sent to class 0x7fff8efc1508
2020-08-27 17:51:36.515 ShellExperiments[2951:67203] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSRunLoop currentRunLoop]: unrecognized selector sent to class 0x7fff8efc1508'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff376bebe7 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff704965bf objc_exception_throw + 48
2 CoreFoundation 0x00007fff3773dbc7 __CFExceptionProem + 0
3 CoreFoundation 0x00007fff3762344b ___forwarding___ + 1427
4 CoreFoundation 0x00007fff37622e28 _CF_forwarding_prep_0 + 120
5 ShellExperiments 0x000000010bba9fad main + 45
6 libdyld.dylib 0x00007fff7163dcc9 start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
and if I compile this using Xcode (and I’m opening Xcode by double click on Package.swift
) it works perfectly fine…
At first, I tried running it under LLDB and see where RunLoop comes from so I checked:
(lldb) image lookup -s NSRunLoop
2 symbols match 'NSRunLoop' in /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation:
Address: CoreFoundation[0x00000000005ba3f8] (CoreFoundation.__DATA.__objc_data + 9760)
Summary: (void *)0x00007fff8efc2188: NSRunLoop Address: CoreFoundation[0x00000000005bb078] (CoreFoundation.__DATA.__objc_data + 12960)
Summary: (void *)0x00007fff97c7a0f0: NSObject
but basically I get same result for both versions - it’s coming from CoreFoundation.framework
.
So I decided to look a bit more what’s generated in both cases and only thing I found somehow interesting is additional LC_DYLIB_LOAD
section in mach header - more specifically, version created by swift build
is lacking LC_LOAD_DYLIB(Foundation)
.
Also, when running from terminal using DYLD_PRINT_LIBRARIES=1
list of loaded dylibs is very different. (Xcode 11.5, macOS 10.15.5)
Whole situation gets even more interesting when I uncomment print
statement (just before runloop thing) - now app works fine in both cases, using swift build
or Xcode. Again, I checked what’s inside mach header and this time, list of loaded dylibs is bit longer but same for swift build
and Xcode build.
Out of curiosity, I checked how this behaves when using Xcode 12b6 - with or without print app doesn’t crash! But still, without print, list of loaded dylibs is different - swift build version is lacking LC_LOAD_DYLIB(Foundation)
that is present in Xcode’s version, just like with Xcode 11.
So here are my questions:
- Why it’s crashing in first place?
- Why it’s not crashing when using Xcode 12b6?
- Why Xcode is linking different stuff?
- How Xcode knows to link different dylibs?
- In general, how swift linker knows which
LC_LOAD_DYLIB
to put? - Where to read more about this process? :)