Search code examples
iosframeworksdylibmach-ootool

Recompilation with Bitcode changes LC_ID_DYLIB


I'm building a dynamic framework from source for iOS with bitcode enabled (using cmake and xcodebuild). I use lipo and install_name_tool to make a fat binary and update LC_ID_DYLIB, for binary to be loaded correctly. When I archive the application, the framework is correctly signed and packaged with the application. This is the output of file:

MyFramework: Mach-O universal binary with 3 architectures: [arm_v7: Mach-O dynamically linked shared library arm_v7] [arm_v7s] [arm64]
MyFramework (for architecture armv7):   Mach-O dynamically linked shared library arm_v7
MyFramework (for architecture armv7s):  Mach-O dynamically linked shared library arm_v7s
MyFramework (for architecture arm64):   Mach-O 64-bit dynamically linked shared library arm64

Looking at otool -l output for LC_ID_DYLIB shows this:

Load command 4
          cmd LC_ID_DYLIB
      cmdsize 64
         name @rpath/MyFramework.framework/MyFramework (offset 24)
   time stamp 1 Thu Jan  1 01:00:01 1970
      current version 1.0.0
compatibility version 1.0.0

It all seems correct. If I upload this archive to App Store, it gets uploaded and processed correctly. After running it from App Store, it crashes right after start, due to loading dynamic frameworks. It is known that apps get recompiled from Bitcode on App Store, so I simulated that by exporting as Ad-Hoc and leaving "Rebuild from Bitcode" option enabled as suggested in Technical Note TN2432. Inspecting the .ipa (which also crashed after start) and the framework in question, this is the output of otool -l:

Load command 3
          cmd LC_ID_DYLIB
      cmdsize 128
         name /Users/legoless/Downloads/ios/build/build-iphoneos/lib/Release/MyFramework_ios.framework/MyFramework_ios (offset 24)
   time stamp 1 Thu Jan  1 01:00:01 1970
      current version 1.0.0
compatibility version 1.0.0

So obviously the LC_ID_DYLIB of this library is incorrect and this the absolute path to the location where the framework was originally built, before making a fat binary. This is replaced at the Rebuild from Bitcode step, but I have no idea why or even where this path is stored in the existing Mach-O file. I used both otool and objdump tools to try to find the reference in the Mach-O binary, with no luck.

In practice, another framework depends on this one and this is the load command for the target framework:

Load command 14
          cmd LC_LOAD_DYLIB
      cmdsize 64
         name @rpath/MyFramework.framework/MyFramework (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1.0.0
compatibility version 1.0.0

Again after Rebuild with Bitcode, the reference gets changed here as well:

Load command 13
          cmd LC_LOAD_DYLIB
      cmdsize 128
         name /Users/legoless/Downloads/ios/build/build-iphoneos/lib/Release/MyFramework_ios.framework/MyFramework_ios (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1.0.0
compatibility version 1.0.0

This only happens for the framework in question, but not for other frameworks, where @rpath is left as it was.

My question still remains:

Where is this absolute path reference stored? And how to remove it, so Rebuild from Bitcode does not affect it anymore?

Thank you!


Solution

  • Doing detailed investigation into this issue, showed that the .xar archive that contains bitcode inside Mach-O file stores quite a bit of information, including linker flags. This information is stored in Table of Contents of the archive and is used to recompile/relink the libraries on bitcode recompilation.

    In my case, I was building the framework with cmake, which added this information into linker flags. Configuring INSTALL_NAME_DIR and BUILD_WITH_INSTALL_RPATH and using @rpath effectively fixed the issue, and install_name_tool was not required anymore.