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