objects: std.ArrayList(Object) = .empty, pub fn deinit(self: *Archive, allocator: Allocator) void { self.objects.deinit(allocator); } pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { const comp = macho_file.base.comp; const io = comp.io; const gpa = comp.gpa; const diags = &comp.link_diags; var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); const handle = macho_file.getFileHandle(handle_index); const offset = if (fat_arch) |ar| ar.offset else 0; const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat(io)).size; var pos: usize = offset + SARMAG; while (true) { if (pos >= end_pos) break; if (!mem.isAligned(pos, 2)) pos += 1; var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined; { const amt = try handle.readPositionalAll(io, &hdr_buffer, pos); if (amt != @sizeOf(ar_hdr)) return error.InputOutput; } const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*; pos += @sizeOf(ar_hdr); if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { return diags.failParse(path, "invalid header delimiter: expected '{f}', found '{f}'", .{ std.ascii.hexEscape(ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower), }); } var hdr_size = try hdr.size(); const name = name: { if (hdr.name()) |n| break :name n; if (try hdr.nameLength()) |len| { hdr_size -= len; const buf = try arena.allocator().alloc(u8, len); const amt = try handle.readPositionalAll(io, buf, pos); if (amt != len) return error.InputOutput; pos += len; const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len; break :name buf[0..actual_len]; } unreachable; }; defer pos += hdr_size; if (mem.eql(u8, name, SYMDEF) or mem.eql(u8, name, SYMDEF64) or mem.eql(u8, name, SYMDEF_SORTED) or mem.eql(u8, name, SYMDEF64_SORTED)) continue; const abs_path = try std.fs.path.resolvePosix(gpa, &.{ comp.dirs.cwd, path.root_dir.path orelse ".", path.sub_path, }); errdefer gpa.free(abs_path); const o_basename = try gpa.dupe(u8, name); errdefer gpa.free(o_basename); const object: Object = .{ .offset = pos, .in_archive = .{ .path = abs_path, .size = hdr_size, }, .path = o_basename, .file_handle = handle_index, .index = undefined, .alive = false, .mtime = hdr.date() catch 0, }; log.debug("extracting object '{s}' from archive '{f}'", .{ o_basename, path }); try self.objects.append(gpa, object); } } pub fn writeHeader( object_name: []const u8, object_size: usize, format: Format, writer: *Writer, ) !void { var hdr: ar_hdr = .{}; const object_name_len = mem.alignForward(usize, object_name.len + 1, ptrWidth(format)); const total_object_size = object_size + object_name_len; { var stream: Writer = .fixed(&hdr.ar_name); stream.print("#1/{d}", .{object_name_len}) catch unreachable; } { var stream: Writer = .fixed(&hdr.ar_size); stream.print("{d}", .{total_object_size}) catch unreachable; } try writer.writeAll(mem.asBytes(&hdr)); try writer.print("{s}\x00", .{object_name}); const padding = object_name_len - object_name.len - 1; if (padding > 0) { try writer.splatByteAll(0, padding); } } // Archive files start with the ARMAG identifying string. Then follows a // `struct ar_hdr', and as many bytes of member file data as its `ar_size' // member indicates, for each member file. /// String that begins an archive file. pub const ARMAG: *const [SARMAG:0]u8 = "!\n"; /// Size of that string. pub const SARMAG: u4 = 8; /// String in ar_fmag at the end of each header. const ARFMAG: *const [2:0]u8 = "`\n"; pub const SYMDEF = "__.SYMDEF"; pub const SYMDEF64 = "__.SYMDEF_64"; pub const SYMDEF_SORTED = "__.SYMDEF SORTED"; pub const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED"; pub const ar_hdr = extern struct { /// Member file name, sometimes / terminated. ar_name: [16]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, /// File date, decimal seconds since Epoch. ar_date: [12]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, /// User ID, in ASCII format. ar_uid: [6]u8 = "0\x20\x20\x20\x20\x20".*, /// Group ID, in ASCII format. ar_gid: [6]u8 = "0\x20\x20\x20\x20\x20".*, /// File mode, in ASCII octal. ar_mode: [8]u8 = "0\x20\x20\x20\x20\x20\x20\x20".*, /// File size, in ASCII decimal. ar_size: [10]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, /// Always contains ARFMAG. ar_fmag: [2]u8 = ARFMAG.*, fn date(self: ar_hdr) !u64 { const value = mem.trimEnd(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); return std.fmt.parseInt(u64, value, 10); } fn size(self: ar_hdr) !u32 { const value = mem.trimEnd(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)}); return std.fmt.parseInt(u32, value, 10); } fn name(self: *const ar_hdr) ?[]const u8 { const value = &self.ar_name; if (mem.startsWith(u8, value, "#1/")) return null; const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len; return value[0..sentinel]; } fn nameLength(self: ar_hdr) !?u32 { const value = &self.ar_name; if (!mem.startsWith(u8, value, "#1/")) return null; const trimmed = mem.trimEnd(u8, self.ar_name["#1/".len..], &[_]u8{0x20}); return try std.fmt.parseInt(u32, trimmed, 10); } }; pub const ArSymtab = struct { entries: std.ArrayList(Entry) = .empty, strtab: StringTable = .{}, pub fn deinit(ar: *ArSymtab, allocator: Allocator) void { ar.entries.deinit(allocator); ar.strtab.deinit(allocator); } pub fn sort(ar: *ArSymtab) void { mem.sort(Entry, ar.entries.items, {}, Entry.lessThan); } pub fn size(ar: ArSymtab, format: Format) usize { const ptr_width = ptrWidth(format); return ptr_width + ar.entries.items.len * 2 * ptr_width + ptr_width + mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width); } pub fn write(ar: ArSymtab, format: Format, macho_file: *MachO, writer: *Writer) !void { const ptr_width = ptrWidth(format); // Header try writeHeader(SYMDEF, ar.size(format), format, writer); // Symtab size try writeInt(format, ar.entries.items.len * 2 * ptr_width, writer); // Symtab entries for (ar.entries.items) |entry| { const file_off = switch (macho_file.getFile(entry.file).?) { .zig_object => |x| x.output_ar_state.file_off, .object => |x| x.output_ar_state.file_off, else => unreachable, }; // Name offset try writeInt(format, entry.off, writer); // File offset try writeInt(format, file_off, writer); } // Strtab size const strtab_size = mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width); const padding = strtab_size - ar.strtab.buffer.items.len; try writeInt(format, strtab_size, writer); // Strtab try writer.writeAll(ar.strtab.buffer.items); if (padding > 0) { try writer.splatByteAll(0, padding); } } const PrintFormat = struct { ar: ArSymtab, macho_file: *MachO, fn default(f: PrintFormat, bw: *Writer) Writer.Error!void { const ar = f.ar; const macho_file = f.macho_file; for (ar.entries.items, 0..) |entry, i| { const name = ar.strtab.getAssumeExists(entry.off); const file = macho_file.getFile(entry.file).?; try bw.print(" {d}: {s} in file({d})({f})\n", .{ i, name, entry.file, file.fmtPath() }); } } }; pub fn fmt(ar: ArSymtab, macho_file: *MachO) std.fmt.Alt(PrintFormat, PrintFormat.default) { return .{ .data = .{ .ar = ar, .macho_file = macho_file } }; } const Entry = struct { /// Symbol name offset off: u32, /// Exporting file file: File.Index, pub fn lessThan(ctx: void, lhs: Entry, rhs: Entry) bool { _ = ctx; if (lhs.off == rhs.off) return lhs.file < rhs.file; return lhs.off < rhs.off; } }; }; pub const Format = enum { p32, p64, }; pub fn ptrWidth(format: Format) usize { return switch (format) { .p32 => @as(usize, 4), .p64 => 8, }; } pub fn writeInt(format: Format, value: u64, writer: *Writer) !void { switch (format) { .p32 => try writer.writeInt(u32, std.math.cast(u32, value) orelse return error.Overflow, .little), .p64 => try writer.writeInt(u64, value, .little), } } pub const ArState = struct { /// File offset of the ar_hdr describing the contributing /// object in the archive. file_off: u64 = 0, /// Total size of the contributing object (excludes ar_hdr and long name with padding). size: u64 = 0, }; const fat = @import("fat.zig"); const link = @import("../../link.zig"); const log = std.log.scoped(.link); const macho = std.macho; const mem = std.mem; const std = @import("std"); const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; const Writer = std.Io.Writer; const Archive = @This(); const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const StringTable = @import("../StringTable.zig");