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