commit 2207c62bb5f6d61fc9f430e849d0aed13cf63d35 (tree)
parent 7cbe05cbd4d89fe88f17e4c82888823864c6cc8d
Author: Mathias Lafeldt <mathias.lafeldt@gmail.com>
Date: Tue, 25 Nov 2025 10:22:35 +0100
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`.
Diffstat:
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
@@ -2959,7 +2959,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;
diff --git a/test/link/macho.zig b/test/link/macho.zig
@@ -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 }));
@@ -2634,6 +2635,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);