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;