Linking process in Swift build vs Xcode build

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).
Zrzut ekranu 2020-08-27 o 18.48.26
Zrzut ekranu 2020-08-27 o 18.49.17
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.
Zrzut ekranu 2020-08-27 o 19.04.10

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:

  1. Why it’s crashing in first place?
  2. Why it’s not crashing when using Xcode 12b6?
  3. Why Xcode is linking different stuff?
  4. How Xcode knows to link different dylibs?
  5. In general, how swift linker knows which LC_LOAD_DYLIB to put?
  6. Where to read more about this process? :)

Interesting, paging @Tony_Parker, who may be able to speak to the Foundation parts of this.

The ability to “fix” it by preceding it with some other call is reminiscent of this issue I had recently. But I am not familiar enough with Foundation’s guts to know if the two issues are actually related or if they are just similar.

It sounds like a linker bug to me (or a build system bug, if it is not invoking the linker correctly), if - despite using RunLoop and import Foundation in your source, the resulting binary has not linked the Foundation library. Foundation provides the implementation of -[NSRunLoop currentRunLoop], so it is not a surprise to me that we would get an unrecognized selector exception if Foundation was never loaded.

This is an implementation detail, but for NSRunLoop and a few other types, the actual symbol is in CoreFoundation, but the entire implementation is in Foundation. Perhaps that is what is tripping up swift build. However, the header comes from Foundation and you explicitly import it, so it should link it.

Thanks for the details, potentially this could be due to using different tools for linking between Xcode and SwiftPM. IIRC, SwiftPM uses swift to link, but Xcode uses clang.

I actually can't reproduce the crash with SwiftPM 5.3, @macshur which version are you using?

hey @NeoNacho

I've tested this using 2 configurations, both on macOS 10.15.5

  1. Non beta (with crashes if no print uncommented) Xcode 11.5
Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53)
Target: x86_64-apple-darwin19.5.0
  1. Beta - Xcode 12b6
Apple Swift version 5.3 (swiftlang-1200.0.28.1 clang-1200.0.30.1)
Target: x86_64-apple-darwin19.5.0

using beta version Xcode/Swift5.3 it's not crashing for me as well. However, I've noticed it does generate binary in different way BUT version generated using swift build still doesn't link to Foundation - only to CoreFoundation
Swift 5.3 build :
D_DYLIB(libSystem.B.dylib)

and Xcode 12b6 "project" compilation after opening Package.swift:
LC_LOAD_DYLIB(Foundation)

I thought it was not crashing thanks to weak loads that were not present in Swift 5.2.4 version.

SwiftPM uses swift to link, but Xcode uses clang .

@NeoNacho Is this the case when Xcode is opened by double clicking Package.swift?

@Tony_Parker Maybe you know how linker knows what to link with just import Foundation?