Static linking in dynamic library and potential conflict with BoringSSL

Hi,

I'm building a dynamic library (.so) that embeds OpenSSL statically for use with NDK in an Android app. IIRC, Android comes with BoringSSL, and I'm seriously concerned with the potential conflicts in symbol resolution. So far, I've bundled the library in the APK with the Swift runtime as is. Convoluted, but it works.

However, after dropping Foundation, I wanted to try the --static-swift-stdlib flag in swift build, and I hit puzzling linker failures on these symbols only:

  • SSL_CTX_set_security_level
  • SSL_get1_peer_certificate
  • OPENSSL_sk_num
  • OPENSSL_sk_value
  • EVP_MAC_fetch
  • OSSL_PARAM_construct_utf8_string
  • OSSL_PARAM_construct_end
  • EVP_CIPHER_get_key_length
  • EVP_CIPHER_get_iv_length
  • EVP_MD_get_size
  • EVP_MAC_free
  • EVP_MAC_CTX_new
  • EVP_MAC_init
  • EVP_MAC_CTX_free
  • EVP_MAC_update
  • EVP_MAC_final

Had libssl/crypto symbols been 100% missing, I would know the answer. Instead, this small list hints at my OpenSSL 3.5.x binary not being linked at all, with my code partially resolving to BoringSSL.

Could it be that the very --static-swift-stdlib flag prevents static linking of other libraries in a dynamic library? FWIW, I've read something somewhat related here:

This portability comes at a cost, namely that everything your program depends on must be statically linked. There is no support for dynamic linking whatsoever — even the dlopen() function will not work.

Thanks

You're probably hitting this SwiftPM bug.

As for BoringSSL, Android ships with many native system libraries that are only made available through Java: they are not in the Android NDK and cannot be used by apps' native libraries. If you need those, you have to build and ship them as native libraries yourself in your app.

Actually, static linking of OpenSSL in the dynamic library works fine without --static-swift-stdlib.

For simplicity, I'm trying Linux first, and I hit the mentioned DispatchStubs issue once, but it went away after upgrading Swift from 6.1.2 to 6.2.

However:

keeshux@pingu:~/src/passepartout/submodules/partout$ swift build --static-swift-stdlib
Building for debugging...
[1/1] Write swift-version-86641172D0FD103.txt
Build complete! (0.10s)

Still depends on the dynamic runtime:

keeshux@pingu:~/src/passepartout/submodules/partout$ ldd .build/debug/libpartout.so 
        linux-vdso.so.1 (0x0000f31ec2774000)
        libswiftSwiftOnoneSupport.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftSwiftOnoneSupport.so (0x0000f31ec20d0000)
        libswiftCore.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftCore.so (0x0000f31ec1a30000)
        libswift_Concurrency.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_Concurrency.so (0x0000f31ec1980000)
        libswift_StringProcessing.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_StringProcessing.so (0x0000f31ec18c0000)
        libswift_RegexParser.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswift_RegexParser.so (0x0000f31ec17a0000)
        libBlocksRuntime.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libBlocksRuntime.so (0x0000f31ec1770000)
        libdispatch.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libdispatch.so (0x0000f31ec16f0000)
        libswiftDispatch.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftDispatch.so (0x0000f31ec16a0000)
        libssl.so.3 => /lib/aarch64-linux-gnu/libssl.so.3 (0x0000f31ec1580000)
        libcrypto.so.3 => /lib/aarch64-linux-gnu/libcrypto.so.3 (0x0000f31ec0ff0000)
        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000f31ec0e20000)
        /lib/ld-linux-aarch64.so.1 (0x0000f31ec2730000)
        libstdc++.so.6 => /lib/aarch64-linux-gnu/libstdc++.so.6 (0x0000f31ec0b80000)
        libm.so.6 => /lib/aarch64-linux-gnu/libm.so.6 (0x0000f31ec0ac0000)
        libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000f31ec0a80000)
        libswiftGlibc.so => /home/keeshux/.local/share/swiftly/toolchains/6.2.0/usr/lib/swift/linux/libswiftGlibc.so (0x0000f31ec0a50000)
        libz.so.1 => /lib/aarch64-linux-gnu/libz.so.1 (0x0000f31ec0a10000)
        libzstd.so.1 => /lib/aarch64-linux-gnu/libzstd.so.1 (0x0000f31ec0950000)

Isn't --swift-static-stdlib supposed to get rid of the .so from the Swift runtime? Or am I expecting the wrong outcome here?

That's fine, but I was talking about the Android BoringSSL: it is not in the NDK and thus cannot affect your build.

No, as the bug I linked you notes, SwiftPM ignores that flag for dynamic library products, only applying it to executables.

Uhm, if the flag is ignored, then why is behavior different with the Android SDK whether I use it or not?

The Android SDK does not bother fixing all configuration bugs in those static resource directories, considering that static SwiftPM flag itself does nothing for dynamic library products, which is all you can use on Android.

For simplicity, we show the static build with executables in the Android Getting Started Guide, but it won't work with shared libraries because of that long-standing Swift toolchain bug for all platforms, which I just noticed and filed earlier this year. Swift's support for static linking the stdlib is still immature, demos with executables shouldn't be construed as anything more.

In other words, that static linking flag is broken in that dynamic library config, and broken code will break in different ways on different platforms. :wink:

I see now. Well, I'll tick my task, stick with the dynamic runtime, and cross my fingers.