authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-10-14 22:24:46-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-10-23 16:27:38-07:00
log5b016e290a5ba335b295afeae104af6b3396a425
tree370ce44e11911967fefd986c42c53c12c22f320b
parenta4cc344aa0947f5b0d0e1a872e6d003b8e580976

move ld script processing to the frontend

along with the relevant logic, making the libraries within subject to the same search criteria as all the other libraries. this unfortunately means doing file system access on all .so files when targeting ELF to determine if they are linker scripts, however, I have a plan to address this.

6 files changed, 328 insertions(+), 297 deletions(-)

src/Compilation.zig+10-16
......@@ -1003,10 +1003,11 @@ pub const LinkObject = struct {
10031003 path: Path,
10041004 must_link: bool = false,
10051005 needed: bool = false,
1006 // When the library is passed via a positional argument, it will be
1007 // added as a full path. If it's `-l<lib>`, then just the basename.
1008 //
1009 // Consistent with `withLOption` variable name in lld ELF driver.
1006 weak: bool = false,
1007 /// When the library is passed via a positional argument, it will be
1008 /// added as a full path. If it's `-l<lib>`, then just the basename.
1009 ///
1010 /// Consistent with `withLOption` variable name in lld ELF driver.
10101011 loption: bool = false,
10111012
10121013 pub fn isObject(lo: LinkObject) bool {
......@@ -1061,6 +1062,9 @@ pub const CreateOptions = struct {
10611062 /// this flag would be set to disable this machinery to avoid false positives.
10621063 disable_lld_caching: bool = false,
10631064 cache_mode: CacheMode = .incremental,
1065 /// This field is intended to be removed.
1066 /// The ELF implementation no longer uses this data, however the MachO and COFF
1067 /// implementations still do.
10641068 lib_dirs: []const []const u8 = &[0][]const u8{},
10651069 rpath_list: []const []const u8 = &[0][]const u8{},
10661070 symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .empty,
......@@ -2563,6 +2567,7 @@ fn addNonIncrementalStuffToCacheManifest(
25632567 _ = try man.addFilePath(obj.path, null);
25642568 man.hash.add(obj.must_link);
25652569 man.hash.add(obj.needed);
2570 man.hash.add(obj.weak);
25662571 man.hash.add(obj.loption);
25672572 }
25682573
......@@ -3219,18 +3224,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
32193224 }));
32203225 }
32213226
3222 for (comp.link_diags.msgs.items) |link_err| {
3223 try bundle.addRootErrorMessage(.{
3224 .msg = try bundle.addString(link_err.msg),
3225 .notes_len = @intCast(link_err.notes.len),
3226 });
3227 const notes_start = try bundle.reserveNotes(@intCast(link_err.notes.len));
3228 for (link_err.notes, 0..) |note, i| {
3229 bundle.extra.items[notes_start + i] = @intFromEnum(try bundle.addErrorMessage(.{
3230 .msg = try bundle.addString(note.msg),
3231 }));
3232 }
3233 }
3227 try comp.link_diags.addMessagesToBundle(&bundle);
32343228
32353229 if (comp.zcu) |zcu| {
32363230 if (bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) {
src/link.zig+17
......@@ -24,6 +24,8 @@ const lldMain = @import("main.zig").lldMain;
2424const Package = @import("Package.zig");
2525const dev = @import("dev.zig");
2626
27pub const LdScript = @import("link/LdScript.zig");
28
2729/// When adding a new field, remember to update `hashAddSystemLibs`.
2830/// These are *always* dynamically linked. Static libraries will be
2931/// provided as positional arguments.
......@@ -336,6 +338,21 @@ pub const Diags = struct {
336338 log.debug("memory allocation failure", .{});
337339 diags.flags.alloc_failure_occurred = true;
338340 }
341
342 pub fn addMessagesToBundle(diags: *const Diags, bundle: *std.zig.ErrorBundle.Wip) Allocator.Error!void {
343 for (diags.msgs.items) |link_err| {
344 try bundle.addRootErrorMessage(.{
345 .msg = try bundle.addString(link_err.msg),
346 .notes_len = @intCast(link_err.notes.len),
347 });
348 const notes_start = try bundle.reserveNotes(@intCast(link_err.notes.len));
349 for (link_err.notes, 0..) |note, i| {
350 bundle.extra.items[notes_start + i] = @intFromEnum(try bundle.addErrorMessage(.{
351 .msg = try bundle.addString(note.msg),
352 }));
353 }
354 }
355 }
339356};
340357
341358pub fn hashAddSystemLibs(
src/link/Elf.zig+14-151
......@@ -1,5 +1,4 @@
11pub const Atom = @import("Elf/Atom.zig");
2pub const LdScript = @import("LdScript.zig");
32
43base: link.File,
54rpath_table: std.StringArrayHashMapUnmanaged(void),
......@@ -16,7 +15,6 @@ z_relro: bool,
1615z_common_page_size: ?u64,
1716/// TODO make this non optional and resolve the default in open()
1817z_max_page_size: ?u64,
19lib_dirs: []const []const u8,
2018hash_style: HashStyle,
2119compress_debug_sections: CompressDebugSections,
2220symbol_wrap_set: std.StringArrayHashMapUnmanaged(void),
......@@ -329,7 +327,6 @@ pub fn createEmpty(
329327 .z_relro = options.z_relro,
330328 .z_common_page_size = options.z_common_page_size,
331329 .z_max_page_size = options.z_max_page_size,
332 .lib_dirs = options.lib_dirs,
333330 .hash_style = options.hash_style,
334331 .compress_debug_sections = options.compress_debug_sections,
335332 .symbol_wrap_set = options.symbol_wrap_set,
......@@ -845,30 +842,17 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
845842 if (comp.libc_installation) |lc| {
846843 const flags = target_util.libcFullLinkFlags(target);
847844
848 var test_path = std.ArrayList(u8).init(arena);
849 var checked_paths = std.ArrayList([]const u8).init(arena);
850
851845 for (flags) |flag| {
852 checked_paths.clearRetainingCapacity();
846 assert(mem.startsWith(u8, flag, "-l"));
853847 const lib_name = flag["-l".len..];
854
855 success: {
856 if (!self.base.isStatic()) {
857 if (try self.accessLibPath(arena, &test_path, &checked_paths, lc.crt_dir.?, lib_name, .dynamic))
858 break :success;
859 }
860 if (try self.accessLibPath(arena, &test_path, &checked_paths, lc.crt_dir.?, lib_name, .static))
861 break :success;
862
863 diags.addMissingLibraryError(
864 checked_paths.items,
865 "missing system library: '{s}' was not found",
866 .{lib_name},
867 );
868 continue;
869 }
870
871 const resolved_path = Path.initCwd(try arena.dupe(u8, test_path.items));
848 const suffix = switch (comp.config.link_mode) {
849 .static => target.staticLibSuffix(),
850 .dynamic => target.dynamicLibSuffix(),
851 };
852 const lib_path = try std.fmt.allocPrint(arena, "{s}/lib{s}{s}", .{
853 lc.crt_dir.?, lib_name, suffix,
854 });
855 const resolved_path = Path.initCwd(lib_path);
872856 parseInputReportingFailure(self, resolved_path, false, false);
873857 }
874858 } else if (target.isGnuLibC()) {
......@@ -1194,11 +1178,6 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void {
11941178 if (csu.crti) |path| try argv.append(try path.toString(arena));
11951179 if (csu.crtbegin) |path| try argv.append(try path.toString(arena));
11961180
1197 for (self.lib_dirs) |lib_dir| {
1198 try argv.append("-L");
1199 try argv.append(lib_dir);
1200 }
1201
12021181 if (comp.config.link_libc) {
12031182 if (self.base.comp.libc_installation) |libc_installation| {
12041183 try argv.append("-L");
......@@ -1340,7 +1319,7 @@ pub const ParseError = error{
13401319 NotSupported,
13411320 InvalidCharacter,
13421321 UnknownFileType,
1343} || LdScript.Error || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError;
1322} || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError;
13441323
13451324fn parseCrtFileReportingFailure(self: *Elf, crt_file: Compilation.CrtFile) void {
13461325 parseInputReportingFailure(self, crt_file.full_object_path, false, false);
......@@ -1358,23 +1337,12 @@ pub fn parseInputReportingFailure(self: *Elf, path: Path, needed: bool, must_lin
13581337 .needed = needed,
13591338 }, &self.shared_objects, &self.files, target) catch |err| switch (err) {
13601339 error.LinkFailure => return, // already reported
1361 error.BadMagic, error.UnexpectedEndOfFile => {
1362 // It could be a linker script.
1363 self.parseLdScript(.{ .path = path, .needed = needed }) catch |err2| switch (err2) {
1364 error.LinkFailure => return, // already reported
1365 else => |e| diags.addParseError(path, "failed to parse linker script: {s}", .{@errorName(e)}),
1366 };
1367 },
13681340 else => |e| diags.addParseError(path, "failed to parse shared object: {s}", .{@errorName(e)}),
13691341 },
13701342 .static_library => parseArchive(self, path, must_link) catch |err| switch (err) {
13711343 error.LinkFailure => return, // already reported
13721344 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
13731345 },
1374 .unknown => self.parseLdScript(.{ .path = path, .needed = needed }) catch |err| switch (err) {
1375 error.LinkFailure => return, // already reported
1376 else => |e| diags.addParseError(path, "failed to parse linker script: {s}", .{@errorName(e)}),
1377 },
13781346 else => diags.addParseError(path, "unrecognized file type", .{}),
13791347 }
13801348}
......@@ -1512,72 +1480,6 @@ fn parseSharedObject(
15121480 }
15131481}
15141482
1515fn parseLdScript(self: *Elf, lib: SystemLib) ParseError!void {
1516 const tracy = trace(@src());
1517 defer tracy.end();
1518
1519 const comp = self.base.comp;
1520 const gpa = comp.gpa;
1521 const diags = &comp.link_diags;
1522
1523 const in_file = try lib.path.root_dir.handle.openFile(lib.path.sub_path, .{});
1524 defer in_file.close();
1525 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
1526 defer gpa.free(data);
1527
1528 var script = try LdScript.parse(gpa, diags, lib.path, data);
1529 defer script.deinit(gpa);
1530
1531 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
1532 defer arena_allocator.deinit();
1533 const arena = arena_allocator.allocator();
1534
1535 var test_path = std.ArrayList(u8).init(arena);
1536 var checked_paths = std.ArrayList([]const u8).init(arena);
1537
1538 for (script.args) |script_arg| {
1539 checked_paths.clearRetainingCapacity();
1540
1541 success: {
1542 if (mem.startsWith(u8, script_arg.path, "-l")) {
1543 const lib_name = script_arg.path["-l".len..];
1544
1545 for (self.lib_dirs) |lib_dir| {
1546 if (!self.base.isStatic()) {
1547 if (try self.accessLibPath(arena, &test_path, &checked_paths, lib_dir, lib_name, .dynamic))
1548 break :success;
1549 }
1550 if (try self.accessLibPath(arena, &test_path, &checked_paths, lib_dir, lib_name, .static))
1551 break :success;
1552 }
1553 } else {
1554 var buffer: [fs.max_path_bytes]u8 = undefined;
1555 if (fs.realpath(script_arg.path, &buffer)) |path| {
1556 test_path.clearRetainingCapacity();
1557 try test_path.writer().writeAll(path);
1558 break :success;
1559 } else |_| {}
1560
1561 try checked_paths.append(try arena.dupe(u8, script_arg.path));
1562 for (self.lib_dirs) |lib_dir| {
1563 if (try self.accessLibPath(arena, &test_path, &checked_paths, lib_dir, script_arg.path, null))
1564 break :success;
1565 }
1566 }
1567
1568 diags.addMissingLibraryError(
1569 checked_paths.items,
1570 "missing library dependency: GNU ld script '{}' requires '{s}', but file not found",
1571 .{ @as(Path, lib.path), script_arg.path },
1572 );
1573 continue;
1574 }
1575
1576 const full_path = Path.initCwd(test_path.items);
1577 parseInputReportingFailure(self, full_path, script_arg.needed, false);
1578 }
1579}
1580
15811483pub fn validateEFlags(self: *Elf, file_index: File.Index, e_flags: elf.Word) !void {
15821484 if (self.first_eflags == null) {
15831485 self.first_eflags = e_flags;
......@@ -1618,39 +1520,6 @@ pub fn validateEFlags(self: *Elf, file_index: File.Index, e_flags: elf.Word) !vo
16181520 }
16191521}
16201522
1621fn accessLibPath(
1622 self: *Elf,
1623 arena: Allocator,
1624 test_path: *std.ArrayList(u8),
1625 checked_paths: ?*std.ArrayList([]const u8),
1626 lib_dir_path: []const u8,
1627 lib_name: []const u8,
1628 link_mode: ?std.builtin.LinkMode,
1629) !bool {
1630 const sep = fs.path.sep_str;
1631 const target = self.getTarget();
1632 test_path.clearRetainingCapacity();
1633 const prefix = if (link_mode != null) "lib" else "";
1634 const suffix = if (link_mode) |mode| switch (mode) {
1635 .static => target.staticLibSuffix(),
1636 .dynamic => target.dynamicLibSuffix(),
1637 } else "";
1638 try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{
1639 lib_dir_path,
1640 prefix,
1641 lib_name,
1642 suffix,
1643 });
1644 if (checked_paths) |cpaths| {
1645 try cpaths.append(try arena.dupe(u8, test_path.items));
1646 }
1647 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
1648 error.FileNotFound => return false,
1649 else => |e| return e,
1650 };
1651 return true;
1652}
1653
16541523/// When resolving symbols, we approach the problem similarly to `mold`.
16551524/// 1. Resolve symbols across all objects (including those preemptively extracted archives).
16561525/// 2. Resolve symbols across all shared objects.
......@@ -1840,7 +1709,7 @@ pub fn initOutputSection(self: *Elf, args: struct {
18401709 ".dtors", ".gnu.warning",
18411710 };
18421711 inline for (name_prefixes) |prefix| {
1843 if (std.mem.eql(u8, args.name, prefix) or std.mem.startsWith(u8, args.name, prefix ++ ".")) {
1712 if (mem.eql(u8, args.name, prefix) or mem.startsWith(u8, args.name, prefix ++ ".")) {
18441713 break :blk prefix;
18451714 }
18461715 }
......@@ -1852,9 +1721,9 @@ pub fn initOutputSection(self: *Elf, args: struct {
18521721 switch (args.type) {
18531722 elf.SHT_NULL => unreachable,
18541723 elf.SHT_PROGBITS => {
1855 if (std.mem.eql(u8, args.name, ".init_array") or std.mem.startsWith(u8, args.name, ".init_array."))
1724 if (mem.eql(u8, args.name, ".init_array") or mem.startsWith(u8, args.name, ".init_array."))
18561725 break :tt elf.SHT_INIT_ARRAY;
1857 if (std.mem.eql(u8, args.name, ".fini_array") or std.mem.startsWith(u8, args.name, ".fini_array."))
1726 if (mem.eql(u8, args.name, ".fini_array") or mem.startsWith(u8, args.name, ".fini_array."))
18581727 break :tt elf.SHT_FINI_ARRAY;
18591728 break :tt args.type;
18601729 },
......@@ -1971,7 +1840,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
19711840 man.hash.add(comp.link_eh_frame_hdr);
19721841 man.hash.add(self.emit_relocs);
19731842 man.hash.add(comp.config.rdynamic);
1974 man.hash.addListOfBytes(self.lib_dirs);
19751843 man.hash.addListOfBytes(self.rpath_table.keys());
19761844 if (output_mode == .Exe) {
19771845 man.hash.add(self.base.stack_size);
......@@ -2265,11 +2133,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
22652133 try argv.appendSlice(&.{ "-wrap", symbol_name });
22662134 }
22672135
2268 for (self.lib_dirs) |lib_dir| {
2269 try argv.append("-L");
2270 try argv.append(lib_dir);
2271 }
2272
22732136 if (comp.config.link_libc) {
22742137 if (comp.libc_installation) |libc_installation| {
22752138 try argv.append("-L");
......@@ -4868,7 +4731,7 @@ fn shString(
48684731 off: u32,
48694732) [:0]const u8 {
48704733 const slice = shstrtab[off..];
4871 return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
4734 return slice[0..mem.indexOfScalar(u8, slice, 0).? :0];
48724735}
48734736
48744737pub fn insertShString(self: *Elf, name: [:0]const u8) error{OutOfMemory}!u32 {
src/link/LdScript.zig-1
......@@ -14,7 +14,6 @@ pub fn deinit(ls: *LdScript, gpa: Allocator) void {
1414
1515pub const Error = error{
1616 LinkFailure,
17 UnexpectedToken,
1817 UnknownCpuArch,
1918 OutOfMemory,
2019};
src/main.zig+281-113
......@@ -3618,6 +3618,28 @@ fn buildOutputType(
36183618 return cleanExit();
36193619}
36203620
3621const LinkerInput = union(enum) {
3622 /// An argument like: -l[name]
3623 named: Named,
3624 /// When a file path is provided.
3625 path: struct {
3626 path: Path,
3627 /// We still need all this info because the path may point to a .so
3628 /// file which may actually be a "linker script" that references
3629 /// library names which need to be resolved.
3630 info: SystemLib,
3631 },
3632 /// Put exactly this string in the dynamic section, no rpath.
3633 exact: struct {
3634 name: []const u8,
3635 },
3636
3637 const Named = struct {
3638 name: []const u8,
3639 info: SystemLib,
3640 };
3641};
3642
36213643const CreateModule = struct {
36223644 global_cache_directory: Cache.Directory,
36233645 modules: std.StringArrayHashMapUnmanaged(CliModule),
......@@ -3760,10 +3782,7 @@ fn createModule(
37603782 // First, remove libc, libc++, and compiler_rt libraries from the system libraries list.
37613783 // We need to know whether the set of system libraries contains anything besides these
37623784 // to decide whether to trigger native path detection logic.
3763 var external_system_libs: std.MultiArrayList(struct {
3764 name: []const u8,
3765 info: SystemLib,
3766 }) = .{};
3785 var external_linker_inputs: std.ArrayListUnmanaged(LinkerInput) = .empty;
37673786 for (create_module.system_libs.keys(), create_module.system_libs.values()) |lib_name, info| {
37683787 if (std.zig.target.isLibCLibName(target, lib_name)) {
37693788 create_module.opts.link_libc = true;
......@@ -3815,13 +3834,13 @@ fn createModule(
38153834 }
38163835 }
38173836
3818 try external_system_libs.append(arena, .{
3837 try external_linker_inputs.append(arena, .{ .named = .{
38193838 .name = lib_name,
38203839 .info = info,
3821 });
3840 } });
38223841 }
3823 // After this point, external_system_libs is used instead of system_libs.
3824 if (external_system_libs.len != 0)
3842 // After this point, external_linker_inputs is used instead of system_libs.
3843 if (external_linker_inputs.items.len != 0)
38253844 create_module.want_native_include_dirs = true;
38263845
38273846 // Resolve the library path arguments with respect to sysroot.
......@@ -3878,7 +3897,7 @@ fn createModule(
38783897 }
38793898
38803899 if (builtin.target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and
3881 external_system_libs.len != 0)
3900 external_linker_inputs.items.len != 0)
38823901 {
38833902 if (create_module.libc_installation == null) {
38843903 create_module.libc_installation = LibCInstallation.findNative(.{
......@@ -3899,20 +3918,67 @@ fn createModule(
38993918 // If any libs in this list are statically provided, we omit them from the
39003919 // resolved list and populate the link_objects array instead.
39013920 {
3902 var test_path = std.ArrayList(u8).init(gpa);
3903 defer test_path.deinit();
3921 var test_path: std.ArrayListUnmanaged(u8) = .empty;
3922 defer test_path.deinit(gpa);
3923
3924 var checked_paths: std.ArrayListUnmanaged(u8) = .empty;
3925 defer checked_paths.deinit(gpa);
39043926
3905 var checked_paths = std.ArrayList(u8).init(gpa);
3906 defer checked_paths.deinit();
3927 var ld_script_bytes: std.ArrayListUnmanaged(u8) = .empty;
3928 defer ld_script_bytes.deinit(gpa);
39073929
3908 var failed_libs = std.ArrayList(struct {
3930 var failed_libs: std.ArrayListUnmanaged(struct {
39093931 name: []const u8,
39103932 strategy: SystemLib.SearchStrategy,
39113933 checked_paths: []const u8,
39123934 preferred_mode: std.builtin.LinkMode,
3913 }).init(arena);
3935 }) = .empty;
3936
3937 // Convert external system libs into a stack so that items can be
3938 // pushed to it.
3939 //
3940 // This is necessary because shared objects might turn out to be
3941 // "linker scripts" that in fact resolve to one or more other
3942 // external system libs, including parameters such as "needed".
3943 //
3944 // Unfortunately, such files need to be detected immediately, so
3945 // that this library search logic can be applied to them.
3946 mem.reverse(LinkerInput, external_linker_inputs.items);
3947
3948 syslib: while (external_linker_inputs.popOrNull()) |external_linker_input| {
3949 const external_system_lib: LinkerInput.Named = switch (external_linker_input) {
3950 .named => |named| named,
3951 .path => |p| p: {
3952 if (fs.path.isAbsolute(p.path.sub_path)) {
3953 try create_module.link_objects.append(arena, .{
3954 .path = p.path,
3955 .needed = p.info.needed,
3956 .weak = p.info.weak,
3957 });
3958 continue;
3959 }
3960 const lib_name, const link_mode = stripLibPrefixAndSuffix(p.path.sub_path, target);
3961 break :p .{
3962 .name = lib_name,
3963 .info = .{
3964 .needed = p.info.needed,
3965 .weak = p.info.weak,
3966 .preferred_mode = link_mode,
3967 .search_strategy = .no_fallback,
3968 },
3969 };
3970 },
3971 .exact => |exact| {
3972 try create_module.link_objects.append(arena, .{
3973 .path = Path.initCwd(exact.name),
3974 .loption = true,
3975 });
3976 continue;
3977 },
3978 };
3979 const lib_name = external_system_lib.name;
3980 const info = external_system_lib.info;
39143981
3915 syslib: for (external_system_libs.items(.name), external_system_libs.items(.info)) |lib_name, info| {
39163982 // Checked in the first pass above while looking for libc libraries.
39173983 assert(!fs.path.isAbsolute(lib_name));
39183984
......@@ -3921,33 +3987,26 @@ fn createModule(
39213987 switch (info.search_strategy) {
39223988 .mode_first, .no_fallback => {
39233989 // check for preferred mode
3924 for (create_module.lib_dirs.items) |lib_dir_path| {
3925 if (try accessLibPath(
3926 &test_path,
3927 &checked_paths,
3928 lib_dir_path,
3929 lib_name,
3930 target,
3931 info.preferred_mode,
3932 )) {
3933 const path = Path.initCwd(try arena.dupe(u8, test_path.items));
3934 switch (info.preferred_mode) {
3935 .static => try create_module.link_objects.append(arena, .{ .path = path }),
3936 .dynamic => try create_module.resolved_system_libs.append(arena, .{
3937 .name = lib_name,
3938 .lib = .{
3939 .needed = info.needed,
3940 .weak = info.weak,
3941 .path = path,
3942 },
3943 }),
3944 }
3945 continue :syslib;
3946 }
3947 }
3990 for (create_module.lib_dirs.items) |lib_dir_path| switch (try accessLibPath(
3991 gpa,
3992 arena,
3993 &test_path,
3994 &checked_paths,
3995 &external_linker_inputs,
3996 create_module,
3997 &ld_script_bytes,
3998 lib_dir_path,
3999 lib_name,
4000 target,
4001 info.preferred_mode,
4002 info,
4003 )) {
4004 .ok => continue :syslib,
4005 .no_match => {},
4006 };
39484007 // check for fallback mode
39494008 if (info.search_strategy == .no_fallback) {
3950 try failed_libs.append(.{
4009 try failed_libs.append(arena, .{
39514010 .name = lib_name,
39524011 .strategy = info.search_strategy,
39534012 .checked_paths = try arena.dupe(u8, checked_paths.items),
......@@ -3955,31 +4014,24 @@ fn createModule(
39554014 });
39564015 continue :syslib;
39574016 }
3958 for (create_module.lib_dirs.items) |lib_dir_path| {
3959 if (try accessLibPath(
3960 &test_path,
3961 &checked_paths,
3962 lib_dir_path,
3963 lib_name,
3964 target,
3965 info.fallbackMode(),
3966 )) {
3967 const path = Path.initCwd(try arena.dupe(u8, test_path.items));
3968 switch (info.fallbackMode()) {
3969 .static => try create_module.link_objects.append(arena, .{ .path = path }),
3970 .dynamic => try create_module.resolved_system_libs.append(arena, .{
3971 .name = lib_name,
3972 .lib = .{
3973 .needed = info.needed,
3974 .weak = info.weak,
3975 .path = path,
3976 },
3977 }),
3978 }
3979 continue :syslib;
3980 }
3981 }
3982 try failed_libs.append(.{
4017 for (create_module.lib_dirs.items) |lib_dir_path| switch (try accessLibPath(
4018 gpa,
4019 arena,
4020 &test_path,
4021 &checked_paths,
4022 &external_linker_inputs,
4023 create_module,
4024 &ld_script_bytes,
4025 lib_dir_path,
4026 lib_name,
4027 target,
4028 info.fallbackMode(),
4029 info,
4030 )) {
4031 .ok => continue :syslib,
4032 .no_match => {},
4033 };
4034 try failed_libs.append(arena, .{
39834035 .name = lib_name,
39844036 .strategy = info.search_strategy,
39854037 .checked_paths = try arena.dupe(u8, checked_paths.items),
......@@ -3990,54 +4042,44 @@ fn createModule(
39904042 .paths_first => {
39914043 for (create_module.lib_dirs.items) |lib_dir_path| {
39924044 // check for preferred mode
3993 if (try accessLibPath(
4045 switch (try accessLibPath(
4046 gpa,
4047 arena,
39944048 &test_path,
39954049 &checked_paths,
4050 &external_linker_inputs,
4051 create_module,
4052 &ld_script_bytes,
39964053 lib_dir_path,
39974054 lib_name,
39984055 target,
39994056 info.preferred_mode,
4057 info,
40004058 )) {
4001 const path = Path.initCwd(try arena.dupe(u8, test_path.items));
4002 switch (info.preferred_mode) {
4003 .static => try create_module.link_objects.append(arena, .{ .path = path }),
4004 .dynamic => try create_module.resolved_system_libs.append(arena, .{
4005 .name = lib_name,
4006 .lib = .{
4007 .needed = info.needed,
4008 .weak = info.weak,
4009 .path = path,
4010 },
4011 }),
4012 }
4013 continue :syslib;
4059 .ok => continue :syslib,
4060 .no_match => {},
40144061 }
40154062
40164063 // check for fallback mode
4017 if (try accessLibPath(
4064 switch (try accessLibPath(
4065 gpa,
4066 arena,
40184067 &test_path,
40194068 &checked_paths,
4069 &external_linker_inputs,
4070 create_module,
4071 &ld_script_bytes,
40204072 lib_dir_path,
40214073 lib_name,
40224074 target,
40234075 info.fallbackMode(),
4076 info,
40244077 )) {
4025 const path = Path.initCwd(try arena.dupe(u8, test_path.items));
4026 switch (info.fallbackMode()) {
4027 .static => try create_module.link_objects.append(arena, .{ .path = path }),
4028 .dynamic => try create_module.resolved_system_libs.append(arena, .{
4029 .name = lib_name,
4030 .lib = .{
4031 .needed = info.needed,
4032 .weak = info.weak,
4033 .path = path,
4034 },
4035 }),
4036 }
4037 continue :syslib;
4078 .ok => continue :syslib,
4079 .no_match => {},
40384080 }
40394081 }
4040 try failed_libs.append(.{
4082 try failed_libs.append(arena, .{
40414083 .name = lib_name,
40424084 .strategy = info.search_strategy,
40434085 .checked_paths = try arena.dupe(u8, checked_paths.items),
......@@ -4059,8 +4101,8 @@ fn createModule(
40594101 process.exit(1);
40604102 }
40614103 }
4062 // After this point, create_module.resolved_system_libs is used instead of
4063 // create_module.external_system_libs.
4104 // After this point, create_module.resolved_system_libs is used instead
4105 // of external_linker_inputs.
40644106
40654107 if (create_module.resolved_system_libs.len != 0)
40664108 create_module.opts.any_dyn_libs = true;
......@@ -6857,33 +6899,45 @@ const ClangSearchSanitizer = struct {
68576899 };
68586900};
68596901
6902const AccessLibPathResult = enum { ok, no_match };
6903
68606904fn accessLibPath(
6861 test_path: *std.ArrayList(u8),
6862 checked_paths: *std.ArrayList(u8),
6905 gpa: Allocator,
6906 arena: Allocator,
6907 /// Allocated via `gpa`.
6908 test_path: *std.ArrayListUnmanaged(u8),
6909 /// Allocated via `gpa`.
6910 checked_paths: *std.ArrayListUnmanaged(u8),
6911 /// Allocated via `arena`.
6912 external_linker_inputs: *std.ArrayListUnmanaged(LinkerInput),
6913 create_module: *CreateModule,
6914 /// Allocated via `gpa`.
6915 ld_script_bytes: *std.ArrayListUnmanaged(u8),
68636916 lib_dir_path: []const u8,
68646917 lib_name: []const u8,
68656918 target: std.Target,
68666919 link_mode: std.builtin.LinkMode,
6867) !bool {
6920 parent: SystemLib,
6921) Allocator.Error!AccessLibPathResult {
68686922 const sep = fs.path.sep_str;
68696923
68706924 if (target.isDarwin() and link_mode == .dynamic) tbd: {
68716925 // Prefer .tbd over .dylib.
68726926 test_path.clearRetainingCapacity();
6873 try test_path.writer().print("{s}" ++ sep ++ "lib{s}.tbd", .{ lib_dir_path, lib_name });
6874 try checked_paths.writer().print("\n {s}", .{test_path.items});
6927 try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.tbd", .{ lib_dir_path, lib_name });
6928 try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
68756929 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
68766930 error.FileNotFound => break :tbd,
68776931 else => |e| fatal("unable to search for tbd library '{s}': {s}", .{
68786932 test_path.items, @errorName(e),
68796933 }),
68806934 };
6881 return true;
6935 return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
68826936 }
68836937
68846938 main_check: {
68856939 test_path.clearRetainingCapacity();
6886 try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{
6940 try test_path.writer(gpa).print("{s}" ++ sep ++ "{s}{s}{s}", .{
68876941 lib_dir_path,
68886942 target.libPrefix(),
68896943 lib_name,
......@@ -6892,49 +6946,148 @@ fn accessLibPath(
68926946 .dynamic => target.dynamicLibSuffix(),
68936947 },
68946948 });
6895 try checked_paths.writer().print("\n {s}", .{test_path.items});
6949 try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
6950
6951 // In the case of .so files, they might actually be "linker scripts"
6952 // that contain references to other libraries.
6953 if (target.ofmt == .elf and mem.endsWith(u8, test_path.items, ".so")) {
6954 var file = fs.cwd().openFile(test_path.items, .{}) catch |err| switch (err) {
6955 error.FileNotFound => break :main_check,
6956 else => |e| fatal("unable to search for {s} library '{s}': {s}", .{
6957 @tagName(link_mode), test_path.items, @errorName(e),
6958 }),
6959 };
6960 defer file.close();
6961 try ld_script_bytes.resize(gpa, @sizeOf(std.elf.Elf64_Ehdr));
6962 const n = file.readAll(ld_script_bytes.items) catch |err| fatal("failed to read {s}: {s}", .{
6963 test_path.items, @errorName(err),
6964 });
6965 elf_file: {
6966 if (n != ld_script_bytes.items.len) break :elf_file;
6967 if (!mem.eql(u8, ld_script_bytes.items[0..4], "\x7fELF")) break :elf_file;
6968 // Appears to be an ELF file.
6969 return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
6970 }
6971 const stat = file.stat() catch |err|
6972 fatal("failed to stat {s}: {s}", .{ test_path.items, @errorName(err) });
6973 const size = std.math.cast(u32, stat.size) orelse
6974 fatal("{s}: linker script too big", .{test_path.items});
6975 try ld_script_bytes.resize(gpa, size);
6976 const buf = ld_script_bytes.items[n..];
6977 const n2 = file.readAll(buf) catch |err|
6978 fatal("failed to read {s}: {s}", .{ test_path.items, @errorName(err) });
6979 if (n2 != buf.len) fatal("failed to read {s}: unexpected end of file", .{test_path.items});
6980 var diags = link.Diags.init(gpa);
6981 defer diags.deinit();
6982 const ld_script_result = link.LdScript.parse(gpa, &diags, Path.initCwd(test_path.items), ld_script_bytes.items);
6983 if (diags.hasErrors()) {
6984 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
6985 try wip_errors.init(gpa);
6986 defer wip_errors.deinit();
6987
6988 try diags.addMessagesToBundle(&wip_errors);
6989
6990 var error_bundle = try wip_errors.toOwnedBundle("");
6991 defer error_bundle.deinit(gpa);
6992
6993 const color: Color = .auto;
6994 error_bundle.renderToStdErr(color.renderOptions());
6995
6996 process.exit(1);
6997 }
6998
6999 var ld_script = ld_script_result catch |err|
7000 fatal("{s}: failed to parse linker script: {s}", .{ test_path.items, @errorName(err) });
7001 defer ld_script.deinit(gpa);
7002
7003 try external_linker_inputs.ensureUnusedCapacity(arena, ld_script.args.len);
7004 for (ld_script.args) |arg| {
7005 const syslib: SystemLib = .{
7006 .needed = arg.needed or parent.needed,
7007 .weak = parent.weak,
7008 .preferred_mode = parent.preferred_mode,
7009 .search_strategy = parent.search_strategy,
7010 };
7011 if (mem.startsWith(u8, arg.path, "-l")) {
7012 external_linker_inputs.appendAssumeCapacity(.{ .named = .{
7013 .name = try arena.dupe(u8, arg.path["-l".len..]),
7014 .info = syslib,
7015 } });
7016 } else {
7017 external_linker_inputs.appendAssumeCapacity(.{ .path = .{
7018 .path = Path.initCwd(try arena.dupe(u8, arg.path)),
7019 .info = syslib,
7020 } });
7021 }
7022 }
7023 return .ok;
7024 }
7025
68967026 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
68977027 error.FileNotFound => break :main_check,
68987028 else => |e| fatal("unable to search for {s} library '{s}': {s}", .{
68997029 @tagName(link_mode), test_path.items, @errorName(e),
69007030 }),
69017031 };
6902 return true;
7032 return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
69037033 }
69047034
69057035 // In the case of Darwin, the main check will be .dylib, so here we
69067036 // additionally check for .so files.
69077037 if (target.isDarwin() and link_mode == .dynamic) so: {
69087038 test_path.clearRetainingCapacity();
6909 try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, lib_name });
6910 try checked_paths.writer().print("\n {s}", .{test_path.items});
7039 try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, lib_name });
7040 try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
69117041 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
69127042 error.FileNotFound => break :so,
69137043 else => |e| fatal("unable to search for so library '{s}': {s}", .{
69147044 test_path.items, @errorName(e),
69157045 }),
69167046 };
6917 return true;
7047 return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
69187048 }
69197049
69207050 // In the case of MinGW, the main check will be .lib but we also need to
69217051 // look for `libfoo.a`.
69227052 if (target.isMinGW() and link_mode == .static) mingw: {
69237053 test_path.clearRetainingCapacity();
6924 try test_path.writer().print("{s}" ++ sep ++ "lib{s}.a", .{
7054 try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.a", .{
69257055 lib_dir_path, lib_name,
69267056 });
6927 try checked_paths.writer().print("\n {s}", .{test_path.items});
7057 try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
69287058 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
69297059 error.FileNotFound => break :mingw,
69307060 else => |e| fatal("unable to search for static library '{s}': {s}", .{
69317061 test_path.items, @errorName(e),
69327062 }),
69337063 };
6934 return true;
7064 return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
69357065 }
69367066
6937 return false;
7067 return .no_match;
7068}
7069
7070fn finishAccessLibPath(
7071 arena: Allocator,
7072 create_module: *CreateModule,
7073 test_path: *std.ArrayListUnmanaged(u8),
7074 link_mode: std.builtin.LinkMode,
7075 parent: SystemLib,
7076 lib_name: []const u8,
7077) Allocator.Error!AccessLibPathResult {
7078 const path = Path.initCwd(try arena.dupe(u8, test_path.items));
7079 switch (link_mode) {
7080 .static => try create_module.link_objects.append(arena, .{ .path = path }),
7081 .dynamic => try create_module.resolved_system_libs.append(arena, .{
7082 .name = lib_name,
7083 .lib = .{
7084 .needed = parent.needed,
7085 .weak = parent.weak,
7086 .path = path,
7087 },
7088 }),
7089 }
7090 return .ok;
69387091}
69397092
69407093fn accessFrameworkPath(
......@@ -7634,3 +7787,18 @@ fn handleModArg(
76347787 c_source_files_owner_index.* = create_module.c_source_files.items.len;
76357788 rc_source_files_owner_index.* = create_module.rc_source_files.items.len;
76367789}
7790
7791fn stripLibPrefixAndSuffix(path: []const u8, target: std.Target) struct { []const u8, std.builtin.LinkMode } {
7792 const prefix = target.libPrefix();
7793 const static_suffix = target.staticLibSuffix();
7794 const dynamic_suffix = target.dynamicLibSuffix();
7795 const basename = fs.path.basename(path);
7796 const unlibbed = if (mem.startsWith(u8, basename, prefix)) basename[prefix.len..] else basename;
7797 if (mem.endsWith(u8, unlibbed, static_suffix)) return .{
7798 unlibbed[0 .. unlibbed.len - static_suffix.len], .static,
7799 };
7800 if (mem.endsWith(u8, unlibbed, dynamic_suffix)) return .{
7801 unlibbed[0 .. unlibbed.len - dynamic_suffix.len], .dynamic,
7802 };
7803 fatal("unrecognized library path: {s}", .{path});
7804}
test/link/elf.zig+6-16
......@@ -2165,13 +2165,11 @@ fn testLdScriptPathError(b: *Build, opts: Options) *Step {
21652165 exe.addLibraryPath(scripts.getDirectory());
21662166 exe.linkLibC();
21672167
2168 expectLinkErrors(
2169 exe,
2170 test_step,
2171 .{
2172 .contains = "error: missing library dependency: GNU ld script '/?/liba.so' requires 'libfoo.so', but file not found",
2173 },
2174 );
2168 // TODO: A future enhancement could make this error message also mention
2169 // the file that references the missing library.
2170 expectLinkErrors(exe, test_step, .{
2171 .stderr_contains = "error: unable to find dynamic system library 'foo' using strategy 'no_fallback'. searched paths:",
2172 });
21752173
21762174 return test_step;
21772175}
......@@ -3907,16 +3905,8 @@ fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
39073905 exe.linkLibrary(dylib);
39083906 exe.linkLibC();
39093907
3910 // TODO: improve the test harness to be able to selectively match lines in error output
3911 // while avoiding jankiness
3912 // expectLinkErrors(exe, test_step, .{ .exact = &.{
3913 // "error: invalid token in LD script: '\\x00\\x00\\x00\\x0c\\x00\\x00\\x00/usr/lib/dyld\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0d' (0:989)",
3914 // "note: while parsing /?/liba.dylib",
3915 // "error: unexpected error: parsing input file failed with error InvalidLdScript",
3916 // "note: while parsing /?/liba.dylib",
3917 // } });
39183908 expectLinkErrors(exe, test_step, .{
3919 .starts_with = "error: invalid token in LD script: '\\x00\\x00\\x00\\x0c\\x00\\x00\\x00/usr/lib/dyld\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0d' (",
3909 .contains = "error: failed to parse shared object: BadMagic",
39203910 });
39213911
39223912 return test_step;