master
  1debug_info: []u8 = &[0]u8{},
  2debug_abbrev: []u8 = &[0]u8{},
  3debug_str: []u8 = &[0]u8{},
  4debug_str_offsets: []u8 = &[0]u8{},
  5
  6pub fn deinit(dwarf: *Dwarf, allocator: Allocator) void {
  7    allocator.free(dwarf.debug_info);
  8    allocator.free(dwarf.debug_abbrev);
  9    allocator.free(dwarf.debug_str);
 10    allocator.free(dwarf.debug_str_offsets);
 11}
 12
 13/// Pulls an offset into __debug_str section from a __debug_str_offs section.
 14/// This is new in DWARFv5 and requires the producer to specify DW_FORM_strx* (`index` arg)
 15/// but also DW_AT_str_offsets_base with DW_FORM_sec_offset (`base` arg) in the opening header
 16/// of a "referencing entity" such as DW_TAG_compile_unit.
 17fn getOffset(debug_str_offsets: []const u8, base: u64, index: u64, dw_fmt: DwarfFormat) error{Overflow}!u64 {
 18    const base_as_usize = math.cast(usize, base) orelse return error.Overflow;
 19    const index_as_usize = math.cast(usize, index) orelse return error.Overflow;
 20    return switch (dw_fmt) {
 21        .dwarf32 => @as(
 22            *align(1) const u32,
 23            @ptrCast(debug_str_offsets.ptr + base_as_usize + index_as_usize * @sizeOf(u32)),
 24        ).*,
 25        .dwarf64 => @as(
 26            *align(1) const u64,
 27            @ptrCast(debug_str_offsets.ptr + base_as_usize + index_as_usize * @sizeOf(u64)),
 28        ).*,
 29    };
 30}
 31
 32pub const InfoReader = struct {
 33    ctx: Dwarf,
 34    pos: usize = 0,
 35
 36    fn bytes(p: InfoReader) []const u8 {
 37        return p.ctx.debug_info;
 38    }
 39
 40    pub fn readCompileUnitHeader(p: *InfoReader) !CompileUnitHeader {
 41        var length: u64 = try p.readInt(u32);
 42        const is_64bit = length == 0xffffffff;
 43        if (is_64bit) {
 44            length = try p.readInt(u64);
 45        }
 46        const dw_fmt: DwarfFormat = if (is_64bit) .dwarf64 else .dwarf32;
 47        const version = try p.readInt(Version);
 48        const rest: struct {
 49            debug_abbrev_offset: u64,
 50            address_size: u8,
 51            unit_type: u8,
 52        } = switch (version) {
 53            4 => .{
 54                .debug_abbrev_offset = try p.readOffset(dw_fmt),
 55                .address_size = try p.readByte(),
 56                .unit_type = 0,
 57            },
 58            5 => .{
 59                // According to the spec, version 5 introduced .unit_type field in the header, and
 60                // it reordered .debug_abbrev_offset with .address_size fields.
 61                .unit_type = try p.readByte(),
 62                .address_size = try p.readByte(),
 63                .debug_abbrev_offset = try p.readOffset(dw_fmt),
 64            },
 65            else => return error.InvalidVersion,
 66        };
 67        return .{
 68            .format = dw_fmt,
 69            .length = length,
 70            .version = version,
 71            .debug_abbrev_offset = rest.debug_abbrev_offset,
 72            .address_size = rest.address_size,
 73            .unit_type = rest.unit_type,
 74        };
 75    }
 76
 77    pub fn seekToDie(p: *InfoReader, code: Code, cuh: CompileUnitHeader, abbrev_reader: *AbbrevReader) !void {
 78        const cuh_length = math.cast(usize, cuh.length) orelse return error.Overflow;
 79        const end_pos = p.pos + switch (cuh.format) {
 80            .dwarf32 => @as(usize, 4),
 81            .dwarf64 => 12,
 82        } + cuh_length;
 83        while (p.pos < end_pos) {
 84            const di_code = try p.readUleb128(u64);
 85            if (di_code == 0) return error.UnexpectedEndOfFile;
 86            if (di_code == code) return;
 87
 88            while (try abbrev_reader.readAttr()) |attr| {
 89                try p.skip(attr.form, cuh);
 90            }
 91        }
 92        return error.UnexpectedEndOfFile;
 93    }
 94
 95    /// When skipping attributes, we don't really need to be able to handle them all
 96    /// since we only ever care about the DW_TAG_compile_unit.
 97    pub fn skip(p: *InfoReader, form: Form, cuh: CompileUnitHeader) !void {
 98        switch (form) {
 99            dw.FORM.sec_offset,
100            dw.FORM.ref_addr,
101            => {
102                _ = try p.readOffset(cuh.format);
103            },
104
105            dw.FORM.addr => {
106                _ = try p.readNBytes(cuh.address_size);
107            },
108
109            dw.FORM.block1,
110            dw.FORM.block2,
111            dw.FORM.block4,
112            dw.FORM.block,
113            => {
114                _ = try p.readBlock(form);
115            },
116
117            dw.FORM.exprloc => {
118                _ = try p.readExprLoc();
119            },
120
121            dw.FORM.flag_present => {},
122
123            dw.FORM.data1,
124            dw.FORM.ref1,
125            dw.FORM.flag,
126            dw.FORM.data2,
127            dw.FORM.ref2,
128            dw.FORM.data4,
129            dw.FORM.ref4,
130            dw.FORM.data8,
131            dw.FORM.ref8,
132            dw.FORM.ref_sig8,
133            dw.FORM.udata,
134            dw.FORM.ref_udata,
135            dw.FORM.sdata,
136            => {
137                _ = try p.readConstant(form);
138            },
139
140            dw.FORM.strp,
141            dw.FORM.string,
142            => {
143                _ = try p.readString(form, cuh);
144            },
145
146            else => if (cuh.version >= 5) switch (form) {
147                dw.FORM.strx,
148                dw.FORM.strx1,
149                dw.FORM.strx2,
150                dw.FORM.strx3,
151                dw.FORM.strx4,
152                => {
153                    // We are just iterating over the __debug_info data, so we don't care about an actual
154                    // string, therefore we set the `base = 0`.
155                    _ = try p.readStringIndexed(form, cuh, 0);
156                },
157
158                dw.FORM.addrx,
159                dw.FORM.addrx1,
160                dw.FORM.addrx2,
161                dw.FORM.addrx3,
162                dw.FORM.addrx4,
163                => {
164                    _ = try p.readIndex(form);
165                },
166
167                else => return error.UnhandledForm,
168            } else return error.UnhandledForm,
169        }
170    }
171
172    pub fn readBlock(p: *InfoReader, form: Form) ![]const u8 {
173        const len: u64 = switch (form) {
174            dw.FORM.block1 => try p.readByte(),
175            dw.FORM.block2 => try p.readInt(u16),
176            dw.FORM.block4 => try p.readInt(u32),
177            dw.FORM.block => try p.readUleb128(u64),
178            else => unreachable,
179        };
180        return p.readNBytes(len);
181    }
182
183    pub fn readExprLoc(p: *InfoReader) ![]const u8 {
184        const len: u64 = try p.readUleb128(u64);
185        return p.readNBytes(len);
186    }
187
188    pub fn readConstant(p: *InfoReader, form: Form) !u64 {
189        return switch (form) {
190            dw.FORM.data1, dw.FORM.ref1, dw.FORM.flag => try p.readByte(),
191            dw.FORM.data2, dw.FORM.ref2 => try p.readInt(u16),
192            dw.FORM.data4, dw.FORM.ref4 => try p.readInt(u32),
193            dw.FORM.data8, dw.FORM.ref8, dw.FORM.ref_sig8 => try p.readInt(u64),
194            dw.FORM.udata, dw.FORM.ref_udata => try p.readUleb128(u64),
195            dw.FORM.sdata => @bitCast(try p.readIleb128(i64)),
196            else => return error.UnhandledConstantForm,
197        };
198    }
199
200    pub fn readIndex(p: *InfoReader, form: Form) !u64 {
201        return switch (form) {
202            dw.FORM.strx1, dw.FORM.addrx1 => try p.readByte(),
203            dw.FORM.strx2, dw.FORM.addrx2 => try p.readInt(u16),
204            dw.FORM.strx3, dw.FORM.addrx3 => error.UnhandledForm,
205            dw.FORM.strx4, dw.FORM.addrx4 => try p.readInt(u32),
206            dw.FORM.strx, dw.FORM.addrx => try p.readUleb128(u64),
207            else => return error.UnhandledIndexForm,
208        };
209    }
210
211    pub fn readString(p: *InfoReader, form: Form, cuh: CompileUnitHeader) ![:0]const u8 {
212        switch (form) {
213            dw.FORM.strp => {
214                const off = try p.readOffset(cuh.format);
215                const off_u = math.cast(usize, off) orelse return error.Overflow;
216                return mem.sliceTo(@as([*:0]const u8, @ptrCast(p.ctx.debug_str.ptr + off_u)), 0);
217            },
218            dw.FORM.string => {
219                const start = p.pos;
220                while (p.pos < p.bytes().len) : (p.pos += 1) {
221                    if (p.bytes()[p.pos] == 0) break;
222                }
223                if (p.bytes()[p.pos] != 0) return error.UnexpectedEndOfFile;
224                return p.bytes()[start..p.pos :0];
225            },
226            else => unreachable,
227        }
228    }
229
230    pub fn readStringIndexed(p: *InfoReader, form: Form, cuh: CompileUnitHeader, base: u64) ![:0]const u8 {
231        switch (form) {
232            dw.FORM.strx,
233            dw.FORM.strx1,
234            dw.FORM.strx2,
235            dw.FORM.strx3,
236            dw.FORM.strx4,
237            => {
238                const index = try p.readIndex(form);
239                const off = math.cast(
240                    usize,
241                    try getOffset(p.ctx.debug_str_offsets, base, index, cuh.format),
242                ) orelse return error.Overflow;
243                return mem.sliceTo(@as([*:0]const u8, @ptrCast(p.ctx.debug_str.ptr + off)), 0);
244            },
245            else => unreachable,
246        }
247    }
248
249    pub fn readByte(p: *InfoReader) !u8 {
250        if (p.pos + 1 > p.bytes().len) return error.UnexpectedEndOfFile;
251        defer p.pos += 1;
252        return p.bytes()[p.pos];
253    }
254
255    pub fn readNBytes(p: *InfoReader, num: u64) ![]const u8 {
256        const num_usize = math.cast(usize, num) orelse return error.Overflow;
257        if (p.pos + num_usize > p.bytes().len) return error.UnexpectedEndOfFile;
258        defer p.pos += num_usize;
259        return p.bytes()[p.pos..][0..num_usize];
260    }
261
262    pub fn readInt(p: *InfoReader, comptime Int: type) !Int {
263        if (p.pos + @sizeOf(Int) > p.bytes().len) return error.UnexpectedEndOfFile;
264        defer p.pos += @sizeOf(Int);
265        return mem.readInt(Int, p.bytes()[p.pos..][0..@sizeOf(Int)], .little);
266    }
267
268    pub fn readOffset(p: *InfoReader, dw_fmt: DwarfFormat) !u64 {
269        return switch (dw_fmt) {
270            .dwarf32 => try p.readInt(u32),
271            .dwarf64 => try p.readInt(u64),
272        };
273    }
274
275    pub fn readUleb128(p: *InfoReader, comptime Type: type) !Type {
276        var reader: std.Io.Reader = .fixed(p.bytes());
277        reader.seek = p.pos;
278        defer p.pos = reader.seek;
279
280        return reader.takeLeb128(Type);
281    }
282
283    pub fn readIleb128(p: *InfoReader, comptime Type: type) !Type {
284        var reader: std.Io.Reader = .fixed(p.bytes());
285        reader.seek = p.pos;
286        defer p.pos = reader.seek;
287
288        return reader.takeLeb128(Type);
289    }
290
291    pub fn seekTo(p: *InfoReader, off: u64) !void {
292        p.pos = math.cast(usize, off) orelse return error.Overflow;
293    }
294};
295
296pub const AbbrevReader = struct {
297    ctx: Dwarf,
298    pos: usize = 0,
299
300    fn bytes(p: AbbrevReader) []const u8 {
301        return p.ctx.debug_abbrev;
302    }
303
304    pub fn hasMore(p: AbbrevReader) bool {
305        return p.pos < p.bytes().len;
306    }
307
308    pub fn readDecl(p: *AbbrevReader) !?AbbrevDecl {
309        const pos = p.pos;
310        const code = try p.readUleb128(Code);
311        if (code == 0) return null;
312
313        const tag = try p.readUleb128(Tag);
314        const has_children = (try p.readByte()) > 0;
315        return .{
316            .code = code,
317            .pos = pos,
318            .len = p.pos - pos,
319            .tag = tag,
320            .has_children = has_children,
321        };
322    }
323
324    pub fn readAttr(p: *AbbrevReader) !?AbbrevAttr {
325        const pos = p.pos;
326        const at = try p.readUleb128(At);
327        const form = try p.readUleb128(Form);
328        return if (at == 0 and form == 0) null else .{
329            .at = at,
330            .form = form,
331            .pos = pos,
332            .len = p.pos - pos,
333        };
334    }
335
336    pub fn readByte(p: *AbbrevReader) !u8 {
337        if (p.pos + 1 > p.bytes().len) return error.Eof;
338        defer p.pos += 1;
339        return p.bytes()[p.pos];
340    }
341
342    pub fn readUleb128(p: *AbbrevReader, comptime Type: type) !Type {
343        var reader: std.Io.Reader = .fixed(p.bytes());
344        reader.seek = p.pos;
345        defer p.pos = reader.seek;
346
347        return reader.takeLeb128(Type);
348    }
349
350    pub fn seekTo(p: *AbbrevReader, off: u64) !void {
351        p.pos = math.cast(usize, off) orelse return error.Overflow;
352    }
353};
354
355const AbbrevDecl = struct {
356    code: Code,
357    pos: usize,
358    len: usize,
359    tag: Tag,
360    has_children: bool,
361};
362
363const AbbrevAttr = struct {
364    at: At,
365    form: Form,
366    pos: usize,
367    len: usize,
368};
369
370const CompileUnitHeader = struct {
371    format: DwarfFormat,
372    length: u64,
373    version: Version,
374    debug_abbrev_offset: u64,
375    address_size: u8,
376    unit_type: u8,
377};
378
379const Die = struct {
380    pos: usize,
381    len: usize,
382};
383
384const DwarfFormat = enum {
385    dwarf32,
386    dwarf64,
387};
388
389const dw = std.dwarf;
390const leb = std.leb;
391const log = std.log.scoped(.link);
392const math = std.math;
393const mem = std.mem;
394const std = @import("std");
395const Allocator = mem.Allocator;
396const Dwarf = @This();
397const File = @import("file.zig").File;
398const MachO = @import("../MachO.zig");
399const Object = @import("Object.zig");
400
401pub const At = u64;
402pub const Code = u64;
403pub const Form = u64;
404pub const Tag = u64;
405pub const Version = u16;
406
407pub const AT = dw.AT;
408pub const FORM = dw.FORM;
409pub const TAG = dw.TAG;