Commit 5b016e290a
Changed files (6)
test
link
src/link/Elf.zig
@@ -1,5 +1,4 @@
pub const Atom = @import("Elf/Atom.zig");
-pub const LdScript = @import("LdScript.zig");
base: link.File,
rpath_table: std.StringArrayHashMapUnmanaged(void),
@@ -16,7 +15,6 @@ z_relro: bool,
z_common_page_size: ?u64,
/// TODO make this non optional and resolve the default in open()
z_max_page_size: ?u64,
-lib_dirs: []const []const u8,
hash_style: HashStyle,
compress_debug_sections: CompressDebugSections,
symbol_wrap_set: std.StringArrayHashMapUnmanaged(void),
@@ -329,7 +327,6 @@ pub fn createEmpty(
.z_relro = options.z_relro,
.z_common_page_size = options.z_common_page_size,
.z_max_page_size = options.z_max_page_size,
- .lib_dirs = options.lib_dirs,
.hash_style = options.hash_style,
.compress_debug_sections = options.compress_debug_sections,
.symbol_wrap_set = options.symbol_wrap_set,
@@ -845,30 +842,17 @@ pub fn flushModule(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_nod
if (comp.libc_installation) |lc| {
const flags = target_util.libcFullLinkFlags(target);
- var test_path = std.ArrayList(u8).init(arena);
- var checked_paths = std.ArrayList([]const u8).init(arena);
-
for (flags) |flag| {
- checked_paths.clearRetainingCapacity();
+ assert(mem.startsWith(u8, flag, "-l"));
const lib_name = flag["-l".len..];
-
- success: {
- if (!self.base.isStatic()) {
- if (try self.accessLibPath(arena, &test_path, &checked_paths, lc.crt_dir.?, lib_name, .dynamic))
- break :success;
- }
- if (try self.accessLibPath(arena, &test_path, &checked_paths, lc.crt_dir.?, lib_name, .static))
- break :success;
-
- diags.addMissingLibraryError(
- checked_paths.items,
- "missing system library: '{s}' was not found",
- .{lib_name},
- );
- continue;
- }
-
- const resolved_path = Path.initCwd(try arena.dupe(u8, test_path.items));
+ const suffix = switch (comp.config.link_mode) {
+ .static => target.staticLibSuffix(),
+ .dynamic => target.dynamicLibSuffix(),
+ };
+ const lib_path = try std.fmt.allocPrint(arena, "{s}/lib{s}{s}", .{
+ lc.crt_dir.?, lib_name, suffix,
+ });
+ const resolved_path = Path.initCwd(lib_path);
parseInputReportingFailure(self, resolved_path, false, false);
}
} else if (target.isGnuLibC()) {
@@ -1194,11 +1178,6 @@ fn dumpArgv(self: *Elf, comp: *Compilation) !void {
if (csu.crti) |path| try argv.append(try path.toString(arena));
if (csu.crtbegin) |path| try argv.append(try path.toString(arena));
- for (self.lib_dirs) |lib_dir| {
- try argv.append("-L");
- try argv.append(lib_dir);
- }
-
if (comp.config.link_libc) {
if (self.base.comp.libc_installation) |libc_installation| {
try argv.append("-L");
@@ -1340,7 +1319,7 @@ pub const ParseError = error{
NotSupported,
InvalidCharacter,
UnknownFileType,
-} || LdScript.Error || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError;
+} || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError;
fn parseCrtFileReportingFailure(self: *Elf, crt_file: Compilation.CrtFile) void {
parseInputReportingFailure(self, crt_file.full_object_path, false, false);
@@ -1358,23 +1337,12 @@ pub fn parseInputReportingFailure(self: *Elf, path: Path, needed: bool, must_lin
.needed = needed,
}, &self.shared_objects, &self.files, target) catch |err| switch (err) {
error.LinkFailure => return, // already reported
- error.BadMagic, error.UnexpectedEndOfFile => {
- // It could be a linker script.
- self.parseLdScript(.{ .path = path, .needed = needed }) catch |err2| switch (err2) {
- error.LinkFailure => return, // already reported
- else => |e| diags.addParseError(path, "failed to parse linker script: {s}", .{@errorName(e)}),
- };
- },
else => |e| diags.addParseError(path, "failed to parse shared object: {s}", .{@errorName(e)}),
},
.static_library => parseArchive(self, path, must_link) catch |err| switch (err) {
error.LinkFailure => return, // already reported
else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
},
- .unknown => self.parseLdScript(.{ .path = path, .needed = needed }) catch |err| switch (err) {
- error.LinkFailure => return, // already reported
- else => |e| diags.addParseError(path, "failed to parse linker script: {s}", .{@errorName(e)}),
- },
else => diags.addParseError(path, "unrecognized file type", .{}),
}
}
@@ -1512,72 +1480,6 @@ fn parseSharedObject(
}
}
-fn parseLdScript(self: *Elf, lib: SystemLib) ParseError!void {
- const tracy = trace(@src());
- defer tracy.end();
-
- const comp = self.base.comp;
- const gpa = comp.gpa;
- const diags = &comp.link_diags;
-
- const in_file = try lib.path.root_dir.handle.openFile(lib.path.sub_path, .{});
- defer in_file.close();
- const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
- defer gpa.free(data);
-
- var script = try LdScript.parse(gpa, diags, lib.path, data);
- defer script.deinit(gpa);
-
- var arena_allocator = std.heap.ArenaAllocator.init(gpa);
- defer arena_allocator.deinit();
- const arena = arena_allocator.allocator();
-
- var test_path = std.ArrayList(u8).init(arena);
- var checked_paths = std.ArrayList([]const u8).init(arena);
-
- for (script.args) |script_arg| {
- checked_paths.clearRetainingCapacity();
-
- success: {
- if (mem.startsWith(u8, script_arg.path, "-l")) {
- const lib_name = script_arg.path["-l".len..];
-
- for (self.lib_dirs) |lib_dir| {
- if (!self.base.isStatic()) {
- if (try self.accessLibPath(arena, &test_path, &checked_paths, lib_dir, lib_name, .dynamic))
- break :success;
- }
- if (try self.accessLibPath(arena, &test_path, &checked_paths, lib_dir, lib_name, .static))
- break :success;
- }
- } else {
- var buffer: [fs.max_path_bytes]u8 = undefined;
- if (fs.realpath(script_arg.path, &buffer)) |path| {
- test_path.clearRetainingCapacity();
- try test_path.writer().writeAll(path);
- break :success;
- } else |_| {}
-
- try checked_paths.append(try arena.dupe(u8, script_arg.path));
- for (self.lib_dirs) |lib_dir| {
- if (try self.accessLibPath(arena, &test_path, &checked_paths, lib_dir, script_arg.path, null))
- break :success;
- }
- }
-
- diags.addMissingLibraryError(
- checked_paths.items,
- "missing library dependency: GNU ld script '{}' requires '{s}', but file not found",
- .{ @as(Path, lib.path), script_arg.path },
- );
- continue;
- }
-
- const full_path = Path.initCwd(test_path.items);
- parseInputReportingFailure(self, full_path, script_arg.needed, false);
- }
-}
-
pub fn validateEFlags(self: *Elf, file_index: File.Index, e_flags: elf.Word) !void {
if (self.first_eflags == null) {
self.first_eflags = e_flags;
@@ -1618,39 +1520,6 @@ pub fn validateEFlags(self: *Elf, file_index: File.Index, e_flags: elf.Word) !vo
}
}
-fn accessLibPath(
- self: *Elf,
- arena: Allocator,
- test_path: *std.ArrayList(u8),
- checked_paths: ?*std.ArrayList([]const u8),
- lib_dir_path: []const u8,
- lib_name: []const u8,
- link_mode: ?std.builtin.LinkMode,
-) !bool {
- const sep = fs.path.sep_str;
- const target = self.getTarget();
- test_path.clearRetainingCapacity();
- const prefix = if (link_mode != null) "lib" else "";
- const suffix = if (link_mode) |mode| switch (mode) {
- .static => target.staticLibSuffix(),
- .dynamic => target.dynamicLibSuffix(),
- } else "";
- try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{
- lib_dir_path,
- prefix,
- lib_name,
- suffix,
- });
- if (checked_paths) |cpaths| {
- try cpaths.append(try arena.dupe(u8, test_path.items));
- }
- fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
- error.FileNotFound => return false,
- else => |e| return e,
- };
- return true;
-}
-
/// When resolving symbols, we approach the problem similarly to `mold`.
/// 1. Resolve symbols across all objects (including those preemptively extracted archives).
/// 2. Resolve symbols across all shared objects.
@@ -1840,7 +1709,7 @@ pub fn initOutputSection(self: *Elf, args: struct {
".dtors", ".gnu.warning",
};
inline for (name_prefixes) |prefix| {
- if (std.mem.eql(u8, args.name, prefix) or std.mem.startsWith(u8, args.name, prefix ++ ".")) {
+ if (mem.eql(u8, args.name, prefix) or mem.startsWith(u8, args.name, prefix ++ ".")) {
break :blk prefix;
}
}
@@ -1852,9 +1721,9 @@ pub fn initOutputSection(self: *Elf, args: struct {
switch (args.type) {
elf.SHT_NULL => unreachable,
elf.SHT_PROGBITS => {
- if (std.mem.eql(u8, args.name, ".init_array") or std.mem.startsWith(u8, args.name, ".init_array."))
+ if (mem.eql(u8, args.name, ".init_array") or mem.startsWith(u8, args.name, ".init_array."))
break :tt elf.SHT_INIT_ARRAY;
- if (std.mem.eql(u8, args.name, ".fini_array") or std.mem.startsWith(u8, args.name, ".fini_array."))
+ if (mem.eql(u8, args.name, ".fini_array") or mem.startsWith(u8, args.name, ".fini_array."))
break :tt elf.SHT_FINI_ARRAY;
break :tt args.type;
},
@@ -1971,7 +1840,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
man.hash.add(comp.link_eh_frame_hdr);
man.hash.add(self.emit_relocs);
man.hash.add(comp.config.rdynamic);
- man.hash.addListOfBytes(self.lib_dirs);
man.hash.addListOfBytes(self.rpath_table.keys());
if (output_mode == .Exe) {
man.hash.add(self.base.stack_size);
@@ -2265,11 +2133,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
try argv.appendSlice(&.{ "-wrap", symbol_name });
}
- for (self.lib_dirs) |lib_dir| {
- try argv.append("-L");
- try argv.append(lib_dir);
- }
-
if (comp.config.link_libc) {
if (comp.libc_installation) |libc_installation| {
try argv.append("-L");
@@ -4868,7 +4731,7 @@ fn shString(
off: u32,
) [:0]const u8 {
const slice = shstrtab[off..];
- return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
+ return slice[0..mem.indexOfScalar(u8, slice, 0).? :0];
}
pub fn insertShString(self: *Elf, name: [:0]const u8) error{OutOfMemory}!u32 {
src/link/LdScript.zig
@@ -14,7 +14,6 @@ pub fn deinit(ls: *LdScript, gpa: Allocator) void {
pub const Error = error{
LinkFailure,
- UnexpectedToken,
UnknownCpuArch,
OutOfMemory,
};
src/Compilation.zig
@@ -1003,10 +1003,11 @@ pub const LinkObject = struct {
path: Path,
must_link: bool = false,
needed: bool = false,
- // When the library is passed via a positional argument, it will be
- // added as a full path. If it's `-l<lib>`, then just the basename.
- //
- // Consistent with `withLOption` variable name in lld ELF driver.
+ weak: bool = false,
+ /// When the library is passed via a positional argument, it will be
+ /// added as a full path. If it's `-l<lib>`, then just the basename.
+ ///
+ /// Consistent with `withLOption` variable name in lld ELF driver.
loption: bool = false,
pub fn isObject(lo: LinkObject) bool {
@@ -1061,6 +1062,9 @@ pub const CreateOptions = struct {
/// this flag would be set to disable this machinery to avoid false positives.
disable_lld_caching: bool = false,
cache_mode: CacheMode = .incremental,
+ /// This field is intended to be removed.
+ /// The ELF implementation no longer uses this data, however the MachO and COFF
+ /// implementations still do.
lib_dirs: []const []const u8 = &[0][]const u8{},
rpath_list: []const []const u8 = &[0][]const u8{},
symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .empty,
@@ -2563,6 +2567,7 @@ fn addNonIncrementalStuffToCacheManifest(
_ = try man.addFilePath(obj.path, null);
man.hash.add(obj.must_link);
man.hash.add(obj.needed);
+ man.hash.add(obj.weak);
man.hash.add(obj.loption);
}
@@ -3219,18 +3224,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
}));
}
- for (comp.link_diags.msgs.items) |link_err| {
- try bundle.addRootErrorMessage(.{
- .msg = try bundle.addString(link_err.msg),
- .notes_len = @intCast(link_err.notes.len),
- });
- const notes_start = try bundle.reserveNotes(@intCast(link_err.notes.len));
- for (link_err.notes, 0..) |note, i| {
- bundle.extra.items[notes_start + i] = @intFromEnum(try bundle.addErrorMessage(.{
- .msg = try bundle.addString(note.msg),
- }));
- }
- }
+ try comp.link_diags.addMessagesToBundle(&bundle);
if (comp.zcu) |zcu| {
if (bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) {
src/link.zig
@@ -24,6 +24,8 @@ const lldMain = @import("main.zig").lldMain;
const Package = @import("Package.zig");
const dev = @import("dev.zig");
+pub const LdScript = @import("link/LdScript.zig");
+
/// When adding a new field, remember to update `hashAddSystemLibs`.
/// These are *always* dynamically linked. Static libraries will be
/// provided as positional arguments.
@@ -336,6 +338,21 @@ pub const Diags = struct {
log.debug("memory allocation failure", .{});
diags.flags.alloc_failure_occurred = true;
}
+
+ pub fn addMessagesToBundle(diags: *const Diags, bundle: *std.zig.ErrorBundle.Wip) Allocator.Error!void {
+ for (diags.msgs.items) |link_err| {
+ try bundle.addRootErrorMessage(.{
+ .msg = try bundle.addString(link_err.msg),
+ .notes_len = @intCast(link_err.notes.len),
+ });
+ const notes_start = try bundle.reserveNotes(@intCast(link_err.notes.len));
+ for (link_err.notes, 0..) |note, i| {
+ bundle.extra.items[notes_start + i] = @intFromEnum(try bundle.addErrorMessage(.{
+ .msg = try bundle.addString(note.msg),
+ }));
+ }
+ }
+ }
};
pub fn hashAddSystemLibs(
src/main.zig
@@ -3618,6 +3618,28 @@ fn buildOutputType(
return cleanExit();
}
+const LinkerInput = union(enum) {
+ /// An argument like: -l[name]
+ named: Named,
+ /// When a file path is provided.
+ path: struct {
+ path: Path,
+ /// We still need all this info because the path may point to a .so
+ /// file which may actually be a "linker script" that references
+ /// library names which need to be resolved.
+ info: SystemLib,
+ },
+ /// Put exactly this string in the dynamic section, no rpath.
+ exact: struct {
+ name: []const u8,
+ },
+
+ const Named = struct {
+ name: []const u8,
+ info: SystemLib,
+ };
+};
+
const CreateModule = struct {
global_cache_directory: Cache.Directory,
modules: std.StringArrayHashMapUnmanaged(CliModule),
@@ -3760,10 +3782,7 @@ fn createModule(
// First, remove libc, libc++, and compiler_rt libraries from the system libraries list.
// We need to know whether the set of system libraries contains anything besides these
// to decide whether to trigger native path detection logic.
- var external_system_libs: std.MultiArrayList(struct {
- name: []const u8,
- info: SystemLib,
- }) = .{};
+ var external_linker_inputs: std.ArrayListUnmanaged(LinkerInput) = .empty;
for (create_module.system_libs.keys(), create_module.system_libs.values()) |lib_name, info| {
if (std.zig.target.isLibCLibName(target, lib_name)) {
create_module.opts.link_libc = true;
@@ -3815,13 +3834,13 @@ fn createModule(
}
}
- try external_system_libs.append(arena, .{
+ try external_linker_inputs.append(arena, .{ .named = .{
.name = lib_name,
.info = info,
- });
+ } });
}
- // After this point, external_system_libs is used instead of system_libs.
- if (external_system_libs.len != 0)
+ // After this point, external_linker_inputs is used instead of system_libs.
+ if (external_linker_inputs.items.len != 0)
create_module.want_native_include_dirs = true;
// Resolve the library path arguments with respect to sysroot.
@@ -3878,7 +3897,7 @@ fn createModule(
}
if (builtin.target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and
- external_system_libs.len != 0)
+ external_linker_inputs.items.len != 0)
{
if (create_module.libc_installation == null) {
create_module.libc_installation = LibCInstallation.findNative(.{
@@ -3899,20 +3918,67 @@ fn createModule(
// If any libs in this list are statically provided, we omit them from the
// resolved list and populate the link_objects array instead.
{
- var test_path = std.ArrayList(u8).init(gpa);
- defer test_path.deinit();
+ var test_path: std.ArrayListUnmanaged(u8) = .empty;
+ defer test_path.deinit(gpa);
+
+ var checked_paths: std.ArrayListUnmanaged(u8) = .empty;
+ defer checked_paths.deinit(gpa);
- var checked_paths = std.ArrayList(u8).init(gpa);
- defer checked_paths.deinit();
+ var ld_script_bytes: std.ArrayListUnmanaged(u8) = .empty;
+ defer ld_script_bytes.deinit(gpa);
- var failed_libs = std.ArrayList(struct {
+ var failed_libs: std.ArrayListUnmanaged(struct {
name: []const u8,
strategy: SystemLib.SearchStrategy,
checked_paths: []const u8,
preferred_mode: std.builtin.LinkMode,
- }).init(arena);
+ }) = .empty;
+
+ // Convert external system libs into a stack so that items can be
+ // pushed to it.
+ //
+ // This is necessary because shared objects might turn out to be
+ // "linker scripts" that in fact resolve to one or more other
+ // external system libs, including parameters such as "needed".
+ //
+ // Unfortunately, such files need to be detected immediately, so
+ // that this library search logic can be applied to them.
+ mem.reverse(LinkerInput, external_linker_inputs.items);
+
+ syslib: while (external_linker_inputs.popOrNull()) |external_linker_input| {
+ const external_system_lib: LinkerInput.Named = switch (external_linker_input) {
+ .named => |named| named,
+ .path => |p| p: {
+ if (fs.path.isAbsolute(p.path.sub_path)) {
+ try create_module.link_objects.append(arena, .{
+ .path = p.path,
+ .needed = p.info.needed,
+ .weak = p.info.weak,
+ });
+ continue;
+ }
+ const lib_name, const link_mode = stripLibPrefixAndSuffix(p.path.sub_path, target);
+ break :p .{
+ .name = lib_name,
+ .info = .{
+ .needed = p.info.needed,
+ .weak = p.info.weak,
+ .preferred_mode = link_mode,
+ .search_strategy = .no_fallback,
+ },
+ };
+ },
+ .exact => |exact| {
+ try create_module.link_objects.append(arena, .{
+ .path = Path.initCwd(exact.name),
+ .loption = true,
+ });
+ continue;
+ },
+ };
+ const lib_name = external_system_lib.name;
+ const info = external_system_lib.info;
- syslib: for (external_system_libs.items(.name), external_system_libs.items(.info)) |lib_name, info| {
// Checked in the first pass above while looking for libc libraries.
assert(!fs.path.isAbsolute(lib_name));
@@ -3921,33 +3987,26 @@ fn createModule(
switch (info.search_strategy) {
.mode_first, .no_fallback => {
// check for preferred mode
- for (create_module.lib_dirs.items) |lib_dir_path| {
- if (try accessLibPath(
- &test_path,
- &checked_paths,
- lib_dir_path,
- lib_name,
- target,
- info.preferred_mode,
- )) {
- const path = Path.initCwd(try arena.dupe(u8, test_path.items));
- switch (info.preferred_mode) {
- .static => try create_module.link_objects.append(arena, .{ .path = path }),
- .dynamic => try create_module.resolved_system_libs.append(arena, .{
- .name = lib_name,
- .lib = .{
- .needed = info.needed,
- .weak = info.weak,
- .path = path,
- },
- }),
- }
- continue :syslib;
- }
- }
+ for (create_module.lib_dirs.items) |lib_dir_path| switch (try accessLibPath(
+ gpa,
+ arena,
+ &test_path,
+ &checked_paths,
+ &external_linker_inputs,
+ create_module,
+ &ld_script_bytes,
+ lib_dir_path,
+ lib_name,
+ target,
+ info.preferred_mode,
+ info,
+ )) {
+ .ok => continue :syslib,
+ .no_match => {},
+ };
// check for fallback mode
if (info.search_strategy == .no_fallback) {
- try failed_libs.append(.{
+ try failed_libs.append(arena, .{
.name = lib_name,
.strategy = info.search_strategy,
.checked_paths = try arena.dupe(u8, checked_paths.items),
@@ -3955,31 +4014,24 @@ fn createModule(
});
continue :syslib;
}
- for (create_module.lib_dirs.items) |lib_dir_path| {
- if (try accessLibPath(
- &test_path,
- &checked_paths,
- lib_dir_path,
- lib_name,
- target,
- info.fallbackMode(),
- )) {
- const path = Path.initCwd(try arena.dupe(u8, test_path.items));
- switch (info.fallbackMode()) {
- .static => try create_module.link_objects.append(arena, .{ .path = path }),
- .dynamic => try create_module.resolved_system_libs.append(arena, .{
- .name = lib_name,
- .lib = .{
- .needed = info.needed,
- .weak = info.weak,
- .path = path,
- },
- }),
- }
- continue :syslib;
- }
- }
- try failed_libs.append(.{
+ for (create_module.lib_dirs.items) |lib_dir_path| switch (try accessLibPath(
+ gpa,
+ arena,
+ &test_path,
+ &checked_paths,
+ &external_linker_inputs,
+ create_module,
+ &ld_script_bytes,
+ lib_dir_path,
+ lib_name,
+ target,
+ info.fallbackMode(),
+ info,
+ )) {
+ .ok => continue :syslib,
+ .no_match => {},
+ };
+ try failed_libs.append(arena, .{
.name = lib_name,
.strategy = info.search_strategy,
.checked_paths = try arena.dupe(u8, checked_paths.items),
@@ -3990,54 +4042,44 @@ fn createModule(
.paths_first => {
for (create_module.lib_dirs.items) |lib_dir_path| {
// check for preferred mode
- if (try accessLibPath(
+ switch (try accessLibPath(
+ gpa,
+ arena,
&test_path,
&checked_paths,
+ &external_linker_inputs,
+ create_module,
+ &ld_script_bytes,
lib_dir_path,
lib_name,
target,
info.preferred_mode,
+ info,
)) {
- const path = Path.initCwd(try arena.dupe(u8, test_path.items));
- switch (info.preferred_mode) {
- .static => try create_module.link_objects.append(arena, .{ .path = path }),
- .dynamic => try create_module.resolved_system_libs.append(arena, .{
- .name = lib_name,
- .lib = .{
- .needed = info.needed,
- .weak = info.weak,
- .path = path,
- },
- }),
- }
- continue :syslib;
+ .ok => continue :syslib,
+ .no_match => {},
}
// check for fallback mode
- if (try accessLibPath(
+ switch (try accessLibPath(
+ gpa,
+ arena,
&test_path,
&checked_paths,
+ &external_linker_inputs,
+ create_module,
+ &ld_script_bytes,
lib_dir_path,
lib_name,
target,
info.fallbackMode(),
+ info,
)) {
- const path = Path.initCwd(try arena.dupe(u8, test_path.items));
- switch (info.fallbackMode()) {
- .static => try create_module.link_objects.append(arena, .{ .path = path }),
- .dynamic => try create_module.resolved_system_libs.append(arena, .{
- .name = lib_name,
- .lib = .{
- .needed = info.needed,
- .weak = info.weak,
- .path = path,
- },
- }),
- }
- continue :syslib;
+ .ok => continue :syslib,
+ .no_match => {},
}
}
- try failed_libs.append(.{
+ try failed_libs.append(arena, .{
.name = lib_name,
.strategy = info.search_strategy,
.checked_paths = try arena.dupe(u8, checked_paths.items),
@@ -4059,8 +4101,8 @@ fn createModule(
process.exit(1);
}
}
- // After this point, create_module.resolved_system_libs is used instead of
- // create_module.external_system_libs.
+ // After this point, create_module.resolved_system_libs is used instead
+ // of external_linker_inputs.
if (create_module.resolved_system_libs.len != 0)
create_module.opts.any_dyn_libs = true;
@@ -6857,33 +6899,45 @@ const ClangSearchSanitizer = struct {
};
};
+const AccessLibPathResult = enum { ok, no_match };
+
fn accessLibPath(
- test_path: *std.ArrayList(u8),
- checked_paths: *std.ArrayList(u8),
+ gpa: Allocator,
+ arena: Allocator,
+ /// Allocated via `gpa`.
+ test_path: *std.ArrayListUnmanaged(u8),
+ /// Allocated via `gpa`.
+ checked_paths: *std.ArrayListUnmanaged(u8),
+ /// Allocated via `arena`.
+ external_linker_inputs: *std.ArrayListUnmanaged(LinkerInput),
+ create_module: *CreateModule,
+ /// Allocated via `gpa`.
+ ld_script_bytes: *std.ArrayListUnmanaged(u8),
lib_dir_path: []const u8,
lib_name: []const u8,
target: std.Target,
link_mode: std.builtin.LinkMode,
-) !bool {
+ parent: SystemLib,
+) Allocator.Error!AccessLibPathResult {
const sep = fs.path.sep_str;
if (target.isDarwin() and link_mode == .dynamic) tbd: {
// Prefer .tbd over .dylib.
test_path.clearRetainingCapacity();
- try test_path.writer().print("{s}" ++ sep ++ "lib{s}.tbd", .{ lib_dir_path, lib_name });
- try checked_paths.writer().print("\n {s}", .{test_path.items});
+ try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.tbd", .{ lib_dir_path, lib_name });
+ try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
error.FileNotFound => break :tbd,
else => |e| fatal("unable to search for tbd library '{s}': {s}", .{
test_path.items, @errorName(e),
}),
};
- return true;
+ return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
}
main_check: {
test_path.clearRetainingCapacity();
- try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{
+ try test_path.writer(gpa).print("{s}" ++ sep ++ "{s}{s}{s}", .{
lib_dir_path,
target.libPrefix(),
lib_name,
@@ -6892,49 +6946,148 @@ fn accessLibPath(
.dynamic => target.dynamicLibSuffix(),
},
});
- try checked_paths.writer().print("\n {s}", .{test_path.items});
+ try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
+
+ // In the case of .so files, they might actually be "linker scripts"
+ // that contain references to other libraries.
+ if (target.ofmt == .elf and mem.endsWith(u8, test_path.items, ".so")) {
+ var file = fs.cwd().openFile(test_path.items, .{}) catch |err| switch (err) {
+ error.FileNotFound => break :main_check,
+ else => |e| fatal("unable to search for {s} library '{s}': {s}", .{
+ @tagName(link_mode), test_path.items, @errorName(e),
+ }),
+ };
+ defer file.close();
+ try ld_script_bytes.resize(gpa, @sizeOf(std.elf.Elf64_Ehdr));
+ const n = file.readAll(ld_script_bytes.items) catch |err| fatal("failed to read {s}: {s}", .{
+ test_path.items, @errorName(err),
+ });
+ elf_file: {
+ if (n != ld_script_bytes.items.len) break :elf_file;
+ if (!mem.eql(u8, ld_script_bytes.items[0..4], "\x7fELF")) break :elf_file;
+ // Appears to be an ELF file.
+ return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
+ }
+ const stat = file.stat() catch |err|
+ fatal("failed to stat {s}: {s}", .{ test_path.items, @errorName(err) });
+ const size = std.math.cast(u32, stat.size) orelse
+ fatal("{s}: linker script too big", .{test_path.items});
+ try ld_script_bytes.resize(gpa, size);
+ const buf = ld_script_bytes.items[n..];
+ const n2 = file.readAll(buf) catch |err|
+ fatal("failed to read {s}: {s}", .{ test_path.items, @errorName(err) });
+ if (n2 != buf.len) fatal("failed to read {s}: unexpected end of file", .{test_path.items});
+ var diags = link.Diags.init(gpa);
+ defer diags.deinit();
+ const ld_script_result = link.LdScript.parse(gpa, &diags, Path.initCwd(test_path.items), ld_script_bytes.items);
+ if (diags.hasErrors()) {
+ var wip_errors: std.zig.ErrorBundle.Wip = undefined;
+ try wip_errors.init(gpa);
+ defer wip_errors.deinit();
+
+ try diags.addMessagesToBundle(&wip_errors);
+
+ var error_bundle = try wip_errors.toOwnedBundle("");
+ defer error_bundle.deinit(gpa);
+
+ const color: Color = .auto;
+ error_bundle.renderToStdErr(color.renderOptions());
+
+ process.exit(1);
+ }
+
+ var ld_script = ld_script_result catch |err|
+ fatal("{s}: failed to parse linker script: {s}", .{ test_path.items, @errorName(err) });
+ defer ld_script.deinit(gpa);
+
+ try external_linker_inputs.ensureUnusedCapacity(arena, ld_script.args.len);
+ for (ld_script.args) |arg| {
+ const syslib: SystemLib = .{
+ .needed = arg.needed or parent.needed,
+ .weak = parent.weak,
+ .preferred_mode = parent.preferred_mode,
+ .search_strategy = parent.search_strategy,
+ };
+ if (mem.startsWith(u8, arg.path, "-l")) {
+ external_linker_inputs.appendAssumeCapacity(.{ .named = .{
+ .name = try arena.dupe(u8, arg.path["-l".len..]),
+ .info = syslib,
+ } });
+ } else {
+ external_linker_inputs.appendAssumeCapacity(.{ .path = .{
+ .path = Path.initCwd(try arena.dupe(u8, arg.path)),
+ .info = syslib,
+ } });
+ }
+ }
+ return .ok;
+ }
+
fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
error.FileNotFound => break :main_check,
else => |e| fatal("unable to search for {s} library '{s}': {s}", .{
@tagName(link_mode), test_path.items, @errorName(e),
}),
};
- return true;
+ return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
}
// In the case of Darwin, the main check will be .dylib, so here we
// additionally check for .so files.
if (target.isDarwin() and link_mode == .dynamic) so: {
test_path.clearRetainingCapacity();
- try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, lib_name });
- try checked_paths.writer().print("\n {s}", .{test_path.items});
+ try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, lib_name });
+ try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
error.FileNotFound => break :so,
else => |e| fatal("unable to search for so library '{s}': {s}", .{
test_path.items, @errorName(e),
}),
};
- return true;
+ return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
}
// 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) mingw: {
test_path.clearRetainingCapacity();
- try test_path.writer().print("{s}" ++ sep ++ "lib{s}.a", .{
+ try test_path.writer(gpa).print("{s}" ++ sep ++ "lib{s}.a", .{
lib_dir_path, lib_name,
});
- try checked_paths.writer().print("\n {s}", .{test_path.items});
+ try checked_paths.writer(gpa).print("\n {s}", .{test_path.items});
fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
error.FileNotFound => break :mingw,
else => |e| fatal("unable to search for static library '{s}': {s}", .{
test_path.items, @errorName(e),
}),
};
- return true;
+ return finishAccessLibPath(arena, create_module, test_path, link_mode, parent, lib_name);
}
- return false;
+ return .no_match;
+}
+
+fn finishAccessLibPath(
+ arena: Allocator,
+ create_module: *CreateModule,
+ test_path: *std.ArrayListUnmanaged(u8),
+ link_mode: std.builtin.LinkMode,
+ parent: SystemLib,
+ lib_name: []const u8,
+) Allocator.Error!AccessLibPathResult {
+ const path = Path.initCwd(try arena.dupe(u8, test_path.items));
+ switch (link_mode) {
+ .static => try create_module.link_objects.append(arena, .{ .path = path }),
+ .dynamic => try create_module.resolved_system_libs.append(arena, .{
+ .name = lib_name,
+ .lib = .{
+ .needed = parent.needed,
+ .weak = parent.weak,
+ .path = path,
+ },
+ }),
+ }
+ return .ok;
}
fn accessFrameworkPath(
@@ -7634,3 +7787,18 @@ fn handleModArg(
c_source_files_owner_index.* = create_module.c_source_files.items.len;
rc_source_files_owner_index.* = create_module.rc_source_files.items.len;
}
+
+fn stripLibPrefixAndSuffix(path: []const u8, target: std.Target) struct { []const u8, std.builtin.LinkMode } {
+ const prefix = target.libPrefix();
+ const static_suffix = target.staticLibSuffix();
+ const dynamic_suffix = target.dynamicLibSuffix();
+ const basename = fs.path.basename(path);
+ const unlibbed = if (mem.startsWith(u8, basename, prefix)) basename[prefix.len..] else basename;
+ if (mem.endsWith(u8, unlibbed, static_suffix)) return .{
+ unlibbed[0 .. unlibbed.len - static_suffix.len], .static,
+ };
+ if (mem.endsWith(u8, unlibbed, dynamic_suffix)) return .{
+ unlibbed[0 .. unlibbed.len - dynamic_suffix.len], .dynamic,
+ };
+ fatal("unrecognized library path: {s}", .{path});
+}
test/link/elf.zig
@@ -2165,13 +2165,11 @@ fn testLdScriptPathError(b: *Build, opts: Options) *Step {
exe.addLibraryPath(scripts.getDirectory());
exe.linkLibC();
- expectLinkErrors(
- exe,
- test_step,
- .{
- .contains = "error: missing library dependency: GNU ld script '/?/liba.so' requires 'libfoo.so', but file not found",
- },
- );
+ // TODO: A future enhancement could make this error message also mention
+ // the file that references the missing library.
+ expectLinkErrors(exe, test_step, .{
+ .stderr_contains = "error: unable to find dynamic system library 'foo' using strategy 'no_fallback'. searched paths:",
+ });
return test_step;
}
@@ -3907,16 +3905,8 @@ fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
exe.linkLibrary(dylib);
exe.linkLibC();
- // TODO: improve the test harness to be able to selectively match lines in error output
- // while avoiding jankiness
- // expectLinkErrors(exe, test_step, .{ .exact = &.{
- // "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)",
- // "note: while parsing /?/liba.dylib",
- // "error: unexpected error: parsing input file failed with error InvalidLdScript",
- // "note: while parsing /?/liba.dylib",
- // } });
expectLinkErrors(exe, test_step, .{
- .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' (",
+ .contains = "error: failed to parse shared object: BadMagic",
});
return test_step;