Commit a94d5895cf

Jakub Konka <kubkon@jakubkonka.com>
2024-02-12 21:45:15
elf: do not prealloc input objects, pread selectively
1 parent fad5e7a
src/link/Elf/Archive.zig
@@ -1,8 +1,5 @@
-path: []const u8,
-data: []const u8,
-
 objects: std.ArrayListUnmanaged(Object) = .{},
-strtab: []const u8 = &[0]u8{},
+strtab: std.ArrayListUnmanaged(u8) = .{},
 
 pub fn isArchive(path: []const u8) !bool {
     const file = try std.fs.cwd().openFile(path, .{});
@@ -14,68 +11,79 @@ pub fn isArchive(path: []const u8) !bool {
 }
 
 pub fn deinit(self: *Archive, allocator: Allocator) void {
-    allocator.free(self.path);
-    allocator.free(self.data);
     self.objects.deinit(allocator);
+    self.strtab.deinit(allocator);
 }
 
-pub fn parse(self: *Archive, elf_file: *Elf) !void {
+pub fn parse(self: *Archive, elf_file: *Elf, path: []const u8, handle_index: File.HandleIndex) !void {
     const comp = elf_file.base.comp;
     const gpa = comp.gpa;
+    const handle = elf_file.fileHandle(handle_index);
+    const size = (try handle.stat()).size;
 
-    var stream = std.io.fixedBufferStream(self.data);
-    const reader = stream.reader();
+    const reader = handle.reader();
     _ = try reader.readBytesNoEof(elf.ARMAG.len);
 
+    var pos: usize = elf.ARMAG.len;
     while (true) {
-        if (stream.pos >= self.data.len) break;
-        if (!mem.isAligned(stream.pos, 2)) stream.pos += 1;
+        if (pos >= size) break;
+        if (!mem.isAligned(pos, 2)) {
+            try handle.seekBy(1);
+            pos += 1;
+        }
 
         const hdr = try reader.readStruct(elf.ar_hdr);
+        pos += @sizeOf(elf.ar_hdr);
 
         if (!mem.eql(u8, &hdr.ar_fmag, elf.ARFMAG)) {
-            try elf_file.reportParseError(self.path, "invalid archive header delimiter: {s}", .{
+            try elf_file.reportParseError(path, "invalid archive header delimiter: {s}", .{
                 std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag),
             });
             return error.MalformedArchive;
         }
 
-        const size = try hdr.size();
+        const obj_size = try hdr.size();
         defer {
-            _ = stream.seekBy(size) catch {};
+            _ = handle.seekBy(obj_size) catch {};
+            pos += obj_size;
         }
 
         if (hdr.isSymtab() or hdr.isSymtab64()) continue;
         if (hdr.isStrtab()) {
-            self.strtab = self.data[stream.pos..][0..size];
+            try self.strtab.resize(gpa, obj_size);
+            const amt = try handle.preadAll(self.strtab.items, pos);
+            if (amt != obj_size) return error.InputOutput;
             continue;
         }
         if (hdr.isSymdef() or hdr.isSymdefSorted()) continue;
 
         const name = if (hdr.name()) |name|
-            try gpa.dupe(u8, name)
+            name
         else if (try hdr.nameOffset()) |off|
-            try gpa.dupe(u8, self.getString(off))
+            self.getString(off)
         else
             unreachable;
 
         const object = Object{
-            .archive = try gpa.dupe(u8, self.path),
-            .path = name,
-            .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]),
+            .archive = .{
+                .path = try gpa.dupe(u8, path),
+                .offset = pos,
+            },
+            .path = try gpa.dupe(u8, name),
+            .file_handle = handle_index,
             .index = undefined,
             .alive = false,
         };
 
-        log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path });
+        log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, path });
 
         try self.objects.append(gpa, object);
     }
 }
 
 fn getString(self: Archive, off: u32) []const u8 {
-    assert(off < self.strtab.len);
-    const name = mem.sliceTo(@as([*:'\n']const u8, @ptrCast(self.strtab.ptr + off)), 0);
+    assert(off < self.strtab.items.len);
+    const name = mem.sliceTo(@as([*:'\n']const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
     return name[0 .. name.len - 1];
 }
 
@@ -86,7 +94,7 @@ pub fn setArHdr(opts: struct {
         name: []const u8,
         name_off: u32,
     },
-    size: u32,
+    size: usize,
 }) elf.ar_hdr {
     var hdr: elf.ar_hdr = .{
         .ar_name = undefined,
src/link/Elf/Atom.zig
@@ -22,6 +22,12 @@ output_section_index: u16 = 0,
 /// Index of the input section containing this atom's relocs.
 relocs_section_index: u32 = 0,
 
+/// Start index of the relocations belonging to this atom.
+rel_index: u32 = 0,
+
+/// Number of relocations belonging to this atom.
+rel_num: u32 = 0,
+
 /// Index of this atom in the linker's atoms table.
 atom_index: Index = 0,
 
@@ -52,7 +58,7 @@ pub fn file(self: Atom, elf_file: *Elf) ?File {
     return elf_file.file(self.file_index);
 }
 
-pub fn inputShdr(self: Atom, elf_file: *Elf) Object.ElfShdr {
+pub fn inputShdr(self: Atom, elf_file: *Elf) elf.Elf64_Shdr {
     return switch (self.file(elf_file).?) {
         .object => |x| x.shdrs.items[self.input_section_index],
         .zig_object => |x| x.inputShdr(self.atom_index, elf_file),
@@ -289,7 +295,7 @@ pub fn relocs(self: Atom, elf_file: *Elf) []align(1) const elf.Elf64_Rela {
     const shndx = self.relocsShndx() orelse return &[0]elf.Elf64_Rela{};
     return switch (self.file(elf_file).?) {
         .zig_object => |x| x.relocs.items[shndx].items,
-        .object => |x| x.getRelocs(shndx),
+        .object => |x| x.relocs.items[self.rel_index..][0..self.rel_num],
         else => unreachable,
     };
 }
src/link/Elf/eh_frame.zig
@@ -5,7 +5,6 @@ pub const Fde = struct {
     cie_index: u32,
     rel_index: u32 = 0,
     rel_num: u32 = 0,
-    rel_section_index: u32 = 0,
     input_section_index: u32 = 0,
     file_index: u32 = 0,
     alive: bool = true,
@@ -20,10 +19,9 @@ pub const Fde = struct {
         return base + fde.out_offset;
     }
 
-    pub fn data(fde: Fde, elf_file: *Elf) []const u8 {
+    pub fn data(fde: Fde, elf_file: *Elf) []u8 {
         const object = elf_file.file(fde.file_index).?.object;
-        const contents = object.shdrContents(fde.input_section_index);
-        return contents[fde.offset..][0..fde.calcSize()];
+        return object.eh_frame_data.items[fde.offset..][0..fde.calcSize()];
     }
 
     pub fn cie(fde: Fde, elf_file: *Elf) Cie {
@@ -50,7 +48,7 @@ pub const Fde = struct {
 
     pub fn relocs(fde: Fde, elf_file: *Elf) []align(1) const elf.Elf64_Rela {
         const object = elf_file.file(fde.file_index).?.object;
-        return object.getRelocs(fde.rel_section_index)[fde.rel_index..][0..fde.rel_num];
+        return object.relocs.items[fde.rel_index..][0..fde.rel_num];
     }
 
     pub fn format(
@@ -106,7 +104,6 @@ pub const Cie = struct {
     size: usize,
     rel_index: u32 = 0,
     rel_num: u32 = 0,
-    rel_section_index: u32 = 0,
     input_section_index: u32 = 0,
     file_index: u32 = 0,
     /// Includes 4byte size cell.
@@ -121,10 +118,9 @@ pub const Cie = struct {
         return base + cie.out_offset;
     }
 
-    pub fn data(cie: Cie, elf_file: *Elf) []const u8 {
+    pub fn data(cie: Cie, elf_file: *Elf) []u8 {
         const object = elf_file.file(cie.file_index).?.object;
-        const contents = object.shdrContents(cie.input_section_index);
-        return contents[cie.offset..][0..cie.calcSize()];
+        return object.eh_frame_data.items[cie.offset..][0..cie.calcSize()];
     }
 
     pub fn calcSize(cie: Cie) usize {
@@ -133,7 +129,7 @@ pub const Cie = struct {
 
     pub fn relocs(cie: Cie, elf_file: *Elf) []align(1) const elf.Elf64_Rela {
         const object = elf_file.file(cie.file_index).?.object;
-        return object.getRelocs(cie.rel_section_index)[cie.rel_index..][0..cie.rel_num];
+        return object.relocs.items[cie.rel_index..][0..cie.rel_num];
     }
 
     pub fn eql(cie: Cie, other: Cie, elf_file: *Elf) bool {
@@ -330,9 +326,6 @@ fn resolveReloc(rec: anytype, sym: *const Symbol, rel: elf.Elf64_Rela, elf_file:
 }
 
 pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void {
-    const comp = elf_file.base.comp;
-    const gpa = comp.gpa;
-
     relocs_log.debug("{x}: .eh_frame", .{elf_file.shdrs.items[elf_file.eh_frame_section_index.?].sh_addr});
 
     for (elf_file.objects.items) |index| {
@@ -341,8 +334,7 @@ pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void {
         for (object.cies.items) |cie| {
             if (!cie.alive) continue;
 
-            const contents = try gpa.dupe(u8, cie.data(elf_file));
-            defer gpa.free(contents);
+            const contents = cie.data(elf_file);
 
             for (cie.relocs(elf_file)) |rel| {
                 const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);
@@ -359,8 +351,7 @@ pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void {
         for (object.fdes.items) |fde| {
             if (!fde.alive) continue;
 
-            const contents = try gpa.dupe(u8, fde.data(elf_file));
-            defer gpa.free(contents);
+            const contents = fde.data(elf_file);
 
             std.mem.writeInt(
                 i32,
@@ -382,9 +373,6 @@ pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void {
 }
 
 pub fn writeEhFrameObject(elf_file: *Elf, writer: anytype) !void {
-    const comp = elf_file.base.comp;
-    const gpa = comp.gpa;
-
     for (elf_file.objects.items) |index| {
         const object = elf_file.file(index).?.object;
 
@@ -400,8 +388,7 @@ pub fn writeEhFrameObject(elf_file: *Elf, writer: anytype) !void {
         for (object.fdes.items) |fde| {
             if (!fde.alive) continue;
 
-            const contents = try gpa.dupe(u8, fde.data(elf_file));
-            defer gpa.free(contents);
+            const contents = fde.data(elf_file);
 
             std.mem.writeInt(
                 i32,
src/link/Elf/file.zig
@@ -162,18 +162,18 @@ pub const File = union(enum) {
         state.name_off = try ar_strtab.insert(allocator, path);
     }
 
-    pub fn updateArSize(file: File) void {
+    pub fn updateArSize(file: File, elf_file: *Elf) !void {
         return switch (file) {
             .zig_object => |x| x.updateArSize(),
-            .object => |x| x.updateArSize(),
+            .object => |x| x.updateArSize(elf_file),
             inline else => unreachable,
         };
     }
 
-    pub fn writeAr(file: File, writer: anytype) !void {
+    pub fn writeAr(file: File, elf_file: *Elf, writer: anytype) !void {
         return switch (file) {
             .zig_object => |x| x.writeAr(writer),
-            .object => |x| x.writeAr(writer),
+            .object => |x| x.writeAr(elf_file, writer),
             inline else => unreachable,
         };
     }
@@ -187,6 +187,9 @@ pub const File = union(enum) {
         object: Object,
         shared_object: SharedObject,
     };
+
+    pub const Handle = std.fs.File;
+    pub const HandleIndex = Index;
 };
 
 const std = @import("std");
src/link/Elf/Object.zig
@@ -1,10 +1,10 @@
-archive: ?[]const u8 = null,
+archive: ?InArchive = null,
 path: []const u8,
-data: []const u8,
+file_handle: File.HandleIndex,
 index: File.Index,
 
 header: ?elf.Elf64_Ehdr = null,
-shdrs: std.ArrayListUnmanaged(ElfShdr) = .{},
+shdrs: std.ArrayListUnmanaged(elf.Elf64_Shdr) = .{},
 
 symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
 strtab: std.ArrayListUnmanaged(u8) = .{},
@@ -12,9 +12,12 @@ first_global: ?Symbol.Index = null,
 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
 atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
 comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup.Index) = .{},
+comdat_group_data: std.ArrayListUnmanaged(u32) = .{},
+relocs: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{},
 
 fdes: std.ArrayListUnmanaged(Fde) = .{},
 cies: std.ArrayListUnmanaged(Cie) = .{},
+eh_frame_data: std.ArrayListUnmanaged(u8) = .{},
 
 alive: bool = true,
 num_dynrelocs: u32 = 0,
@@ -35,24 +38,30 @@ pub fn isObject(path: []const u8) !bool {
 }
 
 pub fn deinit(self: *Object, allocator: Allocator) void {
-    if (self.archive) |path| allocator.free(path);
+    if (self.archive) |*ar| allocator.free(ar.path);
     allocator.free(self.path);
-    allocator.free(self.data);
     self.shdrs.deinit(allocator);
     self.symtab.deinit(allocator);
     self.strtab.deinit(allocator);
     self.symbols.deinit(allocator);
     self.atoms.deinit(allocator);
     self.comdat_groups.deinit(allocator);
+    self.comdat_group_data.deinit(allocator);
+    self.relocs.deinit(allocator);
     self.fdes.deinit(allocator);
     self.cies.deinit(allocator);
+    self.eh_frame_data.deinit(allocator);
 }
 
 pub fn parse(self: *Object, elf_file: *Elf) !void {
-    var stream = std.io.fixedBufferStream(self.data);
-    const reader = stream.reader();
+    const gpa = elf_file.base.comp.gpa;
+    const offset = if (self.archive) |ar| ar.offset else 0;
+    const handle = elf_file.fileHandle(self.file_handle);
+    const file_size = (try handle.stat()).size;
 
-    self.header = try reader.readStruct(elf.Elf64_Ehdr);
+    const header_buffer = try Elf.preadAllAlloc(gpa, handle, offset, @sizeOf(elf.Elf64_Ehdr));
+    defer gpa.free(header_buffer);
+    self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*;
 
     const target = elf_file.base.comp.root_mod.resolved_target.result;
     if (target.cpu.arch != self.header.?.e_machine.toTargetCpuArch().?) {
@@ -66,12 +75,10 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
 
     if (self.header.?.e_shnum == 0) return;
 
-    const comp = elf_file.base.comp;
-    const gpa = comp.gpa;
-
-    if (self.data.len < self.header.?.e_shoff or
-        self.data.len < self.header.?.e_shoff + @as(u64, @intCast(self.header.?.e_shnum)) * @sizeOf(elf.Elf64_Shdr))
-    {
+    const shoff = math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
+    const shnum = math.cast(usize, self.header.?.e_shnum) orelse return error.Overflow;
+    const shsize = shnum * @sizeOf(elf.Elf64_Shdr);
+    if (file_size < offset + shoff or file_size < offset + shoff + shsize) {
         try elf_file.reportParseError2(
             self.index,
             "corrupt header: section header table extends past the end of file",
@@ -80,25 +87,23 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
         return error.MalformedObject;
     }
 
-    const shoff = math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
-    const shdrs = @as(
-        [*]align(1) const elf.Elf64_Shdr,
-        @ptrCast(self.data.ptr + shoff),
-    )[0..self.header.?.e_shnum];
-    try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len);
+    const shdrs_buffer = try Elf.preadAllAlloc(gpa, handle, offset + shoff, shsize);
+    defer gpa.free(shdrs_buffer);
+    const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum];
+    try self.shdrs.appendUnalignedSlice(gpa, shdrs);
 
-    for (shdrs) |shdr| {
+    for (self.shdrs.items) |shdr| {
         if (shdr.sh_type != elf.SHT_NOBITS) {
-            if (self.data.len < shdr.sh_offset or self.data.len < shdr.sh_offset + shdr.sh_size) {
+            if (file_size < offset + shdr.sh_offset or file_size < offset + shdr.sh_offset + shdr.sh_size) {
                 try elf_file.reportParseError2(self.index, "corrupt section: extends past the end of file", .{});
                 return error.MalformedObject;
             }
         }
-        self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr));
     }
 
-    const shstrtab = self.shdrContents(self.header.?.e_shstrndx);
-    for (shdrs) |shdr| {
+    const shstrtab = try self.preadShdrContentsAlloc(gpa, handle, self.header.?.e_shstrndx);
+    defer gpa.free(shstrtab);
+    for (self.shdrs.items) |shdr| {
         if (shdr.sh_name >= shstrtab.len) {
             try elf_file.reportParseError2(self.index, "corrupt section name offset", .{});
             return error.MalformedObject;
@@ -112,10 +117,11 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
     } else null;
 
     if (symtab_index) |index| {
-        const shdr = shdrs[index];
+        const shdr = self.shdrs.items[index];
         self.first_global = shdr.sh_info;
 
-        const raw_symtab = self.shdrContents(index);
+        const raw_symtab = try self.preadShdrContentsAlloc(gpa, handle, index);
+        defer gpa.free(raw_symtab);
         const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch {
             try elf_file.reportParseError2(self.index, "symbol table not evenly divisible", .{});
             return error.MalformedObject;
@@ -123,7 +129,9 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
         const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms];
 
         const strtab_bias = @as(u32, @intCast(self.strtab.items.len));
-        try self.strtab.appendSlice(gpa, self.shdrContents(@as(u16, @intCast(shdr.sh_link))));
+        const strtab = try self.preadShdrContentsAlloc(gpa, handle, shdr.sh_link);
+        defer gpa.free(strtab);
+        try self.strtab.appendSlice(gpa, strtab);
 
         try self.symtab.ensureUnusedCapacity(gpa, symtab.len);
         for (symtab) |sym| {
@@ -138,22 +146,23 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
 }
 
 pub fn init(self: *Object, elf_file: *Elf) !void {
-    try self.initAtoms(elf_file);
-    try self.initSymtab(elf_file);
+    const gpa = elf_file.base.comp.gpa;
+    const handle = elf_file.fileHandle(self.file_handle);
+
+    try self.initAtoms(gpa, handle, elf_file);
+    try self.initSymtab(gpa, elf_file);
 
     for (self.shdrs.items, 0..) |shdr, i| {
         const atom = elf_file.atom(self.atoms.items[i]) orelse continue;
         if (!atom.flags.alive) continue;
         if (shdr.sh_type == elf.SHT_X86_64_UNWIND or mem.eql(u8, atom.name(elf_file), ".eh_frame"))
-            try self.parseEhFrame(@as(u16, @intCast(i)), elf_file);
+            try self.parseEhFrame(gpa, handle, @as(u32, @intCast(i)), elf_file);
     }
 }
 
-fn initAtoms(self: *Object, elf_file: *Elf) !void {
-    const comp = elf_file.base.comp;
-    const gpa = comp.gpa;
+fn initAtoms(self: *Object, allocator: Allocator, handle: std.fs.File, elf_file: *Elf) !void {
     const shdrs = self.shdrs.items;
-    try self.atoms.resize(gpa, shdrs.len);
+    try self.atoms.resize(allocator, shdrs.len);
     @memset(self.atoms.items, 0);
 
     for (shdrs, 0..) |shdr, i| {
@@ -177,8 +186,9 @@ fn initAtoms(self: *Object, elf_file: *Elf) !void {
                     break :blk self.getString(group_info_sym.st_name);
                 };
 
-                const shndx = @as(u16, @intCast(i));
-                const group_raw_data = self.shdrContents(shndx);
+                const shndx = @as(u32, @intCast(i));
+                const group_raw_data = try self.preadShdrContentsAlloc(allocator, handle, shndx);
+                defer allocator.free(group_raw_data);
                 const group_nmembers = @divExact(group_raw_data.len, @sizeOf(u32));
                 const group_members = @as([*]align(1) const u32, @ptrCast(group_raw_data.ptr))[0..group_nmembers];
 
@@ -188,14 +198,20 @@ fn initAtoms(self: *Object, elf_file: *Elf) !void {
                     continue;
                 }
 
+                const group_start = @as(u32, @intCast(self.comdat_group_data.items.len));
+                try self.comdat_group_data.appendUnalignedSlice(allocator, group_members[1..]);
+
                 const gop = try elf_file.getOrCreateComdatGroupOwner(group_signature);
                 const comdat_group_index = try elf_file.addComdatGroup();
                 const comdat_group = elf_file.comdatGroup(comdat_group_index);
                 comdat_group.* = .{
                     .owner = gop.index,
+                    .file = self.index,
                     .shndx = shndx,
+                    .members_start = group_start,
+                    .members_len = @intCast(group_nmembers - 1),
                 };
-                try self.comdat_groups.append(gpa, comdat_group_index);
+                try self.comdat_groups.append(allocator, comdat_group_index);
             },
 
             elf.SHT_SYMTAB_SHNDX => @panic("TODO SHT_SYMTAB_SHNDX"),
@@ -210,7 +226,7 @@ fn initAtoms(self: *Object, elf_file: *Elf) !void {
             else => {
                 const shndx = @as(u16, @intCast(i));
                 if (self.skipShdr(shndx, elf_file)) continue;
-                try self.addAtom(shdr, shndx, elf_file);
+                try self.addAtom(allocator, handle, shdr, shndx, elf_file);
             },
         }
     }
@@ -220,14 +236,19 @@ fn initAtoms(self: *Object, elf_file: *Elf) !void {
         elf.SHT_REL, elf.SHT_RELA => {
             const atom_index = self.atoms.items[shdr.sh_info];
             if (elf_file.atom(atom_index)) |atom| {
-                atom.relocs_section_index = @as(u16, @intCast(i));
+                const relocs = try self.preadRelocsAlloc(allocator, handle, @intCast(i));
+                defer allocator.free(relocs);
+                atom.relocs_section_index = @intCast(i);
+                atom.rel_index = @intCast(self.relocs.items.len);
+                atom.rel_num = @intCast(relocs.len);
+                try self.relocs.appendUnalignedSlice(allocator, relocs);
             }
         },
         else => {},
     };
 }
 
-fn addAtom(self: *Object, shdr: ElfShdr, shndx: u16, elf_file: *Elf) error{OutOfMemory}!void {
+fn addAtom(self: *Object, allocator: Allocator, handle: std.fs.File, shdr: elf.Elf64_Shdr, shndx: u32, elf_file: *Elf) !void {
     const atom_index = try elf_file.addAtom();
     const atom = elf_file.atom(atom_index).?;
     atom.atom_index = atom_index;
@@ -237,7 +258,8 @@ fn addAtom(self: *Object, shdr: ElfShdr, shndx: u16, elf_file: *Elf) error{OutOf
     self.atoms.items[shndx] = atom_index;
 
     if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
-        const data = self.shdrContents(shndx);
+        const data = try self.preadShdrContentsAlloc(allocator, handle, shndx);
+        defer allocator.free(data);
         const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
         atom.size = chdr.ch_size;
         atom.alignment = Alignment.fromNonzeroByteUnits(chdr.ch_addralign);
@@ -247,7 +269,7 @@ fn addAtom(self: *Object, shdr: ElfShdr, shndx: u16, elf_file: *Elf) error{OutOf
     }
 }
 
-fn initOutputSection(self: Object, elf_file: *Elf, shdr: ElfShdr) error{OutOfMemory}!u16 {
+fn initOutputSection(self: Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) error{OutOfMemory}!u16 {
     const name = blk: {
         const name = self.getString(shdr.sh_name);
         if (elf_file.base.isRelocatable()) break :blk name;
@@ -310,12 +332,10 @@ fn skipShdr(self: *Object, index: u16, elf_file: *Elf) bool {
     return ignore;
 }
 
-fn initSymtab(self: *Object, elf_file: *Elf) !void {
-    const comp = elf_file.base.comp;
-    const gpa = comp.gpa;
+fn initSymtab(self: *Object, allocator: Allocator, elf_file: *Elf) !void {
     const first_global = self.first_global orelse self.symtab.items.len;
 
-    try self.symbols.ensureTotalCapacityPrecise(gpa, self.symtab.items.len);
+    try self.symbols.ensureTotalCapacityPrecise(allocator, self.symtab.items.len);
 
     for (self.symtab.items[0..first_global], 0..) |sym, i| {
         const index = try elf_file.addSymbol();
@@ -335,19 +355,24 @@ fn initSymtab(self: *Object, elf_file: *Elf) !void {
     }
 }
 
-fn parseEhFrame(self: *Object, shndx: u16, elf_file: *Elf) !void {
+fn parseEhFrame(self: *Object, allocator: Allocator, handle: std.fs.File, shndx: u32, elf_file: *Elf) !void {
     const relocs_shndx = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) {
-        elf.SHT_RELA => if (shdr.sh_info == shndx) break @as(u16, @intCast(i)),
+        elf.SHT_RELA => if (shdr.sh_info == shndx) break @as(u32, @intCast(i)),
         else => {},
     } else {
+        // TODO: convert into an error
         log.debug("{s}: missing reloc section for unwind info section", .{self.fmtPath()});
         return;
     };
 
-    const comp = elf_file.base.comp;
-    const gpa = comp.gpa;
-    const raw = self.shdrContents(shndx);
-    const relocs = self.getRelocs(relocs_shndx);
+    const raw = try self.preadShdrContentsAlloc(allocator, handle, shndx);
+    defer allocator.free(raw);
+    const data_start = @as(u32, @intCast(self.eh_frame_data.items.len));
+    try self.eh_frame_data.appendSlice(allocator, raw);
+    const relocs = try self.preadRelocsAlloc(allocator, handle, relocs_shndx);
+    defer allocator.free(relocs);
+    const rel_start = @as(u32, @intCast(self.relocs.items.len));
+    try self.relocs.appendUnalignedSlice(allocator, relocs);
     const fdes_start = self.fdes.items.len;
     const cies_start = self.cies.items.len;
 
@@ -355,22 +380,20 @@ fn parseEhFrame(self: *Object, shndx: u16, elf_file: *Elf) !void {
     while (try it.next()) |rec| {
         const rel_range = filterRelocs(relocs, rec.offset, rec.size + 4);
         switch (rec.tag) {
-            .cie => try self.cies.append(gpa, .{
-                .offset = rec.offset,
+            .cie => try self.cies.append(allocator, .{
+                .offset = data_start + rec.offset,
                 .size = rec.size,
-                .rel_index = @as(u32, @intCast(rel_range.start)),
+                .rel_index = rel_start + @as(u32, @intCast(rel_range.start)),
                 .rel_num = @as(u32, @intCast(rel_range.len)),
-                .rel_section_index = relocs_shndx,
                 .input_section_index = shndx,
                 .file_index = self.index,
             }),
-            .fde => try self.fdes.append(gpa, .{
-                .offset = rec.offset,
+            .fde => try self.fdes.append(allocator, .{
+                .offset = data_start + rec.offset,
                 .size = rec.size,
                 .cie_index = undefined,
-                .rel_index = @as(u32, @intCast(rel_range.start)),
+                .rel_index = rel_start + @as(u32, @intCast(rel_range.start)),
                 .rel_num = @as(u32, @intCast(rel_range.len)),
-                .rel_section_index = relocs_shndx,
                 .input_section_index = shndx,
                 .file_index = self.index,
             }),
@@ -773,21 +796,30 @@ pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf
     }
 }
 
-pub fn updateArSize(self: *Object) void {
-    self.output_ar_state.size = self.data.len;
+pub fn updateArSize(self: *Object, elf_file: *Elf) !void {
+    const handle = elf_file.fileHandle(self.file_handle);
+    const size = (try handle.stat()).size;
+    self.output_ar_state.size = size;
 }
 
-pub fn writeAr(self: Object, writer: anytype) !void {
+pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void {
+    const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;
     const name = self.path;
     const hdr = Archive.setArHdr(.{
         .name = if (name.len <= Archive.max_member_name_len)
             .{ .name = name }
         else
             .{ .name_off = self.output_ar_state.name_off },
-        .size = @intCast(self.data.len),
+        .size = size,
     });
     try writer.writeAll(mem.asBytes(&hdr));
-    try writer.writeAll(self.data);
+    const handle = elf_file.fileHandle(self.file_handle);
+    const gpa = elf_file.base.comp.gpa;
+    const data = try gpa.alloc(u8, size);
+    defer gpa.free(data);
+    const amt = try handle.preadAll(data, 0);
+    if (amt != size) return error.InputOutput;
+    try writer.writeAll(data);
 }
 
 pub fn updateSymtabSize(self: *Object, elf_file: *Elf) !void {
@@ -859,12 +891,6 @@ pub fn globals(self: Object) []const Symbol.Index {
     return self.symbols.items[start..];
 }
 
-pub fn shdrContents(self: Object, index: u32) []const u8 {
-    assert(index < self.shdrs.items.len);
-    const shdr = self.shdrs.items[index];
-    return self.data[shdr.sh_offset..][0..shdr.sh_size];
-}
-
 /// Returns atom's code and optionally uncompresses data if required (for compressed sections).
 /// Caller owns the memory.
 pub fn codeDecompressAlloc(self: Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {
@@ -872,8 +898,11 @@ pub fn codeDecompressAlloc(self: Object, elf_file: *Elf, atom_index: Atom.Index)
     const gpa = comp.gpa;
     const atom_ptr = elf_file.atom(atom_index).?;
     assert(atom_ptr.file_index == self.index);
-    const data = self.shdrContents(atom_ptr.input_section_index);
     const shdr = atom_ptr.inputShdr(elf_file);
+    const handle = elf_file.fileHandle(self.file_handle);
+    const data = try self.preadShdrContentsAlloc(gpa, handle, atom_ptr.input_section_index);
+    defer if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) gpa.free(data);
+
     if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
         const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
         switch (chdr.ch_type) {
@@ -892,31 +921,35 @@ pub fn codeDecompressAlloc(self: Object, elf_file: *Elf, atom_index: Atom.Index)
             },
             else => @panic("TODO unhandled compression scheme"),
         }
-    } else return gpa.dupe(u8, data);
-}
+    }
 
-pub fn comdatGroupMembers(self: *Object, index: u16) []align(1) const u32 {
-    const raw = self.shdrContents(index);
-    const nmembers = @divExact(raw.len, @sizeOf(u32));
-    const members = @as([*]align(1) const u32, @ptrCast(raw.ptr))[1..nmembers];
-    return members;
+    return data;
 }
 
 pub fn asFile(self: *Object) File {
     return .{ .object = self };
 }
 
-pub fn getRelocs(self: *Object, shndx: u32) []align(1) const elf.Elf64_Rela {
-    const raw = self.shdrContents(shndx);
-    const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela));
-    return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num];
-}
-
 pub fn getString(self: Object, off: u32) [:0]const u8 {
     assert(off < self.strtab.items.len);
     return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
 }
 
+/// Caller owns the memory.
+fn preadShdrContentsAlloc(self: Object, allocator: Allocator, handle: std.fs.File, index: u32) ![]u8 {
+    assert(index < self.shdrs.items.len);
+    const offset = if (self.archive) |ar| ar.offset else 0;
+    const shdr = self.shdrs.items[index];
+    return Elf.preadAllAlloc(allocator, handle, offset + shdr.sh_offset, shdr.sh_size);
+}
+
+/// Caller owns the memory.
+fn preadRelocsAlloc(self: Object, allocator: Allocator, handle: std.fs.File, shndx: u32) ![]align(1) const elf.Elf64_Rela {
+    const raw = try self.preadShdrContentsAlloc(allocator, handle, shndx);
+    const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela));
+    return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num];
+}
+
 pub fn format(
     self: *Object,
     comptime unused_fmt_string: []const u8,
@@ -1053,7 +1086,7 @@ fn formatComdatGroups(
         const cg_owner = elf_file.comdatGroupOwner(cg.owner);
         if (cg_owner.file != object.index) continue;
         try writer.print("    COMDAT({d})\n", .{cg_index});
-        const cg_members = object.comdatGroupMembers(cg.shndx);
+        const cg_members = cg.comdatGroupMembers(elf_file);
         for (cg_members) |shndx| {
             const atom_index = object.atoms.items[shndx];
             const atom = elf_file.atom(atom_index) orelse continue;
@@ -1074,40 +1107,17 @@ fn formatPath(
 ) !void {
     _ = unused_fmt_string;
     _ = options;
-    if (object.archive) |path| {
-        try writer.writeAll(path);
+    if (object.archive) |ar| {
+        try writer.writeAll(ar.path);
         try writer.writeByte('(');
         try writer.writeAll(object.path);
         try writer.writeByte(')');
     } else try writer.writeAll(object.path);
 }
 
-pub const ElfShdr = struct {
-    sh_name: u32,
-    sh_type: u32,
-    sh_flags: u64,
-    sh_addr: u64,
-    sh_offset: usize,
-    sh_size: usize,
-    sh_link: u32,
-    sh_info: u32,
-    sh_addralign: u64,
-    sh_entsize: u64,
-
-    pub fn fromElf64Shdr(shdr: elf.Elf64_Shdr) error{Overflow}!ElfShdr {
-        return .{
-            .sh_name = shdr.sh_name,
-            .sh_type = shdr.sh_type,
-            .sh_flags = shdr.sh_flags,
-            .sh_addr = shdr.sh_addr,
-            .sh_offset = math.cast(usize, shdr.sh_offset) orelse return error.Overflow,
-            .sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow,
-            .sh_link = shdr.sh_link,
-            .sh_info = shdr.sh_info,
-            .sh_addralign = shdr.sh_addralign,
-            .sh_entsize = shdr.sh_entsize,
-        };
-    }
+const InArchive = struct {
+    path: []const u8,
+    offset: u64,
 };
 
 const Object = @This();
src/link/Elf/SharedObject.zig
@@ -1,22 +1,18 @@
 path: []const u8,
-data: []const u8,
 index: File.Index,
 
 header: ?elf.Elf64_Ehdr = null,
-shdrs: std.ArrayListUnmanaged(ElfShdr) = .{},
+shdrs: std.ArrayListUnmanaged(elf.Elf64_Shdr) = .{},
 
 symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
 strtab: std.ArrayListUnmanaged(u8) = .{},
 /// Version symtab contains version strings of the symbols if present.
 versyms: std.ArrayListUnmanaged(elf.Elf64_Versym) = .{},
 verstrings: std.ArrayListUnmanaged(u32) = .{},
+
 symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
 aliases: ?std.ArrayListUnmanaged(u32) = null,
-
-dynsym_sect_index: ?u16 = null,
-dynamic_sect_index: ?u16 = null,
-versym_sect_index: ?u16 = null,
-verdef_sect_index: ?u16 = null,
+dynamic_table: std.ArrayListUnmanaged(elf.Elf64_Dyn) = .{},
 
 needed: bool,
 alive: bool,
@@ -36,23 +32,24 @@ pub fn isSharedObject(path: []const u8) !bool {
 
 pub fn deinit(self: *SharedObject, allocator: Allocator) void {
     allocator.free(self.path);
-    allocator.free(self.data);
+    self.shdrs.deinit(allocator);
     self.symtab.deinit(allocator);
     self.strtab.deinit(allocator);
     self.versyms.deinit(allocator);
     self.verstrings.deinit(allocator);
     self.symbols.deinit(allocator);
     if (self.aliases) |*aliases| aliases.deinit(allocator);
-    self.shdrs.deinit(allocator);
+    self.dynamic_table.deinit(allocator);
 }
 
-pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
+pub fn parse(self: *SharedObject, elf_file: *Elf, handle: std.fs.File) !void {
     const comp = elf_file.base.comp;
     const gpa = comp.gpa;
-    var stream = std.io.fixedBufferStream(self.data);
-    const reader = stream.reader();
+    const file_size = (try handle.stat()).size;
 
-    self.header = try reader.readStruct(elf.Elf64_Ehdr);
+    const header_buffer = try Elf.preadAllAlloc(gpa, handle, 0, @sizeOf(elf.Elf64_Ehdr));
+    defer gpa.free(header_buffer);
+    self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*;
 
     const target = elf_file.base.comp.root_mod.resolved_target.result;
     if (target.cpu.arch != self.header.?.e_machine.toTargetCpuArch().?) {
@@ -64,9 +61,10 @@ pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
         return error.InvalidCpuArch;
     }
 
-    if (self.data.len < self.header.?.e_shoff or
-        self.data.len < self.header.?.e_shoff + @as(u64, @intCast(self.header.?.e_shnum)) * @sizeOf(elf.Elf64_Shdr))
-    {
+    const shoff = std.math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
+    const shnum = std.math.cast(usize, self.header.?.e_shnum) orelse return error.Overflow;
+    const shsize = shnum * @sizeOf(elf.Elf64_Shdr);
+    if (file_size < shoff or file_size < shoff + shsize) {
         try elf_file.reportParseError2(
             self.index,
             "corrupted header: section header table extends past the end of file",
@@ -75,45 +73,84 @@ pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
         return error.MalformedObject;
     }
 
-    const shoff = std.math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
-
-    const shdrs = @as(
-        [*]align(1) const elf.Elf64_Shdr,
-        @ptrCast(self.data.ptr + shoff),
-    )[0..self.header.?.e_shnum];
-    try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len);
+    const shdrs_buffer = try Elf.preadAllAlloc(gpa, handle, shoff, shsize);
+    defer gpa.free(shdrs_buffer);
+    const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum];
+    try self.shdrs.appendUnalignedSlice(gpa, shdrs);
 
-    for (shdrs, 0..) |shdr, i| {
+    var dynsym_sect_index: ?u32 = null;
+    var dynamic_sect_index: ?u32 = null;
+    var versym_sect_index: ?u32 = null;
+    var verdef_sect_index: ?u32 = null;
+    for (self.shdrs.items, 0..) |shdr, i| {
         if (shdr.sh_type != elf.SHT_NOBITS) {
-            if (self.data.len < shdr.sh_offset or self.data.len < shdr.sh_offset + shdr.sh_size) {
+            if (file_size < shdr.sh_offset or file_size < shdr.sh_offset + shdr.sh_size) {
                 try elf_file.reportParseError2(self.index, "corrupted section header", .{});
                 return error.MalformedObject;
             }
         }
-        self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr));
         switch (shdr.sh_type) {
-            elf.SHT_DYNSYM => self.dynsym_sect_index = @as(u16, @intCast(i)),
-            elf.SHT_DYNAMIC => self.dynamic_sect_index = @as(u16, @intCast(i)),
-            elf.SHT_GNU_VERSYM => self.versym_sect_index = @as(u16, @intCast(i)),
-            elf.SHT_GNU_VERDEF => self.verdef_sect_index = @as(u16, @intCast(i)),
+            elf.SHT_DYNSYM => dynsym_sect_index = @intCast(i),
+            elf.SHT_DYNAMIC => dynamic_sect_index = @intCast(i),
+            elf.SHT_GNU_VERSYM => versym_sect_index = @intCast(i),
+            elf.SHT_GNU_VERDEF => verdef_sect_index = @intCast(i),
             else => {},
         }
     }
 
-    try self.parseVersions(elf_file);
+    if (dynamic_sect_index) |index| {
+        const shdr = self.shdrs.items[index];
+        const raw = try Elf.preadAllAlloc(gpa, handle, shdr.sh_offset, shdr.sh_size);
+        defer gpa.free(raw);
+        const num = @divExact(raw.len, @sizeOf(elf.Elf64_Dyn));
+        const dyntab = @as([*]align(1) const elf.Elf64_Dyn, @ptrCast(raw.ptr))[0..num];
+        try self.dynamic_table.appendUnalignedSlice(gpa, dyntab);
+    }
+
+    const symtab = if (dynsym_sect_index) |index| blk: {
+        const shdr = self.shdrs.items[index];
+        const buffer = try Elf.preadAllAlloc(gpa, handle, shdr.sh_offset, shdr.sh_size);
+        const nsyms = @divExact(buffer.len, @sizeOf(elf.Elf64_Sym));
+        break :blk @as([*]align(1) const elf.Elf64_Sym, @ptrCast(buffer.ptr))[0..nsyms];
+    } else &[0]elf.Elf64_Sym{};
+    defer gpa.free(symtab);
+
+    const strtab = if (dynsym_sect_index) |index| blk: {
+        const symtab_shdr = self.shdrs.items[index];
+        const shdr = self.shdrs.items[symtab_shdr.sh_link];
+        const buffer = try Elf.preadAllAlloc(gpa, handle, shdr.sh_offset, shdr.sh_size);
+        break :blk buffer;
+    } else &[0]u8{};
+    defer gpa.free(strtab);
+
+    try self.parseVersions(elf_file, handle, .{
+        .symtab = symtab,
+        .verdef_sect_index = verdef_sect_index,
+        .versym_sect_index = versym_sect_index,
+    });
+
+    try self.initSymtab(elf_file, .{
+        .symtab = symtab,
+        .strtab = strtab,
+    });
 }
 
-fn parseVersions(self: *SharedObject, elf_file: *Elf) !void {
+fn parseVersions(self: *SharedObject, elf_file: *Elf, handle: std.fs.File, opts: struct {
+    symtab: []align(1) const elf.Elf64_Sym,
+    verdef_sect_index: ?u32,
+    versym_sect_index: ?u32,
+}) !void {
     const comp = elf_file.base.comp;
     const gpa = comp.gpa;
-    const symtab = self.getSymtabRaw();
 
     try self.verstrings.resize(gpa, 2);
     self.verstrings.items[elf.VER_NDX_LOCAL] = 0;
     self.verstrings.items[elf.VER_NDX_GLOBAL] = 0;
 
-    if (self.verdef_sect_index) |shndx| {
-        const verdefs = self.shdrContents(shndx);
+    if (opts.verdef_sect_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        const verdefs = try Elf.preadAllAlloc(gpa, handle, shdr.sh_offset, shdr.sh_size);
+        defer gpa.free(verdefs);
         const nverdefs = self.verdefNum();
         try self.verstrings.resize(gpa, self.verstrings.items.len + nverdefs);
 
@@ -131,10 +168,12 @@ fn parseVersions(self: *SharedObject, elf_file: *Elf) !void {
         }
     }
 
-    try self.versyms.ensureTotalCapacityPrecise(gpa, symtab.len);
+    try self.versyms.ensureTotalCapacityPrecise(gpa, opts.symtab.len);
 
-    if (self.versym_sect_index) |shndx| {
-        const versyms_raw = self.shdrContents(shndx);
+    if (opts.versym_sect_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        const versyms_raw = try Elf.preadAllAlloc(gpa, handle, shdr.sh_offset, shdr.sh_size);
+        defer gpa.free(versyms_raw);
         const nversyms = @divExact(versyms_raw.len, @sizeOf(elf.Elf64_Versym));
         const versyms = @as([*]align(1) const elf.Elf64_Versym, @ptrCast(versyms_raw.ptr))[0..nversyms];
         for (versyms) |ver| {
@@ -144,22 +183,23 @@ fn parseVersions(self: *SharedObject, elf_file: *Elf) !void {
                 ver;
             self.versyms.appendAssumeCapacity(normalized_ver);
         }
-    } else for (0..symtab.len) |_| {
+    } else for (0..opts.symtab.len) |_| {
         self.versyms.appendAssumeCapacity(elf.VER_NDX_GLOBAL);
     }
 }
 
-pub fn init(self: *SharedObject, elf_file: *Elf) !void {
+fn initSymtab(self: *SharedObject, elf_file: *Elf, opts: struct {
+    symtab: []align(1) const elf.Elf64_Sym,
+    strtab: []const u8,
+}) !void {
     const comp = elf_file.base.comp;
     const gpa = comp.gpa;
-    const symtab = self.getSymtabRaw();
-    const strtab = self.getStrtabRaw();
 
-    try self.strtab.appendSlice(gpa, strtab);
-    try self.symtab.ensureTotalCapacityPrecise(gpa, symtab.len);
-    try self.symbols.ensureTotalCapacityPrecise(gpa, symtab.len);
+    try self.strtab.appendSlice(gpa, opts.strtab);
+    try self.symtab.ensureTotalCapacityPrecise(gpa, opts.symtab.len);
+    try self.symbols.ensureTotalCapacityPrecise(gpa, opts.symtab.len);
 
-    for (symtab, 0..) |sym, i| {
+    for (opts.symtab, 0..) |sym, i| {
         const hidden = self.versyms.items[i] & elf.VERSYM_HIDDEN != 0;
         const name = self.getString(sym.st_name);
         // We need to garble up the name so that we don't pick this symbol
@@ -250,11 +290,6 @@ pub fn writeSymtab(self: SharedObject, elf_file: *Elf) void {
     }
 }
 
-pub fn shdrContents(self: SharedObject, index: u16) []const u8 {
-    const shdr = self.shdrs.items[index];
-    return self.data[shdr.sh_offset..][0..shdr.sh_size];
-}
-
 pub fn versionString(self: SharedObject, index: elf.Elf64_Versym) [:0]const u8 {
     const off = self.verstrings.items[index & elf.VERSYM_VERSION];
     return self.getString(off);
@@ -264,16 +299,8 @@ pub fn asFile(self: *SharedObject) File {
     return .{ .shared_object = self };
 }
 
-fn dynamicTable(self: *SharedObject) []align(1) const elf.Elf64_Dyn {
-    const shndx = self.dynamic_sect_index orelse return &[0]elf.Elf64_Dyn{};
-    const raw = self.shdrContents(shndx);
-    const num = @divExact(raw.len, @sizeOf(elf.Elf64_Dyn));
-    return @as([*]align(1) const elf.Elf64_Dyn, @ptrCast(raw.ptr))[0..num];
-}
-
 fn verdefNum(self: *SharedObject) u32 {
-    const entries = self.dynamicTable();
-    for (entries) |entry| switch (entry.d_tag) {
+    for (self.dynamic_table.items) |entry| switch (entry.d_tag) {
         elf.DT_VERDEFNUM => return @as(u32, @intCast(entry.d_val)),
         else => {},
     };
@@ -281,8 +308,7 @@ fn verdefNum(self: *SharedObject) u32 {
 }
 
 pub fn soname(self: *SharedObject) []const u8 {
-    const entries = self.dynamicTable();
-    for (entries) |entry| switch (entry.d_tag) {
+    for (self.dynamic_table.items) |entry| switch (entry.d_tag) {
         elf.DT_SONAME => return self.getString(@as(u32, @intCast(entry.d_val))),
         else => {},
     };
@@ -342,20 +368,6 @@ pub fn getString(self: SharedObject, off: u32) [:0]const u8 {
     return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
 }
 
-pub fn getSymtabRaw(self: SharedObject) []align(1) const elf.Elf64_Sym {
-    const index = self.dynsym_sect_index orelse return &[0]elf.Elf64_Sym{};
-    const raw_symtab = self.shdrContents(index);
-    const nsyms = @divExact(raw_symtab.len, @sizeOf(elf.Elf64_Sym));
-    const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms];
-    return symtab;
-}
-
-pub fn getStrtabRaw(self: SharedObject) []const u8 {
-    const index = self.dynsym_sect_index orelse return &[0]u8{};
-    const shdr = self.shdrs.items[index];
-    return self.shdrContents(@as(u16, @intCast(shdr.sh_link)));
-}
-
 pub fn format(
     self: SharedObject,
     comptime unused_fmt_string: []const u8,
@@ -407,6 +419,5 @@ const mem = std.mem;
 
 const Allocator = mem.Allocator;
 const Elf = @import("../Elf.zig");
-const ElfShdr = @import("Object.zig").ElfShdr;
 const File = @import("file.zig").File;
 const Symbol = @import("Symbol.zig");
src/link/Elf/synthetic_sections.zig
@@ -1582,15 +1582,14 @@ pub const ComdatGroupSection = struct {
 
     pub fn size(cgs: ComdatGroupSection, elf_file: *Elf) usize {
         const cg = elf_file.comdatGroup(cgs.cg_index);
-        const object = cgs.file(elf_file).?.object;
-        const members = object.comdatGroupMembers(cg.shndx);
+        const members = cg.comdatGroupMembers(elf_file);
         return (members.len + 1) * @sizeOf(u32);
     }
 
     pub fn write(cgs: ComdatGroupSection, elf_file: *Elf, writer: anytype) !void {
         const cg = elf_file.comdatGroup(cgs.cg_index);
         const object = cgs.file(elf_file).?.object;
-        const members = object.comdatGroupMembers(cg.shndx);
+        const members = cg.comdatGroupMembers(elf_file);
         try writer.writeInt(u32, elf.GRP_COMDAT, .little);
         for (members) |shndx| {
             const shdr = object.shdrs.items[shndx];
src/link/Elf/ZigObject.zig
@@ -305,19 +305,16 @@ pub fn addAtom(self: *ZigObject, elf_file: *Elf) !Symbol.Index {
 }
 
 /// TODO actually create fake input shdrs and return that instead.
-pub fn inputShdr(self: ZigObject, atom_index: Atom.Index, elf_file: *Elf) Object.ElfShdr {
+pub fn inputShdr(self: ZigObject, atom_index: Atom.Index, elf_file: *Elf) elf.Elf64_Shdr {
     _ = self;
-    const shdr = shdr: {
-        const atom = elf_file.atom(atom_index) orelse break :shdr Elf.null_shdr;
-        const shndx = atom.outputShndx() orelse break :shdr Elf.null_shdr;
-        var shdr = elf_file.shdrs.items[shndx];
-        shdr.sh_addr = 0;
-        shdr.sh_offset = 0;
-        shdr.sh_size = atom.size;
-        shdr.sh_addralign = atom.alignment.toByteUnits(1);
-        break :shdr shdr;
-    };
-    return Object.ElfShdr.fromElf64Shdr(shdr) catch unreachable;
+    const atom = elf_file.atom(atom_index) orelse return Elf.null_shdr;
+    const shndx = atom.outputShndx() orelse return Elf.null_shdr;
+    var shdr = elf_file.shdrs.items[shndx];
+    shdr.sh_addr = 0;
+    shdr.sh_offset = 0;
+    shdr.sh_size = atom.size;
+    shdr.sh_addralign = atom.alignment.toByteUnits(1);
+    return shdr;
 }
 
 pub fn resolveSymbols(self: *ZigObject, elf_file: *Elf) void {
@@ -525,7 +522,7 @@ pub fn writeAr(self: ZigObject, writer: anytype) !void {
             .{ .name = name }
         else
             .{ .name_off = self.output_ar_state.name_off },
-        .size = @intCast(self.data.items.len),
+        .size = self.data.items.len,
     });
     try writer.writeAll(mem.asBytes(&hdr));
     try writer.writeAll(self.data.items);
src/link/Elf.zig
@@ -35,6 +35,10 @@ llvm_object: ?*LlvmObject = null,
 /// Index of each input file also encodes the priority or precedence of one input file
 /// over another.
 files: std.MultiArrayList(File.Entry) = .{},
+/// Long-lived list of all file descriptors.
+/// We store them globally rather than per actual File so that we can re-use
+/// one file handle per every object file within an archive.
+file_handles: std.ArrayListUnmanaged(File.Handle) = .{},
 zig_object_index: ?File.Index = null,
 linker_defined_index: ?File.Index = null,
 objects: std.ArrayListUnmanaged(File.Index) = .{},
@@ -444,6 +448,11 @@ pub fn deinit(self: *Elf) void {
 
     if (self.llvm_object) |llvm_object| llvm_object.deinit();
 
+    for (self.file_handles.items) |fh| {
+        fh.close();
+    }
+    self.file_handles.deinit(gpa);
+
     for (self.files.items(.tags), self.files.items(.data)) |tag, *data| switch (tag) {
         .null => {},
         .zig_object => data.zig_object.deinit(gpa),
@@ -1244,9 +1253,6 @@ pub fn flushModule(self: *Elf, arena: Allocator, prog_node: *std.Progress.Node)
     for (self.objects.items) |index| {
         try self.file(index).?.object.init(self);
     }
-    for (self.shared_objects.items) |index| {
-        try self.file(index).?.shared_object.init(self);
-    }
 
     if (comp.link_errors.items.len > 0) return error.FlushFailure;
 
@@ -1463,7 +1469,7 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const
     for (files.items) |index| {
         const file_ptr = self.file(index).?;
         try file_ptr.updateArStrtab(gpa, &ar_strtab);
-        file_ptr.updateArSize();
+        try file_ptr.updateArSize(self);
     }
 
     // Update file offsets of contributing objects.
@@ -1515,7 +1521,7 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const
     // Write object files
     for (files.items) |index| {
         if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
-        try self.file(index).?.writeAr(buffer.writer());
+        try self.file(index).?.writeAr(self, buffer.writer());
     }
 
     assert(buffer.items.len == total_size);
@@ -1902,13 +1908,13 @@ fn parseObject(self: *Elf, path: []const u8) ParseError!void {
     defer tracy.end();
 
     const gpa = self.base.comp.gpa;
-    const in_file = try std.fs.cwd().openFile(path, .{});
-    defer in_file.close();
-    const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
+    const handle = try std.fs.cwd().openFile(path, .{});
+    const fh = try self.addFileHandle(handle);
+
     const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
     self.files.set(index, .{ .object = .{
         .path = try gpa.dupe(u8, path),
-        .data = data,
+        .file_handle = fh,
         .index = index,
     } });
     try self.objects.append(gpa, index);
@@ -1922,12 +1928,12 @@ fn parseArchive(self: *Elf, path: []const u8, must_link: bool) ParseError!void {
     defer tracy.end();
 
     const gpa = self.base.comp.gpa;
-    const in_file = try std.fs.cwd().openFile(path, .{});
-    defer in_file.close();
-    const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
-    var archive = Archive{ .path = try gpa.dupe(u8, path), .data = data };
+    const handle = try std.fs.cwd().openFile(path, .{});
+    const fh = try self.addFileHandle(handle);
+
+    var archive = Archive{};
     defer archive.deinit(gpa);
-    try archive.parse(self);
+    try archive.parse(self, path, fh);
 
     const objects = try archive.objects.toOwnedSlice(gpa);
     defer gpa.free(objects);
@@ -1948,13 +1954,12 @@ fn parseSharedObject(self: *Elf, lib: SystemLib) ParseError!void {
     defer tracy.end();
 
     const gpa = self.base.comp.gpa;
-    const in_file = try std.fs.cwd().openFile(lib.path, .{});
-    defer in_file.close();
-    const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
+    const handle = try std.fs.cwd().openFile(lib.path, .{});
+    defer handle.close();
+
     const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
     self.files.set(index, .{ .shared_object = .{
         .path = try gpa.dupe(u8, lib.path),
-        .data = data,
         .index = index,
         .needed = lib.needed,
         .alive = lib.needed,
@@ -1962,7 +1967,7 @@ fn parseSharedObject(self: *Elf, lib: SystemLib) ParseError!void {
     try self.shared_objects.append(gpa, index);
 
     const shared_object = self.file(index).?.shared_object;
-    try shared_object.parse(self);
+    try shared_object.parse(self, handle);
 }
 
 fn parseLdScript(self: *Elf, lib: SystemLib) ParseError!void {
@@ -2135,7 +2140,7 @@ fn resolveSymbols(self: *Elf) void {
             const cg = self.comdatGroup(cg_index);
             const cg_owner = self.comdatGroupOwner(cg.owner);
             if (cg_owner.file != index) {
-                for (object.comdatGroupMembers(cg.shndx)) |shndx| {
+                for (cg.comdatGroupMembers(self)) |shndx| {
                     const atom_index = object.atoms.items[shndx];
                     if (self.atom(atom_index)) |atom_ptr| {
                         atom_ptr.flags.alive = false;
@@ -5862,6 +5867,19 @@ pub fn file(self: *Elf, index: File.Index) ?File {
     };
 }
 
+pub fn addFileHandle(self: *Elf, handle: std.fs.File) !File.HandleIndex {
+    const gpa = self.base.comp.gpa;
+    const index: File.HandleIndex = @intCast(self.file_handles.items.len);
+    const fh = try self.file_handles.addOne(gpa);
+    fh.* = handle;
+    return index;
+}
+
+pub fn fileHandle(self: Elf, index: File.HandleIndex) File.Handle {
+    assert(index < self.file_handles.items.len);
+    return self.file_handles.items[index];
+}
+
 /// Returns pointer-to-symbol described at sym_index.
 pub fn symbol(self: *Elf, sym_index: Symbol.Index) *Symbol {
     return &self.symbols.items[sym_index];
@@ -6395,6 +6413,15 @@ fn fmtDumpState(
     }
 }
 
+/// Caller owns the memory.
+pub fn preadAllAlloc(allocator: Allocator, handle: std.fs.File, offset: usize, size: usize) ![]u8 {
+    const buffer = try allocator.alloc(u8, size);
+    errdefer allocator.free(buffer);
+    const amt = try handle.preadAll(buffer, offset);
+    if (amt != size) return error.InputOutput;
+    return buffer;
+}
+
 /// Binary search
 pub fn bsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize {
     if (!@hasDecl(@TypeOf(predicate), "predicate"))
@@ -6441,12 +6468,22 @@ pub const base_tag: link.File.Tag = .elf;
 
 const ComdatGroupOwner = struct {
     file: File.Index = 0,
+
     const Index = u32;
 };
 
 pub const ComdatGroup = struct {
     owner: ComdatGroupOwner.Index,
-    shndx: u16,
+    file: File.Index,
+    shndx: u32,
+    members_start: u32,
+    members_len: u32,
+
+    pub fn comdatGroupMembers(cg: ComdatGroup, elf_file: *Elf) []const u32 {
+        const object = elf_file.file(cg.file).?.object;
+        return object.comdat_group_data.items[cg.members_start..][0..cg.members_len];
+    }
+
     pub const Index = u32;
 };