Linker changes in Xcode 16 / macOS 15?

I have existing code in production that links against the mach-o library, and uses the following code:

NSData *data = [NSData dataWithBytes: getsectiondata(&_mh_execute_header, "__TEXT", "__somePlist_plist", &len) length:len];

In order for this to compile with Sequoia Beta 4 and Xcode 16 beta 4, I have to replace _mh_execute_header with _mh_dylib_header.

If I don't, the compiler raises the following error: ld: Undefined symbols: __mh_execute_header, referenced from: -[MyClass init] in MyClass.o clang: error: linker command failed with exit code 1 (use -v to see invocation)

Any idea why the linking behavior might have changed? Should I file a bug?

(I realize this is a C issue, not Objective-C - but didn't find a tag for that)

Thanks!

Answered by DTS Engineer in 799146022

So, lemme break this down by language:

  • Swift

  • Objective-C

In Swift you can use #dsohandle to get the start of the Mach-O image regardless of the image type, so this problem goes away. Well, you’ll need to combine that with an unsafeBitCast(_:to:).

In C-based languages things are not that simple (when are they ever :-). I asked around internally and got an excellent tip: Use __dso_handle as a ‘universal’ Mach-O image start symbol. The only tricky thing is that you have to declare it yourself:

extern const struct mach_header __dso_handle; 

Please take this for a spin and let me know if you hit any roadblocks.

Share and Enjoy

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

Oh, this seems to be a FAQ right now. See my response on this thread.

Share and Enjoy

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

If I have additional questions should I post on the other thread? The interesting thing is that if I use _mh_dylib_header, I get a build error in Xcode 15/macOS 14.

If I have additional questions should I post on the other thread?

I’m happy to talk about your specific case here.

The interesting thing is that if I use _mh_dylib_header, I get a build error in Xcode 15/macOS 14.

It’s hard to answer this without knowing more about your specific situation. For a baseline:

  1. I used Xcode 15.4 to create a new framework from the macOS > Framework template.

  2. I added an FFFTest class with an implementation like this:

    #import "FFFTest.h"
    
    @import MachO;
    
    @implementation FFFTest
    
    + (void)test {
        NSUInteger len = 0;
        NSData *data = [NSData dataWithBytes: getsectiondata(&_mh_dylib_header, "__TEXT", "__somePlist_plist", &len) length:len];
        NSLog(@"%zu", data.length);
    }
    
    @end
    
  3. It built just fine.

  4. I closed the project and re-opened it in Xcode 16.0b4.

  5. It built just fine there too.

I see two possibilities here:

  • With Xcode 15, your build system is merging this code into your main executable, and that’s not happening with Xcode 16 beta.

  • Xcode 15 might have chosen to use ld64 for your project. See An Apple Library Primer for more on that.

Regardless, you’re gonna have to dig into the Xcode 15 build report to see how your code is being built differently there.

Share and Enjoy

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

We have a linker flag set as follows, and I've used otool to dig into the headers, so I know that we're trying to read header data from an executable, not a dynamic library (Though, the code DOES read the plist data at runtime when building with Xcode 16 using the dylib_header). This leads me to think that perhaps it's Xcode 16 that has introduced a new default build setting that changes the Xcode 15 linker behavior?

-Wl,-rpath,@loader_path/../Frameworks -sectcreate __TEXT __somePlist_plist ../MySubModule/somePlist.plist.enc

A little more background. I dug into the build output, and it is as follows:

Undefined symbols for architecture x86_64:
  "__mh_execute_header", referenced from:
  -[MyClass init] in MyClass.o
ld: symbol(s) not found for architecture x86_64

I'm building on an M1 Machine, and my targets are all set to build both architectures. Interesting that it can't find the symbol for x86_64, even if building just for Apple Silicon.

Interesting that it can't find the symbol for x86_64, even if building just for Apple Silicon.

That’s an interesting discovery, but it didn’t allow me to reproduce the problem )-: Here’s what I tried today:

  1. I used Xcode 15.4 to create a new framework from the macOS > App template.

  2. I added an AAATest class with an implementation like this:

    #import "AAATest.h"
    
    @import MachO;
    
    @implementation AAATest
    
    + (void)test {
        NSUInteger len = 0;
        NSData *data = [NSData dataWithBytes: getsectiondata(&_mh_execute_header, "__TEXT", "__somePlist_plist", &len) length:len];
        NSLog(@"%zu", data.length);
    }
    
    @end
    
  3. I changed the run destination to Any Mac, so Xcode builds both architectures.

  4. The project built just fine.

  5. I closed the project and re-opened it in Xcode 16.0b4.

  6. I confirmed that the run destination from step 3 was preserved.

  7. I built the app again, without any problems.

Looking at the built app I see that both architectures are present:

% file Test760543.app/Contents/MacOS/Test760543 
…
Test760543.app/Contents/MacOS/Test760543 (for architecture x86_64)…
Test760543.app/Contents/MacOS/Test760543 (for architecture arm64)…

And nm reveals that all the symbols for my class are present, along with _mh_execute_header:

% nm -arch x86_64 Test760543.app/Contents/MacOS/Test760543 
0000000100001290 t +[AAATest test]
…
0000000100003c38 S _OBJC_CLASS_$_AAATest
…
0000000100003c10 S _OBJC_METACLASS_$_AAATest
…
0000000100003aa0 s __OBJC_$_CLASS_METHODS_AAATest
…
0000000100003b08 s __OBJC_CLASS_RO_$_AAATest
…
0000000100003ac0 s __OBJC_METACLASS_RO_$_AAATest
…
0000000100000000 T __mh_execute_header
…

Please repeat this exact test. I suspect it’ll work and, if it does, that rules out any environmental issue, allowing us to focus on your project.

Share and Enjoy

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

Naturally, the test project works fine. I'm also assuming that there is some project setting that I'm missing. NB - this is a well established large project with numerous targets and sub-projects.

An additional piece of information, I have found that Archiving succeeds, so it would appear I'm only experiencing the issue when building/running in debug.

Looking in the build log, the call to ld contains an additional flag in my project, as compared to the test project: "-dynamiclib" ... this may explain why the "_mh_dylib_header" change works, but I have yet to find the build setting which would influence Xcode to use this flag.

I was able to build with ENABLE_DEBUG_DYLIB set to NO... though the default appears to be yes, based on the value in the test project.

After finding this, I searched for the setting, and found a known issue about it in the XCode 16 release notes here:

https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes?changes=_1

Known Issues

Some large or complex projects may fail to build and run if they are scanning for specific Mach-O sections in their binaries. (123416939)

Workaround: Try setting the build setting ENABLE_DEBUG_DYLIB=NO. This will disable the debug dylib that enables the new preview execution mode. Setting this to NO will still allow you to preview in Xcode 16 Seed 1 using the legacy execution mode, but support for this mode will be removed in a future build.

Perhaps I need to switch from "legacy execution mode" to the new preview execution mode? Is there a setting for this?

Accepted Answer

Oh, that’s really interesting. I remember skimming over that item in the release notes but I didn’t make the connection here.

And I didn’t notice it in my testing because I started by creating my project with Xcode 15.

Thanks for closing the loop here.

Perhaps I need to switch from "legacy execution mode" to the new preview execution mode?

I think they’re talking about the legacy execution model for previews specifically.

But this does raise the question of what you should do with _mh_*_header in your project. I don’t have a definitive answer to that. I’m going to poke around and get back to you.

Share and Enjoy

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

So, lemme break this down by language:

  • Swift

  • Objective-C

In Swift you can use #dsohandle to get the start of the Mach-O image regardless of the image type, so this problem goes away. Well, you’ll need to combine that with an unsafeBitCast(_:to:).

In C-based languages things are not that simple (when are they ever :-). I asked around internally and got an excellent tip: Use __dso_handle as a ‘universal’ Mach-O image start symbol. The only tricky thing is that you have to declare it yourself:

extern const struct mach_header __dso_handle; 

Please take this for a spin and let me know if you hit any roadblocks.

Share and Enjoy

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

I can't speak to the Swift side without doing a lot of refactoring, but the Objective-C version works! I do get the following warning, however:

Incompatible pointer types passing 'const struct mach_header *' to parameter of type 'const struct mach_header_64 *'

Using this declaration fixes the warning:

extern const struct mach_header_64 __dso_handle; 

I'd call this one settled... is it possible to change the accepted answer to your final post?

Thanks!

Yay!

Using this declaration fixes the warning

Yep. That’s the right option if you’re building 64-bit code (which everyone is these days).

is it possible to change the accepted answer to your final post?

Sadly no (r. 113568518). But I’ve marked mine as Recommended, which generally does the trick (-:

Share and Enjoy

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

Linker changes in Xcode 16 / macOS 15?
 
 
Q