authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-02-07 11:37:33+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-02-07 19:27:26+01:00
log897a554109baa3288d575cac0833e10edd1a316c
tree3d3560bf0ea0b2a4943c27b8e38aa45a8f0fce37
parent80cafad9d32fed9f6a786f4d87f40b8ee622015e

macho: populate output archive symtab


6 files changed, 323 insertions(+), 66 deletions(-)

src/link/MachO.zig+3-1
......@@ -606,7 +606,9 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
606606 self.allocateSyntheticSymbols();
607607 try self.allocateLinkeditSegment();
608608
609 state_log.debug("{}", .{self.dumpState()});
609 if (build_options.enable_logging) {
610 state_log.debug("{}", .{self.dumpState()});
611 }
610612
611613 try self.initDyldInfoSections();
612614
src/link/MachO/Archive.zig+228-61
......@@ -1,63 +1,5 @@
11objects: std.ArrayListUnmanaged(Object) = .{},
22
3// Archive files start with the ARMAG identifying string. Then follows a
4// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
5// member indicates, for each member file.
6/// String that begins an archive file.
7pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
8/// Size of that string.
9pub const SARMAG: u4 = 8;
10
11/// String in ar_fmag at the end of each header.
12const ARFMAG: *const [2:0]u8 = "`\n";
13
14const ar_hdr = extern struct {
15 /// Member file name, sometimes / terminated.
16 ar_name: [16]u8,
17
18 /// File date, decimal seconds since Epoch.
19 ar_date: [12]u8,
20
21 /// User ID, in ASCII format.
22 ar_uid: [6]u8,
23
24 /// Group ID, in ASCII format.
25 ar_gid: [6]u8,
26
27 /// File mode, in ASCII octal.
28 ar_mode: [8]u8,
29
30 /// File size, in ASCII decimal.
31 ar_size: [10]u8,
32
33 /// Always contains ARFMAG.
34 ar_fmag: [2]u8,
35
36 fn date(self: ar_hdr) !u64 {
37 const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
38 return std.fmt.parseInt(u64, value, 10);
39 }
40
41 fn size(self: ar_hdr) !u32 {
42 const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
43 return std.fmt.parseInt(u32, value, 10);
44 }
45
46 fn name(self: *const ar_hdr) ?[]const u8 {
47 const value = &self.ar_name;
48 if (mem.startsWith(u8, value, "#1/")) return null;
49 const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len;
50 return value[0..sentinel];
51 }
52
53 fn nameLength(self: ar_hdr) !?u32 {
54 const value = &self.ar_name;
55 if (!mem.startsWith(u8, value, "#1/")) return null;
56 const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
57 return try std.fmt.parseInt(u32, trimmed, 10);
58 }
59};
60
613pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool {
624 const file = try std.fs.cwd().openFile(path, .{});
635 defer file.close();
......@@ -85,9 +27,9 @@ pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index:
8527 try handle.seekTo(offset);
8628
8729 const reader = handle.reader();
88 _ = try reader.readBytesNoEof(Archive.SARMAG);
30 _ = try reader.readBytesNoEof(SARMAG);
8931
90 var pos: usize = Archive.SARMAG;
32 var pos: usize = SARMAG;
9133 while (true) {
9234 if (pos >= size) break;
9335 if (!mem.isAligned(pos, 2)) {
......@@ -123,7 +65,10 @@ pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index:
12365 pos += hdr_size;
12466 }
12567
126 if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue;
68 if (mem.eql(u8, name, SYMDEF) or
69 mem.eql(u8, name, SYMDEF64) or
70 mem.eql(u8, name, SYMDEF_SORTED) or
71 mem.eql(u8, name, SYMDEF64_SORTED)) continue;
12772
12873 const object = Object{
12974 .archive = .{
......@@ -143,6 +88,227 @@ pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index:
14388 }
14489}
14590
91pub fn writeHeader(
92 object_name: []const u8,
93 object_size: u32,
94 format: Format,
95 writer: anytype,
96) !void {
97 var hdr: ar_hdr = .{
98 .ar_name = undefined,
99 .ar_date = undefined,
100 .ar_uid = undefined,
101 .ar_gid = undefined,
102 .ar_mode = undefined,
103 .ar_size = undefined,
104 .ar_fmag = undefined,
105 };
106 @memset(mem.asBytes(&hdr), 0x20);
107 inline for (@typeInfo(ar_hdr).Struct.fields) |field| {
108 var stream = std.io.fixedBufferStream(&@field(hdr, field.name));
109 stream.writer().print("0", .{}) catch unreachable;
110 }
111 @memcpy(&hdr.ar_fmag, ARFMAG);
112
113 const object_name_len = mem.alignForward(u32, object_name.len + 1, format.ptrWidth());
114 const total_object_size = object_size + object_name_len;
115
116 {
117 var stream = std.io.fixedBufferStream(&hdr.ar_name);
118 stream.writer().print("#1/{d}", .{object_name_len}) catch unreachable;
119 }
120 {
121 var stream = std.io.fixedBufferStream(&hdr.ar_size);
122 stream.writer().print("{d}", .{total_object_size}) catch unreachable;
123 }
124
125 try writer.writeAll(mem.asBytes(&hdr));
126 try writer.print("{s}\x00", .{object_name});
127
128 const padding = object_name_len - object_name.len - 1;
129 if (padding > 0) {
130 try writer.writeByteNTimes(0, padding);
131 }
132}
133
134// Archive files start with the ARMAG identifying string. Then follows a
135// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
136// member indicates, for each member file.
137/// String that begins an archive file.
138pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
139/// Size of that string.
140pub const SARMAG: u4 = 8;
141
142/// String in ar_fmag at the end of each header.
143const ARFMAG: *const [2:0]u8 = "`\n";
144
145const SYMDEF = "__.SYMDEF";
146const SYMDEF64 = "__.SYMDEF_64";
147const SYMDEF_SORTED = "__.SYMDEF SORTED";
148const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED";
149
150const ar_hdr = extern struct {
151 /// Member file name, sometimes / terminated.
152 ar_name: [16]u8,
153
154 /// File date, decimal seconds since Epoch.
155 ar_date: [12]u8,
156
157 /// User ID, in ASCII format.
158 ar_uid: [6]u8,
159
160 /// Group ID, in ASCII format.
161 ar_gid: [6]u8,
162
163 /// File mode, in ASCII octal.
164 ar_mode: [8]u8,
165
166 /// File size, in ASCII decimal.
167 ar_size: [10]u8,
168
169 /// Always contains ARFMAG.
170 ar_fmag: [2]u8,
171
172 fn date(self: ar_hdr) !u64 {
173 const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
174 return std.fmt.parseInt(u64, value, 10);
175 }
176
177 fn size(self: ar_hdr) !u32 {
178 const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
179 return std.fmt.parseInt(u32, value, 10);
180 }
181
182 fn name(self: *const ar_hdr) ?[]const u8 {
183 const value = &self.ar_name;
184 if (mem.startsWith(u8, value, "#1/")) return null;
185 const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len;
186 return value[0..sentinel];
187 }
188
189 fn nameLength(self: ar_hdr) !?u32 {
190 const value = &self.ar_name;
191 if (!mem.startsWith(u8, value, "#1/")) return null;
192 const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
193 return try std.fmt.parseInt(u32, trimmed, 10);
194 }
195};
196
197pub const ArSymtab = struct {
198 entries: std.ArrayListUnmanaged(Entry) = .{},
199 strtab: StringTable = .{},
200 format: Format = .p32,
201
202 pub fn deinit(ar: *ArSymtab, allocator: Allocator) void {
203 ar.entries.deinit(allocator);
204 ar.strtab.deinit(allocator);
205 }
206
207 pub fn sort(ar: *ArSymtab) void {
208 mem.sort(Entry, ar.entries.items, {}, Entry.lessThan);
209 }
210
211 pub fn size(ar: ArSymtab) usize {
212 const ptr_width = ar.format.ptrWidth();
213 return ptr_width + ar.entries.items.len * 2 * ptr_width + ptr_width + mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width);
214 }
215
216 pub fn write(ar: ArSymtab, macho_file: *MachO, writer: anytype) !void {
217 // Header
218 try writeHeader(SYMDEF, ar.size());
219 // Symtab size
220 try ar.writeInt(ar.entries.items.len * 2);
221 // Symtab entries
222 for (ar.entries.items) |entry| {
223 const file_off = switch (macho_file.getFile(entry.file).?) {
224 .zig_object => |x| x.output_ar_state.file_off,
225 .object => |x| x.output_ar_state.file_off,
226 else => unreachable,
227 };
228 // Name offset
229 try ar.writeInt(entry.off);
230 // File offset
231 try ar.writeInt(file_off);
232 }
233 // Strtab size
234 const strtab_size = mem.alignForward(u64, ar.strtab.buffer.items.len, ar.format.ptrWidth());
235 const padding = strtab_size - ar.strtab.buffer.items.len;
236 try ar.writeInt(strtab_size);
237 // Strtab
238 try writer.writeAll(ar.strtab.buffer.items);
239 if (padding > 0) {
240 try writer.writeByteNTimes(0, padding);
241 }
242 }
243
244 fn writeInt(ar: ArSymtab, value: u64, writer: anytype) !void {
245 switch (ar.format) {
246 .p32 => try writer.writeInt(u32, std.math.cast(u32, value) orelse return error.Overflow, .little),
247 .p64 => try writer.writeInt(u64, value, .little),
248 }
249 }
250
251 const FormatContext = struct {
252 ar: ArSymtab,
253 macho_file: *MachO,
254 };
255
256 pub fn fmt(ar: ArSymtab, macho_file: *MachO) std.fmt.Formatter(format2) {
257 return .{ .data = .{ .ar = ar, .macho_file = macho_file } };
258 }
259
260 fn format2(
261 ctx: FormatContext,
262 comptime unused_fmt_string: []const u8,
263 options: std.fmt.FormatOptions,
264 writer: anytype,
265 ) !void {
266 _ = unused_fmt_string;
267 _ = options;
268 const ar = ctx.ar;
269 const macho_file = ctx.macho_file;
270 for (ar.entries.items, 0..) |entry, i| {
271 const name = ar.strtab.getAssumeExists(entry.off);
272 const file = macho_file.getFile(entry.file).?;
273 try writer.print(" {d}: {s} in file({d})({})\n", .{ i, name, entry.file, file.fmtPath() });
274 }
275 }
276
277 const Entry = struct {
278 /// Symbol name offset
279 off: u32,
280 /// Exporting file
281 file: File.Index,
282
283 pub fn lessThan(ctx: void, lhs: Entry, rhs: Entry) bool {
284 _ = ctx;
285 if (lhs.off == rhs.off) return lhs.file < rhs.file;
286 return lhs.off < rhs.off;
287 }
288 };
289};
290
291const Format = enum {
292 p32,
293 p64,
294
295 fn ptrWidth(self: Format) usize {
296 return switch (self) {
297 .p32 => @as(usize, 4),
298 .p64 => 8,
299 };
300 }
301};
302
303pub const ArState = struct {
304 /// File offset of the ar_hdr describing the contributing
305 /// object in the archive.
306 file_off: u64 = 0,
307
308 /// Total size of the contributing object (excludes ar_hdr and long name with padding).
309 size: u64 = 0,
310};
311
146312const fat = @import("fat.zig");
147313const link = @import("../../link.zig");
148314const log = std.log.scoped(.link);
......@@ -155,3 +321,4 @@ const Archive = @This();
155321const File = @import("file.zig").File;
156322const MachO = @import("../MachO.zig");
157323const Object = @import("Object.zig");
324const StringTable = @import("../StringTable.zig");
src/link/MachO/Object.zig+31-2
......@@ -1,4 +1,4 @@
1archive: ?Archive = null,
1archive: ?InArchive = null,
22path: []const u8,
33file_handle: File.HandleIndex,
44mtime: u64,
......@@ -29,8 +29,9 @@ hidden: bool = false,
2929
3030dynamic_relocs: MachO.DynamicRelocs = .{},
3131output_symtab_ctx: MachO.SymtabCtx = .{},
32output_ar_state: Archive.ArState = .{},
3233
33const Archive = struct {
34const InArchive = struct {
3435 path: []const u8,
3536 offset: u64,
3637};
......@@ -1232,6 +1233,33 @@ fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname
12321233 return n_sect;
12331234}
12341235
1236pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void {
1237 const gpa = macho_file.base.comp.gpa;
1238 for (self.symtab.items(.nlist)) |nlist| {
1239 if (!nlist.ext() or (nlist.undf() and !nlist.tentative())) continue;
1240 const off = try ar_symtab.strtab.insert(gpa, self.getString(nlist.n_strx));
1241 try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index });
1242 }
1243}
1244
1245pub fn updateArSize(self: *Object, macho_file: *MachO) !void {
1246 const file = macho_file.getFileHandle(self.file_handle);
1247 const size = (try file.stat()).size;
1248 self.output_ar_state.size = size;
1249}
1250
1251pub fn writeAr(self: Object, macho_file: *MachO, writer: anytype) !void {
1252 // Header
1253 try Archive.writeHeader(self.path, self.output_ar_state.size, writer);
1254 // Data
1255 const file = macho_file.getFileHandle(self.file_handle);
1256 // TODO try using copyRangeAll
1257 const gpa = macho_file.base.comp.gpa;
1258 const data = try file.readToEndAlloc(gpa, self.output_ar_state.size);
1259 defer gpa.free(data);
1260 try writer.writeAll(data);
1261}
1262
12351263pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {
12361264 const tracy = trace(@src());
12371265 defer tracy.end();
......@@ -2241,6 +2269,7 @@ const trace = @import("../../tracy.zig").trace;
22412269const std = @import("std");
22422270
22432271const Allocator = mem.Allocator;
2272const Archive = @import("Archive.zig");
22442273const Atom = @import("Atom.zig");
22452274const Cie = eh_frame.Cie;
22462275const DwarfInfo = @import("DwarfInfo.zig");
src/link/MachO/ZigObject.zig+24
......@@ -48,6 +48,7 @@ relocs: RelocationTable = .{},
4848
4949dynamic_relocs: MachO.DynamicRelocs = .{},
5050output_symtab_ctx: MachO.SymtabCtx = .{},
51output_ar_state: Archive.ArState = .{},
5152
5253pub fn init(self: *ZigObject, macho_file: *MachO) !void {
5354 const comp = macho_file.base.comp;
......@@ -297,6 +298,29 @@ pub fn readFileContents(self: *ZigObject, macho_file: *MachO) !void {
297298 if (amt != size) return error.InputOutput;
298299}
299300
301pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void {
302 const gpa = macho_file.base.comp.gpa;
303 for (self.symbols.items) |sym_index| {
304 const sym = macho_file.getSymbol(sym_index);
305 const file = sym.getFile(macho_file).?;
306 assert(file.getIndex() == self.index);
307 if (!sym.flags.@"export") continue;
308 const off = try ar_symtab.strtab.insert(gpa, sym.getName(macho_file));
309 try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index });
310 }
311}
312
313pub fn updateArSize(self: *ZigObject) void {
314 self.output_ar_state.size = self.data.items.len;
315}
316
317pub fn writeAr(self: ZigObject, writer: anytype) !void {
318 // Header
319 try Archive.writeHeader(self.path, self.output_ar_state.size, writer);
320 // Data
321 try writer.writeAll(self.data.items);
322}
323
300324pub fn scanRelocs(self: *ZigObject, macho_file: *MachO) !void {
301325 for (self.atoms.items) |atom_index| {
302326 const atom = macho_file.getAtom(atom_index) orelse continue;
src/link/MachO/file.zig+8
......@@ -175,6 +175,13 @@ pub const File = union(enum) {
175175 };
176176 }
177177
178 pub fn updateArSymtab(file: File, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void {
179 return switch (file) {
180 .dylib, .internal => unreachable,
181 inline else => |x| x.updateArSymtab(ar_symtab, macho_file),
182 };
183 }
184
178185 pub fn calcSymtabSize(file: File, macho_file: *MachO) !void {
179186 return switch (file) {
180187 inline else => |x| x.calcSymtabSize(macho_file),
......@@ -206,6 +213,7 @@ const macho = std.macho;
206213const std = @import("std");
207214
208215const Allocator = std.mem.Allocator;
216const Archive = @import("Archive.zig");
209217const Atom = @import("Atom.zig");
210218const InternalObject = @import("InternalObject.zig");
211219const MachO = @import("../MachO.zig");
src/link/MachO/relocatable.zig+29-2
......@@ -64,7 +64,9 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c
6464 };
6565 off = allocateSectionsRelocs(macho_file, off);
6666
67 state_log.debug("{}", .{macho_file.dumpState()});
67 if (build_options.enable_logging) {
68 state_log.debug("{}", .{macho_file.dumpState()});
69 }
6870
6971 try macho_file.calcSymtabSize();
7072 try writeAtoms(macho_file);
......@@ -111,6 +113,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
111113 // First, we flush relocatable object file generated with our backends.
112114 if (macho_file.getZigObject()) |zo| {
113115 zo.resolveSymbols(macho_file);
116 zo.asFile().markExportsRelocatable(macho_file);
114117 zo.asFile().claimUnresolvedRelocatable(macho_file);
115118 try macho_file.sortSections();
116119 try macho_file.addAtomsToSections();
......@@ -126,7 +129,9 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
126129 };
127130 off = allocateSectionsRelocs(macho_file, off);
128131
129 state_log.debug("{}", .{macho_file.dumpState()});
132 if (build_options.enable_logging) {
133 state_log.debug("{}", .{macho_file.dumpState()});
134 }
130135
131136 try macho_file.calcSymtabSize();
132137 try writeAtoms(macho_file);
......@@ -150,6 +155,26 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
150155 try zo.readFileContents(macho_file);
151156 }
152157
158 var files = std.ArrayList(File.Index).init(gpa);
159 defer files.deinit();
160 try files.ensureTotalCapacityPrecise(macho_file.objects.items.len + 1);
161 if (macho_file.getZigObject()) |zo| files.appendAssumeCapacity(zo.index);
162 for (macho_file.objects.items) |index| files.appendAssumeCapacity(index);
163
164 // Update ar symtab from parsed objects
165 var ar_symtab: Archive.ArSymtab = .{};
166 defer ar_symtab.deinit(gpa);
167
168 for (files.items) |index| {
169 try macho_file.getFile(index).?.updateArSymtab(&ar_symtab, macho_file);
170 }
171
172 ar_symtab.sort();
173
174 if (build_options.enable_logging) {
175 state_log.debug("ar_symtab\n{}\n", .{ar_symtab.fmt(macho_file)});
176 }
177
153178 var err = try macho_file.addErrorWithNotes(0);
154179 try err.addMsg(macho_file, "TODO implement flushStaticLib", .{});
155180
......@@ -646,6 +671,7 @@ fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
646671}
647672
648673const assert = std.debug.assert;
674const build_options = @import("build_options");
649675const eh_frame = @import("eh_frame.zig");
650676const link = @import("../../link.zig");
651677const load_commands = @import("load_commands.zig");
......@@ -657,6 +683,7 @@ const state_log = std.log.scoped(.link_state);
657683const std = @import("std");
658684const trace = @import("../../tracy.zig").trace;
659685
686const Archive = @import("Archive.zig");
660687const Atom = @import("Atom.zig");
661688const Compilation = @import("../../Compilation.zig");
662689const File = @import("file.zig").File;