diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 4d643222d7..8c0027b5f9 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -280,6 +280,10 @@ pub fn main() !void { builder.enable_darling = true; } else if (mem.eql(u8, arg, "-fno-darling")) { builder.enable_darling = false; + } else if (mem.eql(u8, arg, "-fallow-so-scripts")) { + graph.allow_so_scripts = true; + } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) { + graph.allow_so_scripts = false; } else if (mem.eql(u8, arg, "-freference-trace")) { builder.reference_trace = 256; } else if (mem.startsWith(u8, arg, "-freference-trace=")) { @@ -1341,6 +1345,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void { \\Advanced Options: \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error \\ -fno-reference-trace Disable reference trace + \\ -fallow-so-scripts Allows .so files to be GNU ld scripts + \\ -fno-allow-so-scripts (default) .so files must be ELF files \\ --build-file [file] Override path to build.zig \\ --cache-dir [path] Override path to local Zig cache directory \\ --global-cache-dir [path] Override path to global Zig cache directory diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 988a72c129..041de3e06f 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -123,6 +123,7 @@ pub const Graph = struct { incremental: ?bool = null, random_seed: u32 = 0, dependency_cache: InitializedDepMap = .empty, + allow_so_scripts: ?bool = null, }; const AvailableDeps = []const struct { []const u8, []const u8 }; diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index e0b9494c0c..f0dad8b49a 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -186,6 +186,15 @@ want_lto: ?bool = null, use_llvm: ?bool, use_lld: ?bool, +/// Corresponds to the `-fallow-so-scripts` / `-fno-allow-so-scripts` CLI +/// flags, overriding the global user setting provided to the `zig build` +/// command. +/// +/// The compiler defaults this value to off so that users whose system shared +/// libraries are all ELF files don't have to pay the cost of checking every +/// file to find out if it is a text file instead. +allow_so_scripts: ?bool = null, + /// This is an advanced setting that can change the intent of this Compile step. /// If this value is non-null, it means that this Compile step exists to /// check for compile errors and return *success* if they match, and failure @@ -1036,6 +1045,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { if (b.reference_trace) |some| { try zig_args.append(try std.fmt.allocPrint(arena, "-freference-trace={d}", .{some})); } + try addFlag(&zig_args, "allow-so-scripts", compile.allow_so_scripts orelse b.graph.allow_so_scripts); try addFlag(&zig_args, "llvm", compile.use_llvm); try addFlag(&zig_args, "lld", compile.use_lld); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 4f96e10d87..35b5a30349 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1337,6 +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 => { + var notes = diags.addErrorWithNotes(2) catch return diags.setAllocFailure(); + notes.addMsg("failed to parse shared object: {s}", .{@errorName(err)}) catch return diags.setAllocFailure(); + notes.addNote("while parsing {}", .{path}) catch return diags.setAllocFailure(); + notes.addNote("{s}", .{@as([]const u8, "the file may be a GNU ld script, in which case it is not an ELF file but a text file referencing other libraries to link. In this case, avoid depending on the library, convince your system administrators to refrain from using this kind of file, or pass -fallow-so-scripts to force the compiler to check every shared library in case it is an ld script.")}) catch return diags.setAllocFailure(); + }, else => |e| diags.addParseError(path, "failed to parse shared object: {s}", .{@errorName(e)}), }, .static_library => parseArchive(self, path, must_link) catch |err| switch (err) { diff --git a/src/main.zig b/src/main.zig index 1ec9548883..c68e4b7562 100644 --- a/src/main.zig +++ b/src/main.zig @@ -555,6 +555,8 @@ const usage_build_generic = \\ -fno-each-lib-rpath Prevent adding rpath for each used dynamic library \\ -fallow-shlib-undefined Allows undefined symbols in shared libraries \\ -fno-allow-shlib-undefined Disallows undefined symbols in shared libraries + \\ -fallow-so-scripts Allows .so files to be GNU ld scripts + \\ -fno-allow-so-scripts (default) .so files must be ELF files \\ --build-id[=style] At a minor link-time expense, coordinates stripped binaries \\ fast, uuid, sha1, md5 with debug symbols via a '.note.gnu.build-id' section \\ 0x[hexstring] Maximum 32 bytes @@ -1003,6 +1005,7 @@ fn buildOutputType( .libc_paths_file = try EnvVar.ZIG_LIBC.get(arena), .link_objects = .{}, .native_system_include_paths = &.{}, + .allow_so_scripts = false, }; // before arg parsing, check for the NO_COLOR and CLICOLOR_FORCE environment variables @@ -1573,6 +1576,10 @@ fn buildOutputType( linker_allow_shlib_undefined = true; } else if (mem.eql(u8, arg, "-fno-allow-shlib-undefined")) { linker_allow_shlib_undefined = false; + } else if (mem.eql(u8, arg, "-fallow-so-scripts")) { + create_module.allow_so_scripts = true; + } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) { + create_module.allow_so_scripts = false; } else if (mem.eql(u8, arg, "-z")) { const z_arg = args_iter.nextOrFatal(); if (mem.eql(u8, z_arg, "nodelete")) { @@ -3679,6 +3686,7 @@ const CreateModule = struct { each_lib_rpath: ?bool, libc_paths_file: ?[]const u8, link_objects: std.ArrayListUnmanaged(Compilation.LinkObject), + allow_so_scripts: bool, }; fn createModule( @@ -6950,7 +6958,7 @@ fn accessLibPath( // 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")) { + if (create_module.allow_so_scripts and 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}", .{ diff --git a/test/link/elf.zig b/test/link/elf.zig index 0b8aa45141..fb0e0a7d1d 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -2145,6 +2145,7 @@ fn testLdScript(b: *Build, opts: Options) *Step { exe.addLibraryPath(dso.getEmittedBinDirectory()); exe.addRPath(dso.getEmittedBinDirectory()); exe.linkLibC(); + exe.allow_so_scripts = true; const run = addRunArtifact(exe); run.expectExitCode(0); @@ -2164,6 +2165,7 @@ fn testLdScriptPathError(b: *Build, opts: Options) *Step { exe.linkSystemLibrary2("a", .{}); exe.addLibraryPath(scripts.getDirectory()); exe.linkLibC(); + exe.allow_so_scripts = true; // TODO: A future enhancement could make this error message also mention // the file that references the missing library. @@ -2201,6 +2203,7 @@ fn testLdScriptAllowUndefinedVersion(b: *Build, opts: Options) *Step { }); exe.linkLibrary(so); exe.linkLibC(); + exe.allow_so_scripts = true; const run = addRunArtifact(exe); run.expectStdErrEqual("3\n"); @@ -2223,6 +2226,7 @@ fn testLdScriptDisallowUndefinedVersion(b: *Build, opts: Options) *Step { const ld = b.addWriteFiles().add("add.ld", "VERSION { ADD_1.0 { global: add; sub; local: *; }; }"); so.setLinkerScript(ld); so.linker_allow_undefined_version = false; + so.allow_so_scripts = true; expectLinkErrors( so,