| author | |
| committer | |
| log | ab48934e9cefb510d39ba3fe8c0dcf7619bec4cf |
| tree | b02155986fb9d36b4b5e3ac94263d43c9421c13e |
| parent | bde15cf0806f2b8d6fb0c90602b42a74863ec515 |
Add basic address->symbol resolution support. Uses symtab data from the
MachO image, not external dSYM data; that's left as a future exercise.
The net effect is that we can now map addresses to function names but
not much more. File names and line number data will have to wait until
a future pull request.
Partially fixes #434.4 files changed, 271 insertions(+), 57 deletions(-)
CMakeLists.txt+1| ... | ... | @@ -386,6 +386,7 @@ set(ZIG_STD_FILES |
| 386 | 386 | "index.zig" |
| 387 | 387 | "io.zig" |
| 388 | 388 | "linked_list.zig" |
| 389 | "macho.zig" | |
| 389 | 390 | "math/acos.zig" |
| 390 | 391 | "math/acosh.zig" |
| 391 | 392 | "math/asin.zig" |
std/debug/index.zig+91-57| ... | ... | @@ -5,6 +5,7 @@ const io = std.io; |
| 5 | 5 | const os = std.os; |
| 6 | 6 | const elf = std.elf; |
| 7 | 7 | const DW = std.dwarf; |
| 8 | const macho = std.macho; | |
| 8 | 9 | const ArrayList = std.ArrayList; |
| 9 | 10 | const builtin = @import("builtin"); |
| 10 | 11 | |
| ... | ... | @@ -180,43 +181,57 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator, |
| 180 | 181 | } |
| 181 | 182 | |
| 182 | 183 | fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void { |
| 183 | if (builtin.os == builtin.Os.windows) { | |
| 184 | return error.UnsupportedDebugInfo; | |
| 185 | } | |
| 186 | 184 | // TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal |
| 187 | 185 | // at compile time. I'll call it issue #313 |
| 188 | 186 | const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}"; |
| 189 | 187 | |
| 190 | const compile_unit = findCompileUnit(debug_info, address) catch { | |
| 191 | try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", | |
| 192 | address); | |
| 193 | return; | |
| 194 | }; | |
| 195 | const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); | |
| 196 | if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| { | |
| 197 | defer line_info.deinit(); | |
| 198 | try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ | |
| 199 | DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", | |
| 200 | line_info.file_name, line_info.line, line_info.column, | |
| 201 | address, compile_unit_name); | |
| 202 | if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { | |
| 203 | if (line_info.column == 0) { | |
| 204 | try out_stream.write("\n"); | |
| 205 | } else { | |
| 206 | {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) { | |
| 207 | try out_stream.writeByte(' '); | |
| 208 | }} | |
| 209 | try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); | |
| 188 | switch (builtin.os) { | |
| 189 | builtin.Os.windows => return error.UnsupportedDebugInfo, | |
| 190 | builtin.Os.macosx => { | |
| 191 | // TODO(bnoordhuis) It's theoretically possible to obtain the | |
| 192 | // compilation unit from the symbtab but it's not that useful | |
| 193 | // in practice because the compiler dumps everything in a single | |
| 194 | // object file. Future improvement: use external dSYM data when | |
| 195 | // available. | |
| 196 | const unknown = macho.Symbol { .name = "???", .address = address }; | |
| 197 | const symbol = debug_info.symbol_table.search(address) ?? &unknown; | |
| 198 | try out_stream.print(WHITE ++ "{}" ++ RESET ++ ": " ++ | |
| 199 | DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n", | |
| 200 | symbol.name, address); | |
| 201 | }, | |
| 202 | else => { | |
| 203 | const compile_unit = findCompileUnit(debug_info, address) catch { | |
| 204 | try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", | |
| 205 | address); | |
| 206 | return; | |
| 207 | }; | |
| 208 | const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); | |
| 209 | if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| { | |
| 210 | defer line_info.deinit(); | |
| 211 | try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ | |
| 212 | DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", | |
| 213 | line_info.file_name, line_info.line, line_info.column, | |
| 214 | address, compile_unit_name); | |
| 215 | if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) { | |
| 216 | if (line_info.column == 0) { | |
| 217 | try out_stream.write("\n"); | |
| 218 | } else { | |
| 219 | {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) { | |
| 220 | try out_stream.writeByte(' '); | |
| 221 | }} | |
| 222 | try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); | |
| 223 | } | |
| 224 | } else |err| switch (err) { | |
| 225 | error.EndOfFile => {}, | |
| 226 | else => return err, | |
| 227 | } | |
| 228 | } else |err| switch (err) { | |
| 229 | error.MissingDebugInfo, error.InvalidDebugInfo => { | |
| 230 | try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name); | |
| 231 | }, | |
| 232 | else => return err, | |
| 210 | 233 | } |
| 211 | } else |err| switch (err) { | |
| 212 | error.EndOfFile => {}, | |
| 213 | else => return err, | |
| 214 | } | |
| 215 | } else |err| switch (err) { | |
| 216 | error.MissingDebugInfo, error.InvalidDebugInfo => { | |
| 217 | try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name); | |
| 218 | 234 | }, |
| 219 | else => return err, | |
| 220 | 235 | } |
| 221 | 236 | } |
| 222 | 237 | |
| ... | ... | @@ -249,12 +264,22 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace { |
| 249 | 264 | try scanAllCompileUnits(st); |
| 250 | 265 | return st; |
| 251 | 266 | }, |
| 267 | builtin.ObjectFormat.macho => { | |
| 268 | var exe_file = try os.openSelfExe(); | |
| 269 | defer exe_file.close(); | |
| 270 | ||
| 271 | const st = try allocator.create(ElfStackTrace); | |
| 272 | errdefer allocator.destroy(st); | |
| 273 | ||
| 274 | *st = ElfStackTrace { | |
| 275 | .symbol_table = try macho.loadSymbols(allocator, &io.FileInStream.init(&exe_file)), | |
| 276 | }; | |
| 277 | ||
| 278 | return st; | |
| 279 | }, | |
| 252 | 280 | builtin.ObjectFormat.coff => { |
| 253 | 281 | return error.TodoSupportCoffDebugInfo; |
| 254 | 282 | }, |
| 255 | builtin.ObjectFormat.macho => { | |
| 256 | return error.TodoSupportMachoDebugInfo; | |
| 257 | }, | |
| 258 | 283 | builtin.ObjectFormat.wasm => { |
| 259 | 284 | return error.TodoSupportCOFFDebugInfo; |
| 260 | 285 | }, |
| ... | ... | @@ -297,31 +322,40 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con |
| 297 | 322 | } |
| 298 | 323 | } |
| 299 | 324 | |
| 300 | pub const ElfStackTrace = struct { | |
| 301 | self_exe_file: os.File, | |
| 302 | elf: elf.Elf, | |
| 303 | debug_info: &elf.SectionHeader, | |
| 304 | debug_abbrev: &elf.SectionHeader, | |
| 305 | debug_str: &elf.SectionHeader, | |
| 306 | debug_line: &elf.SectionHeader, | |
| 307 | debug_ranges: ?&elf.SectionHeader, | |
| 308 | abbrev_table_list: ArrayList(AbbrevTableHeader), | |
| 309 | compile_unit_list: ArrayList(CompileUnit), | |
| 310 | ||
| 311 | pub fn allocator(self: &const ElfStackTrace) &mem.Allocator { | |
| 312 | return self.abbrev_table_list.allocator; | |
| 313 | } | |
| 325 | pub const ElfStackTrace = switch (builtin.os) { | |
| 326 | builtin.Os.macosx => struct { | |
| 327 | symbol_table: macho.SymbolTable, | |
| 314 | 328 | |
| 315 | pub fn readString(self: &ElfStackTrace) ![]u8 { | |
| 316 | var in_file_stream = io.FileInStream.init(&self.self_exe_file); | |
| 317 | const in_stream = &in_file_stream.stream; | |
| 318 | return readStringRaw(self.allocator(), in_stream); | |
| 319 | } | |
| 329 | pub fn close(self: &ElfStackTrace) void { | |
| 330 | self.symbol_table.deinit(); | |
| 331 | } | |
| 332 | }, | |
| 333 | else => struct { | |
| 334 | self_exe_file: os.File, | |
| 335 | elf: elf.Elf, | |
| 336 | debug_info: &elf.SectionHeader, | |
| 337 | debug_abbrev: &elf.SectionHeader, | |
| 338 | debug_str: &elf.SectionHeader, | |
| 339 | debug_line: &elf.SectionHeader, | |
| 340 | debug_ranges: ?&elf.SectionHeader, | |
| 341 | abbrev_table_list: ArrayList(AbbrevTableHeader), | |
| 342 | compile_unit_list: ArrayList(CompileUnit), | |
| 343 | ||
| 344 | pub fn allocator(self: &const ElfStackTrace) &mem.Allocator { | |
| 345 | return self.abbrev_table_list.allocator; | |
| 346 | } | |
| 320 | 347 | |
| 321 | pub fn close(self: &ElfStackTrace) void { | |
| 322 | self.self_exe_file.close(); | |
| 323 | self.elf.close(); | |
| 324 | } | |
| 348 | pub fn readString(self: &ElfStackTrace) ![]u8 { | |
| 349 | var in_file_stream = io.FileInStream.init(&self.self_exe_file); | |
| 350 | const in_stream = &in_file_stream.stream; | |
| 351 | return readStringRaw(self.allocator(), in_stream); | |
| 352 | } | |
| 353 | ||
| 354 | pub fn close(self: &ElfStackTrace) void { | |
| 355 | self.self_exe_file.close(); | |
| 356 | self.elf.close(); | |
| 357 | } | |
| 358 | }, | |
| 325 | 359 | }; |
| 326 | 360 | |
| 327 | 361 | const PcRange = struct { |
std/index.zig+2| ... | ... | @@ -21,6 +21,7 @@ pub const endian = @import("endian.zig"); |
| 21 | 21 | pub const fmt = @import("fmt/index.zig"); |
| 22 | 22 | pub const heap = @import("heap.zig"); |
| 23 | 23 | pub const io = @import("io.zig"); |
| 24 | pub const macho = @import("macho.zig"); | |
| 24 | 25 | pub const math = @import("math/index.zig"); |
| 25 | 26 | pub const mem = @import("mem.zig"); |
| 26 | 27 | pub const net = @import("net.zig"); |
| ... | ... | @@ -51,6 +52,7 @@ test "std" { |
| 51 | 52 | _ = @import("endian.zig"); |
| 52 | 53 | _ = @import("fmt/index.zig"); |
| 53 | 54 | _ = @import("io.zig"); |
| 55 | _ = @import("macho.zig"); | |
| 54 | 56 | _ = @import("math/index.zig"); |
| 55 | 57 | _ = @import("mem.zig"); |
| 56 | 58 | _ = @import("heap.zig"); |
std/macho.zig created+177| ... | ... | @@ -0,0 +1,177 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("index.zig"); | |
| 3 | const io = std.io; | |
| 4 | const mem = std.mem; | |
| 5 | ||
| 6 | const MH_MAGIC_64 = 0xFEEDFACF; | |
| 7 | const MH_PIE = 0x200000; | |
| 8 | const LC_SYMTAB = 2; | |
| 9 | ||
| 10 | const MachHeader64 = packed struct { | |
| 11 | magic: u32, | |
| 12 | cputype: u32, | |
| 13 | cpusubtype: u32, | |
| 14 | filetype: u32, | |
| 15 | ncmds: u32, | |
| 16 | sizeofcmds: u32, | |
| 17 | flags: u32, | |
| 18 | reserved: u32, | |
| 19 | }; | |
| 20 | ||
| 21 | const LoadCommand = packed struct { | |
| 22 | cmd: u32, | |
| 23 | cmdsize: u32, | |
| 24 | }; | |
| 25 | ||
| 26 | const SymtabCommand = packed struct { | |
| 27 | symoff: u32, | |
| 28 | nsyms: u32, | |
| 29 | stroff: u32, | |
| 30 | strsize: u32, | |
| 31 | }; | |
| 32 | ||
| 33 | const Nlist64 = packed struct { | |
| 34 | n_strx: u32, | |
| 35 | n_type: u8, | |
| 36 | n_sect: u8, | |
| 37 | n_desc: u16, | |
| 38 | n_value: u64, | |
| 39 | }; | |
| 40 | ||
| 41 | pub const Symbol = struct { | |
| 42 | name: []const u8, | |
| 43 | address: u64, | |
| 44 | ||
| 45 | fn addressLessThan(lhs: &const Symbol, rhs: &const Symbol) bool { | |
| 46 | return lhs.address < rhs.address; | |
| 47 | } | |
| 48 | }; | |
| 49 | ||
| 50 | pub const SymbolTable = struct { | |
| 51 | allocator: &mem.Allocator, | |
| 52 | symbols: []const Symbol, | |
| 53 | strings: []const u8, | |
| 54 | ||
| 55 | // Doubles as an eyecatcher to calculate the PIE slide, see loadSymbols(). | |
| 56 | // Ideally we'd use _mh_execute_header because it's always at 0x100000000 | |
| 57 | // in the image but as it's located in a different section than executable | |
| 58 | // code, its displacement is different. | |
| 59 | pub fn deinit(self: &SymbolTable) void { | |
| 60 | self.allocator.free(self.symbols); | |
| 61 | self.symbols = []const Symbol {}; | |
| 62 | ||
| 63 | self.allocator.free(self.strings); | |
| 64 | self.strings = []const u8 {}; | |
| 65 | } | |
| 66 | ||
| 67 | pub fn search(self: &const SymbolTable, address: usize) ?&const Symbol { | |
| 68 | var min: usize = 0; | |
| 69 | var max: usize = self.symbols.len - 1; // Exclude sentinel. | |
| 70 | while (min < max) { | |
| 71 | const mid = min + (max - min) / 2; | |
| 72 | const curr = &self.symbols[mid]; | |
| 73 | const next = &self.symbols[mid + 1]; | |
| 74 | if (address >= next.address) { | |
| 75 | min = mid + 1; | |
| 76 | } else if (address < curr.address) { | |
| 77 | max = mid; | |
| 78 | } else { | |
| 79 | return curr; | |
| 80 | } | |
| 81 | } | |
| 82 | return null; | |
| 83 | } | |
| 84 | }; | |
| 85 | ||
| 86 | pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable { | |
| 87 | var file = in.file; | |
| 88 | try file.seekTo(0); | |
| 89 | ||
| 90 | var hdr: MachHeader64 = undefined; | |
| 91 | try readNoEof(in, &hdr); | |
| 92 | if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo; | |
| 93 | const is_pie = MH_PIE == (hdr.flags & MH_PIE); | |
| 94 | ||
| 95 | var pos: usize = @sizeOf(@typeOf(hdr)); | |
| 96 | var ncmd: u32 = hdr.ncmds; | |
| 97 | while (ncmd != 0) : (ncmd -= 1) { | |
| 98 | try file.seekTo(pos); | |
| 99 | var lc: LoadCommand = undefined; | |
| 100 | try readNoEof(in, &lc); | |
| 101 | if (lc.cmd == LC_SYMTAB) break; | |
| 102 | pos += lc.cmdsize; | |
| 103 | } else { | |
| 104 | return error.MissingDebugInfo; | |
| 105 | } | |
| 106 | ||
| 107 | var cmd: SymtabCommand = undefined; | |
| 108 | try readNoEof(in, &cmd); | |
| 109 | ||
| 110 | try file.seekTo(cmd.symoff); | |
| 111 | var syms = try allocator.alloc(Nlist64, cmd.nsyms); | |
| 112 | defer allocator.free(syms); | |
| 113 | try readNoEof(in, syms); | |
| 114 | ||
| 115 | try file.seekTo(cmd.stroff); | |
| 116 | var strings = try allocator.alloc(u8, cmd.strsize); | |
| 117 | errdefer allocator.free(strings); | |
| 118 | try in.stream.readNoEof(strings); | |
| 119 | ||
| 120 | var nsyms: usize = 0; | |
| 121 | for (syms) |sym| if (isSymbol(sym)) nsyms += 1; | |
| 122 | if (nsyms == 0) return error.MissingDebugInfo; | |
| 123 | ||
| 124 | var symbols = try allocator.alloc(Symbol, nsyms + 1); // Room for sentinel. | |
| 125 | errdefer allocator.free(symbols); | |
| 126 | ||
| 127 | var pie_slide: usize = 0; | |
| 128 | var nsym: usize = 0; | |
| 129 | for (syms) |sym| { | |
| 130 | if (!isSymbol(sym)) continue; | |
| 131 | const start = sym.n_strx; | |
| 132 | const end = ??mem.indexOfScalarPos(u8, strings, start, 0); | |
| 133 | const name = strings[start..end]; | |
| 134 | const address = sym.n_value; | |
| 135 | symbols[nsym] = Symbol { .name = name, .address = address }; | |
| 136 | nsym += 1; | |
| 137 | if (is_pie and mem.eql(u8, name, "_SymbolTable_deinit")) { | |
| 138 | pie_slide = @ptrToInt(SymbolTable.deinit) - address; | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | // Effectively a no-op, lld emits symbols in ascending order. | |
| 143 | std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan); | |
| 144 | ||
| 145 | // Insert the sentinel. Since we don't know where the last function ends, | |
| 146 | // we arbitrarily limit it to the start address + 4 KB. | |
| 147 | const top = symbols[nsyms - 1].address + 4096; | |
| 148 | symbols[nsyms] = Symbol { .name = "", .address = top }; | |
| 149 | ||
| 150 | if (pie_slide != 0) { | |
| 151 | for (symbols) |*symbol| symbol.address += pie_slide; | |
| 152 | } | |
| 153 | ||
| 154 | return SymbolTable { | |
| 155 | .allocator = allocator, | |
| 156 | .symbols = symbols, | |
| 157 | .strings = strings, | |
| 158 | }; | |
| 159 | } | |
| 160 | ||
| 161 | fn readNoEof(in: &io.FileInStream, sink: var) !void { | |
| 162 | if (@typeOf(sink) == []Nlist64) { | |
| 163 | const T = @typeOf(sink[0]); | |
| 164 | const len = @sizeOf(T) * sink.len; | |
| 165 | const bytes = @ptrCast(&u8, &sink[0]); | |
| 166 | return in.stream.readNoEof(bytes[0..len]); | |
| 167 | } else { | |
| 168 | const T = @typeOf(*sink); | |
| 169 | const len = @sizeOf(T); | |
| 170 | const bytes = @ptrCast(&u8, sink); | |
| 171 | return in.stream.readNoEof(bytes[0..len]); | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | fn isSymbol(sym: &const Nlist64) bool { | |
| 176 | return sym.n_value != 0 and sym.n_desc == 0; | |
| 177 | } |