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