diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index b438331991..0ee6e32826 100644 --- a/lib/std/Build/CompileStep.zig +++ b/lib/std/Build/CompileStep.zig @@ -202,6 +202,11 @@ subsystem: ?std.Target.SubSystem = null, entry_symbol_name: ?[]const u8 = null, +/// List of symbols forced as undefined in the symbol table +/// thus forcing their resolution by the linker. +/// Corresponds to `-u ` for ELF/MachO and `/include:` for COFF/PE. +force_undefined_symbols: std.StringHashMap(void), + /// Overrides the default stack size stack_size: ?u64 = null, @@ -386,6 +391,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep { .override_dest_dir = null, .installed_path = null, .install_step = null, + .force_undefined_symbols = StringHashMap(void).init(owner.allocator), .output_path_source = GeneratedFile{ .step = &self.step }, .output_lib_path_source = GeneratedFile{ .step = &self.step }, @@ -568,6 +574,11 @@ pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { source.addStepDependencies(&self.step); } +pub fn forceUndefinedSymbol(self: *CompileStep, symbol_name: []const u8) void { + const b = self.step.owner; + self.force_undefined_symbols.put(b.dupe(symbol_name), {}) catch @panic("OOM"); +} + pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void { const b = self.step.owner; self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM"); @@ -1266,6 +1277,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(entry); } + { + var it = self.force_undefined_symbols.keyIterator(); + while (it.next()) |symbol_name| { + try zig_args.append("--force_undefined"); + try zig_args.append(symbol_name.*); + } + } + if (self.stack_size) |stack_size| { try zig_args.append("--stack"); try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); diff --git a/src/Compilation.zig b/src/Compilation.zig index 2c9b4e4063..2fdb2ae4ba 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -602,6 +602,7 @@ pub const InitOptions = struct { parent_compilation_link_libc: bool = false, hash_style: link.HashStyle = .both, entry: ?[]const u8 = null, + force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{}, stack_size_override: ?u64 = null, image_base_override: ?u64 = null, self_exe_path: ?[]const u8 = null, @@ -1523,7 +1524,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .headerpad_size = options.headerpad_size, .headerpad_max_install_names = options.headerpad_max_install_names, .dead_strip_dylibs = options.dead_strip_dylibs, - .force_undefined_symbols = .{}, + .force_undefined_symbols = options.force_undefined_symbols, .pdb_source_path = options.pdb_source_path, .pdb_out_path = options.pdb_out_path, }); @@ -2186,7 +2187,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo /// to remind the programmer to update multiple related pieces of code that /// are in different locations. Bump this number when adding or deleting /// anything from the link cache manifest. -pub const link_hash_implementation_version = 7; +pub const link_hash_implementation_version = 8; fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void { const gpa = comp.gpa; @@ -2196,7 +2197,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - comptime assert(link_hash_implementation_version == 7); + comptime assert(link_hash_implementation_version == 8); if (comp.bin_file.options.module) |mod| { const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{ diff --git a/src/clang_options_data.zig b/src/clang_options_data.zig index 7cee0327bf..8134d4cc06 100644 --- a/src/clang_options_data.zig +++ b/src/clang_options_data.zig @@ -1448,7 +1448,7 @@ flagpsl("MT"), .{ .name = "u", .syntax = .flag, - .zig_equivalent = .other, + .zig_equivalent = .force_undefined_symbol, .pd1 = true, .pd2 = false, .psl = true, @@ -7170,7 +7170,14 @@ joinpd1("d"), .pd2 = false, .psl = true, }, -jspd1("u"), +.{ + .name = "u", + .syntax = .joined_or_separate, + .zig_equivalent = .force_undefined_symbol, + .pd1 = true, + .pd2 = false, + .psl = false, +}, .{ .name = "x", .syntax = .joined_or_separate, diff --git a/src/link.zig b/src/link.zig index dc114eb0ad..05519ba6b5 100644 --- a/src/link.zig +++ b/src/link.zig @@ -186,8 +186,7 @@ pub const Options = struct { /// List of symbols forced as undefined in the symbol table /// thus forcing their resolution by the linker. - /// Corresponds to `-u ` for ELF and `/include:` for COFF/PE. - /// TODO add handling for MachO. + /// Corresponds to `-u ` for ELF/MachO and `/include:` for COFF/PE. force_undefined_symbols: std.StringArrayHashMapUnmanaged(void), version: ?std.builtin.Version, diff --git a/src/link/Coff/lld.zig b/src/link/Coff/lld.zig index 358c905b2c..01f724ed41 100644 --- a/src/link/Coff/lld.zig +++ b/src/link/Coff/lld.zig @@ -63,7 +63,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod man = comp.cache_parent.obtain(); self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 7); + comptime assert(Compilation.link_hash_implementation_version == 8); for (self.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index fcab34bf5e..95104ec2e8 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1305,7 +1305,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v // We are about to obtain this lock, so here we give other processes a chance first. self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 7); + comptime assert(Compilation.link_hash_implementation_version == 8); try man.addOptionalFile(self.base.options.linker_script); try man.addOptionalFile(self.base.options.version_script); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 3206b63fa3..4c3301a3b4 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3981,7 +3981,7 @@ pub fn getStubsAtomIndexForSymbol(self: *MachO, sym_with_loc: SymbolWithLoc) ?At /// Returns symbol location corresponding to the set entrypoint. /// Asserts output mode is executable. pub fn getEntryPoint(self: MachO) error{MissingMainEntrypoint}!SymbolWithLoc { - const entry_name = self.base.options.entry orelse "_main"; + const entry_name = self.base.options.entry orelse load_commands.default_entry_point; const global = self.getGlobal(entry_name) orelse { log.err("entrypoint '{s}' not found", .{entry_name}); return error.MissingMainEntrypoint; diff --git a/src/link/MachO/ZldAtom.zig b/src/link/MachO/ZldAtom.zig index e701ee2a13..7e784ded05 100644 --- a/src/link/MachO/ZldAtom.zig +++ b/src/link/MachO/ZldAtom.zig @@ -389,7 +389,7 @@ pub fn addGotEntry(zld: *Zld, target: SymbolWithLoc) !void { try zld.got_table.putNoClobber(gpa, target, got_index); } -fn addStub(zld: *Zld, target: SymbolWithLoc) !void { +pub fn addStub(zld: *Zld, target: SymbolWithLoc) !void { const target_sym = zld.getSymbol(target); if (!target_sym.undf()) return; if (zld.stubs_table.contains(target)) return; diff --git a/src/link/MachO/dead_strip.zig b/src/link/MachO/dead_strip.zig index cd64e72170..ca0d961e2d 100644 --- a/src/link/MachO/dead_strip.zig +++ b/src/link/MachO/dead_strip.zig @@ -12,6 +12,7 @@ const Allocator = mem.Allocator; const AtomIndex = @import("zld.zig").AtomIndex; const Atom = @import("ZldAtom.zig"); const SymbolWithLoc = @import("zld.zig").SymbolWithLoc; +const SymbolResolver = @import("zld.zig").SymbolResolver; const UnwindInfo = @import("UnwindInfo.zig"); const Zld = @import("zld.zig").Zld; @@ -19,7 +20,7 @@ const N_DEAD = @import("zld.zig").N_DEAD; const AtomTable = std.AutoHashMap(AtomIndex, void); -pub fn gcAtoms(zld: *Zld) !void { +pub fn gcAtoms(zld: *Zld, resolver: *const SymbolResolver) !void { const gpa = zld.gpa; var arena = std.heap.ArenaAllocator.init(gpa); @@ -31,27 +32,36 @@ pub fn gcAtoms(zld: *Zld) !void { var alive = AtomTable.init(arena.allocator()); try alive.ensureTotalCapacity(@intCast(u32, zld.atoms.items.len)); - try collectRoots(zld, &roots); + try collectRoots(zld, &roots, resolver); try mark(zld, roots, &alive); prune(zld, alive); } -fn collectRoots(zld: *Zld, roots: *AtomTable) !void { +fn addRoot(zld: *Zld, roots: *AtomTable, file: u32, sym_loc: SymbolWithLoc) !void { + const sym = zld.getSymbol(sym_loc); + assert(!sym.undf()); + const object = &zld.objects.items[file]; + const atom_index = object.getAtomIndexForSymbol(sym_loc.sym_index).?; // panic here means fatal error + log.debug("root(ATOM({d}, %{d}, {d}))", .{ + atom_index, + zld.getAtom(atom_index).sym_index, + file, + }); + _ = try roots.getOrPut(atom_index); +} + +fn collectRoots(zld: *Zld, roots: *AtomTable, resolver: *const SymbolResolver) !void { log.debug("collecting roots", .{}); switch (zld.options.output_mode) { .Exe => { // Add entrypoint as GC root const global: SymbolWithLoc = zld.getEntryPoint(); - const object = zld.objects.items[global.getFile().?]; - const atom_index = object.getAtomIndexForSymbol(global.sym_index).?; // panic here means fatal error - _ = try roots.getOrPut(atom_index); - - log.debug("root(ATOM({d}, %{d}, {?d}))", .{ - atom_index, - zld.getAtom(atom_index).sym_index, - zld.getAtom(atom_index).getFile(), - }); + if (global.getFile()) |file| { + try addRoot(zld, roots, file, global); + } else { + assert(zld.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib. + } }, else => |other| { assert(other == .Lib); @@ -60,20 +70,22 @@ fn collectRoots(zld: *Zld, roots: *AtomTable) !void { const sym = zld.getSymbol(global); if (sym.undf()) continue; - const file = global.getFile() orelse continue; // synthetic globals are atomless - const object = zld.objects.items[file]; - const atom_index = object.getAtomIndexForSymbol(global.sym_index).?; // panic here means fatal error - _ = try roots.getOrPut(atom_index); - - log.debug("root(ATOM({d}, %{d}, {?d}))", .{ - atom_index, - zld.getAtom(atom_index).sym_index, - zld.getAtom(atom_index).getFile(), - }); + if (global.getFile()) |file| { + try addRoot(zld, roots, file, global); + } } }, } + // Add all symbols force-defined by the user. + for (zld.options.force_undefined_symbols.keys()) |sym_name| { + const global_index = resolver.table.get(sym_name).?; + const global = zld.globals.items[global_index]; + const sym = zld.getSymbol(global); + assert(!sym.undf()); + try addRoot(zld, roots, global.getFile().?, global); + } + for (zld.objects.items) |object| { const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig index 43469ac435..228a1ccfaf 100644 --- a/src/link/MachO/load_commands.zig +++ b/src/link/MachO/load_commands.zig @@ -8,6 +8,10 @@ const mem = std.mem; const Allocator = mem.Allocator; const Dylib = @import("Dylib.zig"); +/// Default implicit entrypoint symbol name. +pub const default_entry_point: []const u8 = "_main"; + +/// Default path to dyld. pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig index 07241b54cd..47dd4dd54f 100644 --- a/src/link/MachO/zld.zig +++ b/src/link/MachO/zld.zig @@ -932,6 +932,43 @@ pub const Zld = struct { } } + fn forceSymbolDefined(self: *Zld, name: []const u8, resolver: *SymbolResolver) !void { + const sym_index = try self.allocateSymbol(); + const sym_loc = SymbolWithLoc{ .sym_index = sym_index }; + const sym = self.getSymbolPtr(sym_loc); + sym.n_strx = try self.strtab.insert(self.gpa, name); + sym.n_type = macho.N_UNDF | macho.N_EXT; + const global_index = try self.addGlobal(sym_loc); + try resolver.table.putNoClobber(name, global_index); + try resolver.unresolved.putNoClobber(global_index, {}); + } + + fn resolveSymbols(self: *Zld, resolver: *SymbolResolver) !void { + // We add the specified entrypoint as the first unresolved symbols so that + // we search for it in libraries should there be no object files specified + // on the linker line. + if (self.options.output_mode == .Exe) { + const entry_name = self.options.entry orelse load_commands.default_entry_point; + try self.forceSymbolDefined(entry_name, resolver); + } + + // Force resolution of any symbols requested by the user. + for (self.options.force_undefined_symbols.keys()) |sym_name| { + try self.forceSymbolDefined(sym_name, resolver); + } + + for (self.objects.items, 0..) |_, object_id| { + try self.resolveSymbolsInObject(@intCast(u32, object_id), resolver); + } + + try self.resolveSymbolsInArchives(resolver); + try self.resolveDyldStubBinder(resolver); + try self.resolveSymbolsInDylibs(resolver); + try self.createMhExecuteHeaderSymbol(resolver); + try self.createDsoHandleSymbol(resolver); + try self.resolveSymbolsAtLoading(resolver); + } + fn resolveSymbolsInObject(self: *Zld, object_id: u32, resolver: *SymbolResolver) !void { const object = &self.objects.items[object_id]; const in_symtab = object.in_symtab orelse return; @@ -975,9 +1012,7 @@ pub const Zld = struct { const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = object_id + 1 }; const global_index = resolver.table.get(sym_name) orelse { - const gpa = self.gpa; - const global_index = @intCast(u32, self.globals.items.len); - try self.globals.append(gpa, sym_loc); + const global_index = try self.addGlobal(sym_loc); try resolver.table.putNoClobber(sym_name, global_index); if (sym.undf() and !sym.tentative()) { try resolver.unresolved.putNoClobber(global_index, {}); @@ -1034,8 +1069,10 @@ pub const Zld = struct { }; if (update_global) { - const global_object = &self.objects.items[global.getFile().?]; - global_object.globals_lookup[global.sym_index] = global_index; + if (global.getFile()) |file| { + const global_object = &self.objects.items[file]; + global_object.globals_lookup[global.sym_index] = global_index; + } _ = resolver.unresolved.swapRemove(resolver.table.get(sym_name).?); global.* = sym_loc; } else { @@ -1180,9 +1217,7 @@ pub const Zld = struct { global.* = sym_loc; self.mh_execute_header_index = global_index; } else { - const global_index = @intCast(u32, self.globals.items.len); - try self.globals.append(gpa, sym_loc); - self.mh_execute_header_index = global_index; + self.mh_execute_header_index = try self.addGlobal(sym_loc); } } @@ -1358,6 +1393,12 @@ pub const Zld = struct { return index; } + fn addGlobal(self: *Zld, sym_loc: SymbolWithLoc) !u32 { + const global_index = @intCast(u32, self.globals.items.len); + try self.globals.append(self.gpa, sym_loc); + return global_index; + } + fn allocateSpecialSymbols(self: *Zld) !void { for (&[_]?u32{ self.dso_handle_index, @@ -3507,7 +3548,7 @@ pub const SymbolWithLoc = extern struct { } }; -const SymbolResolver = struct { +pub const SymbolResolver = struct { arena: Allocator, table: std.StringHashMap(u32), unresolved: std.AutoArrayHashMap(u32, void), @@ -3568,7 +3609,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr // We are about to obtain this lock, so here we give other processes a chance first. macho_file.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 7); + comptime assert(Compilation.link_hash_implementation_version == 8); for (options.objects) |obj| { _ = try man.addFile(obj.path, null); @@ -3598,6 +3639,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr } link.hashAddSystemLibs(&man.hash, options.system_libs); man.hash.addOptionalBytes(options.sysroot); + man.hash.addListOfBytes(options.force_undefined_symbols.keys()); try man.addOptionalFile(options.entitlements); // We don't actually care whether it's a cache hit or miss; we just @@ -3980,17 +4022,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr .table = std.StringHashMap(u32).init(arena), .unresolved = std.AutoArrayHashMap(u32, void).init(arena), }; - - for (zld.objects.items, 0..) |_, object_id| { - try zld.resolveSymbolsInObject(@intCast(u32, object_id), &resolver); - } - - try zld.resolveSymbolsInArchives(&resolver); - try zld.resolveDyldStubBinder(&resolver); - try zld.resolveSymbolsInDylibs(&resolver); - try zld.createMhExecuteHeaderSymbol(&resolver); - try zld.createDsoHandleSymbol(&resolver); - try zld.resolveSymbolsAtLoading(&resolver); + try zld.resolveSymbols(&resolver); if (resolver.unresolved.count() > 0) { return error.UndefinedSymbolReference; @@ -4003,11 +4035,8 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr } if (options.output_mode == .Exe) { - const entry_name = options.entry orelse "_main"; - const global_index = resolver.table.get(entry_name) orelse { - log.err("entrypoint '{s}' not found", .{entry_name}); - return error.MissingMainEntrypoint; - }; + const entry_name = options.entry orelse load_commands.default_entry_point; + const global_index = resolver.table.get(entry_name).?; // Error was flagged earlier zld.entry_index = global_index; } @@ -4016,13 +4045,23 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr } if (gc_sections) { - try dead_strip.gcAtoms(&zld); + try dead_strip.gcAtoms(&zld, &resolver); } try zld.createDyldPrivateAtom(); try zld.createTentativeDefAtoms(); try zld.createStubHelperPreambleAtom(); + if (zld.options.output_mode == .Exe) { + const global = zld.getEntryPoint(); + if (zld.getSymbol(global).undf()) { + // We do one additional check here in case the entry point was found in one of the dylibs. + // (I actually have no idea what this would imply but it is a possible outcome and so we + // support it.) + try Atom.addStub(&zld, global); + } + } + for (zld.objects.items) |object| { for (object.atoms.items) |atom_index| { const atom = zld.getAtom(atom_index); @@ -4134,8 +4173,18 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr const seg = zld.segments.items[seg_id]; const global = zld.getEntryPoint(); const sym = zld.getSymbol(global); + + const addr: u64 = if (sym.undf()) blk: { + // In this case, the symbol has been resolved in one of dylibs and so we point + // to the stub as its vmaddr value. + const stub_atom_index = zld.getStubsAtomIndexForSymbol(global).?; + const stub_atom = zld.getAtom(stub_atom_index); + const stub_sym = zld.getSymbol(stub_atom.getSymbolWithLoc()); + break :blk stub_sym.n_value; + } else sym.n_value; + try lc_writer.writeStruct(macho.entry_point_command{ - .entryoff = @intCast(u32, sym.n_value - seg.vmaddr), + .entryoff = @intCast(u32, addr - seg.vmaddr), .stacksize = options.stack_size_override orelse 0, }); } else { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e59b83176c..b1fc25bf26 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3059,7 +3059,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l // We are about to obtain this lock, so here we give other processes a chance first. wasm.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 7); + comptime assert(Compilation.link_hash_implementation_version == 8); for (options.objects) |obj| { _ = try man.addFile(obj.path, null); @@ -4086,7 +4086,7 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! // We are about to obtain this lock, so here we give other processes a chance first. wasm.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 7); + comptime assert(Compilation.link_hash_implementation_version == 8); for (wasm.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); diff --git a/src/main.zig b/src/main.zig index 1a445107ba..76c43476ee 100644 --- a/src/main.zig +++ b/src/main.zig @@ -478,6 +478,7 @@ const usage_build_generic = \\ --sysroot [path] Set the system root directory (usually /) \\ --version [ver] Dynamic library semver \\ --entry [name] Set the entrypoint symbol name + \\ --force_undefined [name] Specify the symbol must be defined for the link to succeed \\ -fsoname[=name] Override the default SONAME value \\ -fno-soname Disable emitting a SONAME \\ -fLLD Force using LLD as the linker @@ -680,6 +681,28 @@ const Listen = union(enum) { stdio, }; +const ArgsIterator = struct { + resp_file: ?ArgIteratorResponseFile = null, + args: []const []const u8, + i: usize = 0, + fn next(it: *@This()) ?[]const u8 { + if (it.i >= it.args.len) { + if (it.resp_file) |*resp| return resp.next(); + return null; + } + defer it.i += 1; + return it.args[it.i]; + } + fn nextOrFatal(it: *@This()) []const u8 { + if (it.i >= it.args.len) { + if (it.resp_file) |*resp| if (resp.next()) |ret| return ret; + fatal("expected parameter after {s}", .{it.args[it.i - 1]}); + } + defer it.i += 1; + return it.args[it.i]; + } +}; + fn buildOutputType( gpa: Allocator, arena: Allocator, @@ -784,6 +807,7 @@ fn buildOutputType( var test_evented_io = false; var test_no_exec = false; var entry: ?[]const u8 = null; + var force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{}; var stack_size_override: ?u64 = null; var image_base_override: ?u64 = null; var use_llvm: ?bool = null; @@ -917,28 +941,7 @@ fn buildOutputType( soname = .yes_default_value; - const Iterator = struct { - resp_file: ?ArgIteratorResponseFile = null, - args: []const []const u8, - i: usize = 0, - fn next(it: *@This()) ?[]const u8 { - if (it.i >= it.args.len) { - if (it.resp_file) |*resp| return resp.next(); - return null; - } - defer it.i += 1; - return it.args[it.i]; - } - fn nextOrFatal(it: *@This()) []const u8 { - if (it.i >= it.args.len) { - if (it.resp_file) |*resp| if (resp.next()) |ret| return ret; - fatal("expected parameter after {s}", .{it.args[it.i - 1]}); - } - defer it.i += 1; - return it.args[it.i]; - } - }; - var args_iter = Iterator{ + var args_iter = ArgsIterator{ .args = all_args[2..], }; @@ -1029,6 +1032,8 @@ fn buildOutputType( optimize_mode_string = args_iter.nextOrFatal(); } else if (mem.eql(u8, arg, "--entry")) { entry = args_iter.nextOrFatal(); + } else if (mem.eql(u8, arg, "--force_undefined")) { + try force_undefined_symbols.put(gpa, args_iter.nextOrFatal(), {}); } else if (mem.eql(u8, arg, "--stack")) { const next_arg = args_iter.nextOrFatal(); stack_size_override = std.fmt.parseUnsigned(u64, next_arg, 0) catch |err| { @@ -1816,6 +1821,9 @@ fn buildOutputType( .entry => { entry = it.only_arg; }, + .force_undefined_symbol => { + try force_undefined_symbols.put(gpa, it.only_arg, {}); + }, .weak_library => try system_libs.put(it.only_arg, .{ .weak = true }), .weak_framework => try frameworks.put(gpa, it.only_arg, .{ .weak = true }), .headerpad_max_install_names => headerpad_max_install_names = true, @@ -1843,17 +1851,14 @@ fn buildOutputType( } } // Parse linker args. - var i: usize = 0; - while (i < linker_args.items.len) : (i += 1) { - const arg = linker_args.items[i]; + var linker_args_it = ArgsIterator{ + .args = linker_args.items, + }; + while (linker_args_it.next()) |arg| { if (mem.eql(u8, arg, "-soname") or mem.eql(u8, arg, "--soname")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - const name = linker_args.items[i]; + const name = linker_args_it.nextOrFatal(); soname = .{ .yes = name }; // Use it as --name. // Example: libsoundio.so.2 @@ -1881,64 +1886,37 @@ fn buildOutputType( } provided_name = name[prefix..end]; } else if (mem.eql(u8, arg, "-rpath")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - try rpath_list.append(linker_args.items[i]); + try rpath_list.append(linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "--subsystem")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - subsystem = try parseSubSystem(linker_args.items[i]); + subsystem = try parseSubSystem(linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "-I") or mem.eql(u8, arg, "--dynamic-linker") or mem.eql(u8, arg, "-dynamic-linker")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - target_dynamic_linker = linker_args.items[i]; + target_dynamic_linker = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "-E") or mem.eql(u8, arg, "--export-dynamic") or mem.eql(u8, arg, "-export-dynamic")) { rdynamic = true; } else if (mem.eql(u8, arg, "--version-script")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - version_script = linker_args.items[i]; + version_script = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "-O")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - linker_optimization = std.fmt.parseUnsigned(u8, linker_args.items[i], 10) catch |err| { - fatal("unable to parse optimization level '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + const opt = linker_args_it.nextOrFatal(); + linker_optimization = std.fmt.parseUnsigned(u8, opt, 10) catch |err| { + fatal("unable to parse optimization level '{s}': {s}", .{ opt, @errorName(err) }); }; } else if (mem.startsWith(u8, arg, "-O")) { linker_optimization = std.fmt.parseUnsigned(u8, arg["-O".len..], 10) catch |err| { fatal("unable to parse optimization level '{s}': {s}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-pagezero_size")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - const next_arg = linker_args.items[i]; + const next_arg = linker_args_it.nextOrFatal(); pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { fatal("unable to parse pagezero size '{s}': {s}", .{ next_arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-headerpad")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - const next_arg = linker_args.items[i]; + const next_arg = linker_args_it.nextOrFatal(); headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| { fatal("unable to parse headerpad size '{s}': {s}", .{ next_arg, @errorName(err) }); }; @@ -1961,11 +1939,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--print-map")) { linker_print_map = true; } else if (mem.eql(u8, arg, "--sort-section")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - const arg1 = linker_args.items[i]; + const arg1 = linker_args_it.nextOrFatal(); linker_sort_section = std.meta.stringToEnum(link.SortSection, arg1) orelse { fatal("expected [name|alignment] after --sort-section, found '{s}'", .{arg1}); }; @@ -1998,28 +1972,16 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "--export=")) { try linker_export_symbol_names.append(arg["--export=".len..]); } else if (mem.eql(u8, arg, "--export")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - try linker_export_symbol_names.append(linker_args.items[i]); + try linker_export_symbol_names.append(linker_args_it.nextOrFatal()); } else if (mem.eql(u8, arg, "--compress-debug-sections")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - const arg1 = linker_args.items[i]; + const arg1 = linker_args_it.nextOrFatal(); linker_compress_debug_sections = std.meta.stringToEnum(link.CompressDebugSections, arg1) orelse { fatal("expected [none|zlib] after --compress-debug-sections, found '{s}'", .{arg1}); }; } else if (mem.startsWith(u8, arg, "-z")) { var z_arg = arg[2..]; if (z_arg.len == 0) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker extension flag after '{s}'", .{arg}); - } - z_arg = linker_args.items[i]; + z_arg = linker_args_it.nextOrFatal(); } if (mem.eql(u8, z_arg, "nodelete")) { linker_z_nodelete = true; @@ -2056,51 +2018,33 @@ fn buildOutputType( fatal("unsupported linker extension flag: -z {s}", .{z_arg}); } } else if (mem.eql(u8, arg, "--major-image-version")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - version.major = std.fmt.parseUnsigned(u32, linker_args.items[i], 10) catch |err| { - fatal("unable to parse major image version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + const major = linker_args_it.nextOrFatal(); + version.major = std.fmt.parseUnsigned(u32, major, 10) catch |err| { + fatal("unable to parse major image version '{s}': {s}", .{ major, @errorName(err) }); }; have_version = true; } else if (mem.eql(u8, arg, "--minor-image-version")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - version.minor = std.fmt.parseUnsigned(u32, linker_args.items[i], 10) catch |err| { - fatal("unable to parse minor image version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + const minor = linker_args_it.nextOrFatal(); + version.minor = std.fmt.parseUnsigned(u32, minor, 10) catch |err| { + fatal("unable to parse minor image version '{s}': {s}", .{ minor, @errorName(err) }); }; have_version = true; } else if (mem.eql(u8, arg, "-e") or mem.eql(u8, arg, "--entry")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - entry = linker_args.items[i]; + entry = linker_args_it.nextOrFatal(); + } else if (mem.eql(u8, arg, "-u")) { + try force_undefined_symbols.put(gpa, linker_args_it.nextOrFatal(), {}); } else if (mem.eql(u8, arg, "--stack") or mem.eql(u8, arg, "-stack_size")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - stack_size_override = std.fmt.parseUnsigned(u64, linker_args.items[i], 0) catch |err| { - fatal("unable to parse stack size override '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + const stack_size = linker_args_it.nextOrFatal(); + stack_size_override = std.fmt.parseUnsigned(u64, stack_size, 0) catch |err| { + fatal("unable to parse stack size override '{s}': {s}", .{ stack_size, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--image-base")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - image_base_override = std.fmt.parseUnsigned(u64, linker_args.items[i], 0) catch |err| { - fatal("unable to parse image base override '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + const image_base = linker_args_it.nextOrFatal(); + image_base_override = std.fmt.parseUnsigned(u64, image_base, 0) catch |err| { + fatal("unable to parse image base override '{s}': {s}", .{ image_base, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - linker_script = linker_args.items[i]; + linker_script = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "--eh-frame-hdr")) { link_eh_frame_hdr = true; } else if (mem.eql(u8, arg, "--no-eh-frame-hdr")) { @@ -2138,130 +2082,74 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--major-os-version") or mem.eql(u8, arg, "--minor-os-version")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } // This option does not do anything. + _ = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "--major-subsystem-version")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - + const major = linker_args_it.nextOrFatal(); major_subsystem_version = std.fmt.parseUnsigned( u32, - linker_args.items[i], + major, 10, ) catch |err| { - fatal("unable to parse major subsystem version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + fatal("unable to parse major subsystem version '{s}': {s}", .{ major, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--minor-subsystem-version")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - + const minor = linker_args_it.nextOrFatal(); minor_subsystem_version = std.fmt.parseUnsigned( u32, - linker_args.items[i], + minor, 10, ) catch |err| { - fatal("unable to parse minor subsystem version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + fatal("unable to parse minor subsystem version '{s}': {s}", .{ minor, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-framework")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - try frameworks.put(gpa, linker_args.items[i], .{}); + try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{}); } else if (mem.eql(u8, arg, "-weak_framework")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - try frameworks.put(gpa, linker_args.items[i], .{ .weak = true }); + try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{ .weak = true }); } else if (mem.eql(u8, arg, "-needed_framework")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - try frameworks.put(gpa, linker_args.items[i], .{ .needed = true }); + try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{ .needed = true }); } else if (mem.eql(u8, arg, "-needed_library")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - try system_libs.put(linker_args.items[i], .{ .needed = true }); + try system_libs.put(linker_args_it.nextOrFatal(), .{ .needed = true }); } else if (mem.startsWith(u8, arg, "-weak-l")) { try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); } else if (mem.eql(u8, arg, "-weak_library")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - try system_libs.put(linker_args.items[i], .{ .weak = true }); + try system_libs.put(linker_args_it.nextOrFatal(), .{ .weak = true }); } else if (mem.eql(u8, arg, "-compatibility_version")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - compatibility_version = std.builtin.Version.parse(linker_args.items[i]) catch |err| { - fatal("unable to parse -compatibility_version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + const compat_version = linker_args_it.nextOrFatal(); + compatibility_version = std.builtin.Version.parse(compat_version) catch |err| { + fatal("unable to parse -compatibility_version '{s}': {s}", .{ compat_version, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-current_version")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - version = std.builtin.Version.parse(linker_args.items[i]) catch |err| { - fatal("unable to parse -current_version '{s}': {s}", .{ linker_args.items[i], @errorName(err) }); + const curr_version = linker_args_it.nextOrFatal(); + version = std.builtin.Version.parse(curr_version) catch |err| { + fatal("unable to parse -current_version '{s}': {s}", .{ curr_version, @errorName(err) }); }; have_version = true; } else if (mem.eql(u8, arg, "--out-implib") or mem.eql(u8, arg, "-implib")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - emit_implib = .{ .yes = linker_args.items[i] }; + emit_implib = .{ .yes = linker_args_it.nextOrFatal() }; emit_implib_arg_provided = true; } else if (mem.eql(u8, arg, "-undefined")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - if (mem.eql(u8, "dynamic_lookup", linker_args.items[i])) { + const lookup_type = linker_args_it.nextOrFatal(); + if (mem.eql(u8, "dynamic_lookup", lookup_type)) { linker_allow_shlib_undefined = true; - } else if (mem.eql(u8, "error", linker_args.items[i])) { + } else if (mem.eql(u8, "error", lookup_type)) { linker_allow_shlib_undefined = false; } else { - fatal("unsupported -undefined option '{s}'", .{linker_args.items[i]}); + fatal("unsupported -undefined option '{s}'", .{lookup_type}); } } else if (mem.eql(u8, arg, "-install_name")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - install_name = linker_args.items[i]; + install_name = linker_args_it.nextOrFatal(); } else if (mem.eql(u8, arg, "-force_load")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } try link_objects.append(.{ - .path = linker_args.items[i], + .path = linker_args_it.nextOrFatal(), .must_link = true, }); } else if (mem.eql(u8, arg, "-hash-style") or mem.eql(u8, arg, "--hash-style")) { - i += 1; - if (i >= linker_args.items.len) { - fatal("expected linker arg after '{s}'", .{arg}); - } - const next_arg = linker_args.items[i]; + const next_arg = linker_args_it.nextOrFatal(); hash_style = std.meta.stringToEnum(link.HashStyle, next_arg) orelse { fatal("expected [sysv|gnu|both] after --hash-style, found '{s}'", .{ next_arg, @@ -3219,6 +3107,7 @@ fn buildOutputType( .link_eh_frame_hdr = link_eh_frame_hdr, .link_emit_relocs = link_emit_relocs, .entry = entry, + .force_undefined_symbols = force_undefined_symbols, .stack_size_override = stack_size_override, .image_base_override = image_base_override, .strip = strip, @@ -5295,6 +5184,7 @@ pub const ClangArgIterator = struct { emit_llvm, sysroot, entry, + force_undefined_symbol, weak_library, weak_framework, headerpad_max_install_names, diff --git a/test/link.zig b/test/link.zig index aa0ed4817e..d319d7fa61 100644 --- a/test/link.zig +++ b/test/link.zig @@ -104,6 +104,14 @@ pub const cases = [_]Case{ .build_root = "test/link/macho/entry", .import = @import("link/macho/entry/build.zig"), }, + .{ + .build_root = "test/link/macho/entry_in_archive", + .import = @import("link/macho/entry_in_archive/build.zig"), + }, + .{ + .build_root = "test/link/macho/entry_in_dylib", + .import = @import("link/macho/entry_in_dylib/build.zig"), + }, .{ .build_root = "test/link/macho/headerpad", .import = @import("link/macho/headerpad/build.zig"), diff --git a/test/link/macho/entry_in_archive/build.zig b/test/link/macho/entry_in_archive/build.zig new file mode 100644 index 0000000000..5d244a93fc --- /dev/null +++ b/test/link/macho/entry_in_archive/build.zig @@ -0,0 +1,36 @@ +const std = @import("std"); + +pub const requires_symlinks = true; + +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const lib = b.addStaticLibrary(.{ + .name = "main", + .optimize = optimize, + .target = .{ .os_tag = .macos }, + }); + lib.addCSourceFile("main.c", &.{}); + lib.linkLibC(); + + const exe = b.addExecutable(.{ + .name = "main", + .optimize = optimize, + .target = .{ .os_tag = .macos }, + }); + exe.linkLibrary(lib); + exe.linkLibC(); + + const run = exe.run(); + run.skip_foreign_checks = true; + run.expectExitCode(0); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/entry_in_archive/main.c b/test/link/macho/entry_in_archive/main.c new file mode 100644 index 0000000000..b9f6deb5be --- /dev/null +++ b/test/link/macho/entry_in_archive/main.c @@ -0,0 +1,5 @@ +#include + +int main(int argc, char* argv[]) { + return 0; +} diff --git a/test/link/macho/entry_in_dylib/bootstrap.c b/test/link/macho/entry_in_dylib/bootstrap.c new file mode 100644 index 0000000000..6e9a2b830c --- /dev/null +++ b/test/link/macho/entry_in_dylib/bootstrap.c @@ -0,0 +1,5 @@ +extern int my_main(); + +int bootstrap() { + return my_main(); +} diff --git a/test/link/macho/entry_in_dylib/build.zig b/test/link/macho/entry_in_dylib/build.zig new file mode 100644 index 0000000000..135661872d --- /dev/null +++ b/test/link/macho/entry_in_dylib/build.zig @@ -0,0 +1,54 @@ +const std = @import("std"); + +pub const requires_symlinks = true; + +pub fn build(b: *std.Build) void { + const test_step = b.step("test", "Test it"); + b.default_step = test_step; + + add(b, test_step, .Debug); + add(b, test_step, .ReleaseFast); + add(b, test_step, .ReleaseSmall); + add(b, test_step, .ReleaseSafe); +} + +fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { + const lib = b.addSharedLibrary(.{ + .name = "bootstrap", + .optimize = optimize, + .target = .{ .os_tag = .macos }, + }); + lib.addCSourceFile("bootstrap.c", &.{}); + lib.linkLibC(); + lib.linker_allow_shlib_undefined = true; + + const exe = b.addExecutable(.{ + .name = "main", + .optimize = optimize, + .target = .{ .os_tag = .macos }, + }); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibrary(lib); + exe.linkLibC(); + exe.entry_symbol_name = "_bootstrap"; + exe.forceUndefinedSymbol("_my_main"); + + const check_exe = exe.checkObject(); + check_exe.checkStart("segname __TEXT"); + check_exe.checkNext("vmaddr {text_vmaddr}"); + + check_exe.checkStart("sectname __stubs"); + check_exe.checkNext("addr {stubs_vmaddr}"); + + check_exe.checkStart("cmd MAIN"); + check_exe.checkNext("entryoff {entryoff}"); + + check_exe.checkComputeCompare("text_vmaddr entryoff +", .{ + .op = .eq, + .value = .{ .variable = "stubs_vmaddr" }, // The entrypoint should be a synthetic stub + }); + + const run = check_exe.runAndCompare(); + run.expectStdOutEqual("Hello!\n"); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/entry_in_dylib/main.c b/test/link/macho/entry_in_dylib/main.c new file mode 100644 index 0000000000..26173b80ba --- /dev/null +++ b/test/link/macho/entry_in_dylib/main.c @@ -0,0 +1,6 @@ +#include + +int my_main() { + fprintf(stdout, "Hello!\n"); + return 0; +} diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index a1719c5ab6..fee877add0 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -468,6 +468,10 @@ const known_options = [_]KnownOpt{ .name = "e", .ident = "entry", }, + .{ + .name = "u", + .ident = "force_undefined_symbol", + }, .{ .name = "weak-l", .ident = "weak_library",