1//! Implements parsing, decoding, and caching of DWARF information.
2//!
3//! This API makes no assumptions about the relationship between the host and
4//! the target being debugged. In other words, any DWARF information can be used
5//! from any host via this API. Note, however, that the limits of 32-bit
6//! addressing can cause very large 64-bit binaries to be impossible to open on
7//! 32-bit hosts.
8//!
9//! For unopinionated types and bits, see `std.dwarf`.
10
11const std = @import("../std.zig");
12const Allocator = std.mem.Allocator;
13const mem = std.mem;
14const DW = std.dwarf;
15const AT = DW.AT;
16const FORM = DW.FORM;
17const Format = DW.Format;
18const RLE = DW.RLE;
19const UT = DW.UT;
20const assert = std.debug.assert;
21const cast = std.math.cast;
22const maxInt = std.math.maxInt;
23const ArrayList = std.ArrayList;
24const Endian = std.builtin.Endian;
25const Io = std.Io;
26const Reader = Io.Reader;
27const Error = std.debug.SelfInfoError;
28
29const Dwarf = @This();
30
31pub const expression = @import("Dwarf/expression.zig");
32pub const Unwind = @import("Dwarf/Unwind.zig");
33pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig");
34
35/// Useful to temporarily enable while working on this file.
36const debug_debug_mode = false;
37
38sections: SectionArray = @splat(null),
39
40/// Filled later by the initializer
41abbrev_table_list: ArrayList(Abbrev.Table) = .empty,
42/// Filled later by the initializer
43compile_unit_list: ArrayList(CompileUnit) = .empty,
44/// Filled later by the initializer
45func_list: ArrayList(Func) = .empty,
46
47/// Populated by `populateRanges`.
48ranges: ArrayList(Range) = .empty,
49
50pub const Range = struct {
51 start: u64,
52 end: u64,
53 /// Index into `compile_unit_list`.
54 compile_unit_index: usize,
55};
56
57pub const Section = struct {
58 data: []const u8,
59 /// If `data` is owned by this Dwarf.
60 owned: bool,
61
62 pub const Id = enum {
63 debug_info,
64 debug_abbrev,
65 debug_str,
66 debug_str_offsets,
67 debug_line,
68 debug_line_str,
69 debug_ranges,
70 debug_loclists,
71 debug_rnglists,
72 debug_addr,
73 debug_names,
74 };
75};
76
77pub const Abbrev = struct {
78 code: u64,
79 tag_id: u64,
80 has_children: bool,
81 attrs: []Attr,
82
83 fn deinit(abbrev: *Abbrev, gpa: Allocator) void {
84 gpa.free(abbrev.attrs);
85 abbrev.* = undefined;
86 }
87
88 const Attr = struct {
89 id: u64,
90 form_id: u64,
91 /// Only valid if form_id is .implicit_const
92 payload: i64,
93 };
94
95 const Table = struct {
96 // offset from .debug_abbrev
97 offset: u64,
98 abbrevs: []Abbrev,
99
100 fn deinit(table: *Table, gpa: Allocator) void {
101 for (table.abbrevs) |*abbrev| {
102 abbrev.deinit(gpa);
103 }
104 gpa.free(table.abbrevs);
105 table.* = undefined;
106 }
107
108 fn get(table: *const Table, abbrev_code: u64) ?*const Abbrev {
109 return for (table.abbrevs) |*abbrev| {
110 if (abbrev.code == abbrev_code) break abbrev;
111 } else null;
112 }
113 };
114};
115
116pub const CompileUnit = struct {
117 version: u16,
118 format: Format,
119 addr_size_bytes: u8,
120 die: Die,
121 pc_range: ?PcRange,
122
123 str_offsets_base: usize,
124 addr_base: usize,
125 rnglists_base: usize,
126 loclists_base: usize,
127 frame_base: ?*const FormValue,
128
129 src_loc_cache: ?SrcLocCache,
130
131 pub const SrcLocCache = struct {
132 line_table: LineTable,
133 directories: []const FileEntry,
134 files: []FileEntry,
135 version: u16,
136
137 pub const LineTable = std.array_hash_map.Auto(u64, LineEntry);
138
139 pub const LineEntry = struct {
140 line: u32,
141 column: u32,
142 /// Offset by 1 depending on whether Dwarf version is >= 5.
143 file: u32,
144
145 pub const invalid: LineEntry = .{
146 .line = undefined,
147 .column = undefined,
148 .file = std.math.maxInt(u32),
149 };
150
151 pub fn isInvalid(le: LineEntry) bool {
152 return le.file == invalid.file;
153 }
154 };
155
156 pub fn findSource(slc: *const SrcLocCache, address: u64) !LineEntry {
157 const index = std.sort.upperBound(u64, slc.line_table.keys(), address, struct {
158 fn order(context: u64, item: u64) std.math.Order {
159 return std.math.order(context, item);
160 }
161 }.order);
162 if (index == 0) return missing();
163 return slc.line_table.values()[index - 1];
164 }
165 };
166};
167
168pub const FormValue = union(enum) {
169 addr: u64,
170 addrx: u64,
171 block: []const u8,
172 udata: u64,
173 data16: *const [16]u8,
174 sdata: i64,
175 exprloc: []const u8,
176 flag: bool,
177 sec_offset: u64,
178 ref: u64,
179 ref_addr: u64,
180 string: [:0]const u8,
181 strp: u64,
182 strx: u64,
183 line_strp: u64,
184 loclistx: u64,
185 rnglistx: u64,
186
187 fn getString(fv: FormValue, di: Dwarf) ![:0]const u8 {
188 switch (fv) {
189 .string => |s| return s,
190 .strp => |off| return di.getString(off),
191 .line_strp => |off| return di.getLineString(off),
192 else => return bad(),
193 }
194 }
195
196 fn getUInt(fv: FormValue, comptime U: type) !U {
197 return switch (fv) {
198 inline .udata,
199 .sdata,
200 .sec_offset,
201 => |c| cast(U, c) orelse bad(),
202 else => bad(),
203 };
204 }
205};
206
207pub const Die = struct {
208 tag_id: u64,
209 has_children: bool,
210 attrs: []Attr,
211
212 const Attr = struct {
213 id: u64,
214 value: FormValue,
215 };
216
217 fn deinit(self: *Die, gpa: Allocator) void {
218 gpa.free(self.attrs);
219 self.* = undefined;
220 }
221
222 fn getAttr(self: *const Die, id: u64) ?*const FormValue {
223 for (self.attrs) |*attr| {
224 if (attr.id == id) return &attr.value;
225 }
226 return null;
227 }
228
229 fn getAttrAddr(
230 self: *const Die,
231 di: *const Dwarf,
232 endian: Endian,
233 id: u64,
234 compile_unit: *const CompileUnit,
235 ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {
236 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
237 return switch (form_value.*) {
238 .addr => |value| value,
239 .addrx => |index| di.readDebugAddr(endian, compile_unit, index),
240 else => bad(),
241 };
242 }
243
244 fn getAttrSecOffset(self: *const Die, id: u64) !u64 {
245 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
246 return form_value.getUInt(u64);
247 }
248
249 fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 {
250 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
251 return switch (form_value.*) {
252 .Const => |value| value.asUnsignedLe(),
253 else => bad(),
254 };
255 }
256
257 fn getAttrRef(self: *const Die, id: u64, unit_offset: u64, unit_len: u64) !u64 {
258 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
259 return switch (form_value.*) {
260 .ref => |offset| if (offset < unit_len) unit_offset + offset else bad(),
261 .ref_addr => |addr| addr,
262 else => bad(),
263 };
264 }
265
266 pub fn getAttrString(
267 self: *const Die,
268 di: *Dwarf,
269 endian: Endian,
270 id: u64,
271 opt_str: ?[]const u8,
272 compile_unit: *const CompileUnit,
273 ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 {
274 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
275 switch (form_value.*) {
276 .string => |value| return value,
277 .strp => |offset| return di.getString(offset),
278 .strx => |index| {
279 const debug_str_offsets = di.section(.debug_str_offsets) orelse return bad();
280 if (compile_unit.str_offsets_base == 0) return bad();
281 switch (compile_unit.format) {
282 .@"32" => {
283 const byte_offset = compile_unit.str_offsets_base + 4 * index;
284 if (byte_offset + 4 > debug_str_offsets.len) return bad();
285 const offset = mem.readInt(u32, debug_str_offsets[@intCast(byte_offset)..][0..4], endian);
286 return getStringGeneric(opt_str, offset);
287 },
288 .@"64" => {
289 const byte_offset = compile_unit.str_offsets_base + 8 * index;
290 if (byte_offset + 8 > debug_str_offsets.len) return bad();
291 const offset = mem.readInt(u64, debug_str_offsets[@intCast(byte_offset)..][0..8], endian);
292 return getStringGeneric(opt_str, offset);
293 },
294 }
295 },
296 .line_strp => |offset| return di.getLineString(offset),
297 else => return bad(),
298 }
299 }
300};
301
302const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
303pub const SectionArray = [num_sections]?Section;
304
305pub const OpenError = ScanError;
306
307/// Initialize DWARF info. The caller has the responsibility to initialize most
308/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the
309/// main binary file (not the secondary debug info file).
310pub fn open(d: *Dwarf, gpa: Allocator, endian: Endian) OpenError!void {
311 try d.scanAllFunctions(gpa, endian);
312 try d.scanAllCompileUnits(gpa, endian);
313}
314
315const PcRange = struct {
316 start: u64,
317 end: u64,
318};
319
320const Func = struct {
321 pc_range: ?PcRange,
322 name: ?[]const u8,
323};
324
325pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {
326 return if (di.sections[@backingInt(dwarf_section)]) |s| s.data else null;
327}
328
329pub fn deinit(di: *Dwarf, gpa: Allocator) void {
330 for (di.sections) |opt_section| {
331 if (opt_section) |s| if (s.owned) gpa.free(s.data);
332 }
333 for (di.abbrev_table_list.items) |*abbrev| {
334 abbrev.deinit(gpa);
335 }
336 di.abbrev_table_list.deinit(gpa);
337 for (di.compile_unit_list.items) |*cu| {
338 if (cu.src_loc_cache) |*slc| {
339 slc.line_table.deinit(gpa);
340 gpa.free(slc.directories);
341 gpa.free(slc.files);
342 }
343 cu.die.deinit(gpa);
344 }
345 di.compile_unit_list.deinit(gpa);
346 di.func_list.deinit(gpa);
347 di.ranges.deinit(gpa);
348 di.* = undefined;
349}
350
351pub fn getSymbolName(di: *const Dwarf, address: u64) ?[]const u8 {
352 // Iterate the function list backwards so that we see child DIEs before their parents. This is
353 // important because `DW_TAG_inlined_subroutine` DIEs will have a range which is a sub-range of
354 // their caller, and we want to return the callee's name, not the caller's.
355 var i: usize = di.func_list.items.len;
356 while (i > 0) {
357 i -= 1;
358 const func = &di.func_list.items[i];
359 if (func.pc_range) |range| {
360 if (address >= range.start and address < range.end) {
361 return func.name;
362 }
363 }
364 }
365
366 return null;
367}
368
369pub const ScanError = error{
370 InvalidDebugInfo,
371 MissingDebugInfo,
372 ReadFailed,
373 EndOfStream,
374 Overflow,
375 StreamTooLong,
376} || Allocator.Error;
377
378fn scanAllFunctions(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
379 var fr: Reader = .fixed(di.section(.debug_info).?);
380 var this_unit_offset: u64 = 0;
381
382 while (this_unit_offset < fr.buffer.len) {
383 fr.seek = @intCast(this_unit_offset);
384
385 const unit_header = try readUnitHeader(&fr, endian);
386 if (unit_header.unit_length == 0) return;
387 const next_offset = unit_header.header_length + unit_header.unit_length;
388
389 const version = try fr.takeInt(u16, endian);
390 if (version < 2 or version > 5) return bad();
391
392 var address_size: u8 = undefined;
393 var debug_abbrev_offset: u64 = undefined;
394 if (version >= 5) {
395 const unit_type = try fr.takeByte();
396 if (unit_type != DW.UT.compile) return bad();
397 address_size = try fr.takeByte();
398 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
399 } else {
400 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
401 address_size = try fr.takeByte();
402 }
403
404 const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset);
405
406 var max_attrs: usize = 0;
407 var zig_padding_abbrev_code: u7 = 0;
408 for (abbrev_table.abbrevs) |abbrev| {
409 max_attrs = @max(max_attrs, abbrev.attrs.len);
410 if (cast(u7, abbrev.code)) |code| {
411 if (abbrev.tag_id == DW.TAG.ZIG_padding and
412 !abbrev.has_children and
413 abbrev.attrs.len == 0)
414 {
415 zig_padding_abbrev_code = code;
416 }
417 }
418 }
419 const attrs_buf = try gpa.alloc(Die.Attr, max_attrs * 3);
420 defer gpa.free(attrs_buf);
421 var attrs_bufs: [3][]Die.Attr = undefined;
422 for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs];
423
424 const next_unit_pos = this_unit_offset + next_offset;
425
426 var compile_unit: CompileUnit = .{
427 .version = version,
428 .format = unit_header.format,
429 .addr_size_bytes = address_size,
430 .die = undefined,
431 .pc_range = null,
432
433 .str_offsets_base = 0,
434 .addr_base = 0,
435 .rnglists_base = 0,
436 .loclists_base = 0,
437 .frame_base = null,
438 .src_loc_cache = null,
439 };
440
441 while (true) {
442 fr.seek = std.mem.findNonePos(u8, fr.buffer, fr.seek, &.{
443 zig_padding_abbrev_code, 0,
444 }) orelse fr.buffer.len;
445 if (fr.seek >= next_unit_pos) break;
446 var die_obj = (try parseDie(
447 &fr,
448 attrs_bufs[0],
449 abbrev_table,
450 unit_header.format,
451 endian,
452 address_size,
453 version,
454 )) orelse continue;
455
456 switch (die_obj.tag_id) {
457 DW.TAG.compile_unit => {
458 compile_unit.die = die_obj;
459 compile_unit.die.attrs = attrs_bufs[1][0..die_obj.attrs.len];
460 @memcpy(compile_unit.die.attrs, die_obj.attrs);
461
462 compile_unit.str_offsets_base = if (die_obj.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0;
463 compile_unit.addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0;
464 compile_unit.rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0;
465 compile_unit.loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0;
466 compile_unit.frame_base = die_obj.getAttr(AT.frame_base);
467 },
468 DW.TAG.subprogram, DW.TAG.inlined_subroutine, DW.TAG.subroutine, DW.TAG.entry_point => {
469 const fn_name = x: {
470 var this_die_obj = die_obj;
471 // Prevent endless loops
472 for (0..3) |_| {
473 if (this_die_obj.getAttr(AT.name)) |_| {
474 break :x try this_die_obj.getAttrString(di, endian, AT.name, di.section(.debug_str), &compile_unit);
475 } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
476 const after_die_offset = fr.seek;
477 defer fr.seek = after_die_offset;
478
479 // Follow the DIE it points to and repeat
480 const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin, this_unit_offset, next_offset);
481 fr.seek = @intCast(ref_offset);
482 this_die_obj = (try parseDie(
483 &fr,
484 attrs_bufs[2],
485 abbrev_table, // wrong abbrev table for different cu
486 unit_header.format,
487 endian,
488 address_size,
489 version,
490 )) orelse return bad();
491 } else if (this_die_obj.getAttr(AT.specification)) |_| {
492 const after_die_offset = fr.seek;
493 defer fr.seek = after_die_offset;
494
495 // Follow the DIE it points to and repeat
496 const ref_offset = try this_die_obj.getAttrRef(AT.specification, this_unit_offset, next_offset);
497 fr.seek = @intCast(ref_offset);
498 this_die_obj = (try parseDie(
499 &fr,
500 attrs_bufs[2],
501 abbrev_table, // wrong abbrev table for different cu
502 unit_header.format,
503 endian,
504 address_size,
505 version,
506 )) orelse return bad();
507 } else {
508 break :x null;
509 }
510 }
511
512 break :x null;
513 };
514
515 var range_added = if (die_obj.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| blk: {
516 if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
517 const pc_end = switch (high_pc_value.*) {
518 .addr => |value| value,
519 .udata => |offset| low_pc + offset,
520 else => return bad(),
521 };
522
523 try di.func_list.append(gpa, .{
524 .name = fn_name,
525 .pc_range = .{
526 .start = low_pc,
527 .end = pc_end,
528 },
529 });
530
531 break :blk true;
532 }
533
534 break :blk false;
535 } else |err| blk: {
536 if (err != error.MissingDebugInfo) return err;
537 break :blk false;
538 };
539
540 if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: {
541 var iter = DebugRangeIterator.init(ranges_value, di, endian, &compile_unit) catch |err| {
542 if (err != error.MissingDebugInfo) return err;
543 break :blk;
544 };
545
546 while (try iter.next()) |range| {
547 range_added = true;
548 try di.func_list.append(gpa, .{
549 .name = fn_name,
550 .pc_range = .{
551 .start = range.start,
552 .end = range.end,
553 },
554 });
555 }
556 }
557
558 if (fn_name != null and !range_added) {
559 try di.func_list.append(gpa, .{
560 .name = fn_name,
561 .pc_range = null,
562 });
563 }
564 },
565 else => {},
566 }
567 }
568
569 this_unit_offset += next_offset;
570 }
571}
572
573fn scanAllCompileUnits(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
574 var fr: Reader = .fixed(di.section(.debug_info).?);
575 var this_unit_offset: u64 = 0;
576
577 var attrs_buf = std.array_list.Managed(Die.Attr).init(gpa);
578 defer attrs_buf.deinit();
579
580 while (this_unit_offset < fr.buffer.len) {
581 fr.seek = @intCast(this_unit_offset);
582
583 const unit_header = try readUnitHeader(&fr, endian);
584 if (unit_header.unit_length == 0) return;
585 const next_offset = unit_header.header_length + unit_header.unit_length;
586
587 const version = try fr.takeInt(u16, endian);
588 if (version < 2 or version > 5) return bad();
589
590 var address_size: u8 = undefined;
591 var debug_abbrev_offset: u64 = undefined;
592 if (version >= 5) {
593 const unit_type = try fr.takeByte();
594 if (unit_type != UT.compile) return bad();
595 address_size = try fr.takeByte();
596 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
597 } else {
598 debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian);
599 address_size = try fr.takeByte();
600 }
601
602 const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset);
603
604 var max_attrs: usize = 0;
605 for (abbrev_table.abbrevs) |abbrev| {
606 max_attrs = @max(max_attrs, abbrev.attrs.len);
607 }
608 try attrs_buf.resize(max_attrs);
609
610 var compile_unit_die = (try parseDie(
611 &fr,
612 attrs_buf.items,
613 abbrev_table,
614 unit_header.format,
615 endian,
616 address_size,
617 version,
618 )) orelse return bad();
619
620 if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad();
621
622 compile_unit_die.attrs = try gpa.dupe(Die.Attr, compile_unit_die.attrs);
623
624 var compile_unit: CompileUnit = .{
625 .version = version,
626 .format = unit_header.format,
627 .addr_size_bytes = address_size,
628 .pc_range = null,
629 .die = compile_unit_die,
630 .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0,
631 .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0,
632 .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
633 .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
634 .frame_base = compile_unit_die.getAttr(AT.frame_base),
635 .src_loc_cache = null,
636 };
637
638 compile_unit.pc_range = x: {
639 if (compile_unit_die.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| {
640 if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| {
641 const pc_end = switch (high_pc_value.*) {
642 .addr => |value| value,
643 .udata => |offset| low_pc + offset,
644 else => return bad(),
645 };
646 break :x PcRange{
647 .start = low_pc,
648 .end = pc_end,
649 };
650 } else {
651 break :x null;
652 }
653 } else |err| {
654 if (err != error.MissingDebugInfo) return err;
655 break :x null;
656 }
657 };
658
659 try di.compile_unit_list.append(gpa, compile_unit);
660
661 this_unit_offset += next_offset;
662 }
663}
664
665pub fn populateRanges(d: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void {
666 assert(d.ranges.items.len == 0);
667
668 for (d.compile_unit_list.items, 0..) |*cu, cu_index| {
669 if (cu.pc_range) |range| {
670 try d.ranges.append(gpa, .{
671 .start = range.start,
672 .end = range.end,
673 .compile_unit_index = cu_index,
674 });
675 continue;
676 }
677 const ranges_value = cu.die.getAttr(AT.ranges) orelse continue;
678 var iter = DebugRangeIterator.init(ranges_value, d, endian, cu) catch continue;
679 while (try iter.next()) |range| {
680 // Not sure why LLVM thinks it's OK to emit these...
681 if (range.start == range.end) continue;
682
683 try d.ranges.append(gpa, .{
684 .start = range.start,
685 .end = range.end,
686 .compile_unit_index = cu_index,
687 });
688 }
689 }
690
691 std.mem.sortUnstable(Range, d.ranges.items, {}, struct {
692 pub fn lessThan(ctx: void, a: Range, b: Range) bool {
693 _ = ctx;
694 return a.start < b.start;
695 }
696 }.lessThan);
697}
698
699const DebugRangeIterator = struct {
700 base_address: u64,
701 section_type: Section.Id,
702 di: *const Dwarf,
703 endian: Endian,
704 compile_unit: *const CompileUnit,
705 fr: Reader,
706
707 pub fn init(ranges_value: *const FormValue, di: *const Dwarf, endian: Endian, compile_unit: *const CompileUnit) !@This() {
708 const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges;
709 const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;
710
711 const ranges_offset = switch (ranges_value.*) {
712 .sec_offset, .udata => |off| off,
713 .rnglistx => |idx| off: {
714 switch (compile_unit.format) {
715 .@"32" => {
716 const offset_loc = compile_unit.rnglists_base + 4 * idx;
717 if (offset_loc + 4 > debug_ranges.len) return bad();
718 const offset = mem.readInt(u32, debug_ranges[@intCast(offset_loc)..][0..4], endian);
719 break :off compile_unit.rnglists_base + offset;
720 },
721 .@"64" => {
722 const offset_loc = compile_unit.rnglists_base + 8 * idx;
723 if (offset_loc + 8 > debug_ranges.len) return bad();
724 const offset = mem.readInt(u64, debug_ranges[@intCast(offset_loc)..][0..8], endian);
725 break :off compile_unit.rnglists_base + offset;
726 },
727 }
728 },
729 else => return bad(),
730 };
731
732 // All the addresses in the list are relative to the value
733 // specified by DW_AT.low_pc or to some other value encoded
734 // in the list itself.
735 // If no starting value is specified use zero.
736 const base_address = compile_unit.die.getAttrAddr(di, endian, AT.low_pc, compile_unit) catch |err| switch (err) {
737 error.MissingDebugInfo => 0,
738 else => return err,
739 };
740
741 var fr: Reader = .fixed(debug_ranges);
742 fr.seek = cast(usize, ranges_offset) orelse return bad();
743
744 return .{
745 .base_address = base_address,
746 .section_type = section_type,
747 .di = di,
748 .endian = endian,
749 .compile_unit = compile_unit,
750 .fr = fr,
751 };
752 }
753
754 // Returns the next range in the list, or null if the end was reached.
755 pub fn next(self: *@This()) !?PcRange {
756 const endian = self.endian;
757 const addr_size_bytes = self.compile_unit.addr_size_bytes;
758 switch (self.section_type) {
759 .debug_rnglists => {
760 const kind = try self.fr.takeByte();
761 switch (kind) {
762 RLE.end_of_list => return null,
763 RLE.base_addressx => {
764 const index = try self.fr.takeLeb128(u64);
765 self.base_address = try self.di.readDebugAddr(endian, self.compile_unit, index);
766 return try self.next();
767 },
768 RLE.startx_endx => {
769 const start_index = try self.fr.takeLeb128(u64);
770 const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
771
772 const end_index = try self.fr.takeLeb128(u64);
773 const end_addr = try self.di.readDebugAddr(endian, self.compile_unit, end_index);
774
775 return .{
776 .start = start_addr,
777 .end = end_addr,
778 };
779 },
780 RLE.startx_length => {
781 const start_index = try self.fr.takeLeb128(u64);
782 const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index);
783
784 const len = try self.fr.takeLeb128(u64);
785 const end_addr = start_addr + len;
786
787 return .{
788 .start = start_addr,
789 .end = end_addr,
790 };
791 },
792 RLE.offset_pair => {
793 const start_addr = try self.fr.takeLeb128(u64);
794 const end_addr = try self.fr.takeLeb128(u64);
795
796 // This is the only kind that uses the base address
797 return .{
798 .start = self.base_address + start_addr,
799 .end = self.base_address + end_addr,
800 };
801 },
802 RLE.base_address => {
803 self.base_address = try readAddress(&self.fr, endian, addr_size_bytes);
804 return try self.next();
805 },
806 RLE.start_end => {
807 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
808 const end_addr = try readAddress(&self.fr, endian, addr_size_bytes);
809
810 return .{
811 .start = start_addr,
812 .end = end_addr,
813 };
814 },
815 RLE.start_length => {
816 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
817 const len = try self.fr.takeLeb128(u64);
818 const end_addr = start_addr + len;
819
820 return .{
821 .start = start_addr,
822 .end = end_addr,
823 };
824 },
825 else => return bad(),
826 }
827 },
828 .debug_ranges => {
829 const start_addr = try readAddress(&self.fr, endian, addr_size_bytes);
830 const end_addr = try readAddress(&self.fr, endian, addr_size_bytes);
831 if (start_addr == 0 and end_addr == 0) return null;
832
833 // The entry with start_addr = max_representable_address selects a new value for the base address
834 const max_representable_address = ~@as(u64, 0) >> @intCast(64 - addr_size_bytes);
835 if (start_addr == max_representable_address) {
836 self.base_address = end_addr;
837 return try self.next();
838 }
839
840 return .{
841 .start = self.base_address + start_addr,
842 .end = self.base_address + end_addr,
843 };
844 },
845 else => unreachable,
846 }
847 }
848};
849
850/// TODO: change this to binary searching the sorted compile unit list
851pub fn findCompileUnit(di: *const Dwarf, endian: Endian, target_address: u64) !*CompileUnit {
852 for (di.compile_unit_list.items) |*compile_unit| {
853 if (compile_unit.pc_range) |range| {
854 if (target_address >= range.start and target_address < range.end) return compile_unit;
855 }
856
857 const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue;
858 var iter = DebugRangeIterator.init(ranges_value, di, endian, compile_unit) catch continue;
859 while (try iter.next()) |range| {
860 if (target_address >= range.start and target_address < range.end) return compile_unit;
861 }
862 }
863
864 return missing();
865}
866
867/// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
868/// seeks in the stream and parses it.
869fn getAbbrevTable(di: *Dwarf, gpa: Allocator, abbrev_offset: u64) !*const Abbrev.Table {
870 for (di.abbrev_table_list.items) |*table| {
871 if (table.offset == abbrev_offset) {
872 return table;
873 }
874 }
875 try di.abbrev_table_list.append(
876 gpa,
877 try di.parseAbbrevTable(gpa, abbrev_offset),
878 );
879 return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1];
880}
881
882fn parseAbbrevTable(di: *Dwarf, gpa: Allocator, offset: u64) !Abbrev.Table {
883 var fr: Reader = .fixed(di.section(.debug_abbrev).?);
884 fr.seek = cast(usize, offset) orelse return bad();
885
886 var abbrevs: std.ArrayList(Abbrev) = .empty;
887 defer {
888 for (abbrevs.items) |*abbrev| {
889 abbrev.deinit(gpa);
890 }
891 abbrevs.deinit(gpa);
892 }
893
894 var attrs: std.ArrayList(Abbrev.Attr) = .empty;
895 defer attrs.deinit(gpa);
896
897 while (true) {
898 const code = try fr.takeLeb128(u64);
899 if (code == 0) break;
900 const tag_id = try fr.takeLeb128(u64);
901 const has_children = (try fr.takeByte()) == DW.CHILDREN.yes;
902
903 while (true) {
904 const attr_id = try fr.takeLeb128(u64);
905 const form_id = try fr.takeLeb128(u64);
906 if (attr_id == 0 and form_id == 0) break;
907 try attrs.append(gpa, .{
908 .id = attr_id,
909 .form_id = form_id,
910 .payload = switch (form_id) {
911 FORM.implicit_const => try fr.takeLeb128(i64),
912 else => undefined,
913 },
914 });
915 }
916 try abbrevs.ensureUnusedCapacity(gpa, 1);
917 abbrevs.appendAssumeCapacity(.{
918 .code = code,
919 .tag_id = tag_id,
920 .has_children = has_children,
921 .attrs = try attrs.toOwnedSlice(gpa),
922 });
923 }
924
925 return .{
926 .offset = offset,
927 .abbrevs = try abbrevs.toOwnedSlice(gpa),
928 };
929}
930
931fn parseDie(
932 fr: *Reader,
933 attrs_buf: []Die.Attr,
934 abbrev_table: *const Abbrev.Table,
935 format: Format,
936 endian: Endian,
937 addr_size_bytes: u8,
938 version: u16,
939) ScanError!?Die {
940 const abbrev_code = try fr.takeLeb128(u64);
941 if (abbrev_code == 0) return null;
942 const table_entry = abbrev_table.get(abbrev_code) orelse return bad();
943
944 const attrs = attrs_buf[0..table_entry.attrs.len];
945 for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{
946 .id = attr.id,
947 .value = try parseFormValue(fr, attr.form_id, format, endian, addr_size_bytes, attr.payload, version),
948 };
949 return .{
950 .tag_id = table_entry.tag_id,
951 .has_children = table_entry.has_children,
952 .attrs = attrs,
953 };
954}
955
956/// Ensures that addresses in the returned LineTable are monotonically increasing.
957fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache {
958 const compile_unit_cwd = try compile_unit.die.getAttrString(d, endian, AT.comp_dir, d.section(.debug_line_str), compile_unit);
959 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
960
961 var fr: Reader = .fixed(d.section(.debug_line).?);
962 fr.seek = @intCast(line_info_offset);
963
964 const unit_header = try readUnitHeader(&fr, endian);
965 if (unit_header.unit_length == 0) return missing();
966
967 const next_offset = unit_header.header_length + unit_header.unit_length;
968
969 const version = try fr.takeInt(u16, endian);
970 if (version < 2) return bad();
971
972 const addr_size_bytes: u8, const seg_size: u8 = if (version >= 5) .{
973 try fr.takeByte(),
974 try fr.takeByte(),
975 } else .{
976 compile_unit.addr_size_bytes,
977 0,
978 };
979 if (seg_size != 0) return bad(); // unsupported
980
981 const prologue_length = try readFormatSizedInt(&fr, unit_header.format, endian);
982 const prog_start_offset = fr.seek + prologue_length;
983
984 const minimum_instruction_length = try fr.takeByte();
985 if (minimum_instruction_length == 0) return bad();
986
987 if (version >= 4) {
988 const maximum_operations_per_instruction = try fr.takeByte();
989 _ = maximum_operations_per_instruction;
990 }
991
992 const default_is_stmt = (try fr.takeByte()) != 0;
993 const line_base = try fr.takeByteSigned();
994
995 const line_range = try fr.takeByte();
996 if (line_range == 0) return bad();
997
998 const opcode_base = try fr.takeByte();
999
1000 const standard_opcode_lengths = try fr.take(opcode_base - 1);
1001
1002 var directories: ArrayList(FileEntry) = .empty;
1003 defer directories.deinit(gpa);
1004 var file_entries: ArrayList(FileEntry) = .empty;
1005 defer file_entries.deinit(gpa);
1006
1007 if (version < 5) {
1008 try directories.append(gpa, .{ .path = compile_unit_cwd });
1009
1010 while (true) {
1011 const dir = try fr.takeSentinel(0);
1012 if (dir.len == 0) break;
1013 try directories.append(gpa, .{ .path = dir });
1014 }
1015
1016 while (true) {
1017 const file_name = try fr.takeSentinel(0);
1018 if (file_name.len == 0) break;
1019 const dir_index = try fr.takeLeb128(u32);
1020 const mtime = try fr.takeLeb128(u64);
1021 const size = try fr.takeLeb128(u64);
1022 try file_entries.append(gpa, .{
1023 .path = file_name,
1024 .dir_index = dir_index,
1025 .mtime = mtime,
1026 .size = size,
1027 });
1028 }
1029 } else {
1030 const FileEntFmt = struct {
1031 content_type_code: u16,
1032 form_code: u16,
1033 };
1034 {
1035 var dir_ent_fmt_buf: [10]FileEntFmt = undefined;
1036 const directory_entry_format_count = try fr.takeByte();
1037 if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();
1038 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {
1039 ent_fmt.* = .{
1040 .content_type_code = try fr.takeLeb128(u8),
1041 .form_code = try fr.takeLeb128(u16),
1042 };
1043 }
1044
1045 const directories_count = try fr.takeLeb128(usize);
1046
1047 for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {
1048 e.* = .{ .path = &.{} };
1049 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| {
1050 const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null, version);
1051 switch (ent_fmt.content_type_code) {
1052 DW.LNCT.path => e.path = try form_value.getString(d.*),
1053 DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
1054 DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64),
1055 DW.LNCT.size => e.size = try form_value.getUInt(u64),
1056 DW.LNCT.MD5 => e.md5 = switch (form_value) {
1057 .data16 => |data16| data16.*,
1058 else => return bad(),
1059 },
1060 else => continue,
1061 }
1062 }
1063 }
1064 }
1065
1066 var file_ent_fmt_buf: [10]FileEntFmt = undefined;
1067 const file_name_entry_format_count = try fr.takeByte();
1068 if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
1069 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
1070 ent_fmt.* = .{
1071 .content_type_code = try fr.takeLeb128(u16),
1072 .form_code = try fr.takeLeb128(u16),
1073 };
1074 }
1075
1076 const file_names_count = try fr.takeLeb128(usize);
1077 try file_entries.ensureUnusedCapacity(gpa, file_names_count);
1078
1079 for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {
1080 e.* = .{ .path = &.{} };
1081 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| {
1082 const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null, version);
1083 switch (ent_fmt.content_type_code) {
1084 DW.LNCT.path => e.path = try form_value.getString(d.*),
1085 DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
1086 DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64),
1087 DW.LNCT.size => e.size = try form_value.getUInt(u64),
1088 DW.LNCT.MD5 => e.md5 = switch (form_value) {
1089 .data16 => |data16| data16.*,
1090 else => return bad(),
1091 },
1092 else => continue,
1093 }
1094 }
1095 }
1096 }
1097
1098 var prog = LineNumberProgram.init(default_is_stmt, version);
1099 var line_table: CompileUnit.SrcLocCache.LineTable = .{};
1100 errdefer line_table.deinit(gpa);
1101
1102 fr.seek = @intCast(prog_start_offset);
1103
1104 const next_unit_pos = line_info_offset + next_offset;
1105
1106 while (fr.seek < next_unit_pos) {
1107 const opcode = try fr.takeByte();
1108
1109 if (opcode == DW.LNS.extended_op) {
1110 const op_size = try fr.takeLeb128(u64);
1111 if (op_size < 1) return bad();
1112 const sub_op = try fr.takeByte();
1113 switch (sub_op) {
1114 DW.LNE.end_sequence => {
1115 // The row being added here is an "end" address, meaning
1116 // that it does not map to the source location here -
1117 // rather it marks the previous address as the last address
1118 // that maps to this source location.
1119
1120 // In this implementation we don't mark end of addresses.
1121 // This is a performance optimization based on the fact
1122 // that we don't need to know if an address is missing
1123 // source location info; we are only interested in being
1124 // able to look up source location info for addresses that
1125 // are known to have debug info.
1126 //if (debug_debug_mode) assert(!line_table.contains(prog.address));
1127 //try line_table.put(gpa, prog.address, CompileUnit.SrcLocCache.LineEntry.invalid);
1128 prog.reset();
1129 },
1130 DW.LNE.set_address => {
1131 prog.address = try readAddress(&fr, endian, addr_size_bytes);
1132 },
1133 DW.LNE.define_file => {
1134 const path = try fr.takeSentinel(0);
1135 const dir_index = try fr.takeLeb128(u32);
1136 const mtime = try fr.takeLeb128(u64);
1137 const size = try fr.takeLeb128(u64);
1138 try file_entries.append(gpa, .{
1139 .path = path,
1140 .dir_index = dir_index,
1141 .mtime = mtime,
1142 .size = size,
1143 });
1144 },
1145 else => try fr.discardAll64(op_size - 1),
1146 }
1147 } else if (opcode >= opcode_base) {
1148 // special opcodes
1149 const adjusted_opcode = opcode - opcode_base;
1150 const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range);
1151 const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range);
1152 prog.line += inc_line;
1153 prog.address += inc_addr;
1154 try prog.addRow(gpa, &line_table);
1155 prog.basic_block = false;
1156 } else {
1157 switch (opcode) {
1158 DW.LNS.copy => {
1159 try prog.addRow(gpa, &line_table);
1160 prog.basic_block = false;
1161 },
1162 DW.LNS.advance_pc => {
1163 const arg = try fr.takeLeb128(u64);
1164 prog.address += arg * minimum_instruction_length;
1165 },
1166 DW.LNS.advance_line => {
1167 const arg = try fr.takeLeb128(i64);
1168 prog.line += arg;
1169 },
1170 DW.LNS.set_file => {
1171 const arg = try fr.takeLeb128(usize);
1172 prog.file = arg;
1173 },
1174 DW.LNS.set_column => {
1175 const arg = try fr.takeLeb128(u64);
1176 prog.column = arg;
1177 },
1178 DW.LNS.negate_stmt => {
1179 prog.is_stmt = !prog.is_stmt;
1180 },
1181 DW.LNS.set_basic_block => {
1182 prog.basic_block = true;
1183 },
1184 DW.LNS.const_add_pc => {
1185 const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range);
1186 prog.address += inc_addr;
1187 },
1188 DW.LNS.fixed_advance_pc => {
1189 const arg = try fr.takeInt(u16, endian);
1190 prog.address += arg;
1191 },
1192 DW.LNS.set_prologue_end => {},
1193 else => {
1194 if (opcode - 1 >= standard_opcode_lengths.len) return bad();
1195 try fr.discardAll(standard_opcode_lengths[opcode - 1]);
1196 },
1197 }
1198 }
1199 }
1200
1201 // Dwarf standard v5, 6.2.5 says
1202 // > Within a sequence, addresses and operation pointers may only increase.
1203 // However, this is empirically not the case in reality, so we sort here.
1204 line_table.sortUnstable(struct {
1205 keys: []const u64,
1206
1207 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
1208 return ctx.keys[a_index] < ctx.keys[b_index];
1209 }
1210 }{ .keys = line_table.keys() });
1211
1212 try directories.shrinkToLen(gpa);
1213 try file_entries.shrinkToLen(gpa);
1214
1215 return .{
1216 .line_table = line_table,
1217 .directories = directories.toOwnedSliceAssert(),
1218 .files = file_entries.toOwnedSliceAssert(),
1219 .version = version,
1220 };
1221}
1222
1223pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *CompileUnit) ScanError!void {
1224 if (cu.src_loc_cache != null) return;
1225 cu.src_loc_cache = try d.runLineNumberProgram(gpa, endian, cu);
1226}
1227
1228pub fn getLineNumberInfo(
1229 d: *Dwarf,
1230 gpa: Allocator,
1231 text_arena: Allocator,
1232 endian: Endian,
1233 compile_unit: *CompileUnit,
1234 target_address: u64,
1235) !std.debug.SourceLocation {
1236 try d.populateSrcLocCache(gpa, endian, compile_unit);
1237 const slc = &compile_unit.src_loc_cache.?;
1238 const entry = try slc.findSource(target_address);
1239 const file_index = entry.file - @intFromBool(slc.version < 5);
1240 if (file_index >= slc.files.len) return bad();
1241 const file_entry = &slc.files[file_index];
1242 if (file_entry.dir_index >= slc.directories.len) return bad();
1243 const dir_name = slc.directories[file_entry.dir_index].path;
1244 const file_name = try std.fs.path.join(text_arena, &.{ dir_name, file_entry.path });
1245 return .{
1246 .line = entry.line,
1247 .column = entry.column,
1248 .file_name = file_name,
1249 };
1250}
1251
1252fn getString(di: Dwarf, offset: u64) ![:0]const u8 {
1253 return getStringGeneric(di.section(.debug_str), offset);
1254}
1255
1256fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 {
1257 return getStringGeneric(di.section(.debug_line_str), offset);
1258}
1259
1260fn readDebugAddr(di: Dwarf, endian: Endian, compile_unit: *const CompileUnit, index: u64) !u64 {
1261 const debug_addr = di.section(.debug_addr) orelse return bad();
1262
1263 // addr_base points to the first item after the header, however we
1264 // need to read the header to know the size of each item. Empirically,
1265 // it may disagree with is_64 on the compile unit.
1266 // The header is 8 or 12 bytes depending on is_64.
1267 if (compile_unit.addr_base < 8) return bad();
1268
1269 const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], endian);
1270 if (version != 5) return bad();
1271
1272 const addr_size = debug_addr[compile_unit.addr_base - 2];
1273 const seg_size = debug_addr[compile_unit.addr_base - 1];
1274
1275 const byte_offset = compile_unit.addr_base + (addr_size + seg_size) * index;
1276 if (byte_offset + addr_size > debug_addr.len) return bad();
1277 return switch (addr_size) {
1278 1 => debug_addr[@intCast(byte_offset)],
1279 2 => mem.readInt(u16, debug_addr[@intCast(byte_offset)..][0..2], endian),
1280 4 => mem.readInt(u32, debug_addr[@intCast(byte_offset)..][0..4], endian),
1281 8 => mem.readInt(u64, debug_addr[@intCast(byte_offset)..][0..8], endian),
1282 else => bad(),
1283 };
1284}
1285
1286fn parseFormValue(
1287 r: *Reader,
1288 form_id: u64,
1289 format: Format,
1290 endian: Endian,
1291 addr_size_bytes: u8,
1292 implicit_const: ?i64,
1293 version: u16,
1294) ScanError!FormValue {
1295 return switch (form_id) {
1296 // DWARF5.pdf page 213: the size of this value is encoded in the
1297 // compilation unit header as address size.
1298 FORM.addr => .{ .addr = try readAddress(r, endian, addr_size_bytes) },
1299 FORM.addrx1 => .{ .addrx = try r.takeByte() },
1300 FORM.addrx2 => .{ .addrx = try r.takeInt(u16, endian) },
1301 FORM.addrx3 => .{ .addrx = try r.takeInt(u24, endian) },
1302 FORM.addrx4 => .{ .addrx = try r.takeInt(u32, endian) },
1303 FORM.addrx => .{ .addrx = try r.takeLeb128(u64) },
1304
1305 FORM.block1 => .{ .block = try r.take(try r.takeByte()) },
1306 FORM.block2 => .{ .block = try r.take(try r.takeInt(u16, endian)) },
1307 FORM.block4 => .{ .block = try r.take(try r.takeInt(u32, endian)) },
1308 FORM.block => .{ .block = try r.take(try r.takeLeb128(usize)) },
1309
1310 FORM.data1 => .{ .udata = try r.takeByte() },
1311 FORM.data2 => .{ .udata = try r.takeInt(u16, endian) },
1312 FORM.data4 => .{ .udata = try r.takeInt(u32, endian) },
1313 FORM.data8 => .{ .udata = try r.takeInt(u64, endian) },
1314 FORM.data16 => .{ .data16 = try r.takeArray(16) },
1315 FORM.udata => .{ .udata = try r.takeLeb128(u64) },
1316 FORM.sdata => .{ .sdata = try r.takeLeb128(i64) },
1317 FORM.exprloc => .{ .exprloc = try r.take(try r.takeLeb128(usize)) },
1318 FORM.flag => .{ .flag = (try r.takeByte()) != 0 },
1319 FORM.flag_present => .{ .flag = true },
1320 FORM.sec_offset => .{ .sec_offset = try readFormatSizedInt(r, format, endian) },
1321
1322 FORM.ref1 => .{ .ref = try r.takeByte() },
1323 FORM.ref2 => .{ .ref = try r.takeInt(u16, endian) },
1324 FORM.ref4 => .{ .ref = try r.takeInt(u32, endian) },
1325 FORM.ref8 => .{ .ref = try r.takeInt(u64, endian) },
1326 FORM.ref_udata => .{ .ref = try r.takeLeb128(u64) },
1327
1328 FORM.ref_addr => .{
1329 .ref_addr = switch (version) {
1330 2 => try readAddress(r, endian, addr_size_bytes),
1331 else => try readFormatSizedInt(r, format, endian),
1332 },
1333 },
1334 FORM.ref_sig8 => .{ .ref = try r.takeInt(u64, endian) },
1335
1336 FORM.string => .{ .string = try r.takeSentinel(0) },
1337 FORM.strp => .{ .strp = try readFormatSizedInt(r, format, endian) },
1338 FORM.strx1 => .{ .strx = try r.takeByte() },
1339 FORM.strx2 => .{ .strx = try r.takeInt(u16, endian) },
1340 FORM.strx3 => .{ .strx = try r.takeInt(u24, endian) },
1341 FORM.strx4 => .{ .strx = try r.takeInt(u32, endian) },
1342 FORM.strx => .{ .strx = try r.takeLeb128(usize) },
1343 FORM.line_strp => .{ .line_strp = try readFormatSizedInt(r, format, endian) },
1344 FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, addr_size_bytes, implicit_const, version),
1345 FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() },
1346 FORM.loclistx => .{ .loclistx = try r.takeLeb128(u64) },
1347 FORM.rnglistx => .{ .rnglistx = try r.takeLeb128(u64) },
1348 else => {
1349 //debug.print("unrecognized form id: {x}\n", .{form_id});
1350 return bad();
1351 },
1352 };
1353}
1354
1355const FileEntry = struct {
1356 path: []const u8,
1357 dir_index: u32 = 0,
1358 mtime: u64 = 0,
1359 size: u64 = 0,
1360 md5: [16]u8 = @splat(0),
1361};
1362
1363const LineNumberProgram = struct {
1364 address: u64,
1365 file: usize,
1366 line: i64,
1367 column: u64,
1368 version: u16,
1369 is_stmt: bool,
1370 basic_block: bool,
1371
1372 default_is_stmt: bool,
1373
1374 // Reset the state machine following the DWARF specification
1375 pub fn reset(self: *LineNumberProgram) void {
1376 self.address = 0;
1377 self.file = 1;
1378 self.line = 1;
1379 self.column = 0;
1380 self.is_stmt = self.default_is_stmt;
1381 self.basic_block = false;
1382 }
1383
1384 pub fn init(is_stmt: bool, version: u16) LineNumberProgram {
1385 return .{
1386 .address = 0,
1387 .file = 1,
1388 .line = 1,
1389 .column = 0,
1390 .version = version,
1391 .is_stmt = is_stmt,
1392 .basic_block = false,
1393 .default_is_stmt = is_stmt,
1394 };
1395 }
1396
1397 pub fn addRow(prog: *LineNumberProgram, gpa: Allocator, table: *CompileUnit.SrcLocCache.LineTable) !void {
1398 if (prog.line == 0) {
1399 //if (debug_debug_mode) @panic("garbage line data");
1400 return;
1401 }
1402 if (debug_debug_mode) assert(!table.contains(prog.address));
1403 try table.put(gpa, prog.address, .{
1404 .line = cast(u32, prog.line) orelse maxInt(u32),
1405 .column = cast(u32, prog.column) orelse maxInt(u32),
1406 .file = cast(u32, prog.file) orelse return bad(),
1407 });
1408 }
1409};
1410
1411const UnitHeader = struct {
1412 format: Format,
1413 header_length: u4,
1414 unit_length: u64,
1415};
1416
1417pub fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
1418 return switch (try r.takeInt(u32, endian)) {
1419 0...0xfffffff0 - 1 => |unit_length| .{
1420 .format = .@"32",
1421 .header_length = 4,
1422 .unit_length = unit_length,
1423 },
1424 0xfffffff0...0xffffffff - 1 => bad(),
1425 0xffffffff => .{
1426 .format = .@"64",
1427 .header_length = 12,
1428 .unit_length = try r.takeInt(u64, endian),
1429 },
1430 };
1431}
1432
1433/// Returns the DWARF register number for an x86_64 register number found in compact unwind info
1434pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 {
1435 return switch (unwind_reg_number) {
1436 1 => 3, // RBX
1437 2 => 12, // R12
1438 3 => 13, // R13
1439 4 => 14, // R14
1440 5 => 15, // R15
1441 6 => 6, // RBP
1442 else => error.InvalidRegister,
1443 };
1444}
1445
1446/// Returns `null` for CPU architectures without an instruction pointer register.
1447pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
1448 return switch (arch) {
1449 .aarch64, .aarch64_be => 32,
1450 .alpha => 64,
1451 .arc, .arceb => 160,
1452 .arm, .armeb, .thumb, .thumbeb => 15,
1453 .csky => 64,
1454 .hexagon => 76,
1455 .kvx => 64,
1456 .lanai => 2,
1457 .loongarch32, .loongarch64 => 64,
1458 .m68k => 26,
1459 .m88k => 64,
1460 .mips, .mipsel, .mips64, .mips64el => 66,
1461 .or1k => 35,
1462 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 67,
1463 .riscv32, .riscv32be, .riscv64, .riscv64be => 65,
1464 .s390x => 65,
1465 .sparc, .sparc64 => 65,
1466 .ve => 144,
1467 .x86 => 8,
1468 .x86_64 => 16,
1469 else => null,
1470 };
1471}
1472
1473pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
1474 return switch (arch) {
1475 .aarch64, .aarch64_be => 29,
1476 .alpha => 15,
1477 .arc, .arceb => 27,
1478 .arm, .armeb, .thumb, .thumbeb => 11,
1479 .csky => 14,
1480 .hexagon => 30,
1481 .kvx => 14,
1482 .lanai => 5,
1483 .loongarch32, .loongarch64 => 22,
1484 .m68k => 14,
1485 .m88k => 30,
1486 .mips, .mipsel, .mips64, .mips64el => 30,
1487 .or1k => 2,
1488 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 1,
1489 .riscv32, .riscv32be, .riscv64, .riscv64be => 8,
1490 .s390x => 11,
1491 .sparc, .sparc64 => 30,
1492 .ve => 9,
1493 .x86 => 5,
1494 .x86_64 => 6,
1495 else => unreachable,
1496 };
1497}
1498
1499pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
1500 return switch (arch) {
1501 .aarch64, .aarch64_be => 31,
1502 .alpha => 30,
1503 .arc, .arceb => 28,
1504 .arm, .armeb, .thumb, .thumbeb => 13,
1505 .csky => 14,
1506 .hexagon => 29,
1507 .kvx => 12,
1508 .lanai => 4,
1509 .loongarch32, .loongarch64 => 3,
1510 .m68k => 15,
1511 .m88k => 31,
1512 .mips, .mipsel, .mips64, .mips64el => 29,
1513 .or1k => 1,
1514 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 1,
1515 .riscv32, .riscv32be, .riscv64, .riscv64be => 2,
1516 .s390x => 15,
1517 .sparc, .sparc64 => 14,
1518 .ve => 11,
1519 .x86 => 4,
1520 .x86_64 => 7,
1521 else => unreachable,
1522 };
1523}
1524
1525/// Tells whether unwinding for this target is supported by the Dwarf standard.
1526///
1527/// See also `std.debug.SelfInfo.can_unwind` which tells whether the Zig standard
1528/// library has a working implementation of unwinding for the current target.
1529pub fn supportsUnwinding(target: *const std.Target) bool {
1530 return switch (target.cpu.arch) {
1531 .amdgcn,
1532 .nvptx,
1533 .nvptx64,
1534 .spirv32,
1535 .spirv64,
1536 => false,
1537
1538 // Conservative guess. Feel free to update this logic with any targets
1539 // that are known to not support Dwarf unwinding.
1540 else => true,
1541 };
1542}
1543
1544/// This function is to make it handy to comment out the return and make it
1545/// into a crash when working on this file.
1546pub fn bad() error{InvalidDebugInfo} {
1547 invalidDebugInfoDetected();
1548 return error.InvalidDebugInfo;
1549}
1550
1551pub fn invalidDebugInfoDetected() void {
1552 if (debug_debug_mode) @panic("bad dwarf");
1553}
1554
1555pub fn missing() error{MissingDebugInfo} {
1556 if (debug_debug_mode) @panic("missing dwarf");
1557 return error.MissingDebugInfo;
1558}
1559
1560fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
1561 const str = opt_str orelse return bad();
1562 if (offset > str.len) return bad();
1563 const casted_offset = cast(usize, offset) orelse return bad();
1564 // Valid strings always have a terminating zero byte
1565 const last = std.mem.findScalarPos(u8, str, casted_offset, 0) orelse return bad();
1566 return str[casted_offset..last :0];
1567}
1568
1569pub fn getSymbols(
1570 di: *Dwarf,
1571 symbol_allocator: Allocator,
1572 text_arena: Allocator,
1573 endian: Endian,
1574 address: u64,
1575 resolve_inline_callers: bool,
1576 symbols: *std.ArrayList(std.debug.Symbol),
1577) std.debug.SelfInfoError!void {
1578 _ = resolve_inline_callers;
1579 const gpa = std.debug.getDebugInfoAllocator();
1580
1581 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
1582 error.EndOfStream => return error.MissingDebugInfo,
1583 error.Overflow => return error.InvalidDebugInfo,
1584 error.ReadFailed, error.InvalidDebugInfo, error.MissingDebugInfo => |e| return e,
1585 };
1586 try symbols.append(symbol_allocator, .{
1587 .name = di.getSymbolName(address),
1588 .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {
1589 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1590 },
1591 .source_location = di.getLineNumberInfo(gpa, text_arena, endian, compile_unit, address) catch |err| switch (err) {
1592 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1593 error.ReadFailed,
1594 error.EndOfStream,
1595 error.Overflow,
1596 error.StreamTooLong,
1597 => return error.InvalidDebugInfo,
1598 else => |e| return e,
1599 },
1600 });
1601}
1602
1603/// DWARF5 7.4: "In the 32-bit DWARF format, all values that represent lengths of DWARF sections and
1604/// offsets relative to the beginning of DWARF sections are represented using four bytes. In the
1605/// 64-bit DWARF format, all values that represent lengths of DWARF sections and offsets relative to
1606/// the beginning of DWARF sections are represented using eight bytes".
1607///
1608/// This function is for reading such values.
1609fn readFormatSizedInt(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
1610 return switch (format) {
1611 .@"32" => try r.takeInt(u32, endian),
1612 .@"64" => try r.takeInt(u64, endian),
1613 };
1614}
1615
1616fn readAddress(r: *Reader, endian: Endian, addr_size_bytes: u8) !u64 {
1617 return switch (addr_size_bytes) {
1618 2 => try r.takeInt(u16, endian),
1619 4 => try r.takeInt(u32, endian),
1620 8 => try r.takeInt(u64, endian),
1621 else => return bad(),
1622 };
1623}