From 4706ec81d4f864bc08804d9600937848ff9e4290 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 14 Oct 2024 22:57:12 -0700 Subject: [PATCH] introduce a CLI flag to enable .so scripts; default off 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. When a GNU ld script is encountered, the error message instructs users about the CLI flag that will immediately solve their problem. --- lib/compiler/build_runner.zig | 6 ++++++ lib/std/Build.zig | 1 + lib/std/Build/Step/Compile.zig | 10 ++++++++++ src/link/Elf.zig | 6 ++++++ src/main.zig | 10 +++++++++- test/link/elf.zig | 4 ++++ 6 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 4d643222d723e81ee126660ee01885e23aadd3fa..8c0027b5f9301fcc3b3cd40182b687ac50967c0c 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 988a72c129fa5526418855920e753b1c21d7e8b3..041de3e06f1b06472d412368cc458081048cf001 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 e0b9494c0c5db556668e984eb13409ebce00e023..f0dad8b49aaaa0ad00124f08fb8cbd9a0f97bbd8 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 4f96e10d87b8e893b1f8c95eec66cf20c1e63e4b..35b5a30349688d72c163c6feea09a7a58d60b493 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 1ec9548883e072c9299cdd8caa005b280ea8f873..c68e4b7562b070016727b77b0442943209b311f8 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 0b8aa4514128d246211e162a5ff2001bf80cffc5..fb0e0a7d1dc4190ded30e4f045ef7ccebc00a2de 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, -- 2.54.0