-
I have a iOS library that exposes a C function. This function is marked with default visibility and used via attribute tags to prevent the compiler/linker from stripping it
#ifdef __cplusplus #define DYLIB_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) #else #define DYLIB_EXPORT __attribute__((visibility("default"))) __attribute__((used)) #endif DYLIB_EXPORT bool say_hello_world();#import "helper_functions.h" #importbool say_hello_world() { NSLog(@"Hello WORLD 🟢!"); return true; } -
From a dynamic library, called
sdk, I want to call this function. In this example it’s a Rust libunsafe extern "C" { fn say_hello_world() -> bool; } #[unsafe(no_mangle)] extern "C" fn sdk_init() -> i32 { unsafe { say_hello_world(); } return 0; } -
When the app is compiled and launched from Xcode (either on
debugorreleaseschemes) then this works, the following snippet works and prints thehello WORLDmessage:#import "Wrapper.h" #import "helper_functions.h" #import "sdk.h" @implementation Wrapper // This function is called on the main view controller mount + (int)initialize { NSBundle *frameworkBundle = [NSBundle bundleWithIdentifier:@"sdk"]; if (![frameworkBundle isLoaded]) { BOOL success = [frameworkBundle load]; if (!success) { return -1; } int status = sdk_init(); return status; } return 0; } @end -
However, when archived and uploaded to TestFlight an extra stripping step happens that gets rid of the
say_hello_worldfunction. The dynamic library then crashes with a null pointer exception:Thread 3 name: Thread 3 Crashed: 0 ??? 0x0000000000000000 0x0 + 0 1 sdk 0x0000000105931ec0 0x105820000 + 1121984
Minimum Reproducible Example
Besides the snippets, the only way to reproduce the example is with a fully compilable iOS app one can generate a stripped binary that runs on device. Therefore I have reduced the code as much as a I can to reproduce the issue in this repo:
Then archive the app, then export with the debugging option. Drag and drop to a connected iPhone finder window to directly install on the device.
Extra notes
- There has been some discussion on the apple developer forums
- The Rust library has been compiled with extra flags to prevent the linker from failing when the function is not present during compilation:
println!("cargo:rustc-link-arg=-Wl,-U,_say_hello_world"); - I know for a fact this is a problem with dead code stripping because if I disable
Dead Code Strippingin the project settings then no code is stripped at all, bloating the binary, but the crash does not happen anymore. Yet this only happens when creating a distributable.ipaand not when running directly from Xcode