Dynamic Library cannot call exposed C function

This is a lengthy one. I have basically compiled a Rust binary into a dylib and packaged into a .xcframework that contains per arch .frameworks. This loads correctly when run from Xcode into a real iOS device. However, when deployed to TestFlight the app crashes.

Here is what is a bit different, the dylib is not fully self-contained. It tries to reach in an use C functions I have exposed in my library code. Calling functions that are just within the dylib and just return works fine, but the moment it tries to call one of the exposed functions it crashes.

A full in-depth step by step of how I packaged the binaries can be found in my website: https://ospfranco.com/complete-guide-to-dylibs-in-ios-and-android

When I look at the TestFlight crash report there are no symbols but the termination cause via WatchDog is:

Termination Reason: CODESIGNING 2 Invalid Page

I have declared my functions as such:

OBJC_EXTERN void ios_prepare_request(const char *url)
#define EXPORT __attribute__((visibility("default"), used, retain))

extern "C" {

EXPORT void ios_prepare_request(const char *url) {
  NSString *urlString = [NSString stringWithUTF8String:url];
  request =
      [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
}

}

// Function used to prevent optimization
void force_symbol_registration() {
  // Force these symbols to be included in the binary by referencing them
  volatile void *ptrs[] = {(void *)ios_prepare_request,};

  // Prevent compiler from optimizing away the array
  (void)ptrs;
}

And I load my framework as:



  opacity::force_symbol_registration();

  // NSBundle *dylib_bundle =
  //     [NSBundle bundleWithIdentifier:@"com.opacitylabs.sdk"];
  // NSString *dylib_path = [dylib_bundle pathForResource:@"sdk" ofType:@""];

  // // Load the dynamic library
  // void *handle = dlopen([dylib_path UTF8String], RTLD_NOW | RTLD_GLOBAL);
  // if (!handle) {
  //   NSString *errorMessage = [NSString stringWithUTF8String:dlerror()];
  //   *error =
  //       [NSError errorWithDomain:@"OpacitySDKDylibError"
  //                           code:1002
  //                       userInfo:@{NSLocalizedDescriptionKey :
  //                       errorMessage}];
  //   return -1; // or appropriate error code
  // }

  // Make sure the main executable's symbols are available
  dlopen(NULL, RTLD_NOW | RTLD_GLOBAL);

  NSBundle *frameworkBundle =
      [NSBundle bundleWithIdentifier:@"com.opacitylabs.sdk"];
  if (![frameworkBundle isLoaded]) {
    BOOL success = [frameworkBundle load];
    if (!success) {
      NSString *errorMessage = @"Failed to load framework";
      *error =
          [NSError errorWithDomain:@"OpacitySDKDylibError"
                              code:1002
                          userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
      return -1;
    }
  }
  • As you can see, I have also tried dlopen both work when run from Xcode but crash when deployed on testflight.
  • I have tried re-signing the xcframework/frameworks on a pre build step but it doesn't work
  • As stated, I can call the functions inside the dylib, but once they try to call my exposed code it crashes

Is this achievable at all or just a limitation of the iOS sandbox?

Answered by DTS Engineer in 851910022

When you build your app for release, what does the on-disk structure look like. For example, here’s what I see when I build a project I created rom one of Xcode’s built-in templates:

% find "Test795348 04-08-2025, 10.47.xcarchive/Products/Applications/Test795348.app"
…/Test795348.app
…/Test795348.app/_CodeSignature
…/Test795348.app/_CodeSignature/CodeResources
…/Test795348.app/Test795348
…/Test795348.app/embedded.mobileprovision
…/Test795348.app/Info.plist
…/Test795348.app/PkgInfo

Specifically, I’m interested in the final disposition of your Mach-O images. Feel free to elide anything that’s not directly related to that, such as your resources.

Also feel free to redact any private info you don’t want to share. In Posting a Crash Report I describe a simple redaction scheme that I’ve found works well for artefacts.


The reason I’m asking this is that iOS imposes some strict limits on how you can embed code within an iOS app. See Placing Content in a Bundle for info about that. And, quoting that doc:

If you put content in the wrong location, you may encounter hard-to-debug code signing and distribution problems.

Which is exactly where you’re at |-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@DTS Engineer no matter what I tried I could not get this to work. I tried a .tbd file, using the -export-symbols-list flag, passing individual functions, etc. They get stripped every time.

I ended up injecting the functions into the library at runtime.

Functions are declared normally:

void ios_prepare_request(const char *url) {
...
}

Then in my library I created a function that takes the function pointers, which I call when I load it:

  NSBundle *frameworkBundle =
      [NSBundle bundleWithIdentifier:@"com.opacitylabs.sdk"];
[frameworkBundle load];
  opacity_core::register_ios_callbacks(ios_prepare_request);

It's more verbose on my side but there is no runtime crash anymore. Thanks for the help anyways!

Dynamic Library cannot call exposed C function
 
 
Q