MachO: fix dynamic lookup of undefined symbols at runtime

Ensures `MH_NOUNDEFS` is not set when dynamic lookup is enabled for
undefined symbols via `linker_allow_shlib_undefined`.
This commit is contained in:
Mathias Lafeldt 2025-11-25 10:22:35 +01:00
parent 53e615b920
commit 8710c5f2f7
No known key found for this signature in database
GPG key ID: ECB164AC027DA2ED
2 changed files with 31 additions and 1 deletions

View file

@ -2937,7 +2937,13 @@ fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } {
fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void {
var header: macho.mach_header_64 = .{};
header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK;
header.flags = macho.MH_DYLDLINK;
// Only set MH_NOUNDEFS if we're not allowing undefined symbols via dynamic lookup.
// When dynamic_lookup is enabled, undefined symbols are resolved at runtime by dyld.
if (self.undefined_treatment != .dynamic_lookup) {
header.flags |= macho.MH_NOUNDEFS;
}
// TODO: if (self.options.namespace == .two_level) {
header.flags |= macho.MH_TWOLEVEL;

View file

@ -68,6 +68,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));
macho_step.dependOn(testTlsZig(b, .{ .target = default_target }));
macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));
macho_step.dependOn(testUndefinedDynamicLookup(b, .{ .target = default_target }));
macho_step.dependOn(testDiscardLocalSymbols(b, .{ .target = default_target }));
macho_step.dependOn(testUnresolvedError(b, .{ .target = default_target }));
macho_step.dependOn(testUnresolvedError2(b, .{ .target = default_target }));
@ -2632,6 +2633,29 @@ fn testUndefinedFlag(b: *Build, opts: Options) *Step {
return test_step;
}
fn testUndefinedDynamicLookup(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "undefined-dynamic-lookup", opts);
// Create a dylib with an undefined external symbol reference
const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dylib,
\\extern int undefined_symbol(void);
\\int call_undefined(void) {
\\ return undefined_symbol();
\\}
, &.{});
dylib.linker_allow_shlib_undefined = true;
// Verify the Mach-O header does NOT contain NOUNDEFS flag
const check = dylib.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("NOUNDEFS");
test_step.dependOn(&check.step);
return test_step;
}
fn testUnresolvedError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error", opts);