Commit 336466c9df

Andrew Kelley <andrew@ziglang.org>
2024-10-22 05:22:27
glibc sometimes makes archives be ld scripts
it is incredible how many bad ideas glibc is bundled into one project.
1 parent ccac111
Changed files (3)
src/link/Elf/Archive.zig
@@ -16,6 +16,15 @@ pub fn parse(
     handle_index: File.HandleIndex,
 ) !Archive {
     const handle = file_handles.items[handle_index];
+    var pos: usize = 0;
+    {
+        var magic_buffer: [elf.ARMAG.len]u8 = undefined;
+        const n = try handle.preadAll(&magic_buffer, pos);
+        if (n != magic_buffer.len) return error.BadMagic;
+        if (!mem.eql(u8, &magic_buffer, elf.ARMAG)) return error.BadMagic;
+        pos += magic_buffer.len;
+    }
+
     const size = (try handle.stat()).size;
 
     var objects: std.ArrayListUnmanaged(Object) = .empty;
@@ -24,17 +33,14 @@ pub fn parse(
     var strtab: std.ArrayListUnmanaged(u8) = .empty;
     defer strtab.deinit(gpa);
 
-    var pos: usize = elf.ARMAG.len;
-    while (true) {
-        if (pos >= size) break;
-        if (!mem.isAligned(pos, 2)) pos += 1;
+    while (pos < size) {
+        pos = mem.alignForward(usize, pos, 2);
 
-        var hdr_buffer: [@sizeOf(elf.ar_hdr)]u8 = undefined;
+        var hdr: elf.ar_hdr = undefined;
         {
-            const amt = try handle.preadAll(&hdr_buffer, pos);
-            if (amt != @sizeOf(elf.ar_hdr)) return error.InputOutput;
+            const n = try handle.preadAll(mem.asBytes(&hdr), pos);
+            if (n != @sizeOf(elf.ar_hdr)) return error.UnexpectedEndOfFile;
         }
-        const hdr = @as(*align(1) const elf.ar_hdr, @ptrCast(&hdr_buffer)).*;
         pos += @sizeOf(elf.ar_hdr);
 
         if (!mem.eql(u8, &hdr.ar_fmag, elf.ARFMAG)) {
src/link/Elf.zig
@@ -1098,20 +1098,6 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void {
     }
 }
 
-pub const ParseError = error{
-    /// Indicates the error is already reported on `Compilation.link_diags`.
-    LinkFailure,
-
-    OutOfMemory,
-    Overflow,
-    InputOutput,
-    EndOfStream,
-    FileSystem,
-    NotSupported,
-    InvalidCharacter,
-    UnknownFileType,
-} || fs.Dir.AccessError || fs.File.SeekError || fs.File.OpenError || fs.File.ReadError;
-
 pub fn openParseObjectReportingFailure(self: *Elf, path: Path) void {
     const diags = &self.base.comp.link_diags;
     const obj = link.openObject(path, false, false) catch |err| {
@@ -1130,7 +1116,7 @@ fn parseObjectReportingFailure(self: *Elf, obj: link.Input.Object) void {
     };
 }
 
-fn parseObject(self: *Elf, obj: link.Input.Object) ParseError!void {
+fn parseObject(self: *Elf, obj: link.Input.Object) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1175,7 +1161,7 @@ fn parseArchive(
     objects: *std.ArrayListUnmanaged(File.Index),
     obj: link.Input.Object,
     is_static_lib: bool,
-) ParseError!void {
+) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
src/link.zig
@@ -1009,11 +1009,25 @@ pub const File = struct {
     }
 
     /// Opens a path as a static library and parses it into the linker.
-    fn openLoadArchive(base: *File, path: Path) anyerror!void {
-        const diags = &base.comp.link_diags;
-        const input = try openArchiveInput(diags, path, false, false);
-        errdefer input.archive.file.close();
-        try loadInput(base, input);
+    /// If `query` is non-null, allows GNU ld scripts.
+    fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void {
+        if (opt_query) |query| {
+            const archive = try openObject(path, query.must_link, query.hidden);
+            errdefer archive.file.close();
+            loadInput(base, .{ .archive = archive }) catch |err| switch (err) {
+                error.BadMagic, error.UnexpectedEndOfFile => {
+                    if (base.tag != .elf) return err;
+                    try loadGnuLdScript(base, path, query, archive.file);
+                    archive.file.close();
+                    return;
+                },
+                else => return err,
+            };
+        } else {
+            const archive = try openObject(path, false, false);
+            errdefer archive.file.close();
+            try loadInput(base, .{ .archive = archive });
+        }
     }
 
     /// Opens a path as a shared library and parses it into the linker.
@@ -1060,7 +1074,7 @@ pub const File = struct {
                     switch (Compilation.classifyFileExt(arg.path)) {
                         .shared_library => try openLoadDso(base, new_path, query),
                         .object => try openLoadObject(base, new_path),
-                        .static_library => try openLoadArchive(base, new_path),
+                        .static_library => try openLoadArchive(base, new_path, query),
                         else => diags.addParseError(path, "GNU ld script references file with unrecognized extension: {s}", .{arg.path}),
                     }
                 } else {
@@ -1408,33 +1422,51 @@ pub const File = struct {
                     assert(mem.startsWith(u8, flag, "-l"));
                     const lib_name = flag["-l".len..];
                     switch (comp.config.link_mode) {
-                        .dynamic => d: {
-                            const path = Path.initCwd(
+                        .dynamic => {
+                            const dso_path = Path.initCwd(
                                 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
                                     crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(),
                                 }) catch return diags.setAllocFailure(),
                             );
-                            base.openLoadDso(path, .{
+                            base.openLoadDso(dso_path, .{
                                 .preferred_mode = .dynamic,
                                 .search_strategy = .paths_first,
                             }) catch |err| switch (err) {
-                                error.FileNotFound => break :d, // also try static
+                                error.FileNotFound => {
+                                    // Also try static.
+                                    const archive_path = Path.initCwd(
+                                        std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
+                                            crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
+                                        }) catch return diags.setAllocFailure(),
+                                    );
+                                    base.openLoadArchive(archive_path, .{
+                                        .preferred_mode = .dynamic,
+                                        .search_strategy = .paths_first,
+                                    }) catch |archive_err| switch (archive_err) {
+                                        error.LinkFailure => return, // error reported via diags
+                                        else => |e| diags.addParseError(dso_path, "failed to parse archive {}: {s}", .{ archive_path, @errorName(e) }),
+                                    };
+                                },
+                                error.LinkFailure => return, // error reported via diags
+                                else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}),
+                            };
+                        },
+                        .static => {
+                            const path = Path.initCwd(
+                                std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
+                                    crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
+                                }) catch return diags.setAllocFailure(),
+                            );
+                            // glibc sometimes makes even archive files GNU ld scripts.
+                            base.openLoadArchive(path, .{
+                                .preferred_mode = .static,
+                                .search_strategy = .no_fallback,
+                            }) catch |err| switch (err) {
                                 error.LinkFailure => return, // error reported via diags
-                                else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}),
+                                else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
                             };
-                            continue;
                         },
-                        .static => {},
                     }
-                    const path = Path.initCwd(
-                        std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
-                            crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
-                        }) catch return diags.setAllocFailure(),
-                    );
-                    base.openLoadArchive(path) catch |err| switch (err) {
-                        error.LinkFailure => return, // error reported via diags
-                        else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
-                    };
                 }
             },
             .load_object => |path| {
@@ -1444,7 +1476,7 @@ pub const File = struct {
                 };
             },
             .load_archive => |path| {
-                base.openLoadArchive(path) catch |err| switch (err) {
+                base.openLoadArchive(path, null) catch |err| switch (err) {
                     error.LinkFailure => return, // error reported via link_diags
                     else => |e| comp.link_diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
                 };