authorgravatar for mathias.lafeldt@gmail.comMathias Lafeldt <mathias.lafeldt@gmail.com> 2025-11-25 10:22:35+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-21 00:31:21+01:00
log2207c62bb5f6d61fc9f430e849d0aed13cf63d35
treed880cf43c215d8323a40c45dea85f4942bdd0b04
parent7cbe05cbd4d89fe88f17e4c82888823864c6cc8d

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`.

2 files changed, 31 insertions(+), 1 deletions(-)

src/link/MachO.zig+7-1
......@@ -2959,7 +2959,13 @@ fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } {
29592959
29602960fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void {
29612961 var header: macho.mach_header_64 = .{};
2962 header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK;
2962 header.flags = macho.MH_DYLDLINK;
2963
2964 // Only set MH_NOUNDEFS if we're not allowing undefined symbols via dynamic lookup.
2965 // When dynamic_lookup is enabled, undefined symbols are resolved at runtime by dyld.
2966 if (self.undefined_treatment != .dynamic_lookup) {
2967 header.flags |= macho.MH_NOUNDEFS;
2968 }
29632969
29642970 // TODO: if (self.options.namespace == .two_level) {
29652971 header.flags |= macho.MH_TWOLEVEL;
test/link/macho.zig+24
......@@ -68,6 +68,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
6868 macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));
6969 macho_step.dependOn(testTlsZig(b, .{ .target = default_target }));
7070 macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));
71 macho_step.dependOn(testUndefinedDynamicLookup(b, .{ .target = default_target }));
7172 macho_step.dependOn(testDiscardLocalSymbols(b, .{ .target = default_target }));
7273 macho_step.dependOn(testUnresolvedError(b, .{ .target = default_target }));
7374 macho_step.dependOn(testUnresolvedError2(b, .{ .target = default_target }));
......@@ -2634,6 +2635,29 @@ fn testUndefinedFlag(b: *Build, opts: Options) *Step {
26342635 return test_step;
26352636}
26362637
2638fn testUndefinedDynamicLookup(b: *Build, opts: Options) *Step {
2639 const test_step = addTestStep(b, "undefined-dynamic-lookup", opts);
2640
2641 // Create a dylib with an undefined external symbol reference
2642 const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
2643 addCSourceBytes(dylib,
2644 \\extern int undefined_symbol(void);
2645 \\int call_undefined(void) {
2646 \\ return undefined_symbol();
2647 \\}
2648 , &.{});
2649 dylib.linker_allow_shlib_undefined = true;
2650
2651 // Verify the Mach-O header does NOT contain NOUNDEFS flag
2652 const check = dylib.checkObject();
2653 check.checkInHeaders();
2654 check.checkExact("header");
2655 check.checkNotPresent("NOUNDEFS");
2656 test_step.dependOn(&check.step);
2657
2658 return test_step;
2659}
2660
26372661fn testUnresolvedError(b: *Build, opts: Options) *Step {
26382662 const test_step = addTestStep(b, "unresolved-error", opts);
26392663