1const Archive = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const assert = std.debug.assert;
6const elf = std.elf;
7const fs = std.fs;
8const log = std.log.scoped(.link);
9const mem = std.mem;
10const Path = std.Build.Cache.Path;
11const Allocator = std.mem.Allocator;
12
13const Diags = @import("../../link.zig").Diags;
14const Elf = @import("../Elf.zig");
15const File = @import("file.zig").File;
16const Object = @import("Object.zig");
17const StringTable = @import("../StringTable.zig");
18
19objects: []const Object,
20/// '\n'-delimited
21strtab: []const u8,
22
23pub fn deinit(a: *Archive, gpa: Allocator) void {
24 gpa.free(a.objects);
25 gpa.free(a.strtab);
26 a.* = undefined;
27}
28
29pub 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
119pub 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
124pub 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
166const strtab_delimiter = '\n';
167pub const max_member_name_len = 15;
168
169pub 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
263pub 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
291pub 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};