diff --git a/src/link.zig b/src/link.zig index e913efa77ae4c3e8113d07ff6cf81b79e098bdb8..6b2d2eecdf7f644ddaa94b71a701de983173fda5 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1819,11 +1819,14 @@ pub const UnresolvedInput = union(enum) { path_query: PathQuery, /// Strings that come from GNU ld scripts. Is it a filename? Is it a path? /// Who knows! Fuck around and find out. - ambiguous_name: NameQuery, + ambiguous_name: AmbiguousNameQuery, pub const NameQuery = struct { name: []const u8, query: Query, + // Corresponds to GNU ld `-l :path/to/filename`, meaning that `name` is a path relative to + // the library search path rather than just a library name. + name_done: bool, }; pub const PathQuery = struct { @@ -1831,6 +1834,11 @@ pub const UnresolvedInput = union(enum) { query: Query, }; + pub const AmbiguousNameQuery = struct { + name: []const u8, + query: Query, + }; + pub const Query = struct { needed: bool = false, weak: bool = false, @@ -1838,14 +1846,6 @@ pub const UnresolvedInput = union(enum) { must_link: bool = false, hidden: bool = false, allow_so_scripts: bool = false, - // Corresponds to GNU ld `-l :path/to/filename`: - // * No extra rpaths. - // * NEEDED entry should be exactly the string - // after the colon. No file system paths prepended. - // * The DSO still must be found at compile/link - // time and its entries used to resolve symbols. - // CGo compilation depends on this. - name_done: bool = false, preferred_mode: std.lang.LinkMode, search_strategy: SearchStrategy, @@ -1891,15 +1891,9 @@ pub const Input = union(enum) { needed: bool, weak: bool, reexport: bool, - /// Does not include any ":" prefix. Indicates that this string, exactly, should go into the NEEDED - /// entry, without affecting any rpaths. - exact_name: ?[]const u8, - }; + fallback_soname: FallbackSoname, - pub const DsoExact = struct { - /// Includes the ":" prefix. This is intended to be put into the DSO - /// section verbatim with no corresponding rpaths. - name: []const u8, + pub const FallbackSoname = enum { basename, full_path }; }; pub fn path(input: Input) Path { @@ -1950,7 +1944,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { man.hash.add(dso.needed); man.hash.add(dso.weak); man.hash.add(dso.reexport); - man.hash.addOptionalBytes(dso.exact_name); + man.hash.add(dso.fallback_soname); }, } } @@ -2001,7 +1995,7 @@ pub fn resolveInputs( .name_query => |name_query| { const query = name_query.query; - // Checked in the first pass above while looking for libc libraries. + // Checked in the first pass in `main.zig` while looking for libc libraries. assert(!fs.path.isAbsolute(name_query.name)); checked_paths.clearRetainingCapacity(); @@ -2229,7 +2223,7 @@ fn resolveLibInput( const lib_name = name_query.name; - if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.query.name_done) tbd: { + if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.name_done) tbd: { // Prefer .tbd over .dylib. const test_path: Path = .{ .root_dir = lib_directory, @@ -2241,13 +2235,22 @@ fn resolveLibInput( else => |e| fatal("searching for tbd library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); + return finishResolveLibInput( + io, + resolved_inputs, + archive_dedup, + test_path, + file, + link_mode, + name_query.query, + .basename, + ); } { const test_path: Path = .{ .root_dir = lib_directory, - .sub_path = if (name_query.query.name_done) lib_name else try std.fmt.allocPrint(arena, "{s}{s}{s}", .{ + .sub_path = if (name_query.name_done) lib_name else try std.fmt.allocPrint(arena, "{s}{s}{s}", .{ target.libPrefix(), lib_name, switch (link_mode) { @@ -2260,7 +2263,7 @@ fn resolveLibInput( switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{ .path = test_path, .query = name_query.query, - }, link_mode, color)) { + }, link_mode, color, .basename)) { .no_match => {}, .ok => return .ok, } @@ -2268,7 +2271,7 @@ fn resolveLibInput( // In the case of Darwin, the main check will be .dylib, so here we // additionally check for .so files. - if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.query.name_done) so: { + if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.name_done) so: { const test_path: Path = .{ .root_dir = lib_directory, .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}), @@ -2279,12 +2282,21 @@ fn resolveLibInput( else => |e| fatal("unable to search for so library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); + return finishResolveLibInput( + io, + resolved_inputs, + archive_dedup, + test_path, + file, + link_mode, + name_query.query, + .basename, + ); } // In the case of MinGW, the main check will be .lib but we also need to // look for `libfoo.a`. - if (target.isMinGW() and link_mode == .static and !name_query.query.name_done) mingw: { + if (target.isMinGW() and link_mode == .static and !name_query.name_done) mingw: { const test_path: Path = .{ .root_dir = lib_directory, .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}), @@ -2295,13 +2307,22 @@ fn resolveLibInput( else => |e| fatal("unable to search for static library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query); + return finishResolveLibInput( + io, + resolved_inputs, + archive_dedup, + test_path, + file, + link_mode, + name_query.query, + .basename, + ); } // In the case of OpenBSD, dynamic libraries are always versioned, without // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned // shared library, and this code is intended to match that upstream behavior. - if (target.isOpenBSDLibC() and link_mode == .dynamic and !name_query.query.name_done) versioned: { + if (target.isOpenBSDLibC() and link_mode == .dynamic and !name_query.name_done) versioned: { const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name}); var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) { @@ -2347,7 +2368,7 @@ fn resolveLibInput( switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{ .path = test_path, .query = name_query.query, - }, link_mode, color)) { + }, link_mode, color, .basename)) { .no_match => {}, .ok => return .ok, } @@ -2388,6 +2409,7 @@ fn finishResolveLibInput( file: Io.File, link_mode: std.lang.LinkMode, query: UnresolvedInput.Query, + fallback_soname: Input.Dso.FallbackSoname, ) ResolveLibInputResult { switch (link_mode) { .static => { @@ -2413,9 +2435,7 @@ fn finishResolveLibInput( .needed = query.needed, .weak = query.weak, .reexport = query.reexport, - // Here we assume that `Path.sub_path` is usable as `exact_name`, however the code after this point - // has no business making that assumption. - .exact_name = if (query.name_done) path.sub_path else null, + .fallback_soname = fallback_soname, }, }), } @@ -2439,8 +2459,34 @@ fn resolvePathInput( color: std.zig.Color, ) Allocator.Error!?ResolveLibInputResult { switch (Compilation.classifyFileExt(pq.path.sub_path)) { - .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .static, color), - .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color), + .static_library => return try resolvePathInputLib( + gpa, + arena, + io, + unresolved_inputs, + resolved_inputs, + ld_script_bytes, + archive_dedup, + target, + pq, + .static, + color, + .full_path, + ), + .shared_library => return try resolvePathInputLib( + gpa, + arena, + io, + unresolved_inputs, + resolved_inputs, + ld_script_bytes, + archive_dedup, + target, + pq, + .dynamic, + color, + .full_path, + ), .object => { var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err| fatal("failed to open object {f}: {t}", .{ pq.path, err }); @@ -2483,6 +2529,7 @@ fn resolvePathInputLib( pq: UnresolvedInput.PathQuery, link_mode: std.lang.LinkMode, color: std.zig.Color, + fallback_soname: Input.Dso.FallbackSoname, ) Allocator.Error!ResolveLibInputResult { try resolved_inputs.ensureUnusedCapacity(gpa, 1); try archive_dedup.ensureUnusedCapacity(gpa, 1); @@ -2510,7 +2557,16 @@ fn resolvePathInputLib( mem.startsWith(u8, buf, std.elf.ARMAG_THIN)) { // Appears to be an ELF or archive file. - return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query); + return finishResolveLibInput( + io, + resolved_inputs, + archive_dedup, + test_path, + file, + link_mode, + pq.query, + fallback_soname, + ); } const stat = file.stat(io) catch |err| fatal("failed to stat {f}: {t}", .{ test_path, err }); @@ -2561,6 +2617,7 @@ fn resolvePathInputLib( unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{ .name = try arena.dupe(u8, arg.path["-l".len..]), .query = query, + .name_done = false, } }); } else { unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{ @@ -2578,7 +2635,16 @@ fn resolvePathInputLib( else => |e| fatal("unable to search for {t} library {f}: {t}", .{ link_mode, test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query); + return finishResolveLibInput( + io, + resolved_inputs, + archive_dedup, + test_path, + file, + link_mode, + pq.query, + fallback_soname, + ); } pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { @@ -2601,7 +2667,7 @@ pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !In .needed = needed, .weak = weak, .reexport = reexport, - .exact_name = null, + .fallback_soname = .full_path, }; } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 4c9a91d25ebcac10fd8186f33b2e22d5ad6ed44f..b0e188390f24c3000d382aae4b76646fd6306644 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -735,7 +735,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { } else { try parseArchive(gpa, io, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj); }, - .dso => |dso| try parseDso(gpa, io, diags, dso, &self.shared_objects, &self.files, target), + .dso => |dso| try parseDso(gpa, comp.arena, io, diags, dso, &self.shared_objects, &self.files, target), } } @@ -1106,6 +1106,7 @@ fn parseArchive( fn parseDso( gpa: Allocator, + arena: Allocator, io: Io, diags: *Diags, dso: link.Input.Dso, @@ -1118,13 +1119,16 @@ fn parseDso( const handle = dso.file; - if (dso.exact_name != null) @panic("TODO"); - const stat: Stat = .init(try handle.stat(io)); var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target); defer header.deinit(gpa); - const soname = header.soname() orelse dso.path.basename(); + const fallback_soname: []const u8 = switch (dso.fallback_soname) { + .full_path => try dso.path.toString(arena), + .basename => fs.path.basename(dso.path.sub_path), + }; + + const soname = header.soname() orelse fallback_soname; const gop = try shared_objects.getOrPut(gpa, soname); if (gop.found_existing) return; @@ -1156,6 +1160,7 @@ fn parseDso( .symbols_extra = .empty, .symbols_resolver = .empty, .output_symtab_ctx = .{}, + .fallback_soname = fallback_soname, }, }); const so = fileLookup(files.*, index, null).?.shared_object; diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index 3030a45bff3806d93b1af418d6eaa49a7a649558..05d886e5fcaa177a8959003d0a0aa581f543f980 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -31,6 +31,8 @@ alive: bool, output_symtab_ctx: Elf.SymtabCtx, +fallback_soname: []const u8, + pub fn deinit(so: *SharedObject, gpa: Allocator) void { gpa.free(so.path.sub_path); so.parsed.deinit(gpa); @@ -427,7 +429,7 @@ pub fn asFile(self: *SharedObject) File { } pub fn soname(self: *SharedObject) []const u8 { - return self.parsed.soname() orelse self.path.basename(); + return self.parsed.soname() orelse self.fallback_soname; } pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void { diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 155f5aeb18a74f6a55543eeb8dd53fd25f240af9..53bd6d47970c35a93d6410298077767065de69a9 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -6228,17 +6228,9 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void }, .res => unreachable, .dso => |dso| { - if (dso.exact_name) |exact_name| { - // This means the path field is still valid, but the NEEDED entry must be set to - // exactly the `exact_name` string. - std.debug.panic("load dso_exact {q}", .{exact_name}); - if (elf.shndx.dynamic != .UNDEF) { - try elf.needed.put(elf.base.comp.gpa, try elf.string(.dynstr, exact_name), {}); - } - } try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1); var fr = dso.file.reader(io, &buf); - elf.loadDso(dso.path, &fr) catch |err| switch (err) { + elf.loadDso(dso.path, dso.fallback_soname, &fr) catch |err| switch (err) { else => |e| return e, error.EndOfStream => return diags.failParse(dso.path, "unexpected eof", .{}), error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail( @@ -6860,7 +6852,12 @@ fn populateArchiveMemberName(elf: *Elf, member_ar_hdr: *std.elf.ar_hdr, member_n @memcpy(dest_slice[0 .. dest_slice.len - 2], member_name); @memcpy(dest_slice[dest_slice.len - 2 ..], "/\n"); // yes, the terminator is weird } -fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void { +fn loadDso( + elf: *Elf, + path: std.Build.Cache.Path, + fallback_soname: link.Input.Dso.FallbackSoname, + fr: *Io.File.Reader, +) (LoadParseInputError || error{BadMagic})!void { const comp = elf.base.comp; const gpa = comp.gpa; const diags = &comp.link_diags; @@ -6956,7 +6953,10 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars } break std.mem.sliceTo(dynstr[@intCast(val)..], 0); } - } else std.fs.path.basename(path.sub_path); + } else switch (fallback_soname) { + .basename => std.fs.path.basename(path.sub_path), + .full_path => try path.toString(comp.arena), + }; try elf.needed.put(gpa, try elf.string(.dynstr, soname), {}); // Scan the symbol table and populate `elf.dso_globals`. diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 8ed1f43086fe355ddf5481287c4d1ca41f2c78a4..c56557dfb02336a36ab88ec32170b705a64d5e53 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1154,6 +1154,13 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { argv.appendAssumeCapacity("--as-needed"); var as_needed = true; + // When we have a DSO input, in order to trick LLD into putting the basename in its + // `DT_NEEDED` entry while still allowing us to tell it the exact path to the shared + // object, we pass it on the CLI as "-l:/absolute/path/to/libfoo.so". This will treat + // the given path not actually as an absolute path, but as relative to the library + // search path, so the root directory must therefore be the only library search path. + try argv.append("-L/"); + for (base.comp.link_inputs) |link_input| switch (link_input) { .res => unreachable, // Windows-only .object, .archive => continue, @@ -1171,24 +1178,16 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { }, } - if (dso.exact_name) |exact_name| { - // LLD does not have a way to specify full path to the DSO while also specifying - // the exact value for NEEDED, so we have to use a combination of -L and -l args. - assert(mem.endsWith(u8, dso.path.sub_path, exact_name)); - const truncated_path: Cache.Path = .{ - .root_dir = dso.path.root_dir, - .sub_path = dso.path.sub_path[0 .. dso.path.sub_path.len - exact_name.len], - }; - try argv.ensureUnusedCapacity(3); - argv.appendAssumeCapacity("-L"); - argv.appendAssumeCapacity(try truncated_path.toString(arena)); - argv.appendAssumeCapacity(try arena.print("-l:{s}", .{exact_name})); - } else { - // By this time, we depend on these libs being dynamically linked - // libraries and not static libraries (the check for that needs to be earlier), - // but they could be full paths to .so files, in which case we - // want to avoid prepending "-l". - argv.appendAssumeCapacity(try dso.path.toString(arena)); + // By this time, we depend on these libs being dynamically linked libraries and + // not static libraries (the check for that needs to be earlier), but they could + // be full file paths, in which case we don't want to use the "-l:" strategy. + switch (dso.fallback_soname) { + .basename => try argv.append(try arena.print("-l:{s}", .{try fs.path.resolve(arena, &.{ + comp.dirs.cwd, + dso.path.root_dir.path orelse ".", + dso.path.sub_path, + })})), + .full_path => try argv.append(try dso.path.toString(arena)), } }, }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 4df893a7e02e0942dd51897dd072c62646be25fb..a1baa45e2786cb4183ee5100fbdff11e0f76eb7b 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -655,11 +655,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { .object, .archive => |obj| try argv.append(try obj.path.toString(arena)), .res => |res| try argv.append(try res.path.toString(arena)), .dso => |dso| { - if (dso.exact_name) |exact_name| { - try argv.appendSlice(&.{ "-l", try arena.print(":{s}", .{exact_name}) }); - } else { - try argv.append(try dso.path.toString(arena)); - } + try argv.append(try dso.path.toString(arena)); }, }; @@ -789,11 +785,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { } else { argv.appendAssumeCapacity("-l"); } - if (dso.exact_name) |exact_name| { - argv.appendAssumeCapacity(try arena.print(":{s}", .{exact_name})); - } else { - argv.appendAssumeCapacity(try dso.path.toString(arena)); - } + argv.appendAssumeCapacity(try dso.path.toString(arena)); }, }; diff --git a/src/main.zig b/src/main.zig index 095a563ca2b605924aa3c41eeb9285fedf582897..e8f142f772868ef619333afa756be482b48808bb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1351,6 +1351,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.eql(u8, arg, "--needed-library") or mem.eql(u8, arg, "-needed-l") or @@ -1366,6 +1367,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) { try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ @@ -1377,6 +1379,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.eql(u8, arg, "-D")) { try cc_argv.appendSlice(arena, &.{ arg, args_iter.nextOrFatal() }); @@ -1839,6 +1842,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.cutPrefix(u8, arg, "-needed-l")) |name| { try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ @@ -1850,6 +1854,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.cutPrefix(u8, arg, "-weak-l")) |name| { try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ @@ -1861,6 +1866,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.startsWith(u8, arg, "-D")) { try cc_argv.append(arena, arg); @@ -2095,8 +2101,8 @@ fn buildOutputType( .preferred_mode = lib_preferred_mode, .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, - .name_done = true, }, + .name_done = true, } }); } else { const compiler_rt_classification = target_util.classifyCompilerRtLibName(it.only_arg); @@ -2122,6 +2128,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } } @@ -2552,6 +2559,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }), .weak_framework => try create_module.frameworks.put(arena, it.only_arg, .{ .weak = true }), .headerpad_max_install_names => headerpad_max_install_names = true, @@ -2921,6 +2929,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.cutPrefix(u8, arg, "-weak-l")) |rest| { try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ @@ -2932,6 +2941,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.eql(u8, arg, "-weak_library")) { try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ @@ -2943,6 +2953,7 @@ fn buildOutputType( .search_strategy = lib_search_strategy, .allow_so_scripts = allow_so_scripts, }, + .name_done = false, } }); } else if (mem.eql(u8, arg, "-compatibility_version")) { const compat_version = linker_args_it.nextOrFatal(); @@ -4183,6 +4194,10 @@ fn createModule( fatal("cannot use absolute path as a system library: {s}", .{lib_name}); } + if (!nq.name_done and std.mem.findScalar(u8, nq.name, '/') != null) { + fatal("cannot use path separator in system library name: {s}", .{lib_name}); + } + unresolved_link_inputs.appendAssumeCapacity(cli_link_input); any_name_queries_remaining = true; },