| 1 | const Archive = @This(); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const Io = std.Io; |
| 5 | const assert = std.debug.assert; |
| 6 | const elf = std.elf; |
| 7 | const fs = std.fs; |
| 8 | const log = std.log.scoped(.link); |
| 9 | const mem = std.mem; |
| 10 | const Path = std.Build.Cache.Path; |
| 11 | const Allocator = std.mem.Allocator; |
| 12 | |
| 13 | const Diags = @import("../../link.zig").Diags; |
| 14 | const Elf = @import("../Elf.zig"); |
| 15 | const File = @import("file.zig").File; |
| 16 | const Object = @import("Object.zig"); |
| 17 | const StringTable = @import("../StringTable.zig"); |
| 18 | |
| 19 | objects: []const Object, |
| 20 | /// '\n'-delimited |
| 21 | strtab: []const u8, |
| 22 | |
| 23 | pub fn deinit(a: *Archive, gpa: Allocator) void { |
| 24 | gpa.free(a.objects); |
| 25 | gpa.free(a.strtab); |
| 26 | a.* = undefined; |
| 27 | } |
| 28 | |
| 29 | pub fn parse( |
| 30 | gpa: Allocator, |
| 31 | io: Io, |
| 32 | diags: *Diags, |
| 33 | file_handles: *const std.ArrayList(File.Handle), |
| 34 | path: Path, |
| 35 | handle_index: File.HandleIndex, |
| 36 | ) !Archive { |
| 37 | const file = file_handles.items[handle_index]; |
| 38 | var pos: usize = 0; |
| 39 | { |
| 40 | var magic_buffer: [elf.ARMAG.len]u8 = undefined; |
| 41 | const n = try file.readPositionalAll(io, &magic_buffer, pos); |
| 42 | if (n != magic_buffer.len) return error.BadMagic; |
| 43 | if (!mem.eql(u8, &magic_buffer, elf.ARMAG)) return error.BadMagic; |
| 44 | pos += magic_buffer.len; |
| 45 | } |
| 46 | |
| 47 | const size = (try file.stat(io)).size; |
| 48 | |
| 49 | var objects: std.ArrayList(Object) = .empty; |
| 50 | defer objects.deinit(gpa); |
| 51 | |
| 52 | var strtab: std.ArrayList(u8) = .empty; |
| 53 | defer strtab.deinit(gpa); |
| 54 | |
| 55 | while (pos < size) { |
| 56 | var hdr: elf.ar_hdr = undefined; |
| 57 | { |
| 58 | const n = try file.readPositionalAll(io, mem.asBytes(&hdr), pos); |
| 59 | if (n != @sizeOf(elf.ar_hdr)) return error.UnexpectedEndOfFile; |
| 60 | } |
| 61 | pos += @sizeOf(elf.ar_hdr); |
| 62 | |
| 63 | if (!mem.eql(u8, &hdr.ar_fmag, elf.ARFMAG)) { |
| 64 | return diags.failParse(path, "invalid archive header delimiter: {f}", .{ |
| 65 | std.ascii.hexEscape(&hdr.ar_fmag, .lower), |
| 66 | }); |
| 67 | } |
| 68 | |
| 69 | const obj_size = try hdr.size(); |
| 70 | defer pos = std.mem.alignForward(usize, pos + obj_size, 2); |
| 71 | |
| 72 | if (hdr.isSymtab() or hdr.isSymtab64()) continue; |
| 73 | if (hdr.isStrtab()) { |
| 74 | try strtab.resize(gpa, obj_size); |
| 75 | const amt = try file.readPositionalAll(io, strtab.items, pos); |
| 76 | if (amt != obj_size) return error.InputOutput; |
| 77 | continue; |
| 78 | } |
| 79 | if (hdr.isSymdef() or hdr.isSymdefSorted()) continue; |
| 80 | |
| 81 | const name = if (hdr.name()) |name| |
| 82 | name |
| 83 | else if (try hdr.nameOffset()) |off| |
| 84 | stringTableLookup(strtab.items, off) |
| 85 | else |
| 86 | unreachable; |
| 87 | |
| 88 | const object: Object = .{ |
| 89 | .archive = .{ |
| 90 | .path = .{ |
| 91 | .root_dir = path.root_dir, |
| 92 | .sub_path = try gpa.dupe(u8, path.sub_path), |
| 93 | }, |
| 94 | .offset = pos, |
| 95 | .size = obj_size, |
| 96 | }, |
| 97 | .path = Path.initCwd(try gpa.dupe(u8, name)), |
| 98 | .file_handle = handle_index, |
| 99 | .index = undefined, |
| 100 | .alive = false, |
| 101 | }; |
| 102 | |
| 103 | log.debug("extracting object '{f}' from archive '{f}'", .{ |
| 104 | @as(Path, object.path), @as(Path, path), |
| 105 | }); |
| 106 | |
| 107 | try objects.append(gpa, object); |
| 108 | } |
| 109 | |
| 110 | try objects.shrinkToLen(gpa); |
| 111 | try strtab.shrinkToLen(gpa); |
| 112 | |
| 113 | return .{ |
| 114 | .objects = objects.toOwnedSliceAssert(), |
| 115 | .strtab = strtab.toOwnedSliceAssert(), |
| 116 | }; |
| 117 | } |
| 118 | |
| 119 | pub fn stringTableLookup(strtab: []const u8, off: u32) [:'\n']const u8 { |
| 120 | const slice = strtab[off..]; |
| 121 | return slice[0..mem.findScalar(u8, slice, '\n').? :'\n']; |
| 122 | } |
| 123 | |
| 124 | pub fn setArHdr(opts: struct { |
| 125 | name: union(enum) { |
| 126 | symtab: void, |
| 127 | strtab: void, |
| 128 | name: []const u8, |
| 129 | name_off: u32, |
| 130 | }, |
| 131 | size: usize, |
| 132 | }) elf.ar_hdr { |
| 133 | var hdr: elf.ar_hdr = .{ |
| 134 | .ar_name = undefined, |
| 135 | .ar_date = undefined, |
| 136 | .ar_uid = undefined, |
| 137 | .ar_gid = undefined, |
| 138 | .ar_mode = undefined, |
| 139 | .ar_size = undefined, |
| 140 | .ar_fmag = undefined, |
| 141 | }; |
| 142 | @memset(mem.asBytes(&hdr), 0x20); |
| 143 | |
| 144 | { |
| 145 | var writer: Io.Writer = .fixed(&hdr.ar_name); |
| 146 | switch (opts.name) { |
| 147 | .symtab => writer.print("{s}", .{elf.SYM64NAME}) catch unreachable, |
| 148 | .strtab => writer.print("//", .{}) catch unreachable, |
| 149 | .name => |x| writer.print("{s}/", .{x}) catch unreachable, |
| 150 | .name_off => |x| writer.print("/{d}", .{x}) catch unreachable, |
| 151 | } |
| 152 | } |
| 153 | hdr.ar_date[0] = '0'; |
| 154 | hdr.ar_uid[0] = '0'; |
| 155 | hdr.ar_gid[0] = '0'; |
| 156 | hdr.ar_mode[0] = '0'; |
| 157 | { |
| 158 | var writer: Io.Writer = .fixed(&hdr.ar_size); |
| 159 | writer.print("{d}", .{opts.size}) catch unreachable; |
| 160 | } |
| 161 | hdr.ar_fmag = elf.ARFMAG.*; |
| 162 | |
| 163 | return hdr; |
| 164 | } |
| 165 | |
| 166 | const strtab_delimiter = '\n'; |
| 167 | pub const max_member_name_len = 15; |
| 168 | |
| 169 | pub const ArSymtab = struct { |
| 170 | symtab: std.ArrayList(Entry) = .empty, |
| 171 | strtab: StringTable = .{}, |
| 172 | |
| 173 | pub fn deinit(ar: *ArSymtab, allocator: Allocator) void { |
| 174 | ar.symtab.deinit(allocator); |
| 175 | ar.strtab.deinit(allocator); |
| 176 | } |
| 177 | |
| 178 | pub fn sort(ar: *ArSymtab) void { |
| 179 | mem.sort(Entry, ar.symtab.items, {}, Entry.lessThan); |
| 180 | } |
| 181 | |
| 182 | pub fn size(ar: ArSymtab, kind: enum { p32, p64 }) usize { |
| 183 | const ptr_size: usize = switch (kind) { |
| 184 | .p32 => 4, |
| 185 | .p64 => 8, |
| 186 | }; |
| 187 | var ss: usize = ptr_size + ar.symtab.items.len * ptr_size; |
| 188 | for (ar.symtab.items) |entry| { |
| 189 | ss += ar.strtab.getAssumeExists(entry.off).len + 1; |
| 190 | } |
| 191 | return ss; |
| 192 | } |
| 193 | |
| 194 | pub fn write(ar: ArSymtab, kind: enum { p32, p64 }, elf_file: *Elf, writer: anytype) !void { |
| 195 | assert(kind == .p64); // TODO p32 |
| 196 | const hdr = setArHdr(.{ .name = .symtab, .size = @intCast(ar.size(.p64)) }); |
| 197 | try writer.writeAll(mem.asBytes(&hdr)); |
| 198 | |
| 199 | const comp = elf_file.base.comp; |
| 200 | const gpa = comp.gpa; |
| 201 | var offsets = std.AutoHashMap(File.Index, u64).init(gpa); |
| 202 | defer offsets.deinit(); |
| 203 | try offsets.ensureUnusedCapacity(@intCast(elf_file.objects.items.len + 1)); |
| 204 | |
| 205 | if (elf_file.zigObjectPtr()) |zig_object| { |
| 206 | offsets.putAssumeCapacityNoClobber(zig_object.index, zig_object.output_ar_state.file_off); |
| 207 | } |
| 208 | for (elf_file.objects.items) |index| { |
| 209 | offsets.putAssumeCapacityNoClobber(index, elf_file.file(index).?.object.output_ar_state.file_off); |
| 210 | } |
| 211 | |
| 212 | // Number of symbols |
| 213 | try writer.writeInt(u64, @as(u64, @intCast(ar.symtab.items.len)), .big); |
| 214 | |
| 215 | // Offsets to files |
| 216 | for (ar.symtab.items) |entry| { |
| 217 | const off = offsets.get(entry.file_index).?; |
| 218 | try writer.writeInt(u64, off, .big); |
| 219 | } |
| 220 | |
| 221 | // Strings |
| 222 | for (ar.symtab.items) |entry| { |
| 223 | try writer.print("{s}\x00", .{ar.strtab.getAssumeExists(entry.off)}); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | const Format = struct { |
| 228 | ar: ArSymtab, |
| 229 | elf_file: *Elf, |
| 230 | |
| 231 | fn default(f: Format, writer: *Io.Writer) Io.Writer.Error!void { |
| 232 | const ar = f.ar; |
| 233 | const elf_file = f.elf_file; |
| 234 | for (ar.symtab.items, 0..) |entry, i| { |
| 235 | const name = ar.strtab.getAssumeExists(entry.off); |
| 236 | const file = elf_file.file(entry.file_index).?; |
| 237 | try writer.print(" {d}: {s} in file({d})({f})\n", .{ i, name, entry.file_index, file.fmtPath() }); |
| 238 | } |
| 239 | } |
| 240 | }; |
| 241 | |
| 242 | pub fn fmt(ar: ArSymtab, elf_file: *Elf) std.fmt.Alt(Format, Format.default) { |
| 243 | return .{ .data = .{ |
| 244 | .ar = ar, |
| 245 | .elf_file = elf_file, |
| 246 | } }; |
| 247 | } |
| 248 | |
| 249 | const Entry = struct { |
| 250 | /// Offset into the string table. |
| 251 | off: u32, |
| 252 | /// Index of the file defining the global. |
| 253 | file_index: File.Index, |
| 254 | |
| 255 | pub fn lessThan(ctx: void, lhs: Entry, rhs: Entry) bool { |
| 256 | _ = ctx; |
| 257 | if (lhs.off == rhs.off) return lhs.file_index < rhs.file_index; |
| 258 | return lhs.off < rhs.off; |
| 259 | } |
| 260 | }; |
| 261 | }; |
| 262 | |
| 263 | pub const ArStrtab = struct { |
| 264 | buffer: std.ArrayList(u8) = .empty, |
| 265 | |
| 266 | pub fn deinit(ar: *ArStrtab, allocator: Allocator) void { |
| 267 | ar.buffer.deinit(allocator); |
| 268 | } |
| 269 | |
| 270 | pub fn insert(ar: *ArStrtab, allocator: Allocator, name: []const u8) error{OutOfMemory}!u32 { |
| 271 | const off = @as(u32, @intCast(ar.buffer.items.len)); |
| 272 | try ar.buffer.print(allocator, "{s}/{c}", .{ name, strtab_delimiter }); |
| 273 | return off; |
| 274 | } |
| 275 | |
| 276 | pub fn size(ar: ArStrtab) usize { |
| 277 | return ar.buffer.items.len; |
| 278 | } |
| 279 | |
| 280 | pub fn write(ar: ArStrtab, writer: anytype) !void { |
| 281 | const hdr = setArHdr(.{ .name = .strtab, .size = @intCast(ar.size()) }); |
| 282 | try writer.writeAll(mem.asBytes(&hdr)); |
| 283 | try writer.writeAll(ar.buffer.items); |
| 284 | } |
| 285 | |
| 286 | pub fn format(ar: ArStrtab, writer: *Io.Writer) Io.Writer.Error!void { |
| 287 | try writer.print("{f}", .{std.ascii.hexEscape(ar.buffer.items, .lower)}); |
| 288 | } |
| 289 | }; |
| 290 | |
| 291 | pub const ArState = struct { |
| 292 | /// Name offset in the string table. |
| 293 | name_off: u32 = 0, |
| 294 | |
| 295 | /// File offset of the ar_hdr describing the contributing |
| 296 | /// object in the archive. |
| 297 | file_off: u64 = 0, |
| 298 | |
| 299 | /// Total size of the contributing object (excludes ar_hdr). |
| 300 | size: u64 = 0, |
| 301 | }; |