link: handle -u flag in all linkers

Also clean up parsing of linker args - reuse `ArgsIterator`.

In MachO, ensure we add every symbol marked with `-u` as undefined
before proceeding with symbol resolution. Additionally, ensure those
symbols are never garbage collected.

MachO entry_in_dylib test: pass `-u _my_main` when linking executable
so that it is not incorrectly garbage collected by the linker.
This commit is contained in:
Jakub Konka 2023-04-01 11:51:05 +02:00
parent 2dd178443a
commit a88c0b4d08
12 changed files with 185 additions and 246 deletions

View File

@ -202,6 +202,11 @@ subsystem: ?std.Target.SubSystem = null,
entry_symbol_name: ?[]const u8 = 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 <symbol>` for ELF/MachO and `/include:<symbol>` for COFF/PE.
force_undefined_symbols: std.StringHashMap(void),
/// Overrides the default stack size /// Overrides the default stack size
stack_size: ?u64 = null, stack_size: ?u64 = null,
@ -386,6 +391,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep {
.override_dest_dir = null, .override_dest_dir = null,
.installed_path = null, .installed_path = null,
.install_step = null, .install_step = null,
.force_undefined_symbols = StringHashMap(void).init(owner.allocator),
.output_path_source = GeneratedFile{ .step = &self.step }, .output_path_source = GeneratedFile{ .step = &self.step },
.output_lib_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); 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 { pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void {
const b = self.step.owner; const b = self.step.owner;
self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM"); 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); 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| { if (self.stack_size) |stack_size| {
try zig_args.append("--stack"); try zig_args.append("--stack");
try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size}));

View File

@ -602,6 +602,7 @@ pub const InitOptions = struct {
parent_compilation_link_libc: bool = false, parent_compilation_link_libc: bool = false,
hash_style: link.HashStyle = .both, hash_style: link.HashStyle = .both,
entry: ?[]const u8 = null, entry: ?[]const u8 = null,
force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{},
stack_size_override: ?u64 = null, stack_size_override: ?u64 = null,
image_base_override: ?u64 = null, image_base_override: ?u64 = null,
self_exe_path: ?[]const u8 = 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_size = options.headerpad_size,
.headerpad_max_install_names = options.headerpad_max_install_names, .headerpad_max_install_names = options.headerpad_max_install_names,
.dead_strip_dylibs = options.dead_strip_dylibs, .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_source_path = options.pdb_source_path,
.pdb_out_path = options.pdb_out_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 /// to remind the programmer to update multiple related pieces of code that
/// are in different locations. Bump this number when adding or deleting /// are in different locations. Bump this number when adding or deleting
/// anything from the link cache manifest. /// 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 { fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
const gpa = comp.gpa; const gpa = comp.gpa;
@ -2196,7 +2197,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
defer arena_allocator.deinit(); defer arena_allocator.deinit();
const arena = arena_allocator.allocator(); 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| { if (comp.bin_file.options.module) |mod| {
const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{ const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{

View File

@ -1448,7 +1448,7 @@ flagpsl("MT"),
.{ .{
.name = "u", .name = "u",
.syntax = .flag, .syntax = .flag,
.zig_equivalent = .other, .zig_equivalent = .force_undefined_symbol,
.pd1 = true, .pd1 = true,
.pd2 = false, .pd2 = false,
.psl = true, .psl = true,
@ -7170,7 +7170,14 @@ joinpd1("d"),
.pd2 = false, .pd2 = false,
.psl = true, .psl = true,
}, },
jspd1("u"), .{
.name = "u",
.syntax = .joined_or_separate,
.zig_equivalent = .force_undefined_symbol,
.pd1 = true,
.pd2 = false,
.psl = false,
},
.{ .{
.name = "x", .name = "x",
.syntax = .joined_or_separate, .syntax = .joined_or_separate,

View File

@ -186,8 +186,7 @@ pub const Options = struct {
/// List of symbols forced as undefined in the symbol table /// List of symbols forced as undefined in the symbol table
/// thus forcing their resolution by the linker. /// thus forcing their resolution by the linker.
/// Corresponds to `-u <symbol>` for ELF and `/include:<symbol>` for COFF/PE. /// Corresponds to `-u <symbol>` for ELF/MachO and `/include:<symbol>` for COFF/PE.
/// TODO add handling for MachO.
force_undefined_symbols: std.StringArrayHashMapUnmanaged(void), force_undefined_symbols: std.StringArrayHashMapUnmanaged(void),
version: ?std.builtin.Version, version: ?std.builtin.Version,

View File

@ -63,7 +63,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
man = comp.cache_parent.obtain(); man = comp.cache_parent.obtain();
self.base.releaseLock(); 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| { for (self.base.options.objects) |obj| {
_ = try man.addFile(obj.path, null); _ = try man.addFile(obj.path, null);

View File

@ -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. // We are about to obtain this lock, so here we give other processes a chance first.
self.base.releaseLock(); 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.linker_script);
try man.addOptionalFile(self.base.options.version_script); try man.addOptionalFile(self.base.options.version_script);

View File

@ -12,6 +12,7 @@ const Allocator = mem.Allocator;
const AtomIndex = @import("zld.zig").AtomIndex; const AtomIndex = @import("zld.zig").AtomIndex;
const Atom = @import("ZldAtom.zig"); const Atom = @import("ZldAtom.zig");
const SymbolWithLoc = @import("zld.zig").SymbolWithLoc; const SymbolWithLoc = @import("zld.zig").SymbolWithLoc;
const SymbolResolver = @import("zld.zig").SymbolResolver;
const UnwindInfo = @import("UnwindInfo.zig"); const UnwindInfo = @import("UnwindInfo.zig");
const Zld = @import("zld.zig").Zld; const Zld = @import("zld.zig").Zld;
@ -19,7 +20,7 @@ const N_DEAD = @import("zld.zig").N_DEAD;
const AtomTable = std.AutoHashMap(AtomIndex, void); 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; const gpa = zld.gpa;
var arena = std.heap.ArenaAllocator.init(gpa); var arena = std.heap.ArenaAllocator.init(gpa);
@ -31,12 +32,25 @@ pub fn gcAtoms(zld: *Zld) !void {
var alive = AtomTable.init(arena.allocator()); var alive = AtomTable.init(arena.allocator());
try alive.ensureTotalCapacity(@intCast(u32, zld.atoms.items.len)); try alive.ensureTotalCapacity(@intCast(u32, zld.atoms.items.len));
try collectRoots(zld, &roots); try collectRoots(zld, &roots, resolver);
try mark(zld, roots, &alive); try mark(zld, roots, &alive);
prune(zld, 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", .{}); log.debug("collecting roots", .{});
switch (zld.options.output_mode) { switch (zld.options.output_mode) {
@ -44,15 +58,7 @@ fn collectRoots(zld: *Zld, roots: *AtomTable) !void {
// Add entrypoint as GC root // Add entrypoint as GC root
const global: SymbolWithLoc = zld.getEntryPoint(); const global: SymbolWithLoc = zld.getEntryPoint();
if (global.getFile()) |file| { if (global.getFile()) |file| {
const object = zld.objects.items[file]; try addRoot(zld, roots, file, global);
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(),
});
} else { } else {
assert(zld.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib. assert(zld.getSymbol(global).undf()); // Stub as our entrypoint is in a dylib.
} }
@ -64,20 +70,22 @@ fn collectRoots(zld: *Zld, roots: *AtomTable) !void {
const sym = zld.getSymbol(global); const sym = zld.getSymbol(global);
if (sym.undf()) continue; if (sym.undf()) continue;
const file = global.getFile() orelse continue; // synthetic globals are atomless if (global.getFile()) |file| {
const object = zld.objects.items[file]; try addRoot(zld, roots, file, global);
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(),
});
} }
}, },
} }
// 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| { for (zld.objects.items) |object| {
const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; const has_subsections = object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;

View File

@ -932,20 +932,29 @@ 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 { fn resolveSymbols(self: *Zld, resolver: *SymbolResolver) !void {
// We add the specified entrypoint as the first unresolved symbols so that // 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 // we search for it in libraries should there be no object files specified
// on the linker line. // on the linker line.
if (self.options.output_mode == .Exe) { if (self.options.output_mode == .Exe) {
const entry_name = self.options.entry orelse load_commands.default_entry_point; const entry_name = self.options.entry orelse load_commands.default_entry_point;
const sym_index = try self.allocateSymbol(); try self.forceSymbolDefined(entry_name, resolver);
const sym_loc = SymbolWithLoc{ .sym_index = sym_index }; }
const sym = self.getSymbolPtr(sym_loc);
sym.n_strx = try self.strtab.insert(self.gpa, entry_name); // Force resolution of any symbols requested by the user.
sym.n_type = macho.N_UNDF | macho.N_EXT; for (self.options.force_undefined_symbols.keys()) |sym_name| {
const global_index = try self.addGlobal(sym_loc); try self.forceSymbolDefined(sym_name, resolver);
try resolver.table.putNoClobber(entry_name, global_index);
try resolver.unresolved.putNoClobber(global_index, {});
} }
for (self.objects.items, 0..) |_, object_id| { for (self.objects.items, 0..) |_, object_id| {
@ -3539,7 +3548,7 @@ pub const SymbolWithLoc = extern struct {
} }
}; };
const SymbolResolver = struct { pub const SymbolResolver = struct {
arena: Allocator, arena: Allocator,
table: std.StringHashMap(u32), table: std.StringHashMap(u32),
unresolved: std.AutoArrayHashMap(u32, void), unresolved: std.AutoArrayHashMap(u32, void),
@ -3600,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. // We are about to obtain this lock, so here we give other processes a chance first.
macho_file.base.releaseLock(); macho_file.base.releaseLock();
comptime assert(Compilation.link_hash_implementation_version == 7); comptime assert(Compilation.link_hash_implementation_version == 8);
for (options.objects) |obj| { for (options.objects) |obj| {
_ = try man.addFile(obj.path, null); _ = try man.addFile(obj.path, null);
@ -3630,6 +3639,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
} }
link.hashAddSystemLibs(&man.hash, options.system_libs); link.hashAddSystemLibs(&man.hash, options.system_libs);
man.hash.addOptionalBytes(options.sysroot); man.hash.addOptionalBytes(options.sysroot);
man.hash.addListOfBytes(options.force_undefined_symbols.keys());
try man.addOptionalFile(options.entitlements); try man.addOptionalFile(options.entitlements);
// We don't actually care whether it's a cache hit or miss; we just // We don't actually care whether it's a cache hit or miss; we just
@ -4035,7 +4045,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr
} }
if (gc_sections) { if (gc_sections) {
try dead_strip.gcAtoms(&zld); try dead_strip.gcAtoms(&zld, &resolver);
} }
try zld.createDyldPrivateAtom(); try zld.createDyldPrivateAtom();

View File

@ -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. // We are about to obtain this lock, so here we give other processes a chance first.
wasm.base.releaseLock(); wasm.base.releaseLock();
comptime assert(Compilation.link_hash_implementation_version == 7); comptime assert(Compilation.link_hash_implementation_version == 8);
for (options.objects) |obj| { for (options.objects) |obj| {
_ = try man.addFile(obj.path, null); _ = 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. // We are about to obtain this lock, so here we give other processes a chance first.
wasm.base.releaseLock(); 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| { for (wasm.base.options.objects) |obj| {
_ = try man.addFile(obj.path, null); _ = try man.addFile(obj.path, null);

View File

@ -478,6 +478,7 @@ const usage_build_generic =
\\ --sysroot [path] Set the system root directory (usually /) \\ --sysroot [path] Set the system root directory (usually /)
\\ --version [ver] Dynamic library semver \\ --version [ver] Dynamic library semver
\\ --entry [name] Set the entrypoint symbol name \\ --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 \\ -fsoname[=name] Override the default SONAME value
\\ -fno-soname Disable emitting a SONAME \\ -fno-soname Disable emitting a SONAME
\\ -fLLD Force using LLD as the linker \\ -fLLD Force using LLD as the linker
@ -680,6 +681,28 @@ const Listen = union(enum) {
stdio, 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( fn buildOutputType(
gpa: Allocator, gpa: Allocator,
arena: Allocator, arena: Allocator,
@ -784,6 +807,7 @@ fn buildOutputType(
var test_evented_io = false; var test_evented_io = false;
var test_no_exec = false; var test_no_exec = false;
var entry: ?[]const u8 = null; var entry: ?[]const u8 = null;
var force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .{};
var stack_size_override: ?u64 = null; var stack_size_override: ?u64 = null;
var image_base_override: ?u64 = null; var image_base_override: ?u64 = null;
var use_llvm: ?bool = null; var use_llvm: ?bool = null;
@ -917,28 +941,7 @@ fn buildOutputType(
soname = .yes_default_value; soname = .yes_default_value;
const Iterator = struct { var args_iter = ArgsIterator{
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{
.args = all_args[2..], .args = all_args[2..],
}; };
@ -1029,6 +1032,8 @@ fn buildOutputType(
optimize_mode_string = args_iter.nextOrFatal(); optimize_mode_string = args_iter.nextOrFatal();
} else if (mem.eql(u8, arg, "--entry")) { } else if (mem.eql(u8, arg, "--entry")) {
entry = args_iter.nextOrFatal(); 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")) { } else if (mem.eql(u8, arg, "--stack")) {
const next_arg = args_iter.nextOrFatal(); const next_arg = args_iter.nextOrFatal();
stack_size_override = std.fmt.parseUnsigned(u64, next_arg, 0) catch |err| { stack_size_override = std.fmt.parseUnsigned(u64, next_arg, 0) catch |err| {
@ -1816,6 +1821,9 @@ fn buildOutputType(
.entry => { .entry => {
entry = it.only_arg; 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_library => try system_libs.put(it.only_arg, .{ .weak = true }),
.weak_framework => try frameworks.put(gpa, 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, .headerpad_max_install_names => headerpad_max_install_names = true,
@ -1843,17 +1851,14 @@ fn buildOutputType(
} }
} }
// Parse linker args. // Parse linker args.
var i: usize = 0; var linker_args_it = ArgsIterator{
while (i < linker_args.items.len) : (i += 1) { .args = linker_args.items,
const arg = linker_args.items[i]; };
while (linker_args_it.next()) |arg| {
if (mem.eql(u8, arg, "-soname") or if (mem.eql(u8, arg, "-soname") or
mem.eql(u8, arg, "--soname")) mem.eql(u8, arg, "--soname"))
{ {
i += 1; const name = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
const name = linker_args.items[i];
soname = .{ .yes = name }; soname = .{ .yes = name };
// Use it as --name. // Use it as --name.
// Example: libsoundio.so.2 // Example: libsoundio.so.2
@ -1881,64 +1886,37 @@ fn buildOutputType(
} }
provided_name = name[prefix..end]; provided_name = name[prefix..end];
} else if (mem.eql(u8, arg, "-rpath")) { } else if (mem.eql(u8, arg, "-rpath")) {
i += 1; try rpath_list.append(linker_args_it.nextOrFatal());
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
try rpath_list.append(linker_args.items[i]);
} else if (mem.eql(u8, arg, "--subsystem")) { } else if (mem.eql(u8, arg, "--subsystem")) {
i += 1; subsystem = try parseSubSystem(linker_args_it.nextOrFatal());
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
subsystem = try parseSubSystem(linker_args.items[i]);
} else if (mem.eql(u8, arg, "-I") or } else if (mem.eql(u8, arg, "-I") or
mem.eql(u8, arg, "--dynamic-linker") or mem.eql(u8, arg, "--dynamic-linker") or
mem.eql(u8, arg, "-dynamic-linker")) mem.eql(u8, arg, "-dynamic-linker"))
{ {
i += 1; target_dynamic_linker = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
target_dynamic_linker = linker_args.items[i];
} else if (mem.eql(u8, arg, "-E") or } else if (mem.eql(u8, arg, "-E") or
mem.eql(u8, arg, "--export-dynamic") or mem.eql(u8, arg, "--export-dynamic") or
mem.eql(u8, arg, "-export-dynamic")) mem.eql(u8, arg, "-export-dynamic"))
{ {
rdynamic = true; rdynamic = true;
} else if (mem.eql(u8, arg, "--version-script")) { } else if (mem.eql(u8, arg, "--version-script")) {
i += 1; version_script = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
version_script = linker_args.items[i];
} else if (mem.eql(u8, arg, "-O")) { } else if (mem.eql(u8, arg, "-O")) {
i += 1; const opt = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { linker_optimization = std.fmt.parseUnsigned(u8, opt, 10) catch |err| {
fatal("expected linker arg after '{s}'", .{arg}); fatal("unable to parse optimization level '{s}': {s}", .{ opt, @errorName(err) });
}
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) });
}; };
} else if (mem.startsWith(u8, arg, "-O")) { } else if (mem.startsWith(u8, arg, "-O")) {
linker_optimization = std.fmt.parseUnsigned(u8, arg["-O".len..], 10) catch |err| { linker_optimization = std.fmt.parseUnsigned(u8, arg["-O".len..], 10) catch |err| {
fatal("unable to parse optimization level '{s}': {s}", .{ arg, @errorName(err) }); fatal("unable to parse optimization level '{s}': {s}", .{ arg, @errorName(err) });
}; };
} else if (mem.eql(u8, arg, "-pagezero_size")) { } else if (mem.eql(u8, arg, "-pagezero_size")) {
i += 1; const next_arg = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
const next_arg = linker_args.items[i];
pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { 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) }); fatal("unable to parse pagezero size '{s}': {s}", .{ next_arg, @errorName(err) });
}; };
} else if (mem.eql(u8, arg, "-headerpad")) { } else if (mem.eql(u8, arg, "-headerpad")) {
i += 1; const next_arg = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
const next_arg = linker_args.items[i];
headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| { 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) }); 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")) { } else if (mem.eql(u8, arg, "--print-map")) {
linker_print_map = true; linker_print_map = true;
} else if (mem.eql(u8, arg, "--sort-section")) { } else if (mem.eql(u8, arg, "--sort-section")) {
i += 1; const arg1 = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
const arg1 = linker_args.items[i];
linker_sort_section = std.meta.stringToEnum(link.SortSection, arg1) orelse { linker_sort_section = std.meta.stringToEnum(link.SortSection, arg1) orelse {
fatal("expected [name|alignment] after --sort-section, found '{s}'", .{arg1}); fatal("expected [name|alignment] after --sort-section, found '{s}'", .{arg1});
}; };
@ -1998,28 +1972,16 @@ fn buildOutputType(
} else if (mem.startsWith(u8, arg, "--export=")) { } else if (mem.startsWith(u8, arg, "--export=")) {
try linker_export_symbol_names.append(arg["--export=".len..]); try linker_export_symbol_names.append(arg["--export=".len..]);
} else if (mem.eql(u8, arg, "--export")) { } else if (mem.eql(u8, arg, "--export")) {
i += 1; try linker_export_symbol_names.append(linker_args_it.nextOrFatal());
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
try linker_export_symbol_names.append(linker_args.items[i]);
} else if (mem.eql(u8, arg, "--compress-debug-sections")) { } else if (mem.eql(u8, arg, "--compress-debug-sections")) {
i += 1; const arg1 = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
const arg1 = linker_args.items[i];
linker_compress_debug_sections = std.meta.stringToEnum(link.CompressDebugSections, arg1) orelse { linker_compress_debug_sections = std.meta.stringToEnum(link.CompressDebugSections, arg1) orelse {
fatal("expected [none|zlib] after --compress-debug-sections, found '{s}'", .{arg1}); fatal("expected [none|zlib] after --compress-debug-sections, found '{s}'", .{arg1});
}; };
} else if (mem.startsWith(u8, arg, "-z")) { } else if (mem.startsWith(u8, arg, "-z")) {
var z_arg = arg[2..]; var z_arg = arg[2..];
if (z_arg.len == 0) { if (z_arg.len == 0) {
i += 1; z_arg = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker extension flag after '{s}'", .{arg});
}
z_arg = linker_args.items[i];
} }
if (mem.eql(u8, z_arg, "nodelete")) { if (mem.eql(u8, z_arg, "nodelete")) {
linker_z_nodelete = true; linker_z_nodelete = true;
@ -2056,51 +2018,33 @@ fn buildOutputType(
fatal("unsupported linker extension flag: -z {s}", .{z_arg}); fatal("unsupported linker extension flag: -z {s}", .{z_arg});
} }
} else if (mem.eql(u8, arg, "--major-image-version")) { } else if (mem.eql(u8, arg, "--major-image-version")) {
i += 1; const major = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { version.major = std.fmt.parseUnsigned(u32, major, 10) catch |err| {
fatal("expected linker arg after '{s}'", .{arg}); fatal("unable to parse major image version '{s}': {s}", .{ major, @errorName(err) });
}
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) });
}; };
have_version = true; have_version = true;
} else if (mem.eql(u8, arg, "--minor-image-version")) { } else if (mem.eql(u8, arg, "--minor-image-version")) {
i += 1; const minor = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { version.minor = std.fmt.parseUnsigned(u32, minor, 10) catch |err| {
fatal("expected linker arg after '{s}'", .{arg}); fatal("unable to parse minor image version '{s}': {s}", .{ minor, @errorName(err) });
}
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) });
}; };
have_version = true; have_version = true;
} else if (mem.eql(u8, arg, "-e") or mem.eql(u8, arg, "--entry")) { } else if (mem.eql(u8, arg, "-e") or mem.eql(u8, arg, "--entry")) {
i += 1; entry = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { } else if (mem.eql(u8, arg, "-u")) {
fatal("expected linker arg after '{s}'", .{arg}); try force_undefined_symbols.put(gpa, linker_args_it.nextOrFatal(), {});
}
entry = linker_args.items[i];
} else if (mem.eql(u8, arg, "--stack") or mem.eql(u8, arg, "-stack_size")) { } else if (mem.eql(u8, arg, "--stack") or mem.eql(u8, arg, "-stack_size")) {
i += 1; const stack_size = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { stack_size_override = std.fmt.parseUnsigned(u64, stack_size, 0) catch |err| {
fatal("expected linker arg after '{s}'", .{arg}); fatal("unable to parse stack size override '{s}': {s}", .{ stack_size, @errorName(err) });
}
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) });
}; };
} else if (mem.eql(u8, arg, "--image-base")) { } else if (mem.eql(u8, arg, "--image-base")) {
i += 1; const image_base = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { image_base_override = std.fmt.parseUnsigned(u64, image_base, 0) catch |err| {
fatal("expected linker arg after '{s}'", .{arg}); fatal("unable to parse image base override '{s}': {s}", .{ image_base, @errorName(err) });
}
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) });
}; };
} else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) { } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
i += 1; linker_script = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
linker_script = linker_args.items[i];
} else if (mem.eql(u8, arg, "--eh-frame-hdr")) { } else if (mem.eql(u8, arg, "--eh-frame-hdr")) {
link_eh_frame_hdr = true; link_eh_frame_hdr = true;
} else if (mem.eql(u8, arg, "--no-eh-frame-hdr")) { } 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 } else if (mem.eql(u8, arg, "--major-os-version") or
mem.eql(u8, arg, "--minor-os-version")) 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. // This option does not do anything.
_ = linker_args_it.nextOrFatal();
} else if (mem.eql(u8, arg, "--major-subsystem-version")) { } else if (mem.eql(u8, arg, "--major-subsystem-version")) {
i += 1; const major = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
major_subsystem_version = std.fmt.parseUnsigned( major_subsystem_version = std.fmt.parseUnsigned(
u32, u32,
linker_args.items[i], major,
10, 10,
) catch |err| { ) 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")) { } else if (mem.eql(u8, arg, "--minor-subsystem-version")) {
i += 1; const minor = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
minor_subsystem_version = std.fmt.parseUnsigned( minor_subsystem_version = std.fmt.parseUnsigned(
u32, u32,
linker_args.items[i], minor,
10, 10,
) catch |err| { ) 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")) { } else if (mem.eql(u8, arg, "-framework")) {
i += 1; try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{});
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
try frameworks.put(gpa, linker_args.items[i], .{});
} else if (mem.eql(u8, arg, "-weak_framework")) { } else if (mem.eql(u8, arg, "-weak_framework")) {
i += 1; try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{ .weak = true });
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
try frameworks.put(gpa, linker_args.items[i], .{ .weak = true });
} else if (mem.eql(u8, arg, "-needed_framework")) { } else if (mem.eql(u8, arg, "-needed_framework")) {
i += 1; try frameworks.put(gpa, linker_args_it.nextOrFatal(), .{ .needed = true });
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
try frameworks.put(gpa, linker_args.items[i], .{ .needed = true });
} else if (mem.eql(u8, arg, "-needed_library")) { } else if (mem.eql(u8, arg, "-needed_library")) {
i += 1; try system_libs.put(linker_args_it.nextOrFatal(), .{ .needed = true });
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
try system_libs.put(linker_args.items[i], .{ .needed = true });
} else if (mem.startsWith(u8, arg, "-weak-l")) { } else if (mem.startsWith(u8, arg, "-weak-l")) {
try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); try system_libs.put(arg["-weak-l".len..], .{ .weak = true });
} else if (mem.eql(u8, arg, "-weak_library")) { } else if (mem.eql(u8, arg, "-weak_library")) {
i += 1; try system_libs.put(linker_args_it.nextOrFatal(), .{ .weak = true });
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
try system_libs.put(linker_args.items[i], .{ .weak = true });
} else if (mem.eql(u8, arg, "-compatibility_version")) { } else if (mem.eql(u8, arg, "-compatibility_version")) {
i += 1; const compat_version = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { compatibility_version = std.builtin.Version.parse(compat_version) catch |err| {
fatal("expected linker arg after '{s}'", .{arg}); fatal("unable to parse -compatibility_version '{s}': {s}", .{ compat_version, @errorName(err) });
}
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) });
}; };
} else if (mem.eql(u8, arg, "-current_version")) { } else if (mem.eql(u8, arg, "-current_version")) {
i += 1; const curr_version = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { version = std.builtin.Version.parse(curr_version) catch |err| {
fatal("expected linker arg after '{s}'", .{arg}); fatal("unable to parse -current_version '{s}': {s}", .{ curr_version, @errorName(err) });
}
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) });
}; };
have_version = true; have_version = true;
} else if (mem.eql(u8, arg, "--out-implib") or } else if (mem.eql(u8, arg, "--out-implib") or
mem.eql(u8, arg, "-implib")) mem.eql(u8, arg, "-implib"))
{ {
i += 1; emit_implib = .{ .yes = linker_args_it.nextOrFatal() };
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
emit_implib = .{ .yes = linker_args.items[i] };
emit_implib_arg_provided = true; emit_implib_arg_provided = true;
} else if (mem.eql(u8, arg, "-undefined")) { } else if (mem.eql(u8, arg, "-undefined")) {
i += 1; const lookup_type = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) { if (mem.eql(u8, "dynamic_lookup", lookup_type)) {
fatal("expected linker arg after '{s}'", .{arg});
}
if (mem.eql(u8, "dynamic_lookup", linker_args.items[i])) {
linker_allow_shlib_undefined = true; 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; linker_allow_shlib_undefined = false;
} else { } 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")) { } else if (mem.eql(u8, arg, "-install_name")) {
i += 1; install_name = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
install_name = linker_args.items[i];
} else if (mem.eql(u8, arg, "-force_load")) { } 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(.{ try link_objects.append(.{
.path = linker_args.items[i], .path = linker_args_it.nextOrFatal(),
.must_link = true, .must_link = true,
}); });
} else if (mem.eql(u8, arg, "-hash-style") or } else if (mem.eql(u8, arg, "-hash-style") or
mem.eql(u8, arg, "--hash-style")) mem.eql(u8, arg, "--hash-style"))
{ {
i += 1; const next_arg = linker_args_it.nextOrFatal();
if (i >= linker_args.items.len) {
fatal("expected linker arg after '{s}'", .{arg});
}
const next_arg = linker_args.items[i];
hash_style = std.meta.stringToEnum(link.HashStyle, next_arg) orelse { hash_style = std.meta.stringToEnum(link.HashStyle, next_arg) orelse {
fatal("expected [sysv|gnu|both] after --hash-style, found '{s}'", .{ fatal("expected [sysv|gnu|both] after --hash-style, found '{s}'", .{
next_arg, next_arg,
@ -3219,6 +3107,7 @@ fn buildOutputType(
.link_eh_frame_hdr = link_eh_frame_hdr, .link_eh_frame_hdr = link_eh_frame_hdr,
.link_emit_relocs = link_emit_relocs, .link_emit_relocs = link_emit_relocs,
.entry = entry, .entry = entry,
.force_undefined_symbols = force_undefined_symbols,
.stack_size_override = stack_size_override, .stack_size_override = stack_size_override,
.image_base_override = image_base_override, .image_base_override = image_base_override,
.strip = strip, .strip = strip,
@ -5295,6 +5184,7 @@ pub const ClangArgIterator = struct {
emit_llvm, emit_llvm,
sysroot, sysroot,
entry, entry,
force_undefined_symbol,
weak_library, weak_library,
weak_framework, weak_framework,
headerpad_max_install_names, headerpad_max_install_names,

View File

@ -31,6 +31,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
exe.linkLibrary(lib); exe.linkLibrary(lib);
exe.linkLibC(); exe.linkLibC();
exe.entry_symbol_name = "_bootstrap"; exe.entry_symbol_name = "_bootstrap";
exe.forceUndefinedSymbol("_my_main");
const check_exe = exe.checkObject(); const check_exe = exe.checkObject();
check_exe.checkStart("segname __TEXT"); check_exe.checkStart("segname __TEXT");

View File

@ -468,6 +468,10 @@ const known_options = [_]KnownOpt{
.name = "e", .name = "e",
.ident = "entry", .ident = "entry",
}, },
.{
.name = "u",
.ident = "force_undefined_symbol",
},
.{ .{
.name = "weak-l", .name = "weak-l",
.ident = "weak_library", .ident = "weak_library",