authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-05-25 06:25:46+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-05-25 06:25:46+02:00
log0b0625ccf494fcd94743179a3e6b02055db8ebe8
tree2724b5d6fa26f8971b24dc96fc1f9ff973cdef2b
parent710d745a548cffd2535cba2aaf904f9fc96aca94
parent42c058575e2be291e351fe7dd4fdd6f5e4b9b703
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20062 from ziglang/macho-perf-bug

link/macho: fix perf bug in DWARF parsing

8 files changed, 488 insertions(+), 525 deletions(-)

CMakeLists.txt+1-1
......@@ -613,7 +613,6 @@ set(ZIG_STAGE2_SOURCES
613613 src/link/MachO/Atom.zig
614614 src/link/MachO/CodeSignature.zig
615615 src/link/MachO/DebugSymbols.zig
616 src/link/MachO/DwarfInfo.zig
617616 src/link/MachO/Dylib.zig
618617 src/link/MachO/InternalObject.zig
619618 src/link/MachO/Object.zig
......@@ -625,6 +624,7 @@ set(ZIG_STAGE2_SOURCES
625624 src/link/MachO/dyld_info/Rebase.zig
626625 src/link/MachO/dyld_info/Trie.zig
627626 src/link/MachO/dyld_info/bind.zig
627 src/link/MachO/dwarf.zig
628628 src/link/MachO/eh_frame.zig
629629 src/link/MachO/fat.zig
630630 src/link/MachO/file.zig
lib/std/Build/Step/CheckObject.zig+18-2
......@@ -1087,9 +1087,25 @@ const MachODumper = struct {
10871087 try writer.writeAll(symtab_label ++ "\n");
10881088
10891089 for (ctx.symtab.items) |sym| {
1090 if (sym.stab()) continue;
10911090 const sym_name = ctx.getString(sym.n_strx);
1092 if (sym.sect()) {
1091 if (sym.stab()) {
1092 const tt = switch (sym.n_type) {
1093 macho.N_SO => "SO",
1094 macho.N_OSO => "OSO",
1095 macho.N_BNSYM => "BNSYM",
1096 macho.N_ENSYM => "ENSYM",
1097 macho.N_FUN => "FUN",
1098 macho.N_GSYM => "GSYM",
1099 macho.N_STSYM => "STSYM",
1100 else => "UNKNOWN STAB",
1101 };
1102 try writer.print("{x}", .{sym.n_value});
1103 if (sym.n_sect > 0) {
1104 const sect = ctx.sections.items[sym.n_sect - 1];
1105 try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() });
1106 }
1107 try writer.print(" {s} (stab) {s}\n", .{ tt, sym_name });
1108 } else if (sym.sect()) {
10931109 const sect = ctx.sections.items[sym.n_sect - 1];
10941110 try writer.print("{x} ({s},{s})", .{
10951111 sym.n_value,
src/link/MachO.zig+7-1
......@@ -535,6 +535,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
535535
536536 try self.addUndefinedGlobals();
537537 try self.resolveSymbols();
538 try self.parseDebugInfo();
538539 try self.resolveSyntheticSymbols();
539540
540541 try self.convertTentativeDefinitions();
......@@ -1409,6 +1410,12 @@ fn markLive(self: *MachO) void {
14091410 }
14101411}
14111412
1413pub fn parseDebugInfo(self: *MachO) !void {
1414 for (self.objects.items) |index| {
1415 try self.getFile(index).?.object.parseDebugInfo(self);
1416 }
1417}
1418
14121419fn resolveSyntheticSymbols(self: *MachO) !void {
14131420 const internal = self.getInternalObject() orelse return;
14141421
......@@ -4840,7 +4847,6 @@ const Cache = std.Build.Cache;
48404847const CodeSignature = @import("MachO/CodeSignature.zig");
48414848const Compilation = @import("../Compilation.zig");
48424849pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
4843const DwarfInfo = @import("MachO/DwarfInfo.zig");
48444850const Dylib = @import("MachO/Dylib.zig");
48454851const ExportTrieSection = synthetic.ExportTrieSection;
48464852const File = @import("MachO/file.zig").File;
src/link/MachO/DwarfInfo.zig deleted-490
......@@ -1,490 +0,0 @@
1/// Abbreviation table indexed by offset in the .debug_abbrev bytestream
2abbrev_tables: std.AutoArrayHashMapUnmanaged(u64, AbbrevTable) = .{},
3/// List of compile units as they appear in the .debug_info bytestream
4compile_units: std.ArrayListUnmanaged(CompileUnit) = .{},
5/// Debug info string table
6strtab: std.ArrayListUnmanaged(u8) = .{},
7/// Debug info data
8di_data: std.ArrayListUnmanaged(u8) = .{},
9
10pub fn init(dw: *DwarfInfo, allocator: Allocator, di: DebugInfo) !void {
11 try dw.strtab.ensureTotalCapacityPrecise(allocator, di.debug_str.len);
12 dw.strtab.appendSliceAssumeCapacity(di.debug_str);
13 try dw.parseAbbrevTables(allocator, di);
14 try dw.parseCompileUnits(allocator, di);
15}
16
17pub fn deinit(dw: *DwarfInfo, allocator: Allocator) void {
18 dw.abbrev_tables.deinit(allocator);
19 for (dw.compile_units.items) |*cu| {
20 cu.deinit(allocator);
21 }
22 dw.compile_units.deinit(allocator);
23 dw.strtab.deinit(allocator);
24 dw.di_data.deinit(allocator);
25}
26
27fn appendDiData(dw: *DwarfInfo, allocator: Allocator, values: []const u8) error{OutOfMemory}!u32 {
28 const index: u32 = @intCast(dw.di_data.items.len);
29 try dw.di_data.ensureUnusedCapacity(allocator, values.len);
30 dw.di_data.appendSliceAssumeCapacity(values);
31 return index;
32}
33
34fn getString(dw: DwarfInfo, off: usize) [:0]const u8 {
35 assert(off < dw.strtab.items.len);
36 return mem.sliceTo(@as([*:0]const u8, @ptrCast(dw.strtab.items.ptr + off)), 0);
37}
38
39fn parseAbbrevTables(dw: *DwarfInfo, allocator: Allocator, di: DebugInfo) !void {
40 const tracy = trace(@src());
41 defer tracy.end();
42
43 const debug_abbrev = di.debug_abbrev;
44 var stream = std.io.fixedBufferStream(debug_abbrev);
45 var creader = std.io.countingReader(stream.reader());
46 const reader = creader.reader();
47
48 while (true) {
49 if (creader.bytes_read >= debug_abbrev.len) break;
50
51 try dw.abbrev_tables.ensureUnusedCapacity(allocator, 1);
52 const table_gop = dw.abbrev_tables.getOrPutAssumeCapacity(@intCast(creader.bytes_read));
53 assert(!table_gop.found_existing);
54 const table = table_gop.value_ptr;
55 table.* = .{};
56
57 while (true) {
58 const code = try leb.readULEB128(Code, reader);
59 if (code == 0) break;
60
61 try table.decls.ensureUnusedCapacity(allocator, 1);
62 const decl_gop = table.decls.getOrPutAssumeCapacity(code);
63 assert(!decl_gop.found_existing);
64 const decl = decl_gop.value_ptr;
65 decl.* = .{
66 .code = code,
67 .tag = undefined,
68 .children = false,
69 };
70 decl.tag = try leb.readULEB128(Tag, reader);
71 decl.children = (try reader.readByte()) > 0;
72
73 while (true) {
74 const at = try leb.readULEB128(At, reader);
75 const form = try leb.readULEB128(Form, reader);
76 if (at == 0 and form == 0) break;
77
78 try decl.attrs.ensureUnusedCapacity(allocator, 1);
79 const attr_gop = decl.attrs.getOrPutAssumeCapacity(at);
80 assert(!attr_gop.found_existing);
81 const attr = attr_gop.value_ptr;
82 attr.* = .{
83 .at = at,
84 .form = form,
85 };
86 }
87 }
88 }
89}
90
91fn parseCompileUnits(dw: *DwarfInfo, allocator: Allocator, di: DebugInfo) !void {
92 const tracy = trace(@src());
93 defer tracy.end();
94
95 const debug_info = di.debug_info;
96 var stream = std.io.fixedBufferStream(debug_info);
97 var creader = std.io.countingReader(stream.reader());
98 const reader = creader.reader();
99
100 while (true) {
101 if (creader.bytes_read == debug_info.len) break;
102
103 const cu = try dw.compile_units.addOne(allocator);
104 cu.* = .{
105 .header = undefined,
106 .pos = creader.bytes_read,
107 };
108
109 var length: u64 = try reader.readInt(u32, .little);
110 const is_64bit = length == 0xffffffff;
111 if (is_64bit) {
112 length = try reader.readInt(u64, .little);
113 }
114 cu.header.format = if (is_64bit) .dwarf64 else .dwarf32;
115 cu.header.length = length;
116 cu.header.version = try reader.readInt(u16, .little);
117 cu.header.debug_abbrev_offset = try readOffset(cu.header.format, reader);
118 cu.header.address_size = try reader.readInt(u8, .little);
119
120 const table = dw.abbrev_tables.get(cu.header.debug_abbrev_offset).?;
121 try dw.parseDie(allocator, cu, table, di, null, &creader);
122 }
123}
124
125fn parseDie(
126 dw: *DwarfInfo,
127 allocator: Allocator,
128 cu: *CompileUnit,
129 table: AbbrevTable,
130 di: DebugInfo,
131 parent: ?u32,
132 creader: anytype,
133) anyerror!void {
134 const tracy = trace(@src());
135 defer tracy.end();
136
137 while (creader.bytes_read < cu.nextCompileUnitOffset()) {
138 const die = try cu.addDie(allocator);
139 cu.diePtr(die).* = .{ .code = undefined };
140 if (parent) |p| {
141 try cu.diePtr(p).children.append(allocator, die);
142 } else {
143 try cu.children.append(allocator, die);
144 }
145
146 const code = try leb.readULEB128(Code, creader.reader());
147 cu.diePtr(die).code = code;
148
149 if (code == 0) {
150 if (parent == null) continue;
151 return; // Close scope
152 }
153
154 const decl = table.decls.get(code) orelse return error.MalformedDwarf; // TODO better errors
155 const data = di.debug_info;
156 try cu.diePtr(die).values.ensureTotalCapacityPrecise(allocator, decl.attrs.values().len);
157
158 for (decl.attrs.values()) |attr| {
159 const start = std.math.cast(usize, creader.bytes_read) orelse return error.Overflow;
160 try advanceByFormSize(cu, attr.form, creader);
161 const end = std.math.cast(usize, creader.bytes_read) orelse return error.Overflow;
162 const index = try dw.appendDiData(allocator, data[start..end]);
163 cu.diePtr(die).values.appendAssumeCapacity(.{ .index = index, .len = @intCast(end - start) });
164 }
165
166 if (decl.children) {
167 // Open scope
168 try dw.parseDie(allocator, cu, table, di, die, creader);
169 }
170 }
171}
172
173fn advanceByFormSize(cu: *CompileUnit, form: Form, creader: anytype) !void {
174 const tracy = trace(@src());
175 defer tracy.end();
176
177 const reader = creader.reader();
178 switch (form) {
179 dwarf.FORM.strp,
180 dwarf.FORM.sec_offset,
181 dwarf.FORM.ref_addr,
182 => {
183 _ = try readOffset(cu.header.format, reader);
184 },
185
186 dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}),
187
188 dwarf.FORM.block1,
189 dwarf.FORM.block2,
190 dwarf.FORM.block4,
191 dwarf.FORM.block,
192 => {
193 const len: u64 = switch (form) {
194 dwarf.FORM.block1 => try reader.readInt(u8, .little),
195 dwarf.FORM.block2 => try reader.readInt(u16, .little),
196 dwarf.FORM.block4 => try reader.readInt(u32, .little),
197 dwarf.FORM.block => try leb.readULEB128(u64, reader),
198 else => unreachable,
199 };
200 var i: u64 = 0;
201 while (i < len) : (i += 1) {
202 _ = try reader.readByte();
203 }
204 },
205
206 dwarf.FORM.exprloc => {
207 const len = try leb.readULEB128(u64, reader);
208 var i: u64 = 0;
209 while (i < len) : (i += 1) {
210 _ = try reader.readByte();
211 }
212 },
213 dwarf.FORM.flag_present => {},
214
215 dwarf.FORM.data1,
216 dwarf.FORM.ref1,
217 dwarf.FORM.flag,
218 => try reader.skipBytes(1, .{}),
219
220 dwarf.FORM.data2,
221 dwarf.FORM.ref2,
222 => try reader.skipBytes(2, .{}),
223
224 dwarf.FORM.data4,
225 dwarf.FORM.ref4,
226 => try reader.skipBytes(4, .{}),
227
228 dwarf.FORM.data8,
229 dwarf.FORM.ref8,
230 dwarf.FORM.ref_sig8,
231 => try reader.skipBytes(8, .{}),
232
233 dwarf.FORM.udata,
234 dwarf.FORM.ref_udata,
235 => {
236 _ = try leb.readULEB128(u64, reader);
237 },
238
239 dwarf.FORM.sdata => {
240 _ = try leb.readILEB128(i64, reader);
241 },
242
243 dwarf.FORM.string => {
244 while (true) {
245 const byte = try reader.readByte();
246 if (byte == 0x0) break;
247 }
248 },
249
250 else => {
251 // TODO better errors
252 log.err("unhandled DW_FORM_* value with identifier {x}", .{form});
253 return error.UnhandledDwFormValue;
254 },
255 }
256}
257
258fn readOffset(format: Format, reader: anytype) !u64 {
259 return switch (format) {
260 .dwarf32 => try reader.readInt(u32, .little),
261 .dwarf64 => try reader.readInt(u64, .little),
262 };
263}
264
265pub const AbbrevTable = struct {
266 /// Table of abbreviation declarations indexed by their assigned code value
267 decls: std.AutoArrayHashMapUnmanaged(Code, Decl) = .{},
268
269 pub fn deinit(table: *AbbrevTable, gpa: Allocator) void {
270 for (table.decls.values()) |*decl| {
271 decl.deinit(gpa);
272 }
273 table.decls.deinit(gpa);
274 }
275};
276
277pub const Decl = struct {
278 code: Code,
279 tag: Tag,
280 children: bool,
281
282 /// Table of attributes indexed by their AT value
283 attrs: std.AutoArrayHashMapUnmanaged(At, Attr) = .{},
284
285 pub fn deinit(decl: *Decl, gpa: Allocator) void {
286 decl.attrs.deinit(gpa);
287 }
288};
289
290pub const Attr = struct {
291 at: At,
292 form: Form,
293};
294
295pub const At = u64;
296pub const Code = u64;
297pub const Form = u64;
298pub const Tag = u64;
299
300pub const CompileUnitHeader = struct {
301 format: Format,
302 length: u64,
303 version: u16,
304 debug_abbrev_offset: u64,
305 address_size: u8,
306};
307
308pub const CompileUnit = struct {
309 header: CompileUnitHeader,
310 pos: u64,
311 dies: std.ArrayListUnmanaged(Die) = .{},
312 children: std.ArrayListUnmanaged(Die.Index) = .{},
313
314 pub fn deinit(cu: *CompileUnit, gpa: Allocator) void {
315 for (cu.dies.items) |*die| {
316 die.deinit(gpa);
317 }
318 cu.dies.deinit(gpa);
319 cu.children.deinit(gpa);
320 }
321
322 pub fn addDie(cu: *CompileUnit, gpa: Allocator) !Die.Index {
323 const index = @as(Die.Index, @intCast(cu.dies.items.len));
324 _ = try cu.dies.addOne(gpa);
325 return index;
326 }
327
328 pub fn diePtr(cu: *CompileUnit, index: Die.Index) *Die {
329 return &cu.dies.items[index];
330 }
331
332 pub fn getCompileDir(cu: CompileUnit, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
333 assert(cu.dies.items.len > 0);
334 const die = cu.dies.items[0];
335 const res = die.find(dwarf.AT.comp_dir, cu, ctx) orelse return null;
336 return res.getString(cu.header.format, ctx);
337 }
338
339 pub fn getSourceFile(cu: CompileUnit, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
340 assert(cu.dies.items.len > 0);
341 const die = cu.dies.items[0];
342 const res = die.find(dwarf.AT.name, cu, ctx) orelse return null;
343 return res.getString(cu.header.format, ctx);
344 }
345
346 pub fn nextCompileUnitOffset(cu: CompileUnit) u64 {
347 return cu.pos + switch (cu.header.format) {
348 .dwarf32 => @as(u64, 4),
349 .dwarf64 => 12,
350 } + cu.header.length;
351 }
352};
353
354pub const Die = struct {
355 code: Code,
356 values: std.ArrayListUnmanaged(struct { index: u32, len: u32 }) = .{},
357 children: std.ArrayListUnmanaged(Die.Index) = .{},
358
359 pub fn deinit(die: *Die, gpa: Allocator) void {
360 die.values.deinit(gpa);
361 die.children.deinit(gpa);
362 }
363
364 pub fn find(die: Die, at: At, cu: CompileUnit, ctx: DwarfInfo) ?DieValue {
365 const table = ctx.abbrev_tables.get(cu.header.debug_abbrev_offset) orelse return null;
366 const decl = table.decls.get(die.code).?;
367 const index = decl.attrs.getIndex(at) orelse return null;
368 const attr = decl.attrs.values()[index];
369 const value = die.values.items[index];
370 return .{ .attr = attr, .bytes = ctx.di_data.items[value.index..][0..value.len] };
371 }
372
373 pub const Index = u32;
374};
375
376pub const DieValue = struct {
377 attr: Attr,
378 bytes: []const u8,
379
380 pub fn getFlag(value: DieValue) ?bool {
381 return switch (value.attr.form) {
382 dwarf.FORM.flag => value.bytes[0] == 1,
383 dwarf.FORM.flag_present => true,
384 else => null,
385 };
386 }
387
388 pub fn getString(value: DieValue, format: Format, ctx: DwarfInfo) error{Overflow}!?[:0]const u8 {
389 switch (value.attr.form) {
390 dwarf.FORM.string => {
391 return mem.sliceTo(@as([*:0]const u8, @ptrCast(value.bytes.ptr)), 0);
392 },
393 dwarf.FORM.strp => {
394 const off = switch (format) {
395 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
396 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
397 };
398 const off_u = std.math.cast(usize, off) orelse return error.Overflow;
399 return ctx.getString(off_u);
400 },
401 else => return null,
402 }
403 }
404
405 pub fn getSecOffset(value: DieValue, format: Format) ?u64 {
406 return switch (value.attr.form) {
407 dwarf.FORM.sec_offset => switch (format) {
408 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
409 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
410 },
411 else => null,
412 };
413 }
414
415 pub fn getConstant(value: DieValue) !?i128 {
416 var stream = std.io.fixedBufferStream(value.bytes);
417 const reader = stream.reader();
418 return switch (value.attr.form) {
419 dwarf.FORM.data1 => value.bytes[0],
420 dwarf.FORM.data2 => mem.readInt(u16, value.bytes[0..2], .little),
421 dwarf.FORM.data4 => mem.readInt(u32, value.bytes[0..4], .little),
422 dwarf.FORM.data8 => mem.readInt(u64, value.bytes[0..8], .little),
423 dwarf.FORM.udata => try leb.readULEB128(u64, reader),
424 dwarf.FORM.sdata => try leb.readILEB128(i64, reader),
425 else => null,
426 };
427 }
428
429 pub fn getReference(value: DieValue, format: Format) !?u64 {
430 var stream = std.io.fixedBufferStream(value.bytes);
431 const reader = stream.reader();
432 return switch (value.attr.form) {
433 dwarf.FORM.ref1 => value.bytes[0],
434 dwarf.FORM.ref2 => mem.readInt(u16, value.bytes[0..2], .little),
435 dwarf.FORM.ref4 => mem.readInt(u32, value.bytes[0..4], .little),
436 dwarf.FORM.ref8 => mem.readInt(u64, value.bytes[0..8], .little),
437 dwarf.FORM.ref_udata => try leb.readULEB128(u64, reader),
438 dwarf.FORM.ref_addr => switch (format) {
439 .dwarf32 => mem.readInt(u32, value.bytes[0..4], .little),
440 .dwarf64 => mem.readInt(u64, value.bytes[0..8], .little),
441 },
442 else => null,
443 };
444 }
445
446 pub fn getAddr(value: DieValue, header: CompileUnitHeader) ?u64 {
447 return switch (value.attr.form) {
448 dwarf.FORM.addr => switch (header.address_size) {
449 1 => value.bytes[0],
450 2 => mem.readInt(u16, value.bytes[0..2], .little),
451 4 => mem.readInt(u32, value.bytes[0..4], .little),
452 8 => mem.readInt(u64, value.bytes[0..8], .little),
453 else => null,
454 },
455 else => null,
456 };
457 }
458
459 pub fn getExprloc(value: DieValue) !?[]const u8 {
460 if (value.attr.form != dwarf.FORM.exprloc) return null;
461 var stream = std.io.fixedBufferStream(value.bytes);
462 var creader = std.io.countingReader(stream.reader());
463 const reader = creader.reader();
464 const expr_len = try leb.readULEB128(u64, reader);
465 return value.bytes[creader.bytes_read..][0..expr_len];
466 }
467};
468
469pub const Format = enum {
470 dwarf32,
471 dwarf64,
472};
473
474const DebugInfo = struct {
475 debug_info: []const u8,
476 debug_abbrev: []const u8,
477 debug_str: []const u8,
478};
479
480const assert = std.debug.assert;
481const dwarf = std.dwarf;
482const leb = std.leb;
483const log = std.log.scoped(.link);
484const mem = std.mem;
485const std = @import("std");
486const trace = @import("../../tracy.zig").trace;
487
488const Allocator = mem.Allocator;
489const DwarfInfo = @This();
490const MachO = @import("../MachO.zig");
src/link/MachO/Object.zig+126-31
......@@ -13,7 +13,7 @@ symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
1313atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
1414
1515platform: ?MachO.Platform = null,
16dwarf_info: ?DwarfInfo = null,
16compile_unit: ?CompileUnit = null,
1717stab_files: std.ArrayListUnmanaged(StabFile) = .{},
1818
1919eh_frame_sect_index: ?u8 = null,
......@@ -31,12 +31,6 @@ dynamic_relocs: MachO.DynamicRelocs = .{},
3131output_symtab_ctx: MachO.SymtabCtx = .{},
3232output_ar_state: Archive.ArState = .{},
3333
34const InArchive = struct {
35 path: []const u8,
36 offset: u64,
37 size: u32,
38};
39
4034pub fn isObject(path: []const u8) !bool {
4135 const file = try std.fs.cwd().openFile(path, .{});
4236 defer file.close();
......@@ -60,7 +54,6 @@ pub fn deinit(self: *Object, allocator: Allocator) void {
6054 self.fdes.deinit(allocator);
6155 self.eh_frame_data.deinit(allocator);
6256 self.unwind_records.deinit(allocator);
63 if (self.dwarf_info) |*dw| dw.deinit(allocator);
6457 for (self.stab_files.items) |*sf| {
6558 sf.stabs.deinit(allocator);
6659 }
......@@ -251,8 +244,6 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
251244 // }
252245 }
253246
254 try self.initDwarfInfo(macho_file);
255
256247 for (self.atoms.items) |atom_index| {
257248 const atom = macho_file.getAtom(atom_index).?;
258249 const isec = atom.getInputSection(macho_file);
......@@ -1214,7 +1205,7 @@ fn parseUnwindRecords(self: *Object, macho_file: *MachO) !void {
12141205/// and record that so that we can emit symbol stabs.
12151206/// TODO in the future, we want parse debug info and debug line sections so that
12161207/// we can provide nice error locations to the user.
1217fn initDwarfInfo(self: *Object, macho_file: *MachO) !void {
1208pub fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {
12181209 const tracy = trace(@src());
12191210 defer tracy.end();
12201211
......@@ -1240,17 +1231,107 @@ fn initDwarfInfo(self: *Object, macho_file: *MachO) !void {
12401231 const debug_str = if (debug_str_index) |index| try self.getSectionData(@intCast(index), macho_file) else &[0]u8{};
12411232 defer gpa.free(debug_str);
12421233
1243 var dwarf_info = DwarfInfo{};
1244 errdefer dwarf_info.deinit(gpa);
1245 dwarf_info.init(gpa, .{
1234 self.compile_unit = self.findCompileUnit(.{
1235 .gpa = gpa,
12461236 .debug_info = debug_info,
12471237 .debug_abbrev = debug_abbrev,
12481238 .debug_str = debug_str,
1249 }) catch {
1250 try macho_file.reportParseError2(self.index, "invalid __DWARF info found", .{});
1251 return error.MalformedObject;
1239 }) catch null; // TODO figure out what errors are fatal, and when we silently fail
1240}
1241
1242fn findCompileUnit(self: *Object, args: struct {
1243 gpa: Allocator,
1244 debug_info: []const u8,
1245 debug_abbrev: []const u8,
1246 debug_str: []const u8,
1247}) !CompileUnit {
1248 var cu_wip: struct {
1249 comp_dir: ?[:0]const u8 = null,
1250 tu_name: ?[:0]const u8 = null,
1251 } = .{};
1252
1253 const gpa = args.gpa;
1254 var info_reader = dwarf.InfoReader{ .bytes = args.debug_info, .strtab = args.debug_str };
1255 var abbrev_reader = dwarf.AbbrevReader{ .bytes = args.debug_abbrev };
1256
1257 const cuh = try info_reader.readCompileUnitHeader();
1258 try abbrev_reader.seekTo(cuh.debug_abbrev_offset);
1259
1260 const cu_decl = (try abbrev_reader.readDecl()) orelse return error.Eof;
1261 if (cu_decl.tag != dwarf.TAG.compile_unit) return error.UnexpectedTag;
1262
1263 try info_reader.seekToDie(cu_decl.code, cuh, &abbrev_reader);
1264
1265 while (try abbrev_reader.readAttr()) |attr| switch (attr.at) {
1266 dwarf.AT.name => {
1267 cu_wip.tu_name = try info_reader.readString(attr.form, cuh);
1268 },
1269 dwarf.AT.comp_dir => {
1270 cu_wip.comp_dir = try info_reader.readString(attr.form, cuh);
1271 },
1272 else => switch (attr.form) {
1273 dwarf.FORM.sec_offset,
1274 dwarf.FORM.ref_addr,
1275 => {
1276 _ = try info_reader.readOffset(cuh.format);
1277 },
1278
1279 dwarf.FORM.addr => {
1280 _ = try info_reader.readNBytes(cuh.address_size);
1281 },
1282
1283 dwarf.FORM.block1,
1284 dwarf.FORM.block2,
1285 dwarf.FORM.block4,
1286 dwarf.FORM.block,
1287 => {
1288 _ = try info_reader.readBlock(attr.form);
1289 },
1290
1291 dwarf.FORM.exprloc => {
1292 _ = try info_reader.readExprLoc();
1293 },
1294
1295 dwarf.FORM.flag_present => {},
1296
1297 dwarf.FORM.data1,
1298 dwarf.FORM.ref1,
1299 dwarf.FORM.flag,
1300 dwarf.FORM.data2,
1301 dwarf.FORM.ref2,
1302 dwarf.FORM.data4,
1303 dwarf.FORM.ref4,
1304 dwarf.FORM.data8,
1305 dwarf.FORM.ref8,
1306 dwarf.FORM.ref_sig8,
1307 dwarf.FORM.udata,
1308 dwarf.FORM.ref_udata,
1309 dwarf.FORM.sdata,
1310 => {
1311 _ = try info_reader.readConstant(attr.form);
1312 },
1313
1314 dwarf.FORM.strp,
1315 dwarf.FORM.string,
1316 => {
1317 _ = try info_reader.readString(attr.form, cuh);
1318 },
1319
1320 else => {
1321 // TODO actual errors?
1322 log.err("unhandled DW_FORM_* value with identifier {x}", .{attr.form});
1323 return error.UnhandledForm;
1324 },
1325 },
1326 };
1327
1328 if (cu_wip.comp_dir == null) return error.MissingCompDir;
1329 if (cu_wip.tu_name == null) return error.MissingTuName;
1330
1331 return .{
1332 .comp_dir = try self.addString(gpa, cu_wip.comp_dir.?),
1333 .tu_name = try self.addString(gpa, cu_wip.tu_name.?),
12521334 };
1253 self.dwarf_info = dwarf_info;
12541335}
12551336
12561337pub fn resolveSymbols(self: *Object, macho_file: *MachO) void {
......@@ -1591,10 +1672,9 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {
15911672}
15921673
15931674pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void {
1594 if (self.dwarf_info) |dw| {
1595 const cu = dw.compile_units.items[0];
1596 const comp_dir = try cu.getCompileDir(dw) orelse return;
1597 const tu_name = try cu.getSourceFile(dw) orelse return;
1675 if (self.compile_unit) |cu| {
1676 const comp_dir = cu.getCompDir(self);
1677 const tu_name = cu.getTuName(self);
15981678
15991679 self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO
16001680 self.output_symtab_ctx.strsize += @as(u32, @intCast(comp_dir.len + 1)); // comp_dir
......@@ -1709,10 +1789,9 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O
17091789
17101790 var index = self.output_symtab_ctx.istab;
17111791
1712 if (self.dwarf_info) |dw| {
1713 const cu = dw.compile_units.items[0];
1714 const comp_dir = try cu.getCompileDir(dw) orelse return;
1715 const tu_name = try cu.getSourceFile(dw) orelse return;
1792 if (self.compile_unit) |cu| {
1793 const comp_dir = cu.getCompDir(self);
1794 const tu_name = cu.getTuName(self);
17161795
17171796 // Open scope
17181797 // N_SO comp_dir
......@@ -1958,10 +2037,7 @@ pub fn hasEhFrameRecords(self: Object) bool {
19582037}
19592038
19602039pub fn hasDebugInfo(self: Object) bool {
1961 if (self.dwarf_info) |dw| {
1962 return dw.compile_units.items.len > 0;
1963 }
1964 return self.hasSymbolStabs();
2040 return self.compile_unit != null or self.hasSymbolStabs();
19652041}
19662042
19672043fn hasSymbolStabs(self: Object) bool {
......@@ -2194,6 +2270,25 @@ const StabFile = struct {
21942270 };
21952271};
21962272
2273const CompileUnit = struct {
2274 comp_dir: u32,
2275 tu_name: u32,
2276
2277 fn getCompDir(cu: CompileUnit, object: *const Object) [:0]const u8 {
2278 return object.getString(cu.comp_dir);
2279 }
2280
2281 fn getTuName(cu: CompileUnit, object: *const Object) [:0]const u8 {
2282 return object.getString(cu.tu_name);
2283 }
2284};
2285
2286const InArchive = struct {
2287 path: []const u8,
2288 offset: u64,
2289 size: u32,
2290};
2291
21972292const x86_64 = struct {
21982293 fn parseRelocs(
21992294 self: *const Object,
......@@ -2548,6 +2643,7 @@ const aarch64 = struct {
25482643};
25492644
25502645const assert = std.debug.assert;
2646const dwarf = @import("dwarf.zig");
25512647const eh_frame = @import("eh_frame.zig");
25522648const log = std.log.scoped(.link);
25532649const macho = std.macho;
......@@ -2560,7 +2656,6 @@ const Allocator = mem.Allocator;
25602656const Archive = @import("Archive.zig");
25612657const Atom = @import("Atom.zig");
25622658const Cie = eh_frame.Cie;
2563const DwarfInfo = @import("DwarfInfo.zig");
25642659const Fde = eh_frame.Fde;
25652660const File = @import("file.zig").File;
25662661const LoadCommandIterator = macho.LoadCommandIterator;
src/link/MachO/dwarf.zig created+286
......@@ -0,0 +1,286 @@
1pub const InfoReader = struct {
2 bytes: []const u8,
3 strtab: []const u8,
4 pos: usize = 0,
5
6 pub fn readCompileUnitHeader(p: *InfoReader) !CompileUnitHeader {
7 var length: u64 = try p.readInt(u32);
8 const is_64bit = length == 0xffffffff;
9 if (is_64bit) {
10 length = try p.readInt(u64);
11 }
12 const dw_fmt: DwarfFormat = if (is_64bit) .dwarf64 else .dwarf32;
13 return .{
14 .format = dw_fmt,
15 .length = length,
16 .version = try p.readInt(u16),
17 .debug_abbrev_offset = try p.readOffset(dw_fmt),
18 .address_size = try p.readByte(),
19 };
20 }
21
22 pub fn seekToDie(p: *InfoReader, code: Code, cuh: CompileUnitHeader, abbrev_reader: *AbbrevReader) !void {
23 const cuh_length = math.cast(usize, cuh.length) orelse return error.Overflow;
24 const end_pos = p.pos + switch (cuh.format) {
25 .dwarf32 => @as(usize, 4),
26 .dwarf64 => 12,
27 } + cuh_length;
28 while (p.pos < end_pos) {
29 const di_code = try p.readULEB128(u64);
30 if (di_code == 0) return error.Eof;
31 if (di_code == code) return;
32
33 while (try abbrev_reader.readAttr()) |attr| switch (attr.at) {
34 dwarf.FORM.sec_offset,
35 dwarf.FORM.ref_addr,
36 => {
37 _ = try p.readOffset(cuh.format);
38 },
39
40 dwarf.FORM.addr => {
41 _ = try p.readNBytes(cuh.address_size);
42 },
43
44 dwarf.FORM.block1,
45 dwarf.FORM.block2,
46 dwarf.FORM.block4,
47 dwarf.FORM.block,
48 => {
49 _ = try p.readBlock(attr.form);
50 },
51
52 dwarf.FORM.exprloc => {
53 _ = try p.readExprLoc();
54 },
55
56 dwarf.FORM.flag_present => {},
57
58 dwarf.FORM.data1,
59 dwarf.FORM.ref1,
60 dwarf.FORM.flag,
61 dwarf.FORM.data2,
62 dwarf.FORM.ref2,
63 dwarf.FORM.data4,
64 dwarf.FORM.ref4,
65 dwarf.FORM.data8,
66 dwarf.FORM.ref8,
67 dwarf.FORM.ref_sig8,
68 dwarf.FORM.udata,
69 dwarf.FORM.ref_udata,
70 dwarf.FORM.sdata,
71 => {
72 _ = try p.readConstant(attr.form);
73 },
74
75 dwarf.FORM.strp,
76 dwarf.FORM.string,
77 => {
78 _ = try p.readString(attr.form, cuh);
79 },
80
81 else => {
82 // TODO better errors
83 log.err("unhandled DW_FORM_* value with identifier {x}", .{attr.form});
84 return error.UnhandledDwFormValue;
85 },
86 };
87 }
88 }
89
90 pub fn readBlock(p: *InfoReader, form: Form) ![]const u8 {
91 const len: u64 = switch (form) {
92 dwarf.FORM.block1 => try p.readByte(),
93 dwarf.FORM.block2 => try p.readInt(u16),
94 dwarf.FORM.block4 => try p.readInt(u32),
95 dwarf.FORM.block => try p.readULEB128(u64),
96 else => unreachable,
97 };
98 return p.readNBytes(len);
99 }
100
101 pub fn readExprLoc(p: *InfoReader) ![]const u8 {
102 const len: u64 = try p.readULEB128(u64);
103 return p.readNBytes(len);
104 }
105
106 pub fn readConstant(p: *InfoReader, form: Form) !u64 {
107 return switch (form) {
108 dwarf.FORM.data1, dwarf.FORM.ref1, dwarf.FORM.flag => try p.readByte(),
109 dwarf.FORM.data2, dwarf.FORM.ref2 => try p.readInt(u16),
110 dwarf.FORM.data4, dwarf.FORM.ref4 => try p.readInt(u32),
111 dwarf.FORM.data8, dwarf.FORM.ref8, dwarf.FORM.ref_sig8 => try p.readInt(u64),
112 dwarf.FORM.udata, dwarf.FORM.ref_udata => try p.readULEB128(u64),
113 dwarf.FORM.sdata => @bitCast(try p.readILEB128(i64)),
114 else => return error.UnhandledConstantForm,
115 };
116 }
117
118 pub fn readString(p: *InfoReader, form: Form, cuh: CompileUnitHeader) ![:0]const u8 {
119 switch (form) {
120 dwarf.FORM.strp => {
121 const off = try p.readOffset(cuh.format);
122 const off_u = math.cast(usize, off) orelse return error.Overflow;
123 return mem.sliceTo(@as([*:0]const u8, @ptrCast(p.strtab.ptr + off_u)), 0);
124 },
125 dwarf.FORM.string => {
126 const start = p.pos;
127 while (p.pos < p.bytes.len) : (p.pos += 1) {
128 if (p.bytes[p.pos] == 0) break;
129 }
130 if (p.bytes[p.pos] != 0) return error.Eof;
131 return p.bytes[start..p.pos :0];
132 },
133 else => unreachable,
134 }
135 }
136
137 pub fn readByte(p: *InfoReader) !u8 {
138 if (p.pos + 1 > p.bytes.len) return error.Eof;
139 defer p.pos += 1;
140 return p.bytes[p.pos];
141 }
142
143 pub fn readNBytes(p: *InfoReader, num: u64) ![]const u8 {
144 const num_usize = math.cast(usize, num) orelse return error.Overflow;
145 if (p.pos + num_usize > p.bytes.len) return error.Eof;
146 defer p.pos += num_usize;
147 return p.bytes[p.pos..][0..num_usize];
148 }
149
150 pub fn readInt(p: *InfoReader, comptime Int: type) !Int {
151 if (p.pos + @sizeOf(Int) > p.bytes.len) return error.Eof;
152 defer p.pos += @sizeOf(Int);
153 return mem.readInt(Int, p.bytes[p.pos..][0..@sizeOf(Int)], .little);
154 }
155
156 pub fn readOffset(p: *InfoReader, dw_fmt: DwarfFormat) !u64 {
157 return switch (dw_fmt) {
158 .dwarf32 => try p.readInt(u32),
159 .dwarf64 => try p.readInt(u64),
160 };
161 }
162
163 pub fn readULEB128(p: *InfoReader, comptime Type: type) !Type {
164 var stream = std.io.fixedBufferStream(p.bytes[p.pos..]);
165 var creader = std.io.countingReader(stream.reader());
166 const value: Type = try leb.readULEB128(Type, creader.reader());
167 p.pos += math.cast(usize, creader.bytes_read) orelse return error.Overflow;
168 return value;
169 }
170
171 pub fn readILEB128(p: *InfoReader, comptime Type: type) !Type {
172 var stream = std.io.fixedBufferStream(p.bytes[p.pos..]);
173 var creader = std.io.countingReader(stream.reader());
174 const value: Type = try leb.readILEB128(Type, creader.reader());
175 p.pos += math.cast(usize, creader.bytes_read) orelse return error.Overflow;
176 return value;
177 }
178
179 pub fn seekTo(p: *InfoReader, off: u64) !void {
180 p.pos = math.cast(usize, off) orelse return error.Overflow;
181 }
182};
183
184pub const AbbrevReader = struct {
185 bytes: []const u8,
186 pos: usize = 0,
187
188 pub fn hasMore(p: AbbrevReader) bool {
189 return p.pos < p.bytes.len;
190 }
191
192 pub fn readDecl(p: *AbbrevReader) !?AbbrevDecl {
193 const pos = p.pos;
194 const code = try p.readULEB128(Code);
195 if (code == 0) return null;
196
197 const tag = try p.readULEB128(Tag);
198 const has_children = (try p.readByte()) > 0;
199 return .{
200 .code = code,
201 .pos = pos,
202 .len = p.pos - pos,
203 .tag = tag,
204 .has_children = has_children,
205 };
206 }
207
208 pub fn readAttr(p: *AbbrevReader) !?AbbrevAttr {
209 const pos = p.pos;
210 const at = try p.readULEB128(At);
211 const form = try p.readULEB128(Form);
212 return if (at == 0 and form == 0) null else .{
213 .at = at,
214 .form = form,
215 .pos = pos,
216 .len = p.pos - pos,
217 };
218 }
219
220 pub fn readByte(p: *AbbrevReader) !u8 {
221 if (p.pos + 1 > p.bytes.len) return error.Eof;
222 defer p.pos += 1;
223 return p.bytes[p.pos];
224 }
225
226 pub fn readULEB128(p: *AbbrevReader, comptime Type: type) !Type {
227 var stream = std.io.fixedBufferStream(p.bytes[p.pos..]);
228 var creader = std.io.countingReader(stream.reader());
229 const value: Type = try leb.readULEB128(Type, creader.reader());
230 p.pos += math.cast(usize, creader.bytes_read) orelse return error.Overflow;
231 return value;
232 }
233
234 pub fn seekTo(p: *AbbrevReader, off: u64) !void {
235 p.pos = math.cast(usize, off) orelse return error.Overflow;
236 }
237};
238
239const AbbrevDecl = struct {
240 code: Code,
241 pos: usize,
242 len: usize,
243 tag: Tag,
244 has_children: bool,
245};
246
247const AbbrevAttr = struct {
248 at: At,
249 form: Form,
250 pos: usize,
251 len: usize,
252};
253
254const CompileUnitHeader = struct {
255 format: DwarfFormat,
256 length: u64,
257 version: u16,
258 debug_abbrev_offset: u64,
259 address_size: u8,
260};
261
262const Die = struct {
263 pos: usize,
264 len: usize,
265};
266
267const DwarfFormat = enum {
268 dwarf32,
269 dwarf64,
270};
271
272const dwarf = std.dwarf;
273const leb = std.leb;
274const log = std.log.scoped(.link);
275const math = std.math;
276const mem = std.mem;
277const std = @import("std");
278
279const At = u64;
280const Code = u64;
281const Form = u64;
282const Tag = u64;
283
284pub const AT = dwarf.AT;
285pub const FORM = dwarf.FORM;
286pub const TAG = dwarf.TAG;
src/link/MachO/relocatable.zig+1
......@@ -46,6 +46,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c
4646
4747 try macho_file.addUndefinedGlobals();
4848 try macho_file.resolveSymbols();
49 try macho_file.parseDebugInfo();
4950 try macho_file.dedupLiterals();
5051 markExports(macho_file);
5152 claimUnresolved(macho_file);
test/link/macho.zig+49
......@@ -51,6 +51,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
5151 macho_step.dependOn(testRelocatableZig(b, .{ .target = default_target }));
5252 macho_step.dependOn(testSectionBoundarySymbols(b, .{ .target = default_target }));
5353 macho_step.dependOn(testSegmentBoundarySymbols(b, .{ .target = default_target }));
54 macho_step.dependOn(testSymbolStabs(b, .{ .target = default_target }));
5455 macho_step.dependOn(testStackSize(b, .{ .target = default_target }));
5556 macho_step.dependOn(testTentative(b, .{ .target = default_target }));
5657 macho_step.dependOn(testThunks(b, .{ .target = aarch64_target }));
......@@ -1993,6 +1994,54 @@ fn testSegmentBoundarySymbols(b: *Build, opts: Options) *Step {
19931994 return test_step;
19941995}
19951996
1997fn testSymbolStabs(b: *Build, opts: Options) *Step {
1998 const test_step = addTestStep(b, "symbol-stabs", opts);
1999
2000 const a_o = addObject(b, opts, .{ .name = "a", .c_source_bytes =
2001 \\int foo = 42;
2002 \\int getFoo() {
2003 \\ return foo;
2004 \\}
2005 });
2006
2007 const b_o = addObject(b, opts, .{ .name = "b", .c_source_bytes =
2008 \\int bar = 24;
2009 \\int getBar() {
2010 \\ return bar;
2011 \\}
2012 });
2013
2014 const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
2015 \\#include <stdio.h>
2016 \\extern int getFoo();
2017 \\extern int getBar();
2018 \\int main() {
2019 \\ printf("foo=%d,bar=%d", getFoo(), getBar());
2020 \\ return 0;
2021 \\}
2022 });
2023
2024 const exe = addExecutable(b, opts, .{ .name = "main" });
2025 exe.addObject(a_o);
2026 exe.addObject(b_o);
2027 exe.addObject(main_o);
2028
2029 const run = addRunArtifact(exe);
2030 run.expectStdOutEqual("foo=42,bar=24");
2031 test_step.dependOn(&run.step);
2032
2033 const check = exe.checkObject();
2034 check.checkInSymtab();
2035 check.checkContains("a.o"); // TODO we really should do a fuzzy search like OSO <ignore>/a.o
2036 check.checkInSymtab();
2037 check.checkContains("b.o");
2038 check.checkInSymtab();
2039 check.checkContains("main.o");
2040 test_step.dependOn(&check.step);
2041
2042 return test_step;
2043}
2044
19962045fn testStackSize(b: *Build, opts: Options) *Step {
19972046 const test_step = addTestStep(b, "stack-size", opts);
19982047