Static linking libFoundation segfault (or how to avoid libFoundation name clash)

Hello all!

For context, this is all running either in a CentOS 7.4 container or a Rocky 9.2 container, both hosted by Rocky 9.2.

We primarily compile Swift code as dynamic libraries with executable test targets with CMake. This has largely worked well. But we have an issue with a 3rd party application that has an unrelated libFoundation.so in the LD_LIBRARY_PATH. When this application is in the same environment as the Swift libraries, the Swift libraries try to load Swift symbols from the 3rd party libFoundation and can't find them. The solution we've come to is statically linking Foundation with the Swift code.

The trouble is, while I've been able to get the dynamic libraries compiling and not dynamically linking Foundation, the programs segfault when they run.

I can confirm with ldd that the binaries link the expected libraries:

$ ldd lib/libStaticLinking.so
        linux-vdso.so.1 (0x00007ffcc99e9000)
        [...]/libpng16.so (0x00007fb059e5a000)
        [...]/freetype2/objs/.libs/libfreetype.so (0x00007fb059ba0000)
        libm.so.6 => /lib64/libm.so.6 (0x00007fb059ab5000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fb059800000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fb059a9a000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fb059400000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fb05a65d000)
        libz.so.1 => /lib64/libz.so.1 (0x00007fb059a7e000)
        libbz2.so.1 => /lib64/libbz2.so.1 (0x00007fb059a6b000)
$ ldd tests/StaticLinkingTests
        linux-vdso.so.1 (0x00007ffcc11f7000)
        [...]/lib/libpng16.so (0x00007f678922c000)
        [...]/freetype2/objs/.libs/libfreetype.so (0x00007f6788f72000)
        libStaticLinking.so => /home/users/jonathanp/StaticLinkingTest/build/install/libStaticLinking.so (0x00007f67889a2000)
        libswiftSwiftOnoneSupport.so => [...]/libswiftSwiftOnoneSupport.so (0x00007f678895e000)
        libswiftCore.so => [...]/libswiftCore.so (0x00007f6788296000)
        libswift_Concurrency.so => [...]/libswift_Concurrency.so (0x00007f67881fe000)
        libswift_StringProcessing.so => [...]/libswift_StringProcessing.so (0x00007f6788122000)
        libswift_RegexParser.so =>[...]/libswift_RegexParser.so (0x00007f678800e000)
        libswiftGlibc.so => [...]/libswiftGlibc.so (0x00007f6787ffa000)
        libBlocksRuntime.so => [...]/libBlocksRuntime.so (0x00007f6787ff6000)
        libdispatch.so => [...]/libdispatch.so (0x00007f6787f94000)
        libswiftDispatch.so => [...]/libswiftDispatch.so (0x00007f6787f67000)
        libFoundation.so => [...]/libFoundation.so (0x00007f678792c000)
        libFoundationEssentials.so => [...]/libFoundationEssentials.so (0x00007f678736c000)
        libFoundationInternationalization.so => [...]/libFoundationInternationalization.so (0x00007f6787180000)
        libXCTest.so =>[...]/libXCTest.so (0x00007f6787127000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f678703a000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f6786e00000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f6787020000)
        libbz2.so.1 => /lib64/libbz2.so.1 (0x00007f678700d000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f6786a00000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f6786de5000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f6789466000)
        lib_FoundationICU.so => [...]/lib_FoundationICU.so (0x00007f6784544000)
        libswiftSynchronization.so => [...]/libswiftSynchronization.so (0x00007f6786dd2000)

This is an example backtrace from LLDB:

Test Suite 'All tests' started at 2025-04-29 14:33:33.316
Test Suite 'install.xctest' started at 2025-04-29 14:33:33.317
Test Suite 'StaticLinkingTests' started at 2025-04-29 14:33:33.317
Test Case 'StaticLinkingTests.testLibrary' started at 2025-04-29 14:33:33.317
Process 1064117 stopped
* thread #1, name = 'StaticLinkingTe', stop reason = signal SIGSEGV: address not mapped to object (fault address: 0x0)
    frame #0: 0x00007ffff72d83c9 libswiftCore.so`swift::TargetMetadata<swift::InProcess>::getGenericArgs() const + 9
libswiftCore.so`swift::TargetMetadata<swift::InProcess>::getGenericArgs:
->  0x7ffff72d83c9 <+9>:  movq   (%rdi), %rcx
    0x7ffff72d83cc <+12>: xorl   %eax, %eax
    0x7ffff72d83ce <+14>: cmpq   $0x800, %rcx ; imm = 0x800 
    0x7ffff72d83d5 <+21>: cmovael %eax, %ecx
Target 0: (StaticLinkingTests) stopped.

The test targets are never run in an environment with the problematic 3rd party application, so what I'm trying to set up is:

  1. Swift dynamic library, statically linking Foundation.
  2. Swift executable, dynamically linking the Swift dynamic library, Foundation, and XCTest.

I've set up a repro project here: GitHub - JonathanPenner3D/StaticLinkingTest

I'd love any pointers or help with getting this working, or alternatively another solution to avoiding the name clash between the two libFoundation libraries. We don't compile the 3rd party application from source so we can't do something like have it use rpath instead of LD_LIBRARY_PATH for finding its libFoundation.

Out of an abundance of caution with my security-conscious employer I've replaced any non-generic file paths with [...]

I realized that I made my underlying problem myself, due to two things:

  1. I mistakenly thought that RPATH had lower precedence than LD_LIBRARY_PATH
  2. When I set up our install of Swift in our build system I mistakenly had it include its libraries in LD_LIBRARY_PATH, when the compiler already passes -rpath to the linker.

I fixed our install of Swift by not also setting LD_LIBRARY_PATH, but still had issues with the -rpath not actually setting the RPATH, but rather the RUNPATH, which does have lower precedence than LD_LIBRARY_PATH.

I added this flag: -Xlinker --disable-new-dtags, which told gold to set -rpath arguments in the RPATH instead of RUNPATH.

With that combination of things our Swift libraries can now happily coexist with the 3rd party application.

While my original goal has been achieved, I'm still wondering why my repro case segfaults when compiling with -static-stdlib.

Once again, another misunderstanding on my part. It turns out we're not out of the woods yet, since there's still the .so name conflict at runtime when our Swift library runs after the 3rd party application has launched and has loaded its own libFoundation. So even though there's no pollution of the LD_LIBRARY_PATH with the two libFoundation files (since Swift's is in the RPATH), the linker won't attempt to load the other libFoundation, causing issues.

So depending on execution order:

  1. Swift library runs first, loads Swift's libFoundation. 3rd party app loads, tries to load its symbols from Swift's libFoundation - failure.
  2. 3rd party app loads, loads its own libFoundation. Swift library loads, tries to load its symbols from 3rd party libFoundation - failure.

So I'm back to statically linking Foundation to get around this.

I've made Segfault when running test with statically linked libFoundation · Issue #81367 · swiftlang/swift · GitHub to hopefully get some help with this.