| 1 | objects: std.ArrayList(Object) = .empty, |
| 2 | |
| 3 | pub fn deinit(self: *Archive, allocator: Allocator) void { |
| 4 | self.objects.deinit(allocator); |
| 5 | } |
| 6 | |
| 7 | pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { |
| 8 | const comp = macho_file.base.comp; |
| 9 | const io = comp.io; |
| 10 | const gpa = comp.gpa; |
| 11 | const diags = &comp.link_diags; |
| 12 | |
| 13 | var arena = std.heap.ArenaAllocator.init(gpa); |
| 14 | defer arena.deinit(); |
| 15 | |
| 16 | const handle = macho_file.getFileHandle(handle_index); |
| 17 | const offset = if (fat_arch) |ar| ar.offset else 0; |
| 18 | const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat(io)).size; |
| 19 | |
| 20 | var pos: usize = offset + SARMAG; |
| 21 | while (true) { |
| 22 | if (pos >= end_pos) break; |
| 23 | if (!mem.isAligned(pos, 2)) pos += 1; |
| 24 | |
| 25 | var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined; |
| 26 | { |
| 27 | const amt = try handle.readPositionalAll(io, &hdr_buffer, pos); |
| 28 | if (amt != @sizeOf(ar_hdr)) return error.InputOutput; |
| 29 | } |
| 30 | const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*; |
| 31 | pos += @sizeOf(ar_hdr); |
| 32 | |
| 33 | if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { |
| 34 | return diags.failParse(path, "invalid header delimiter: expected '{f}', found '{f}'", .{ |
| 35 | std.ascii.hexEscape(ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower), |
| 36 | }); |
| 37 | } |
| 38 | |
| 39 | var hdr_size = try hdr.size(); |
| 40 | const name = name: { |
| 41 | if (hdr.name()) |n| break :name n; |
| 42 | if (try hdr.nameLength()) |len| { |
| 43 | hdr_size -= len; |
| 44 | const buf = try arena.allocator().alloc(u8, len); |
| 45 | const amt = try handle.readPositionalAll(io, buf, pos); |
| 46 | if (amt != len) return error.InputOutput; |
| 47 | pos += len; |
| 48 | const actual_len = mem.findScalar(u8, buf, @as(u8, 0)) orelse len; |
| 49 | break :name buf[0..actual_len]; |
| 50 | } |
| 51 | unreachable; |
| 52 | }; |
| 53 | defer pos += hdr_size; |
| 54 | |
| 55 | if (mem.eql(u8, name, SYMDEF) or |
| 56 | mem.eql(u8, name, SYMDEF64) or |
| 57 | mem.eql(u8, name, SYMDEF_SORTED) or |
| 58 | mem.eql(u8, name, SYMDEF64_SORTED)) continue; |
| 59 | |
| 60 | const abs_path = try std.fs.path.resolvePosix(gpa, &.{ |
| 61 | comp.dirs.cwd, |
| 62 | path.root_dir.path orelse ".", |
| 63 | path.sub_path, |
| 64 | }); |
| 65 | errdefer gpa.free(abs_path); |
| 66 | |
| 67 | const o_basename = try gpa.dupe(u8, name); |
| 68 | errdefer gpa.free(o_basename); |
| 69 | |
| 70 | const object: Object = .{ |
| 71 | .offset = pos, |
| 72 | .in_archive = .{ |
| 73 | .path = abs_path, |
| 74 | .size = hdr_size, |
| 75 | }, |
| 76 | .path = o_basename, |
| 77 | .file_handle = handle_index, |
| 78 | .index = undefined, |
| 79 | .alive = false, |
| 80 | .mtime = hdr.date() catch 0, |
| 81 | }; |
| 82 | |
| 83 | log.debug("extracting object '{s}' from archive '{f}'", .{ o_basename, path }); |
| 84 | |
| 85 | try self.objects.append(gpa, object); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | pub fn writeHeader( |
| 90 | object_name: []const u8, |
| 91 | object_size: usize, |
| 92 | writer: *Writer, |
| 93 | ) !void { |
| 94 | var hdr: ar_hdr = .{}; |
| 95 | |
| 96 | const object_name_start = writer.end + @sizeOf(ar_hdr); |
| 97 | const object_start = mem.alignForward(usize, object_name_start + object_name.len + 1, 8); |
| 98 | const object_name_len = object_start - object_name_start; |
| 99 | const total_object_size = object_size + object_name_len; |
| 100 | |
| 101 | { |
| 102 | var stream: Writer = .fixed(&hdr.ar_name); |
| 103 | stream.print("#1/{d}", .{object_name_len}) catch unreachable; |
| 104 | } |
| 105 | { |
| 106 | var stream: Writer = .fixed(&hdr.ar_size); |
| 107 | stream.print("{d}", .{total_object_size}) catch unreachable; |
| 108 | } |
| 109 | |
| 110 | try writer.writeAll(mem.asBytes(&hdr)); |
| 111 | try writer.print("{s}\x00", .{object_name}); |
| 112 | |
| 113 | const padding = object_name_len - object_name.len - 1; |
| 114 | if (padding > 0) { |
| 115 | try writer.splatByteAll(0, padding); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | // Archive files start with the ARMAG identifying string. Then follows a |
| 120 | // `struct ar_hdr', and as many bytes of member file data as its `ar_size' |
| 121 | // member indicates, for each member file. |
| 122 | /// String that begins an archive file. |
| 123 | pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; |
| 124 | /// Size of that string. |
| 125 | pub const SARMAG: u4 = 8; |
| 126 | |
| 127 | /// String in ar_fmag at the end of each header. |
| 128 | const ARFMAG: *const [2:0]u8 = "`\n"; |
| 129 | |
| 130 | pub const SYMDEF = "__.SYMDEF"; |
| 131 | pub const SYMDEF64 = "__.SYMDEF_64"; |
| 132 | pub const SYMDEF_SORTED = "__.SYMDEF SORTED"; |
| 133 | pub const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED"; |
| 134 | |
| 135 | pub const ar_hdr = extern struct { |
| 136 | /// Member file name, sometimes / terminated. |
| 137 | ar_name: [16]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, |
| 138 | /// File date, decimal seconds since Epoch. |
| 139 | ar_date: [12]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, |
| 140 | /// User ID, in ASCII format. |
| 141 | ar_uid: [6]u8 = "0\x20\x20\x20\x20\x20".*, |
| 142 | /// Group ID, in ASCII format. |
| 143 | ar_gid: [6]u8 = "0\x20\x20\x20\x20\x20".*, |
| 144 | /// File mode, in ASCII octal. |
| 145 | ar_mode: [8]u8 = "0\x20\x20\x20\x20\x20\x20\x20".*, |
| 146 | /// File size, in ASCII decimal. |
| 147 | ar_size: [10]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, |
| 148 | /// Always contains ARFMAG. |
| 149 | ar_fmag: [2]u8 = ARFMAG.*, |
| 150 | |
| 151 | fn date(self: ar_hdr) !u64 { |
| 152 | const value = mem.trimEnd(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); |
| 153 | return std.fmt.parseInt(u64, value, 10); |
| 154 | } |
| 155 | |
| 156 | fn size(self: ar_hdr) !u32 { |
| 157 | const value = mem.trimEnd(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)}); |
| 158 | return std.fmt.parseInt(u32, value, 10); |
| 159 | } |
| 160 | |
| 161 | fn name(self: *const ar_hdr) ?[]const u8 { |
| 162 | const value = &self.ar_name; |
| 163 | if (mem.startsWith(u8, value, "#1/")) return null; |
| 164 | const sentinel = mem.findScalar(u8, value, '/') orelse value.len; |
| 165 | return value[0..sentinel]; |
| 166 | } |
| 167 | |
| 168 | fn nameLength(self: ar_hdr) !?u32 { |
| 169 | const value = &self.ar_name; |
| 170 | if (!mem.startsWith(u8, value, "#1/")) return null; |
| 171 | const trimmed = mem.trimEnd(u8, self.ar_name["#1/".len..], &[_]u8{0x20}); |
| 172 | return try std.fmt.parseInt(u32, trimmed, 10); |
| 173 | } |
| 174 | }; |
| 175 | |
| 176 | pub const ArSymtab = struct { |
| 177 | entries: std.ArrayList(Entry) = .empty, |
| 178 | strtab: StringTable = .{}, |
| 179 | |
| 180 | pub fn deinit(ar: *ArSymtab, allocator: Allocator) void { |
| 181 | ar.entries.deinit(allocator); |
| 182 | ar.strtab.deinit(allocator); |
| 183 | } |
| 184 | |
| 185 | pub fn sort(ar: *ArSymtab) void { |
| 186 | mem.sort(Entry, ar.entries.items, {}, Entry.lessThan); |
| 187 | } |
| 188 | |
| 189 | pub fn size(ar: ArSymtab, format: Format) usize { |
| 190 | const ptr_width = ptrWidth(format); |
| 191 | return ptr_width + ar.entries.items.len * 2 * ptr_width + ptr_width + mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width); |
| 192 | } |
| 193 | |
| 194 | pub fn write(ar: ArSymtab, format: Format, macho_file: *MachO, writer: *Writer) !void { |
| 195 | const ptr_width = ptrWidth(format); |
| 196 | // Header |
| 197 | try writeHeader(SYMDEF, ar.size(format), writer); |
| 198 | // Symtab size |
| 199 | try writeInt(format, ar.entries.items.len * 2 * ptr_width, writer); |
| 200 | // Symtab entries |
| 201 | for (ar.entries.items) |entry| { |
| 202 | const file_off = switch (macho_file.getFile(entry.file).?) { |
| 203 | .zig_object => |x| x.output_ar_state.file_off, |
| 204 | .object => |x| x.output_ar_state.file_off, |
| 205 | else => unreachable, |
| 206 | }; |
| 207 | // Name offset |
| 208 | try writeInt(format, entry.off, writer); |
| 209 | // File offset |
| 210 | try writeInt(format, file_off, writer); |
| 211 | } |
| 212 | // Strtab size |
| 213 | const strtab_size = mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width); |
| 214 | const padding = strtab_size - ar.strtab.buffer.items.len; |
| 215 | try writeInt(format, strtab_size, writer); |
| 216 | // Strtab |
| 217 | try writer.writeAll(ar.strtab.buffer.items); |
| 218 | if (padding > 0) { |
| 219 | try writer.splatByteAll(0, padding); |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | const PrintFormat = struct { |
| 224 | ar: ArSymtab, |
| 225 | macho_file: *MachO, |
| 226 | |
| 227 | fn default(f: PrintFormat, bw: *Writer) Writer.Error!void { |
| 228 | const ar = f.ar; |
| 229 | const macho_file = f.macho_file; |
| 230 | for (ar.entries.items, 0..) |entry, i| { |
| 231 | const name = ar.strtab.getAssumeExists(entry.off); |
| 232 | const file = macho_file.getFile(entry.file).?; |
| 233 | try bw.print(" {d}: {s} in file({d})({f})\n", .{ i, name, entry.file, file.fmtPath() }); |
| 234 | } |
| 235 | } |
| 236 | }; |
| 237 | |
| 238 | pub fn fmt(ar: ArSymtab, macho_file: *MachO) std.fmt.Alt(PrintFormat, PrintFormat.default) { |
| 239 | return .{ .data = .{ .ar = ar, .macho_file = macho_file } }; |
| 240 | } |
| 241 | |
| 242 | const Entry = struct { |
| 243 | /// Symbol name offset |
| 244 | off: u32, |
| 245 | /// Exporting file |
| 246 | file: File.Index, |
| 247 | |
| 248 | pub fn lessThan(ctx: void, lhs: Entry, rhs: Entry) bool { |
| 249 | _ = ctx; |
| 250 | if (lhs.off == rhs.off) return lhs.file < rhs.file; |
| 251 | return lhs.off < rhs.off; |
| 252 | } |
| 253 | }; |
| 254 | }; |
| 255 | |
| 256 | pub const Format = enum { |
| 257 | p32, |
| 258 | p64, |
| 259 | }; |
| 260 | |
| 261 | pub fn ptrWidth(format: Format) usize { |
| 262 | return switch (format) { |
| 263 | .p32 => @as(usize, 4), |
| 264 | .p64 => 8, |
| 265 | }; |
| 266 | } |
| 267 | |
| 268 | pub fn writeInt(format: Format, value: u64, writer: *Writer) !void { |
| 269 | switch (format) { |
| 270 | .p32 => try writer.writeInt(u32, std.math.cast(u32, value) orelse return error.Overflow, .little), |
| 271 | .p64 => try writer.writeInt(u64, value, .little), |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | pub const ArState = struct { |
| 276 | /// File offset of the ar_hdr describing the contributing |
| 277 | /// object in the archive. |
| 278 | file_off: u64 = 0, |
| 279 | |
| 280 | /// Total size of the contributing object (excludes ar_hdr and long name with padding). |
| 281 | size: u64 = 0, |
| 282 | }; |
| 283 | |
| 284 | const fat = @import("fat.zig"); |
| 285 | const link = @import("../../link.zig"); |
| 286 | const log = std.log.scoped(.link); |
| 287 | const macho = std.macho; |
| 288 | const mem = std.mem; |
| 289 | const std = @import("std"); |
| 290 | const Allocator = std.mem.Allocator; |
| 291 | const Path = std.Build.Cache.Path; |
| 292 | const Writer = std.Io.Writer; |
| 293 | |
| 294 | const Archive = @This(); |
| 295 | const File = @import("file.zig").File; |
| 296 | const MachO = @import("../MachO.zig"); |
| 297 | const Object = @import("Object.zig"); |
| 298 | const StringTable = @import("../StringTable.zig"); |