1const std = @import("std.zig");
2const debug = std.debug;
3const fs = std.fs;
4const io = std.io;
5const mem = std.mem;
6const math = std.math;
7const leb = @import("leb128.zig");
8
9const ArrayList = std.ArrayList;
10
11pub const TAG = @import("dwarf/TAG.zig");
12pub const AT = @import("dwarf/AT.zig");
13pub const OP = @import("dwarf/OP.zig");
14
15pub const FORM = struct {
16    pub const addr = 0x01;
17    pub const block2 = 0x03;
18    pub const block4 = 0x04;
19    pub const data2 = 0x05;
20    pub const data4 = 0x06;
21    pub const data8 = 0x07;
22    pub const string = 0x08;
23    pub const block = 0x09;
24    pub const block1 = 0x0a;
25    pub const data1 = 0x0b;
26    pub const flag = 0x0c;
27    pub const sdata = 0x0d;
28    pub const strp = 0x0e;
29    pub const udata = 0x0f;
30    pub const ref_addr = 0x10;
31    pub const ref1 = 0x11;
32    pub const ref2 = 0x12;
33    pub const ref4 = 0x13;
34    pub const ref8 = 0x14;
35    pub const ref_udata = 0x15;
36    pub const indirect = 0x16;
37    pub const sec_offset = 0x17;
38    pub const exprloc = 0x18;
39    pub const flag_present = 0x19;
40    pub const ref_sig8 = 0x20;
41
42    // Extensions for Fission.  See http://gcc.gnu.org/wiki/DebugFission.
43    pub const GNU_addr_index = 0x1f01;
44    pub const GNU_str_index = 0x1f02;
45
46    // Extensions for DWZ multifile.
47    // See http://www.dwarfstd.org/ShowIssue.php?issue=120604.1&type=open .
48    pub const GNU_ref_alt = 0x1f20;
49    pub const GNU_strp_alt = 0x1f21;
50};
51
52pub const ATE = struct {
53    pub const @"void" = 0x0;
54    pub const address = 0x1;
55    pub const boolean = 0x2;
56    pub const complex_float = 0x3;
57    pub const float = 0x4;
58    pub const signed = 0x5;
59    pub const signed_char = 0x6;
60    pub const unsigned = 0x7;
61    pub const unsigned_char = 0x8;
62
63    // DWARF 3.
64    pub const imaginary_float = 0x9;
65    pub const packed_decimal = 0xa;
66    pub const numeric_string = 0xb;
67    pub const edited = 0xc;
68    pub const signed_fixed = 0xd;
69    pub const unsigned_fixed = 0xe;
70    pub const decimal_float = 0xf;
71
72    // DWARF 4.
73    pub const UTF = 0x10;
74
75    pub const lo_user = 0x80;
76    pub const hi_user = 0xff;
77
78    // HP extensions.
79    pub const HP_float80 = 0x80; // Floating-point (80 bit).
80    pub const HP_complex_float80 = 0x81; // Complex floating-point (80 bit).
81    pub const HP_float128 = 0x82; // Floating-point (128 bit).
82    pub const HP_complex_float128 = 0x83; // Complex fp (128 bit).
83    pub const HP_floathpintel = 0x84; // Floating-point (82 bit IA64).
84    pub const HP_imaginary_float80 = 0x85;
85    pub const HP_imaginary_float128 = 0x86;
86    pub const HP_VAX_float = 0x88; // F or G floating.
87    pub const HP_VAX_float_d = 0x89; // D floating.
88    pub const HP_packed_decimal = 0x8a; // Cobol.
89    pub const HP_zoned_decimal = 0x8b; // Cobol.
90    pub const HP_edited = 0x8c; // Cobol.
91    pub const HP_signed_fixed = 0x8d; // Cobol.
92    pub const HP_unsigned_fixed = 0x8e; // Cobol.
93    pub const HP_VAX_complex_float = 0x8f; // F or G floating complex.
94    pub const HP_VAX_complex_float_d = 0x90; // D floating complex.
95};
96
97pub const CFA = struct {
98    pub const advance_loc = 0x40;
99    pub const offset = 0x80;
100    pub const restore = 0xc0;
101    pub const nop = 0x00;
102    pub const set_loc = 0x01;
103    pub const advance_loc1 = 0x02;
104    pub const advance_loc2 = 0x03;
105    pub const advance_loc4 = 0x04;
106    pub const offset_extended = 0x05;
107    pub const restore_extended = 0x06;
108    pub const @"undefined" = 0x07;
109    pub const same_value = 0x08;
110    pub const register = 0x09;
111    pub const remember_state = 0x0a;
112    pub const restore_state = 0x0b;
113    pub const def_cfa = 0x0c;
114    pub const def_cfa_register = 0x0d;
115    pub const def_cfa_offset = 0x0e;
116
117    // DWARF 3.
118    pub const def_cfa_expression = 0x0f;
119    pub const expression = 0x10;
120    pub const offset_extended_sf = 0x11;
121    pub const def_cfa_sf = 0x12;
122    pub const def_cfa_offset_sf = 0x13;
123    pub const val_offset = 0x14;
124    pub const val_offset_sf = 0x15;
125    pub const val_expression = 0x16;
126
127    pub const lo_user = 0x1c;
128    pub const hi_user = 0x3f;
129
130    // SGI/MIPS specific.
131    pub const MIPS_advance_loc8 = 0x1d;
132
133    // GNU extensions.
134    pub const GNU_window_save = 0x2d;
135    pub const GNU_args_size = 0x2e;
136    pub const GNU_negative_offset_extended = 0x2f;
137};
138
139pub const CHILDREN = struct {
140    pub const no = 0x00;
141    pub const yes = 0x01;
142};
143
144pub const LNS = struct {
145    pub const extended_op = 0x00;
146    pub const copy = 0x01;
147    pub const advance_pc = 0x02;
148    pub const advance_line = 0x03;
149    pub const set_file = 0x04;
150    pub const set_column = 0x05;
151    pub const negate_stmt = 0x06;
152    pub const set_basic_block = 0x07;
153    pub const const_add_pc = 0x08;
154    pub const fixed_advance_pc = 0x09;
155    pub const set_prologue_end = 0x0a;
156    pub const set_epilogue_begin = 0x0b;
157    pub const set_isa = 0x0c;
158};
159
160pub const LNE = struct {
161    pub const end_sequence = 0x01;
162    pub const set_address = 0x02;
163    pub const define_file = 0x03;
164    pub const set_discriminator = 0x04;
165    pub const lo_user = 0x80;
166    pub const hi_user = 0xff;
167};
168
169pub const LANG = struct {
170    pub const C89 = 0x0001;
171    pub const C = 0x0002;
172    pub const Ada83 = 0x0003;
173    pub const C_plus_plus = 0x0004;
174    pub const Cobol74 = 0x0005;
175    pub const Cobol85 = 0x0006;
176    pub const Fortran77 = 0x0007;
177    pub const Fortran90 = 0x0008;
178    pub const Pascal83 = 0x0009;
179    pub const Modula2 = 0x000a;
180    pub const Java = 0x000b;
181    pub const C99 = 0x000c;
182    pub const Ada95 = 0x000d;
183    pub const Fortran95 = 0x000e;
184    pub const PLI = 0x000f;
185    pub const ObjC = 0x0010;
186    pub const ObjC_plus_plus = 0x0011;
187    pub const UPC = 0x0012;
188    pub const D = 0x0013;
189    pub const Python = 0x0014;
190    pub const Go = 0x0016;
191    pub const C_plus_plus_11 = 0x001a;
192    pub const Rust = 0x001c;
193    pub const C11 = 0x001d;
194    pub const C_plus_plus_14 = 0x0021;
195    pub const Fortran03 = 0x0022;
196    pub const Fortran08 = 0x0023;
197    pub const lo_user = 0x8000;
198    pub const hi_user = 0xffff;
199    pub const Mips_Assembler = 0x8001;
200    pub const Upc = 0x8765;
201    pub const HP_Bliss = 0x8003;
202    pub const HP_Basic91 = 0x8004;
203    pub const HP_Pascal91 = 0x8005;
204    pub const HP_IMacro = 0x8006;
205    pub const HP_Assembler = 0x8007;
206};
207
208pub const UT = struct {
209    pub const compile = 0x01;
210    pub const @"type" = 0x02;
211    pub const partial = 0x03;
212    pub const skeleton = 0x04;
213    pub const split_compile = 0x05;
214    pub const split_type = 0x06;
215    pub const lo_user = 0x80;
216    pub const hi_user = 0xff;
217};
218
219pub const LNCT = struct {
220    pub const path = 0x1;
221    pub const directory_index = 0x2;
222    pub const timestamp = 0x3;
223    pub const size = 0x4;
224    pub const MD5 = 0x5;
225    pub const lo_user = 0x2000;
226    pub const hi_user = 0x3fff;
227};
228
229const PcRange = struct {
230    start: u64,
231    end: u64,
232};
233
234const Func = struct {
235    pc_range: ?PcRange,
236    name: ?[]const u8,
237};
238
239const CompileUnit = struct {
240    version: u16,
241    is_64: bool,
242    die: *Die,
243    pc_range: ?PcRange,
244};
245
246const AbbrevTable = ArrayList(AbbrevTableEntry);
247
248const AbbrevTableHeader = struct {
249    // offset from .debug_abbrev
250    offset: u64,
251    table: AbbrevTable,
252};
253
254const AbbrevTableEntry = struct {
255    has_children: bool,
256    abbrev_code: u64,
257    tag_id: u64,
258    attrs: ArrayList(AbbrevAttr),
259};
260
261const AbbrevAttr = struct {
262    attr_id: u64,
263    form_id: u64,
264};
265
266const FormValue = union(enum) {
267    Address: u64,
268    Block: []u8,
269    Const: Constant,
270    ExprLoc: []u8,
271    Flag: bool,
272    SecOffset: u64,
273    Ref: u64,
274    RefAddr: u64,
275    String: []const u8,
276    StrPtr: u64,
277};
278
279const Constant = struct {
280    payload: u64,
281    signed: bool,
282
283    fn asUnsignedLe(self: *const Constant) !u64 {
284        if (self.signed) return error.InvalidDebugInfo;
285        return self.payload;
286    }
287};
288
289const Die = struct {
290    tag_id: u64,
291    has_children: bool,
292    attrs: ArrayList(Attr),
293
294    const Attr = struct {
295        id: u64,
296        value: FormValue,
297    };
298
299    fn getAttr(self: *const Die, id: u64) ?*const FormValue {
300        for (self.attrs.items) |*attr| {
301            if (attr.id == id) return &attr.value;
302        }
303        return null;
304    }
305
306    fn getAttrAddr(self: *const Die, id: u64) !u64 {
307        const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
308        return switch (form_value.*) {
309            FormValue.Address => |value| value,
310            else => error.InvalidDebugInfo,
311        };
312    }
313
314    fn getAttrSecOffset(self: *const Die, id: u64) !u64 {
315        const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
316        return switch (form_value.*) {
317            FormValue.Const => |value| value.asUnsignedLe(),
318            FormValue.SecOffset => |value| value,
319            else => error.InvalidDebugInfo,
320        };
321    }
322
323    fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 {
324        const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
325        return switch (form_value.*) {
326            FormValue.Const => |value| value.asUnsignedLe(),
327            else => error.InvalidDebugInfo,
328        };
329    }
330
331    fn getAttrRef(self: *const Die, id: u64) !u64 {
332        const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
333        return switch (form_value.*) {
334            FormValue.Ref => |value| value,
335            else => error.InvalidDebugInfo,
336        };
337    }
338
339    pub fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64) ![]const u8 {
340        const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
341        return switch (form_value.*) {
342            FormValue.String => |value| value,
343            FormValue.StrPtr => |offset| di.getString(offset),
344            else => error.InvalidDebugInfo,
345        };
346    }
347};
348
349const FileEntry = struct {
350    file_name: []const u8,
351    dir_index: usize,
352    mtime: usize,
353    len_bytes: usize,
354};
355
356const LineNumberProgram = struct {
357    address: u64,
358    file: usize,
359    line: i64,
360    column: u64,
361    is_stmt: bool,
362    basic_block: bool,
363    end_sequence: bool,
364
365    default_is_stmt: bool,
366    target_address: u64,
367    include_dirs: []const []const u8,
368    file_entries: *ArrayList(FileEntry),
369
370    prev_valid: bool,
371    prev_address: u64,
372    prev_file: usize,
373    prev_line: i64,
374    prev_column: u64,
375    prev_is_stmt: bool,
376    prev_basic_block: bool,
377    prev_end_sequence: bool,
378
379    // Reset the state machine following the DWARF specification
380    pub fn reset(self: *LineNumberProgram) void {
381        self.address = 0;
382        self.file = 1;
383        self.line = 1;
384        self.column = 0;
385        self.is_stmt = self.default_is_stmt;
386        self.basic_block = false;
387        self.end_sequence = false;
388        // Invalidate all the remaining fields
389        self.prev_valid = false;
390        self.prev_address = 0;
391        self.prev_file = undefined;
392        self.prev_line = undefined;
393        self.prev_column = undefined;
394        self.prev_is_stmt = undefined;
395        self.prev_basic_block = undefined;
396        self.prev_end_sequence = undefined;
397    }
398
399    pub fn init(is_stmt: bool, include_dirs: []const []const u8, file_entries: *ArrayList(FileEntry), target_address: u64) LineNumberProgram {
400        return LineNumberProgram{
401            .address = 0,
402            .file = 1,
403            .line = 1,
404            .column = 0,
405            .is_stmt = is_stmt,
406            .basic_block = false,
407            .end_sequence = false,
408            .include_dirs = include_dirs,
409            .file_entries = file_entries,
410            .default_is_stmt = is_stmt,
411            .target_address = target_address,
412            .prev_valid = false,
413            .prev_address = 0,
414            .prev_file = undefined,
415            .prev_line = undefined,
416            .prev_column = undefined,
417            .prev_is_stmt = undefined,
418            .prev_basic_block = undefined,
419            .prev_end_sequence = undefined,
420        };
421    }
422
423    pub fn checkLineMatch(self: *LineNumberProgram) !?debug.LineInfo {
424        if (self.prev_valid and self.target_address >= self.prev_address and self.target_address < self.address) {
425            const file_entry = if (self.prev_file == 0) {
426                return error.MissingDebugInfo;
427            } else if (self.prev_file - 1 >= self.file_entries.items.len) {
428                return error.InvalidDebugInfo;
429            } else &self.file_entries.items[self.prev_file - 1];
430
431            const dir_name = if (file_entry.dir_index >= self.include_dirs.len) {
432                return error.InvalidDebugInfo;
433            } else self.include_dirs[file_entry.dir_index];
434            const file_name = try fs.path.join(self.file_entries.allocator, &[_][]const u8{ dir_name, file_entry.file_name });
435            errdefer self.file_entries.allocator.free(file_name);
436            return debug.LineInfo{
437                .line = if (self.prev_line >= 0) @intCast(u64, self.prev_line) else 0,
438                .column = self.prev_column,
439                .file_name = file_name,
440                .allocator = self.file_entries.allocator,
441            };
442        }
443
444        self.prev_valid = true;
445        self.prev_address = self.address;
446        self.prev_file = self.file;
447        self.prev_line = self.line;
448        self.prev_column = self.column;
449        self.prev_is_stmt = self.is_stmt;
450        self.prev_basic_block = self.basic_block;
451        self.prev_end_sequence = self.end_sequence;
452        return null;
453    }
454};
455
456fn readUnitLength(in_stream: anytype, endian: std.builtin.Endian, is_64: *bool) !u64 {
457    const first_32_bits = try in_stream.readInt(u32, endian);
458    is_64.* = (first_32_bits == 0xffffffff);
459    if (is_64.*) {
460        return in_stream.readInt(u64, endian);
461    } else {
462        if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo;
463        // TODO this cast should not be needed
464        return @as(u64, first_32_bits);
465    }
466}
467
468// TODO the nosuspends here are workarounds
469fn readAllocBytes(allocator: mem.Allocator, in_stream: anytype, size: usize) ![]u8 {
470    const buf = try allocator.alloc(u8, size);
471    errdefer allocator.free(buf);
472    if ((try nosuspend in_stream.read(buf)) < size) return error.EndOfFile;
473    return buf;
474}
475
476// TODO the nosuspends here are workarounds
477fn readAddress(in_stream: anytype, endian: std.builtin.Endian, is_64: bool) !u64 {
478    return nosuspend if (is_64)
479        try in_stream.readInt(u64, endian)
480    else
481        @as(u64, try in_stream.readInt(u32, endian));
482}
483
484fn parseFormValueBlockLen(allocator: mem.Allocator, in_stream: anytype, size: usize) !FormValue {
485    const buf = try readAllocBytes(allocator, in_stream, size);
486    return FormValue{ .Block = buf };
487}
488
489// TODO the nosuspends here are workarounds
490fn parseFormValueBlock(allocator: mem.Allocator, in_stream: anytype, endian: std.builtin.Endian, size: usize) !FormValue {
491    const block_len = try nosuspend in_stream.readVarInt(usize, endian, size);
492    return parseFormValueBlockLen(allocator, in_stream, block_len);
493}
494
495fn parseFormValueConstant(allocator: mem.Allocator, in_stream: anytype, signed: bool, endian: std.builtin.Endian, comptime size: i32) !FormValue {
496    _ = allocator;
497    // TODO: Please forgive me, I've worked around zig not properly spilling some intermediate values here.
498    // `nosuspend` should be removed from all the function calls once it is fixed.
499    return FormValue{
500        .Const = Constant{
501            .signed = signed,
502            .payload = switch (size) {
503                1 => try nosuspend in_stream.readInt(u8, endian),
504                2 => try nosuspend in_stream.readInt(u16, endian),
505                4 => try nosuspend in_stream.readInt(u32, endian),
506                8 => try nosuspend in_stream.readInt(u64, endian),
507                -1 => blk: {
508                    if (signed) {
509                        const x = try nosuspend leb.readILEB128(i64, in_stream);
510                        break :blk @bitCast(u64, x);
511                    } else {
512                        const x = try nosuspend leb.readULEB128(u64, in_stream);
513                        break :blk x;
514                    }
515                },
516                else => @compileError("Invalid size"),
517            },
518        },
519    };
520}
521
522// TODO the nosuspends here are workarounds
523fn parseFormValueRef(allocator: mem.Allocator, in_stream: anytype, endian: std.builtin.Endian, size: i32) !FormValue {
524    _ = allocator;
525    return FormValue{
526        .Ref = switch (size) {
527            1 => try nosuspend in_stream.readInt(u8, endian),
528            2 => try nosuspend in_stream.readInt(u16, endian),
529            4 => try nosuspend in_stream.readInt(u32, endian),
530            8 => try nosuspend in_stream.readInt(u64, endian),
531            -1 => try nosuspend leb.readULEB128(u64, in_stream),
532            else => unreachable,
533        },
534    };
535}
536
537// TODO the nosuspends here are workarounds
538fn parseFormValue(allocator: mem.Allocator, in_stream: anytype, form_id: u64, endian: std.builtin.Endian, is_64: bool) anyerror!FormValue {
539    return switch (form_id) {
540        FORM.addr => FormValue{ .Address = try readAddress(in_stream, endian, @sizeOf(usize) == 8) },
541        FORM.block1 => parseFormValueBlock(allocator, in_stream, endian, 1),
542        FORM.block2 => parseFormValueBlock(allocator, in_stream, endian, 2),
543        FORM.block4 => parseFormValueBlock(allocator, in_stream, endian, 4),
544        FORM.block => {
545            const block_len = try nosuspend leb.readULEB128(usize, in_stream);
546            return parseFormValueBlockLen(allocator, in_stream, block_len);
547        },
548        FORM.data1 => parseFormValueConstant(allocator, in_stream, false, endian, 1),
549        FORM.data2 => parseFormValueConstant(allocator, in_stream, false, endian, 2),
550        FORM.data4 => parseFormValueConstant(allocator, in_stream, false, endian, 4),
551        FORM.data8 => parseFormValueConstant(allocator, in_stream, false, endian, 8),
552        FORM.udata, FORM.sdata => {
553            const signed = form_id == FORM.sdata;
554            return parseFormValueConstant(allocator, in_stream, signed, endian, -1);
555        },
556        FORM.exprloc => {
557            const size = try nosuspend leb.readULEB128(usize, in_stream);
558            const buf = try readAllocBytes(allocator, in_stream, size);
559            return FormValue{ .ExprLoc = buf };
560        },
561        FORM.flag => FormValue{ .Flag = (try nosuspend in_stream.readByte()) != 0 },
562        FORM.flag_present => FormValue{ .Flag = true },
563        FORM.sec_offset => FormValue{ .SecOffset = try readAddress(in_stream, endian, is_64) },
564
565        FORM.ref1 => parseFormValueRef(allocator, in_stream, endian, 1),
566        FORM.ref2 => parseFormValueRef(allocator, in_stream, endian, 2),
567        FORM.ref4 => parseFormValueRef(allocator, in_stream, endian, 4),
568        FORM.ref8 => parseFormValueRef(allocator, in_stream, endian, 8),
569        FORM.ref_udata => parseFormValueRef(allocator, in_stream, endian, -1),
570
571        FORM.ref_addr => FormValue{ .RefAddr = try readAddress(in_stream, endian, is_64) },
572        FORM.ref_sig8 => FormValue{ .Ref = try nosuspend in_stream.readInt(u64, endian) },
573
574        FORM.string => FormValue{ .String = try in_stream.readUntilDelimiterAlloc(allocator, 0, math.maxInt(usize)) },
575        FORM.strp => FormValue{ .StrPtr = try readAddress(in_stream, endian, is_64) },
576        FORM.indirect => {
577            const child_form_id = try nosuspend leb.readULEB128(u64, in_stream);
578            const F = @TypeOf(async parseFormValue(allocator, in_stream, child_form_id, endian, is_64));
579            var frame = try allocator.create(F);
580            defer allocator.destroy(frame);
581            return await @asyncCall(frame, {}, parseFormValue, .{ allocator, in_stream, child_form_id, endian, is_64 });
582        },
583        else => error.InvalidDebugInfo,
584    };
585}
586
587fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*const AbbrevTableEntry {
588    for (abbrev_table.items) |*table_entry| {
589        if (table_entry.abbrev_code == abbrev_code) return table_entry;
590    }
591    return null;
592}
593
594pub const DwarfInfo = struct {
595    endian: std.builtin.Endian,
596    // No memory is owned by the DwarfInfo
597    debug_info: []const u8,
598    debug_abbrev: []const u8,
599    debug_str: []const u8,
600    debug_line: []const u8,
601    debug_ranges: ?[]const u8,
602    // Filled later by the initializer
603    abbrev_table_list: ArrayList(AbbrevTableHeader) = undefined,
604    compile_unit_list: ArrayList(CompileUnit) = undefined,
605    func_list: ArrayList(Func) = undefined,
606
607    pub fn allocator(self: DwarfInfo) mem.Allocator {
608        return self.abbrev_table_list.allocator;
609    }
610
611    pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 {
612        for (di.func_list.items) |*func| {
613            if (func.pc_range) |range| {
614                if (address >= range.start and address < range.end) {
615                    return func.name;
616                }
617            }
618        }
619
620        return null;
621    }
622
623    fn scanAllFunctions(di: *DwarfInfo) !void {
624        var stream = io.fixedBufferStream(di.debug_info);
625        const in = &stream.reader();
626        const seekable = &stream.seekableStream();
627        var this_unit_offset: u64 = 0;
628
629        while (this_unit_offset < try seekable.getEndPos()) {
630            try seekable.seekTo(this_unit_offset);
631
632            var is_64: bool = undefined;
633            const unit_length = try readUnitLength(in, di.endian, &is_64);
634            if (unit_length == 0) return;
635            const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
636
637            const version = try in.readInt(u16, di.endian);
638            if (version < 2 or version > 5) return error.InvalidDebugInfo;
639
640            const debug_abbrev_offset = if (is_64) try in.readInt(u64, di.endian) else try in.readInt(u32, di.endian);
641
642            const address_size = try in.readByte();
643            if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
644
645            const compile_unit_pos = try seekable.getPos();
646            const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset);
647
648            try seekable.seekTo(compile_unit_pos);
649
650            const next_unit_pos = this_unit_offset + next_offset;
651
652            while ((try seekable.getPos()) < next_unit_pos) {
653                const die_obj = (try di.parseDie(in, abbrev_table, is_64)) orelse continue;
654                defer die_obj.attrs.deinit();
655
656                const after_die_offset = try seekable.getPos();
657
658                switch (die_obj.tag_id) {
659                    TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => {
660                        const fn_name = x: {
661                            var depth: i32 = 3;
662                            var this_die_obj = die_obj;
663                            // Prenvent endless loops
664                            while (depth > 0) : (depth -= 1) {
665                                if (this_die_obj.getAttr(AT.name)) |_| {
666                                    const name = try this_die_obj.getAttrString(di, AT.name);
667                                    break :x name;
668                                } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
669                                    // Follow the DIE it points to and repeat
670                                    const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin);
671                                    if (ref_offset > next_offset) return error.InvalidDebugInfo;
672                                    try seekable.seekTo(this_unit_offset + ref_offset);
673                                    this_die_obj = (try di.parseDie(in, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
674                                } else if (this_die_obj.getAttr(AT.specification)) |_| {
675                                    // Follow the DIE it points to and repeat
676                                    const ref_offset = try this_die_obj.getAttrRef(AT.specification);
677                                    if (ref_offset > next_offset) return error.InvalidDebugInfo;
678                                    try seekable.seekTo(this_unit_offset + ref_offset);
679                                    this_die_obj = (try di.parseDie(in, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
680                                } else {
681                                    break :x null;
682                                }
683                            }
684
685                            break :x null;
686                        };
687
688                        const pc_range = x: {
689                            if (die_obj.getAttrAddr(AT.low_pc)) |low_pc| {
690                                if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
691                                    const pc_end = switch (high_pc_value.*) {
692                                        FormValue.Address => |value| value,
693                                        FormValue.Const => |value| b: {
694                                            const offset = try value.asUnsignedLe();
695                                            break :b (low_pc + offset);
696                                        },
697                                        else => return error.InvalidDebugInfo,
698                                    };
699                                    break :x PcRange{
700                                        .start = low_pc,
701                                        .end = pc_end,
702                                    };
703                                } else {
704                                    break :x null;
705                                }
706                            } else |err| {
707                                if (err != error.MissingDebugInfo) return err;
708                                break :x null;
709                            }
710                        };
711
712                        try di.func_list.append(Func{
713                            .name = fn_name,
714                            .pc_range = pc_range,
715                        });
716                    },
717                    else => {},
718                }
719
720                try seekable.seekTo(after_die_offset);
721            }
722
723            this_unit_offset += next_offset;
724        }
725    }
726
727    fn scanAllCompileUnits(di: *DwarfInfo) !void {
728        var stream = io.fixedBufferStream(di.debug_info);
729        const in = &stream.reader();
730        const seekable = &stream.seekableStream();
731        var this_unit_offset: u64 = 0;
732
733        while (this_unit_offset < try seekable.getEndPos()) {
734            try seekable.seekTo(this_unit_offset);
735
736            var is_64: bool = undefined;
737            const unit_length = try readUnitLength(in, di.endian, &is_64);
738            if (unit_length == 0) return;
739            const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
740
741            const version = try in.readInt(u16, di.endian);
742            if (version < 2 or version > 5) return error.InvalidDebugInfo;
743
744            const debug_abbrev_offset = if (is_64) try in.readInt(u64, di.endian) else try in.readInt(u32, di.endian);
745
746            const address_size = try in.readByte();
747            if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
748
749            const compile_unit_pos = try seekable.getPos();
750            const abbrev_table = try di.getAbbrevTable(debug_abbrev_offset);
751
752            try seekable.seekTo(compile_unit_pos);
753
754            const compile_unit_die = try di.allocator().create(Die);
755            compile_unit_die.* = (try di.parseDie(in, abbrev_table, is_64)) orelse return error.InvalidDebugInfo;
756
757            if (compile_unit_die.tag_id != TAG.compile_unit) return error.InvalidDebugInfo;
758
759            const pc_range = x: {
760                if (compile_unit_die.getAttrAddr(AT.low_pc)) |low_pc| {
761                    if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| {
762                        const pc_end = switch (high_pc_value.*) {
763                            FormValue.Address => |value| value,
764                            FormValue.Const => |value| b: {
765                                const offset = try value.asUnsignedLe();
766                                break :b (low_pc + offset);
767                            },
768                            else => return error.InvalidDebugInfo,
769                        };
770                        break :x PcRange{
771                            .start = low_pc,
772                            .end = pc_end,
773                        };
774                    } else {
775                        break :x null;
776                    }
777                } else |err| {
778                    if (err != error.MissingDebugInfo) return err;
779                    break :x null;
780                }
781            };
782
783            try di.compile_unit_list.append(CompileUnit{
784                .version = version,
785                .is_64 = is_64,
786                .pc_range = pc_range,
787                .die = compile_unit_die,
788            });
789
790            this_unit_offset += next_offset;
791        }
792    }
793
794    pub fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit {
795        for (di.compile_unit_list.items) |*compile_unit| {
796            if (compile_unit.pc_range) |range| {
797                if (target_address >= range.start and target_address < range.end) return compile_unit;
798            }
799            if (di.debug_ranges) |debug_ranges| {
800                if (compile_unit.die.getAttrSecOffset(AT.ranges)) |ranges_offset| {
801                    var stream = io.fixedBufferStream(debug_ranges);
802                    const in = &stream.reader();
803                    const seekable = &stream.seekableStream();
804
805                    // All the addresses in the list are relative to the value
806                    // specified by DW_AT.low_pc or to some other value encoded
807                    // in the list itself.
808                    // If no starting value is specified use zero.
809                    var base_address = compile_unit.die.getAttrAddr(AT.low_pc) catch |err| switch (err) {
810                        error.MissingDebugInfo => 0,
811                        else => return err,
812                    };
813
814                    try seekable.seekTo(ranges_offset);
815
816                    while (true) {
817                        const begin_addr = try in.readInt(usize, di.endian);
818                        const end_addr = try in.readInt(usize, di.endian);
819                        if (begin_addr == 0 and end_addr == 0) {
820                            break;
821                        }
822                        // This entry selects a new value for the base address
823                        if (begin_addr == math.maxInt(usize)) {
824                            base_address = end_addr;
825                            continue;
826                        }
827                        if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) {
828                            return compile_unit;
829                        }
830                    }
831                } else |err| {
832                    if (err != error.MissingDebugInfo) return err;
833                    continue;
834                }
835            }
836        }
837        return error.MissingDebugInfo;
838    }
839
840    /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
841    /// seeks in the stream and parses it.
842    fn getAbbrevTable(di: *DwarfInfo, abbrev_offset: u64) !*const AbbrevTable {
843        for (di.abbrev_table_list.items) |*header| {
844            if (header.offset == abbrev_offset) {
845                return &header.table;
846            }
847        }
848        try di.abbrev_table_list.append(AbbrevTableHeader{
849            .offset = abbrev_offset,
850            .table = try di.parseAbbrevTable(abbrev_offset),
851        });
852        return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1].table;
853    }
854
855    fn parseAbbrevTable(di: *DwarfInfo, offset: u64) !AbbrevTable {
856        var stream = io.fixedBufferStream(di.debug_abbrev);
857        const in = &stream.reader();
858        const seekable = &stream.seekableStream();
859
860        try seekable.seekTo(offset);
861        var result = AbbrevTable.init(di.allocator());
862        errdefer result.deinit();
863        while (true) {
864            const abbrev_code = try leb.readULEB128(u64, in);
865            if (abbrev_code == 0) return result;
866            try result.append(AbbrevTableEntry{
867                .abbrev_code = abbrev_code,
868                .tag_id = try leb.readULEB128(u64, in),
869                .has_children = (try in.readByte()) == CHILDREN.yes,
870                .attrs = ArrayList(AbbrevAttr).init(di.allocator()),
871            });
872            const attrs = &result.items[result.items.len - 1].attrs;
873
874            while (true) {
875                const attr_id = try leb.readULEB128(u64, in);
876                const form_id = try leb.readULEB128(u64, in);
877                if (attr_id == 0 and form_id == 0) break;
878                try attrs.append(AbbrevAttr{
879                    .attr_id = attr_id,
880                    .form_id = form_id,
881                });
882            }
883        }
884    }
885
886    fn parseDie(di: *DwarfInfo, in_stream: anytype, abbrev_table: *const AbbrevTable, is_64: bool) !?Die {
887        const abbrev_code = try leb.readULEB128(u64, in_stream);
888        if (abbrev_code == 0) return null;
889        const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo;
890
891        var result = Die{
892            .tag_id = table_entry.tag_id,
893            .has_children = table_entry.has_children,
894            .attrs = ArrayList(Die.Attr).init(di.allocator()),
895        };
896        try result.attrs.resize(table_entry.attrs.items.len);
897        for (table_entry.attrs.items) |attr, i| {
898            result.attrs.items[i] = Die.Attr{
899                .id = attr.attr_id,
900                .value = try parseFormValue(di.allocator(), in_stream, attr.form_id, di.endian, is_64),
901            };
902        }
903        return result;
904    }
905
906    pub fn getLineNumberInfo(di: *DwarfInfo, compile_unit: CompileUnit, target_address: u64) !debug.LineInfo {
907        var stream = io.fixedBufferStream(di.debug_line);
908        const in = &stream.reader();
909        const seekable = &stream.seekableStream();
910
911        const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir);
912        const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
913
914        try seekable.seekTo(line_info_offset);
915
916        var is_64: bool = undefined;
917        const unit_length = try readUnitLength(in, di.endian, &is_64);
918        if (unit_length == 0) {
919            return error.MissingDebugInfo;
920        }
921        const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4));
922
923        const version = try in.readInt(u16, di.endian);
924        if (version < 2 or version > 4) return error.InvalidDebugInfo;
925
926        const prologue_length = if (is_64) try in.readInt(u64, di.endian) else try in.readInt(u32, di.endian);
927        const prog_start_offset = (try seekable.getPos()) + prologue_length;
928
929        const minimum_instruction_length = try in.readByte();
930        if (minimum_instruction_length == 0) return error.InvalidDebugInfo;
931
932        if (version >= 4) {
933            // maximum_operations_per_instruction
934            _ = try in.readByte();
935        }
936
937        const default_is_stmt = (try in.readByte()) != 0;
938        const line_base = try in.readByteSigned();
939
940        const line_range = try in.readByte();
941        if (line_range == 0) return error.InvalidDebugInfo;
942
943        const opcode_base = try in.readByte();
944
945        const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1);
946        defer di.allocator().free(standard_opcode_lengths);
947
948        {
949            var i: usize = 0;
950            while (i < opcode_base - 1) : (i += 1) {
951                standard_opcode_lengths[i] = try in.readByte();
952            }
953        }
954
955        var include_directories = ArrayList([]const u8).init(di.allocator());
956        try include_directories.append(compile_unit_cwd);
957        while (true) {
958            const dir = try in.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize));
959            if (dir.len == 0) break;
960            try include_directories.append(dir);
961        }
962
963        var file_entries = ArrayList(FileEntry).init(di.allocator());
964        var prog = LineNumberProgram.init(default_is_stmt, include_directories.items, &file_entries, target_address);
965
966        while (true) {
967            const file_name = try in.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize));
968            if (file_name.len == 0) break;
969            const dir_index = try leb.readULEB128(usize, in);
970            const mtime = try leb.readULEB128(usize, in);
971            const len_bytes = try leb.readULEB128(usize, in);
972            try file_entries.append(FileEntry{
973                .file_name = file_name,
974                .dir_index = dir_index,
975                .mtime = mtime,
976                .len_bytes = len_bytes,
977            });
978        }
979
980        try seekable.seekTo(prog_start_offset);
981
982        const next_unit_pos = line_info_offset + next_offset;
983
984        while ((try seekable.getPos()) < next_unit_pos) {
985            const opcode = try in.readByte();
986
987            if (opcode == LNS.extended_op) {
988                const op_size = try leb.readULEB128(u64, in);
989                if (op_size < 1) return error.InvalidDebugInfo;
990                var sub_op = try in.readByte();
991                switch (sub_op) {
992                    LNE.end_sequence => {
993                        prog.end_sequence = true;
994                        if (try prog.checkLineMatch()) |info| return info;
995                        prog.reset();
996                    },
997                    LNE.set_address => {
998                        const addr = try in.readInt(usize, di.endian);
999                        prog.address = addr;
1000                    },
1001                    LNE.define_file => {
1002                        const file_name = try in.readUntilDelimiterAlloc(di.allocator(), 0, math.maxInt(usize));
1003                        const dir_index = try leb.readULEB128(usize, in);
1004                        const mtime = try leb.readULEB128(usize, in);
1005                        const len_bytes = try leb.readULEB128(usize, in);
1006                        try file_entries.append(FileEntry{
1007                            .file_name = file_name,
1008                            .dir_index = dir_index,
1009                            .mtime = mtime,
1010                            .len_bytes = len_bytes,
1011                        });
1012                    },
1013                    else => {
1014                        const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo;
1015                        try seekable.seekBy(fwd_amt);
1016                    },
1017                }
1018            } else if (opcode >= opcode_base) {
1019                // special opcodes
1020                const adjusted_opcode = opcode - opcode_base;
1021                const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range);
1022                const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range);
1023                prog.line += inc_line;
1024                prog.address += inc_addr;
1025                if (try prog.checkLineMatch()) |info| return info;
1026                prog.basic_block = false;
1027            } else {
1028                switch (opcode) {
1029                    LNS.copy => {
1030                        if (try prog.checkLineMatch()) |info| return info;
1031                        prog.basic_block = false;
1032                    },
1033                    LNS.advance_pc => {
1034                        const arg = try leb.readULEB128(usize, in);
1035                        prog.address += arg * minimum_instruction_length;
1036                    },
1037                    LNS.advance_line => {
1038                        const arg = try leb.readILEB128(i64, in);
1039                        prog.line += arg;
1040                    },
1041                    LNS.set_file => {
1042                        const arg = try leb.readULEB128(usize, in);
1043                        prog.file = arg;
1044                    },
1045                    LNS.set_column => {
1046                        const arg = try leb.readULEB128(u64, in);
1047                        prog.column = arg;
1048                    },
1049                    LNS.negate_stmt => {
1050                        prog.is_stmt = !prog.is_stmt;
1051                    },
1052                    LNS.set_basic_block => {
1053                        prog.basic_block = true;
1054                    },
1055                    LNS.const_add_pc => {
1056                        const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range);
1057                        prog.address += inc_addr;
1058                    },
1059                    LNS.fixed_advance_pc => {
1060                        const arg = try in.readInt(u16, di.endian);
1061                        prog.address += arg;
1062                    },
1063                    LNS.set_prologue_end => {},
1064                    else => {
1065                        if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo;
1066                        const len_bytes = standard_opcode_lengths[opcode - 1];
1067                        try seekable.seekBy(len_bytes);
1068                    },
1069                }
1070            }
1071        }
1072
1073        return error.MissingDebugInfo;
1074    }
1075
1076    fn getString(di: *DwarfInfo, offset: u64) ![]const u8 {
1077        if (offset > di.debug_str.len)
1078            return error.InvalidDebugInfo;
1079        const casted_offset = math.cast(usize, offset) catch
1080            return error.InvalidDebugInfo;
1081
1082        // Valid strings always have a terminating zero byte
1083        if (mem.indexOfScalarPos(u8, di.debug_str, casted_offset, 0)) |last| {
1084            return di.debug_str[casted_offset..last];
1085        }
1086
1087        return error.InvalidDebugInfo;
1088    }
1089};
1090
1091/// Initialize DWARF info. The caller has the responsibility to initialize most
1092/// the DwarfInfo fields before calling. These fields can be left undefined:
1093/// * abbrev_table_list
1094/// * compile_unit_list
1095pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void {
1096    di.abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator);
1097    di.compile_unit_list = ArrayList(CompileUnit).init(allocator);
1098    di.func_list = ArrayList(Func).init(allocator);
1099    try di.scanAllFunctions();
1100    try di.scanAllCompileUnits();
1101}
1102