authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-21 21:03:55+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-21 13:08:49-07:00
log0a1843452e6d39d4a2adc9c7feefce274a5d30c6
tree7012d3b9006801793eed12f69dd0b2bbd8fc11aa
parentcf33f1003fd02abaf91078725d9c972dd51fc5e1

link: clean up and unify shared library input rules

For determining the `DT_NEEDED` value: * the `DT_SONAME` always takes precedence * otherwise: * `-l` arguments use the resolved path's basename * raw `/path/to/libfoo.so` arguments use the full path All ELF linkers, including LLD, now implement exactly those semantics. The operand to `-l` is not allowed to contain path separators, because the behavior of this in other linkers is confusing and likely not supported. (This is not the case for `-l :path/to/libfoo.so`.)

7 files changed, 161 insertions(+), 82 deletions(-)

src/link.zig+103-37
...@@ -1819,11 +1819,14 @@ pub const UnresolvedInput = union(enum) {...@@ -1819,11 +1819,14 @@ pub const UnresolvedInput = union(enum) {
1819 path_query: PathQuery,1819 path_query: PathQuery,
1820 /// Strings that come from GNU ld scripts. Is it a filename? Is it a path?1820 /// Strings that come from GNU ld scripts. Is it a filename? Is it a path?
1821 /// Who knows! Fuck around and find out.1821 /// Who knows! Fuck around and find out.
1822 ambiguous_name: NameQuery,1822 ambiguous_name: AmbiguousNameQuery,
18231823
1824 pub const NameQuery = struct {1824 pub const NameQuery = struct {
1825 name: []const u8,1825 name: []const u8,
1826 query: Query,1826 query: Query,
1827 // Corresponds to GNU ld `-l :path/to/filename`, meaning that `name` is a path relative to
1828 // the library search path rather than just a library name.
1829 name_done: bool,
1827 };1830 };
18281831
1829 pub const PathQuery = struct {1832 pub const PathQuery = struct {
...@@ -1831,6 +1834,11 @@ pub const UnresolvedInput = union(enum) {...@@ -1831,6 +1834,11 @@ pub const UnresolvedInput = union(enum) {
1831 query: Query,1834 query: Query,
1832 };1835 };
18331836
1837 pub const AmbiguousNameQuery = struct {
1838 name: []const u8,
1839 query: Query,
1840 };
1841
1834 pub const Query = struct {1842 pub const Query = struct {
1835 needed: bool = false,1843 needed: bool = false,
1836 weak: bool = false,1844 weak: bool = false,
...@@ -1838,14 +1846,6 @@ pub const UnresolvedInput = union(enum) {...@@ -1838,14 +1846,6 @@ pub const UnresolvedInput = union(enum) {
1838 must_link: bool = false,1846 must_link: bool = false,
1839 hidden: bool = false,1847 hidden: bool = false,
1840 allow_so_scripts: bool = false,1848 allow_so_scripts: bool = false,
1841 // Corresponds to GNU ld `-l :path/to/filename`:
1842 // * No extra rpaths.
1843 // * NEEDED entry should be exactly the string
1844 // after the colon. No file system paths prepended.
1845 // * The DSO still must be found at compile/link
1846 // time and its entries used to resolve symbols.
1847 // CGo compilation depends on this.
1848 name_done: bool = false,
1849 preferred_mode: std.lang.LinkMode,1849 preferred_mode: std.lang.LinkMode,
1850 search_strategy: SearchStrategy,1850 search_strategy: SearchStrategy,
18511851
...@@ -1891,15 +1891,9 @@ pub const Input = union(enum) {...@@ -1891,15 +1891,9 @@ pub const Input = union(enum) {
1891 needed: bool,1891 needed: bool,
1892 weak: bool,1892 weak: bool,
1893 reexport: bool,1893 reexport: bool,
1894 /// Does not include any ":" prefix. Indicates that this string, exactly, should go into the NEEDED1894 fallback_soname: FallbackSoname,
1895 /// entry, without affecting any rpaths.
1896 exact_name: ?[]const u8,
1897 };
18981895
1899 pub const DsoExact = struct {1896 pub const FallbackSoname = enum { basename, full_path };
1900 /// Includes the ":" prefix. This is intended to be put into the DSO
1901 /// section verbatim with no corresponding rpaths.
1902 name: []const u8,
1903 };1897 };
19041898
1905 pub fn path(input: Input) Path {1899 pub fn path(input: Input) Path {
...@@ -1950,7 +1944,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {...@@ -1950,7 +1944,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
1950 man.hash.add(dso.needed);1944 man.hash.add(dso.needed);
1951 man.hash.add(dso.weak);1945 man.hash.add(dso.weak);
1952 man.hash.add(dso.reexport);1946 man.hash.add(dso.reexport);
1953 man.hash.addOptionalBytes(dso.exact_name);1947 man.hash.add(dso.fallback_soname);
1954 },1948 },
1955 }1949 }
1956 }1950 }
...@@ -2001,7 +1995,7 @@ pub fn resolveInputs(...@@ -2001,7 +1995,7 @@ pub fn resolveInputs(
2001 .name_query => |name_query| {1995 .name_query => |name_query| {
2002 const query = name_query.query;1996 const query = name_query.query;
20031997
2004 // Checked in the first pass above while looking for libc libraries.1998 // Checked in the first pass in `main.zig` while looking for libc libraries.
2005 assert(!fs.path.isAbsolute(name_query.name));1999 assert(!fs.path.isAbsolute(name_query.name));
20062000
2007 checked_paths.clearRetainingCapacity();2001 checked_paths.clearRetainingCapacity();
...@@ -2229,7 +2223,7 @@ fn resolveLibInput(...@@ -2229,7 +2223,7 @@ fn resolveLibInput(
22292223
2230 const lib_name = name_query.name;2224 const lib_name = name_query.name;
22312225
2232 if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.query.name_done) tbd: {2226 if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.name_done) tbd: {
2233 // Prefer .tbd over .dylib.2227 // Prefer .tbd over .dylib.
2234 const test_path: Path = .{2228 const test_path: Path = .{
2235 .root_dir = lib_directory,2229 .root_dir = lib_directory,
...@@ -2241,13 +2235,22 @@ fn resolveLibInput(...@@ -2241,13 +2235,22 @@ fn resolveLibInput(
2241 else => |e| fatal("searching for tbd library {qf}: {t}", .{ test_path, e }),2235 else => |e| fatal("searching for tbd library {qf}: {t}", .{ test_path, e }),
2242 };2236 };
2243 errdefer file.close(io);2237 errdefer file.close(io);
2244 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);2238 return finishResolveLibInput(
2239 io,
2240 resolved_inputs,
2241 archive_dedup,
2242 test_path,
2243 file,
2244 link_mode,
2245 name_query.query,
2246 .basename,
2247 );
2245 }2248 }
22462249
2247 {2250 {
2248 const test_path: Path = .{2251 const test_path: Path = .{
2249 .root_dir = lib_directory,2252 .root_dir = lib_directory,
2250 .sub_path = if (name_query.query.name_done) lib_name else try std.fmt.allocPrint(arena, "{s}{s}{s}", .{2253 .sub_path = if (name_query.name_done) lib_name else try std.fmt.allocPrint(arena, "{s}{s}{s}", .{
2251 target.libPrefix(),2254 target.libPrefix(),
2252 lib_name,2255 lib_name,
2253 switch (link_mode) {2256 switch (link_mode) {
...@@ -2260,7 +2263,7 @@ fn resolveLibInput(...@@ -2260,7 +2263,7 @@ fn resolveLibInput(
2260 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{2263 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{
2261 .path = test_path,2264 .path = test_path,
2262 .query = name_query.query,2265 .query = name_query.query,
2263 }, link_mode, color)) {2266 }, link_mode, color, .basename)) {
2264 .no_match => {},2267 .no_match => {},
2265 .ok => return .ok,2268 .ok => return .ok,
2266 }2269 }
...@@ -2268,7 +2271,7 @@ fn resolveLibInput(...@@ -2268,7 +2271,7 @@ fn resolveLibInput(
22682271
2269 // In the case of Darwin, the main check will be .dylib, so here we2272 // In the case of Darwin, the main check will be .dylib, so here we
2270 // additionally check for .so files.2273 // additionally check for .so files.
2271 if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.query.name_done) so: {2274 if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.name_done) so: {
2272 const test_path: Path = .{2275 const test_path: Path = .{
2273 .root_dir = lib_directory,2276 .root_dir = lib_directory,
2274 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),2277 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),
...@@ -2279,12 +2282,21 @@ fn resolveLibInput(...@@ -2279,12 +2282,21 @@ fn resolveLibInput(
2279 else => |e| fatal("unable to search for so library {qf}: {t}", .{ test_path, e }),2282 else => |e| fatal("unable to search for so library {qf}: {t}", .{ test_path, e }),
2280 };2283 };
2281 errdefer file.close(io);2284 errdefer file.close(io);
2282 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);2285 return finishResolveLibInput(
2286 io,
2287 resolved_inputs,
2288 archive_dedup,
2289 test_path,
2290 file,
2291 link_mode,
2292 name_query.query,
2293 .basename,
2294 );
2283 }2295 }
22842296
2285 // In the case of MinGW, the main check will be .lib but we also need to2297 // In the case of MinGW, the main check will be .lib but we also need to
2286 // look for `libfoo.a`.2298 // look for `libfoo.a`.
2287 if (target.isMinGW() and link_mode == .static and !name_query.query.name_done) mingw: {2299 if (target.isMinGW() and link_mode == .static and !name_query.name_done) mingw: {
2288 const test_path: Path = .{2300 const test_path: Path = .{
2289 .root_dir = lib_directory,2301 .root_dir = lib_directory,
2290 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),2302 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),
...@@ -2295,13 +2307,22 @@ fn resolveLibInput(...@@ -2295,13 +2307,22 @@ fn resolveLibInput(
2295 else => |e| fatal("unable to search for static library {qf}: {t}", .{ test_path, e }),2307 else => |e| fatal("unable to search for static library {qf}: {t}", .{ test_path, e }),
2296 };2308 };
2297 errdefer file.close(io);2309 errdefer file.close(io);
2298 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);2310 return finishResolveLibInput(
2311 io,
2312 resolved_inputs,
2313 archive_dedup,
2314 test_path,
2315 file,
2316 link_mode,
2317 name_query.query,
2318 .basename,
2319 );
2299 }2320 }
23002321
2301 // In the case of OpenBSD, dynamic libraries are always versioned, without2322 // In the case of OpenBSD, dynamic libraries are always versioned, without
2302 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned2323 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned
2303 // shared library, and this code is intended to match that upstream behavior.2324 // shared library, and this code is intended to match that upstream behavior.
2304 if (target.isOpenBSDLibC() and link_mode == .dynamic and !name_query.query.name_done) versioned: {2325 if (target.isOpenBSDLibC() and link_mode == .dynamic and !name_query.name_done) versioned: {
2305 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name});2326 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name});
23062327
2307 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) {2328 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) {
...@@ -2347,7 +2368,7 @@ fn resolveLibInput(...@@ -2347,7 +2368,7 @@ fn resolveLibInput(
2347 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{2368 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{
2348 .path = test_path,2369 .path = test_path,
2349 .query = name_query.query,2370 .query = name_query.query,
2350 }, link_mode, color)) {2371 }, link_mode, color, .basename)) {
2351 .no_match => {},2372 .no_match => {},
2352 .ok => return .ok,2373 .ok => return .ok,
2353 }2374 }
...@@ -2388,6 +2409,7 @@ fn finishResolveLibInput(...@@ -2388,6 +2409,7 @@ fn finishResolveLibInput(
2388 file: Io.File,2409 file: Io.File,
2389 link_mode: std.lang.LinkMode,2410 link_mode: std.lang.LinkMode,
2390 query: UnresolvedInput.Query,2411 query: UnresolvedInput.Query,
2412 fallback_soname: Input.Dso.FallbackSoname,
2391) ResolveLibInputResult {2413) ResolveLibInputResult {
2392 switch (link_mode) {2414 switch (link_mode) {
2393 .static => {2415 .static => {
...@@ -2413,9 +2435,7 @@ fn finishResolveLibInput(...@@ -2413,9 +2435,7 @@ fn finishResolveLibInput(
2413 .needed = query.needed,2435 .needed = query.needed,
2414 .weak = query.weak,2436 .weak = query.weak,
2415 .reexport = query.reexport,2437 .reexport = query.reexport,
2416 // Here we assume that `Path.sub_path` is usable as `exact_name`, however the code after this point2438 .fallback_soname = fallback_soname,
2417 // has no business making that assumption.
2418 .exact_name = if (query.name_done) path.sub_path else null,
2419 },2439 },
2420 }),2440 }),
2421 }2441 }
...@@ -2439,8 +2459,34 @@ fn resolvePathInput(...@@ -2439,8 +2459,34 @@ fn resolvePathInput(
2439 color: std.zig.Color,2459 color: std.zig.Color,
2440) Allocator.Error!?ResolveLibInputResult {2460) Allocator.Error!?ResolveLibInputResult {
2441 switch (Compilation.classifyFileExt(pq.path.sub_path)) {2461 switch (Compilation.classifyFileExt(pq.path.sub_path)) {
2442 .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .static, color),2462 .static_library => return try resolvePathInputLib(
2443 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color),2463 gpa,
2464 arena,
2465 io,
2466 unresolved_inputs,
2467 resolved_inputs,
2468 ld_script_bytes,
2469 archive_dedup,
2470 target,
2471 pq,
2472 .static,
2473 color,
2474 .full_path,
2475 ),
2476 .shared_library => return try resolvePathInputLib(
2477 gpa,
2478 arena,
2479 io,
2480 unresolved_inputs,
2481 resolved_inputs,
2482 ld_script_bytes,
2483 archive_dedup,
2484 target,
2485 pq,
2486 .dynamic,
2487 color,
2488 .full_path,
2489 ),
2444 .object => {2490 .object => {
2445 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|2491 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|
2446 fatal("failed to open object {f}: {t}", .{ pq.path, err });2492 fatal("failed to open object {f}: {t}", .{ pq.path, err });
...@@ -2483,6 +2529,7 @@ fn resolvePathInputLib(...@@ -2483,6 +2529,7 @@ fn resolvePathInputLib(
2483 pq: UnresolvedInput.PathQuery,2529 pq: UnresolvedInput.PathQuery,
2484 link_mode: std.lang.LinkMode,2530 link_mode: std.lang.LinkMode,
2485 color: std.zig.Color,2531 color: std.zig.Color,
2532 fallback_soname: Input.Dso.FallbackSoname,
2486) Allocator.Error!ResolveLibInputResult {2533) Allocator.Error!ResolveLibInputResult {
2487 try resolved_inputs.ensureUnusedCapacity(gpa, 1);2534 try resolved_inputs.ensureUnusedCapacity(gpa, 1);
2488 try archive_dedup.ensureUnusedCapacity(gpa, 1);2535 try archive_dedup.ensureUnusedCapacity(gpa, 1);
...@@ -2510,7 +2557,16 @@ fn resolvePathInputLib(...@@ -2510,7 +2557,16 @@ fn resolvePathInputLib(
2510 mem.startsWith(u8, buf, std.elf.ARMAG_THIN))2557 mem.startsWith(u8, buf, std.elf.ARMAG_THIN))
2511 {2558 {
2512 // Appears to be an ELF or archive file.2559 // Appears to be an ELF or archive file.
2513 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query);2560 return finishResolveLibInput(
2561 io,
2562 resolved_inputs,
2563 archive_dedup,
2564 test_path,
2565 file,
2566 link_mode,
2567 pq.query,
2568 fallback_soname,
2569 );
2514 }2570 }
2515 const stat = file.stat(io) catch |err|2571 const stat = file.stat(io) catch |err|
2516 fatal("failed to stat {f}: {t}", .{ test_path, err });2572 fatal("failed to stat {f}: {t}", .{ test_path, err });
...@@ -2561,6 +2617,7 @@ fn resolvePathInputLib(...@@ -2561,6 +2617,7 @@ fn resolvePathInputLib(
2561 unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{2617 unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{
2562 .name = try arena.dupe(u8, arg.path["-l".len..]),2618 .name = try arena.dupe(u8, arg.path["-l".len..]),
2563 .query = query,2619 .query = query,
2620 .name_done = false,
2564 } });2621 } });
2565 } else {2622 } else {
2566 unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{2623 unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{
...@@ -2578,7 +2635,16 @@ fn resolvePathInputLib(...@@ -2578,7 +2635,16 @@ fn resolvePathInputLib(
2578 else => |e| fatal("unable to search for {t} library {f}: {t}", .{ link_mode, test_path, e }),2635 else => |e| fatal("unable to search for {t} library {f}: {t}", .{ link_mode, test_path, e }),
2579 };2636 };
2580 errdefer file.close(io);2637 errdefer file.close(io);
2581 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query);2638 return finishResolveLibInput(
2639 io,
2640 resolved_inputs,
2641 archive_dedup,
2642 test_path,
2643 file,
2644 link_mode,
2645 pq.query,
2646 fallback_soname,
2647 );
2582}2648}
25832649
2584pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object {2650pub 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...@@ -2601,7 +2667,7 @@ pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !In
2601 .needed = needed,2667 .needed = needed,
2602 .weak = weak,2668 .weak = weak,
2603 .reexport = reexport,2669 .reexport = reexport,
2604 .exact_name = null,2670 .fallback_soname = .full_path,
2605 };2671 };
2606}2672}
26072673
src/link/Elf.zig+9-4
...@@ -735,7 +735,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {...@@ -735,7 +735,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
735 } else {735 } else {
736 try parseArchive(gpa, io, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj);736 try parseArchive(gpa, io, diags, &self.file_handles, &self.files, target, debug_fmt_strip, default_sym_version, &self.objects, obj);
737 },737 },
738 .dso => |dso| try parseDso(gpa, io, diags, dso, &self.shared_objects, &self.files, target),738 .dso => |dso| try parseDso(gpa, comp.arena, io, diags, dso, &self.shared_objects, &self.files, target),
739 }739 }
740}740}
741741
...@@ -1106,6 +1106,7 @@ fn parseArchive(...@@ -1106,6 +1106,7 @@ fn parseArchive(
11061106
1107fn parseDso(1107fn parseDso(
1108 gpa: Allocator,1108 gpa: Allocator,
1109 arena: Allocator,
1109 io: Io,1110 io: Io,
1110 diags: *Diags,1111 diags: *Diags,
1111 dso: link.Input.Dso,1112 dso: link.Input.Dso,
...@@ -1118,13 +1119,16 @@ fn parseDso(...@@ -1118,13 +1119,16 @@ fn parseDso(
11181119
1119 const handle = dso.file;1120 const handle = dso.file;
11201121
1121 if (dso.exact_name != null) @panic("TODO");
1122
1123 const stat: Stat = .init(try handle.stat(io));1122 const stat: Stat = .init(try handle.stat(io));
1124 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);1123 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);
1125 defer header.deinit(gpa);1124 defer header.deinit(gpa);
11261125
1127 const soname = header.soname() orelse dso.path.basename();1126 const fallback_soname: []const u8 = switch (dso.fallback_soname) {
1127 .full_path => try dso.path.toString(arena),
1128 .basename => fs.path.basename(dso.path.sub_path),
1129 };
1130
1131 const soname = header.soname() orelse fallback_soname;
11281132
1129 const gop = try shared_objects.getOrPut(gpa, soname);1133 const gop = try shared_objects.getOrPut(gpa, soname);
1130 if (gop.found_existing) return;1134 if (gop.found_existing) return;
...@@ -1156,6 +1160,7 @@ fn parseDso(...@@ -1156,6 +1160,7 @@ fn parseDso(
1156 .symbols_extra = .empty,1160 .symbols_extra = .empty,
1157 .symbols_resolver = .empty,1161 .symbols_resolver = .empty,
1158 .output_symtab_ctx = .{},1162 .output_symtab_ctx = .{},
1163 .fallback_soname = fallback_soname,
1159 },1164 },
1160 });1165 });
1161 const so = fileLookup(files.*, index, null).?.shared_object;1166 const so = fileLookup(files.*, index, null).?.shared_object;
src/link/Elf/SharedObject.zig+3-1
...@@ -31,6 +31,8 @@ alive: bool,...@@ -31,6 +31,8 @@ alive: bool,
3131
32output_symtab_ctx: Elf.SymtabCtx,32output_symtab_ctx: Elf.SymtabCtx,
3333
34fallback_soname: []const u8,
35
34pub fn deinit(so: *SharedObject, gpa: Allocator) void {36pub fn deinit(so: *SharedObject, gpa: Allocator) void {
35 gpa.free(so.path.sub_path);37 gpa.free(so.path.sub_path);
36 so.parsed.deinit(gpa);38 so.parsed.deinit(gpa);
...@@ -427,7 +429,7 @@ pub fn asFile(self: *SharedObject) File {...@@ -427,7 +429,7 @@ pub fn asFile(self: *SharedObject) File {
427}429}
428430
429pub fn soname(self: *SharedObject) []const u8 {431pub fn soname(self: *SharedObject) []const u8 {
430 return self.parsed.soname() orelse self.path.basename();432 return self.parsed.soname() orelse self.fallback_soname;
431}433}
432434
433pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void {435pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void {
src/link/Elf2.zig+11-11
...@@ -6228,17 +6228,9 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void...@@ -6228,17 +6228,9 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void
6228 },6228 },
6229 .res => unreachable,6229 .res => unreachable,
6230 .dso => |dso| {6230 .dso => |dso| {
6231 if (dso.exact_name) |exact_name| {
6232 // This means the path field is still valid, but the NEEDED entry must be set to
6233 // exactly the `exact_name` string.
6234 std.debug.panic("load dso_exact {q}", .{exact_name});
6235 if (elf.shndx.dynamic != .UNDEF) {
6236 try elf.needed.put(elf.base.comp.gpa, try elf.string(.dynstr, exact_name), {});
6237 }
6238 }
6239 try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1);6231 try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1);
6240 var fr = dso.file.reader(io, &buf);6232 var fr = dso.file.reader(io, &buf);
6241 elf.loadDso(dso.path, &fr) catch |err| switch (err) {6233 elf.loadDso(dso.path, dso.fallback_soname, &fr) catch |err| switch (err) {
6242 else => |e| return e,6234 else => |e| return e,
6243 error.EndOfStream => return diags.failParse(dso.path, "unexpected eof", .{}),6235 error.EndOfStream => return diags.failParse(dso.path, "unexpected eof", .{}),
6244 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(6236 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...@@ -6860,7 +6852,12 @@ fn populateArchiveMemberName(elf: *Elf, member_ar_hdr: *std.elf.ar_hdr, member_n
6860 @memcpy(dest_slice[0 .. dest_slice.len - 2], member_name);6852 @memcpy(dest_slice[0 .. dest_slice.len - 2], member_name);
6861 @memcpy(dest_slice[dest_slice.len - 2 ..], "/\n"); // yes, the terminator is weird6853 @memcpy(dest_slice[dest_slice.len - 2 ..], "/\n"); // yes, the terminator is weird
6862}6854}
6863fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {6855fn loadDso(
6856 elf: *Elf,
6857 path: std.Build.Cache.Path,
6858 fallback_soname: link.Input.Dso.FallbackSoname,
6859 fr: *Io.File.Reader,
6860) (LoadParseInputError || error{BadMagic})!void {
6864 const comp = elf.base.comp;6861 const comp = elf.base.comp;
6865 const gpa = comp.gpa;6862 const gpa = comp.gpa;
6866 const diags = &comp.link_diags;6863 const diags = &comp.link_diags;
...@@ -6956,7 +6953,10 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars...@@ -6956,7 +6953,10 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars
6956 }6953 }
6957 break std.mem.sliceTo(dynstr[@intCast(val)..], 0);6954 break std.mem.sliceTo(dynstr[@intCast(val)..], 0);
6958 }6955 }
6959 } else std.fs.path.basename(path.sub_path);6956 } else switch (fallback_soname) {
6957 .basename => std.fs.path.basename(path.sub_path),
6958 .full_path => try path.toString(comp.arena),
6959 };
6960 try elf.needed.put(gpa, try elf.string(.dynstr, soname), {});6960 try elf.needed.put(gpa, try elf.string(.dynstr, soname), {});
69616961
6962 // Scan the symbol table and populate `elf.dso_globals`.6962 // Scan the symbol table and populate `elf.dso_globals`.
src/link/Lld.zig+17-18
...@@ -1154,6 +1154,13 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {...@@ -1154,6 +1154,13 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
1154 argv.appendAssumeCapacity("--as-needed");1154 argv.appendAssumeCapacity("--as-needed");
1155 var as_needed = true;1155 var as_needed = true;
11561156
1157 // When we have a DSO input, in order to trick LLD into putting the basename in its
1158 // `DT_NEEDED` entry while still allowing us to tell it the exact path to the shared
1159 // object, we pass it on the CLI as "-l:/absolute/path/to/libfoo.so". This will treat
1160 // the given path not actually as an absolute path, but as relative to the library
1161 // search path, so the root directory must therefore be the only library search path.
1162 try argv.append("-L/");
1163
1157 for (base.comp.link_inputs) |link_input| switch (link_input) {1164 for (base.comp.link_inputs) |link_input| switch (link_input) {
1158 .res => unreachable, // Windows-only1165 .res => unreachable, // Windows-only
1159 .object, .archive => continue,1166 .object, .archive => continue,
...@@ -1171,24 +1178,16 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {...@@ -1171,24 +1178,16 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
1171 },1178 },
1172 }1179 }
11731180
1174 if (dso.exact_name) |exact_name| {1181 // By this time, we depend on these libs being dynamically linked libraries and
1175 // LLD does not have a way to specify full path to the DSO while also specifying1182 // not static libraries (the check for that needs to be earlier), but they could
1176 // the exact value for NEEDED, so we have to use a combination of -L and -l args.1183 // be full file paths, in which case we don't want to use the "-l:" strategy.
1177 assert(mem.endsWith(u8, dso.path.sub_path, exact_name));1184 switch (dso.fallback_soname) {
1178 const truncated_path: Cache.Path = .{1185 .basename => try argv.append(try arena.print("-l:{s}", .{try fs.path.resolve(arena, &.{
1179 .root_dir = dso.path.root_dir,1186 comp.dirs.cwd,
1180 .sub_path = dso.path.sub_path[0 .. dso.path.sub_path.len - exact_name.len],1187 dso.path.root_dir.path orelse ".",
1181 };1188 dso.path.sub_path,
1182 try argv.ensureUnusedCapacity(3);1189 })})),
1183 argv.appendAssumeCapacity("-L");1190 .full_path => try argv.append(try dso.path.toString(arena)),
1184 argv.appendAssumeCapacity(try truncated_path.toString(arena));
1185 argv.appendAssumeCapacity(try arena.print("-l:{s}", .{exact_name}));
1186 } else {
1187 // By this time, we depend on these libs being dynamically linked
1188 // libraries and not static libraries (the check for that needs to be earlier),
1189 // but they could be full paths to .so files, in which case we
1190 // want to avoid prepending "-l".
1191 argv.appendAssumeCapacity(try dso.path.toString(arena));
1192 }1191 }
1193 },1192 },
1194 };1193 };
src/link/MachO.zig+2-10
...@@ -655,11 +655,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {...@@ -655,11 +655,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
655 .object, .archive => |obj| try argv.append(try obj.path.toString(arena)),655 .object, .archive => |obj| try argv.append(try obj.path.toString(arena)),
656 .res => |res| try argv.append(try res.path.toString(arena)),656 .res => |res| try argv.append(try res.path.toString(arena)),
657 .dso => |dso| {657 .dso => |dso| {
658 if (dso.exact_name) |exact_name| {658 try argv.append(try dso.path.toString(arena));
659 try argv.appendSlice(&.{ "-l", try arena.print(":{s}", .{exact_name}) });
660 } else {
661 try argv.append(try dso.path.toString(arena));
662 }
663 },659 },
664 };660 };
665661
...@@ -789,11 +785,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {...@@ -789,11 +785,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
789 } else {785 } else {
790 argv.appendAssumeCapacity("-l");786 argv.appendAssumeCapacity("-l");
791 }787 }
792 if (dso.exact_name) |exact_name| {788 argv.appendAssumeCapacity(try dso.path.toString(arena));
793 argv.appendAssumeCapacity(try arena.print(":{s}", .{exact_name}));
794 } else {
795 argv.appendAssumeCapacity(try dso.path.toString(arena));
796 }
797 },789 },
798 };790 };
799791
src/main.zig+16-1
...@@ -1351,6 +1351,7 @@ fn buildOutputType(...@@ -1351,6 +1351,7 @@ fn buildOutputType(
1351 .search_strategy = lib_search_strategy,1351 .search_strategy = lib_search_strategy,
1352 .allow_so_scripts = allow_so_scripts,1352 .allow_so_scripts = allow_so_scripts,
1353 },1353 },
1354 .name_done = false,
1354 } });1355 } });
1355 } else if (mem.eql(u8, arg, "--needed-library") or1356 } else if (mem.eql(u8, arg, "--needed-library") or
1356 mem.eql(u8, arg, "-needed-l") or1357 mem.eql(u8, arg, "-needed-l") or
...@@ -1366,6 +1367,7 @@ fn buildOutputType(...@@ -1366,6 +1367,7 @@ fn buildOutputType(
1366 .search_strategy = lib_search_strategy,1367 .search_strategy = lib_search_strategy,
1367 .allow_so_scripts = allow_so_scripts,1368 .allow_so_scripts = allow_so_scripts,
1368 },1369 },
1370 .name_done = false,
1369 } });1371 } });
1370 } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) {1372 } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) {
1371 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{1373 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{
...@@ -1377,6 +1379,7 @@ fn buildOutputType(...@@ -1377,6 +1379,7 @@ fn buildOutputType(
1377 .search_strategy = lib_search_strategy,1379 .search_strategy = lib_search_strategy,
1378 .allow_so_scripts = allow_so_scripts,1380 .allow_so_scripts = allow_so_scripts,
1379 },1381 },
1382 .name_done = false,
1380 } });1383 } });
1381 } else if (mem.eql(u8, arg, "-D")) {1384 } else if (mem.eql(u8, arg, "-D")) {
1382 try cc_argv.appendSlice(arena, &.{ arg, args_iter.nextOrFatal() });1385 try cc_argv.appendSlice(arena, &.{ arg, args_iter.nextOrFatal() });
...@@ -1839,6 +1842,7 @@ fn buildOutputType(...@@ -1839,6 +1842,7 @@ fn buildOutputType(
1839 .search_strategy = lib_search_strategy,1842 .search_strategy = lib_search_strategy,
1840 .allow_so_scripts = allow_so_scripts,1843 .allow_so_scripts = allow_so_scripts,
1841 },1844 },
1845 .name_done = false,
1842 } });1846 } });
1843 } else if (mem.cutPrefix(u8, arg, "-needed-l")) |name| {1847 } else if (mem.cutPrefix(u8, arg, "-needed-l")) |name| {
1844 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{1848 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{
...@@ -1850,6 +1854,7 @@ fn buildOutputType(...@@ -1850,6 +1854,7 @@ fn buildOutputType(
1850 .search_strategy = lib_search_strategy,1854 .search_strategy = lib_search_strategy,
1851 .allow_so_scripts = allow_so_scripts,1855 .allow_so_scripts = allow_so_scripts,
1852 },1856 },
1857 .name_done = false,
1853 } });1858 } });
1854 } else if (mem.cutPrefix(u8, arg, "-weak-l")) |name| {1859 } else if (mem.cutPrefix(u8, arg, "-weak-l")) |name| {
1855 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{1860 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{
...@@ -1861,6 +1866,7 @@ fn buildOutputType(...@@ -1861,6 +1866,7 @@ fn buildOutputType(
1861 .search_strategy = lib_search_strategy,1866 .search_strategy = lib_search_strategy,
1862 .allow_so_scripts = allow_so_scripts,1867 .allow_so_scripts = allow_so_scripts,
1863 },1868 },
1869 .name_done = false,
1864 } });1870 } });
1865 } else if (mem.startsWith(u8, arg, "-D")) {1871 } else if (mem.startsWith(u8, arg, "-D")) {
1866 try cc_argv.append(arena, arg);1872 try cc_argv.append(arena, arg);
...@@ -2095,8 +2101,8 @@ fn buildOutputType(...@@ -2095,8 +2101,8 @@ fn buildOutputType(
2095 .preferred_mode = lib_preferred_mode,2101 .preferred_mode = lib_preferred_mode,
2096 .search_strategy = lib_search_strategy,2102 .search_strategy = lib_search_strategy,
2097 .allow_so_scripts = allow_so_scripts,2103 .allow_so_scripts = allow_so_scripts,
2098 .name_done = true,
2099 },2104 },
2105 .name_done = true,
2100 } });2106 } });
2101 } else {2107 } else {
2102 const compiler_rt_classification = target_util.classifyCompilerRtLibName(it.only_arg);2108 const compiler_rt_classification = target_util.classifyCompilerRtLibName(it.only_arg);
...@@ -2122,6 +2128,7 @@ fn buildOutputType(...@@ -2122,6 +2128,7 @@ fn buildOutputType(
2122 .search_strategy = lib_search_strategy,2128 .search_strategy = lib_search_strategy,
2123 .allow_so_scripts = allow_so_scripts,2129 .allow_so_scripts = allow_so_scripts,
2124 },2130 },
2131 .name_done = false,
2125 } });2132 } });
2126 }2133 }
2127 }2134 }
...@@ -2552,6 +2559,7 @@ fn buildOutputType(...@@ -2552,6 +2559,7 @@ fn buildOutputType(
2552 .search_strategy = lib_search_strategy,2559 .search_strategy = lib_search_strategy,
2553 .allow_so_scripts = allow_so_scripts,2560 .allow_so_scripts = allow_so_scripts,
2554 },2561 },
2562 .name_done = false,
2555 } }),2563 } }),
2556 .weak_framework => try create_module.frameworks.put(arena, it.only_arg, .{ .weak = true }),2564 .weak_framework => try create_module.frameworks.put(arena, it.only_arg, .{ .weak = true }),
2557 .headerpad_max_install_names => headerpad_max_install_names = true,2565 .headerpad_max_install_names => headerpad_max_install_names = true,
...@@ -2921,6 +2929,7 @@ fn buildOutputType(...@@ -2921,6 +2929,7 @@ fn buildOutputType(
2921 .search_strategy = lib_search_strategy,2929 .search_strategy = lib_search_strategy,
2922 .allow_so_scripts = allow_so_scripts,2930 .allow_so_scripts = allow_so_scripts,
2923 },2931 },
2932 .name_done = false,
2924 } });2933 } });
2925 } else if (mem.cutPrefix(u8, arg, "-weak-l")) |rest| {2934 } else if (mem.cutPrefix(u8, arg, "-weak-l")) |rest| {
2926 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{2935 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{
...@@ -2932,6 +2941,7 @@ fn buildOutputType(...@@ -2932,6 +2941,7 @@ fn buildOutputType(
2932 .search_strategy = lib_search_strategy,2941 .search_strategy = lib_search_strategy,
2933 .allow_so_scripts = allow_so_scripts,2942 .allow_so_scripts = allow_so_scripts,
2934 },2943 },
2944 .name_done = false,
2935 } });2945 } });
2936 } else if (mem.eql(u8, arg, "-weak_library")) {2946 } else if (mem.eql(u8, arg, "-weak_library")) {
2937 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{2947 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{
...@@ -2943,6 +2953,7 @@ fn buildOutputType(...@@ -2943,6 +2953,7 @@ fn buildOutputType(
2943 .search_strategy = lib_search_strategy,2953 .search_strategy = lib_search_strategy,
2944 .allow_so_scripts = allow_so_scripts,2954 .allow_so_scripts = allow_so_scripts,
2945 },2955 },
2956 .name_done = false,
2946 } });2957 } });
2947 } else if (mem.eql(u8, arg, "-compatibility_version")) {2958 } else if (mem.eql(u8, arg, "-compatibility_version")) {
2948 const compat_version = linker_args_it.nextOrFatal();2959 const compat_version = linker_args_it.nextOrFatal();
...@@ -4183,6 +4194,10 @@ fn createModule(...@@ -4183,6 +4194,10 @@ fn createModule(
4183 fatal("cannot use absolute path as a system library: {s}", .{lib_name});4194 fatal("cannot use absolute path as a system library: {s}", .{lib_name});
4184 }4195 }
41854196
4197 if (!nq.name_done and std.mem.findScalar(u8, nq.name, '/') != null) {
4198 fatal("cannot use path separator in system library name: {s}", .{lib_name});
4199 }
4200
4186 unresolved_link_inputs.appendAssumeCapacity(cli_link_input);4201 unresolved_link_inputs.appendAssumeCapacity(cli_link_input);
4187 any_name_queries_remaining = true;4202 any_name_queries_remaining = true;
4188 },4203 },