blob 45abfda8 (88038B) - Raw
1 const std = @import("std.zig"); 2 const math = std.math; 3 const mem = std.mem; 4 const io = std.io; 5 const os = std.os; 6 const elf = std.elf; 7 const DW = std.dwarf; 8 const macho = std.macho; 9 const coff = std.coff; 10 const pdb = std.pdb; 11 const windows = os.windows; 12 const ArrayList = std.ArrayList; 13 const builtin = @import("builtin"); 14 const maxInt = std.math.maxInt; 15 16 const leb = @import("debug/leb128.zig"); 17 18 pub const FailingAllocator = @import("debug/failing_allocator.zig").FailingAllocator; 19 pub const failing_allocator = &FailingAllocator.init(global_allocator, 0).allocator; 20 21 pub const runtime_safety = switch (builtin.mode) { 22 builtin.Mode.Debug, builtin.Mode.ReleaseSafe => true, 23 builtin.Mode.ReleaseFast, builtin.Mode.ReleaseSmall => false, 24 }; 25 26 const Module = struct { 27 mod_info: pdb.ModInfo, 28 module_name: []u8, 29 obj_file_name: []u8, 30 31 populated: bool, 32 symbols: []u8, 33 subsect_info: []u8, 34 checksum_offset: ?usize, 35 }; 36 37 /// Tries to write to stderr, unbuffered, and ignores any error returned. 38 /// Does not append a newline. 39 var stderr_file: os.File = undefined; 40 var stderr_file_out_stream: os.File.OutStream = undefined; 41 42 var stderr_stream: ?*io.OutStream(os.File.WriteError) = null; 43 var stderr_mutex = std.Mutex.init(); 44 pub fn warn(comptime fmt: []const u8, args: ...) void { 45 const held = stderr_mutex.acquire(); 46 defer held.release(); 47 const stderr = getStderrStream() catch return; 48 stderr.print(fmt, args) catch return; 49 } 50 51 pub fn getStderrStream() !*io.OutStream(os.File.WriteError) { 52 if (stderr_stream) |st| { 53 return st; 54 } else { 55 stderr_file = try io.getStdErr(); 56 stderr_file_out_stream = stderr_file.outStream(); 57 const st = &stderr_file_out_stream.stream; 58 stderr_stream = st; 59 return st; 60 } 61 } 62 63 /// TODO multithreaded awareness 64 var self_debug_info: ?DebugInfo = null; 65 66 pub fn getSelfDebugInfo() !*DebugInfo { 67 if (self_debug_info) |*info| { 68 return info; 69 } else { 70 self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator()); 71 return &self_debug_info.?; 72 } 73 } 74 75 fn wantTtyColor() bool { 76 var bytes: [128]u8 = undefined; 77 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator; 78 return if (std.os.getEnvVarOwned(allocator, "ZIG_DEBUG_COLOR")) |_| true else |_| stderr_file.isTty(); 79 } 80 81 /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. 82 /// TODO multithreaded awareness 83 pub fn dumpCurrentStackTrace(start_addr: ?usize) void { 84 const stderr = getStderrStream() catch return; 85 const debug_info = getSelfDebugInfo() catch |err| { 86 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return; 87 return; 88 }; 89 writeCurrentStackTrace(stderr, debug_info, wantTtyColor(), start_addr) catch |err| { 90 stderr.print("Unable to dump stack trace: {}\n", @errorName(err)) catch return; 91 return; 92 }; 93 } 94 95 /// Returns a slice with the same pointer as addresses, with a potentially smaller len. 96 /// On Windows, when first_address is not null, we ask for at least 32 stack frames, 97 /// and then try to find the first address. If addresses.len is more than 32, we 98 /// capture that many stack frames exactly, and then look for the first address, 99 /// chopping off the irrelevant frames and shifting so that the returned addresses pointer 100 /// equals the passed in addresses pointer. 101 pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace) void { 102 switch (builtin.os) { 103 builtin.Os.windows => { 104 const addrs = stack_trace.instruction_addresses; 105 const u32_addrs_len = @intCast(u32, addrs.len); 106 const first_addr = first_address orelse { 107 stack_trace.index = windows.RtlCaptureStackBackTrace( 108 0, 109 u32_addrs_len, 110 @ptrCast(**c_void, addrs.ptr), 111 null, 112 ); 113 return; 114 }; 115 var addr_buf_stack: [32]usize = undefined; 116 const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; 117 const n = windows.RtlCaptureStackBackTrace(0, u32_addrs_len, @ptrCast(**c_void, addr_buf.ptr), null); 118 const first_index = for (addr_buf[0..n]) |addr, i| { 119 if (addr == first_addr) { 120 break i; 121 } 122 } else { 123 stack_trace.index = 0; 124 return; 125 }; 126 const slice = addr_buf[first_index..n]; 127 // We use a for loop here because slice and addrs may alias. 128 for (slice) |addr, i| { 129 addrs[i] = addr; 130 } 131 stack_trace.index = slice.len; 132 }, 133 else => { 134 var it = StackIterator.init(first_address); 135 for (stack_trace.instruction_addresses) |*addr, i| { 136 addr.* = it.next() orelse { 137 stack_trace.index = i; 138 return; 139 }; 140 } 141 stack_trace.index = stack_trace.instruction_addresses.len; 142 }, 143 } 144 } 145 146 /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. 147 /// TODO multithreaded awareness 148 pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { 149 const stderr = getStderrStream() catch return; 150 const debug_info = getSelfDebugInfo() catch |err| { 151 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return; 152 return; 153 }; 154 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, wantTtyColor()) catch |err| { 155 stderr.print("Unable to dump stack trace: {}\n", @errorName(err)) catch return; 156 return; 157 }; 158 } 159 160 /// This function invokes undefined behavior when `ok` is `false`. 161 /// In Debug and ReleaseSafe modes, calls to this function are always 162 /// generated, and the `unreachable` statement triggers a panic. 163 /// In ReleaseFast and ReleaseSmall modes, calls to this function are 164 /// optimized away, and in fact the optimizer is able to use the assertion 165 /// in its heuristics. 166 /// Inside a test block, it is best to use the `std.testing` module rather 167 /// than this function, because this function may not detect a test failure 168 /// in ReleaseFast and ReleaseSafe mode. Outside of a test block, this assert 169 /// function is the correct function to use. 170 pub fn assert(ok: bool) void { 171 if (!ok) unreachable; // assertion failure 172 } 173 174 pub fn panic(comptime format: []const u8, args: ...) noreturn { 175 @setCold(true); 176 const first_trace_addr = @returnAddress(); 177 panicExtra(null, first_trace_addr, format, args); 178 } 179 180 /// TODO multithreaded awareness 181 var panicking: u8 = 0; // TODO make this a bool 182 183 pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: ...) noreturn { 184 @setCold(true); 185 186 if (@atomicRmw(u8, &panicking, builtin.AtomicRmwOp.Xchg, 1, builtin.AtomicOrder.SeqCst) == 1) { 187 // Panicked during a panic. 188 189 // TODO detect if a different thread caused the panic, because in that case 190 // we would want to return here instead of calling abort, so that the thread 191 // which first called panic can finish printing a stack trace. 192 os.abort(); 193 } 194 const stderr = getStderrStream() catch os.abort(); 195 stderr.print(format ++ "\n", args) catch os.abort(); 196 if (trace) |t| { 197 dumpStackTrace(t.*); 198 } 199 dumpCurrentStackTrace(first_trace_addr); 200 201 os.abort(); 202 } 203 204 const RED = "\x1b[31;1m"; 205 const GREEN = "\x1b[32;1m"; 206 const CYAN = "\x1b[36;1m"; 207 const WHITE = "\x1b[37;1m"; 208 const DIM = "\x1b[2m"; 209 const RESET = "\x1b[0m"; 210 211 pub fn writeStackTrace( 212 stack_trace: builtin.StackTrace, 213 out_stream: var, 214 allocator: *mem.Allocator, 215 debug_info: *DebugInfo, 216 tty_color: bool, 217 ) !void { 218 var frame_index: usize = 0; 219 var frames_left: usize = std.math.min(stack_trace.index, stack_trace.instruction_addresses.len); 220 221 while (frames_left != 0) : ({ 222 frames_left -= 1; 223 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; 224 }) { 225 const return_address = stack_trace.instruction_addresses[frame_index]; 226 try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_color); 227 } 228 } 229 230 pub const StackIterator = struct { 231 first_addr: ?usize, 232 fp: usize, 233 234 pub fn init(first_addr: ?usize) StackIterator { 235 return StackIterator{ 236 .first_addr = first_addr, 237 .fp = @frameAddress(), 238 }; 239 } 240 241 fn next(self: *StackIterator) ?usize { 242 if (self.fp == 0) return null; 243 self.fp = @intToPtr(*const usize, self.fp).*; 244 if (self.fp == 0) return null; 245 246 if (self.first_addr) |addr| { 247 while (self.fp != 0) : (self.fp = @intToPtr(*const usize, self.fp).*) { 248 const return_address = @intToPtr(*const usize, self.fp + @sizeOf(usize)).*; 249 if (addr == return_address) { 250 self.first_addr = null; 251 return return_address; 252 } 253 } 254 } 255 256 const return_address = @intToPtr(*const usize, self.fp + @sizeOf(usize)).*; 257 return return_address; 258 } 259 }; 260 261 pub fn writeCurrentStackTrace(out_stream: var, debug_info: *DebugInfo, tty_color: bool, start_addr: ?usize) !void { 262 switch (builtin.os) { 263 builtin.Os.windows => return writeCurrentStackTraceWindows(out_stream, debug_info, tty_color, start_addr), 264 else => {}, 265 } 266 var it = StackIterator.init(start_addr); 267 while (it.next()) |return_address| { 268 try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_color); 269 } 270 } 271 272 pub fn writeCurrentStackTraceWindows( 273 out_stream: var, 274 debug_info: *DebugInfo, 275 tty_color: bool, 276 start_addr: ?usize, 277 ) !void { 278 var addr_buf: [1024]usize = undefined; 279 const n = windows.RtlCaptureStackBackTrace(0, addr_buf.len, @ptrCast(**c_void, &addr_buf), null); 280 const addrs = addr_buf[0..n]; 281 var start_i: usize = if (start_addr) |saddr| blk: { 282 for (addrs) |addr, i| { 283 if (addr == saddr) break :blk i; 284 } 285 return; 286 } else 0; 287 for (addrs[start_i..]) |addr| { 288 try printSourceAtAddress(debug_info, out_stream, addr, tty_color); 289 } 290 } 291 292 pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void { 293 switch (builtin.os) { 294 builtin.Os.macosx => return printSourceAtAddressMacOs(debug_info, out_stream, address, tty_color), 295 builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => return printSourceAtAddressLinux(debug_info, out_stream, address, tty_color), 296 builtin.Os.windows => return printSourceAtAddressWindows(debug_info, out_stream, address, tty_color), 297 else => return error.UnsupportedOperatingSystem, 298 } 299 } 300 301 fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_address: usize, tty_color: bool) !void { 302 const allocator = getDebugInfoAllocator(); 303 const base_address = os.getBaseAddress(); 304 const relative_address = relocated_address - base_address; 305 306 var coff_section: *coff.Section = undefined; 307 const mod_index = for (di.sect_contribs) |sect_contrib| { 308 if (sect_contrib.Section > di.coff.sections.len) continue; 309 // Remember that SectionContribEntry.Section is 1-based. 310 coff_section = &di.coff.sections.toSlice()[sect_contrib.Section - 1]; 311 312 const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset; 313 const vaddr_end = vaddr_start + sect_contrib.Size; 314 if (relative_address >= vaddr_start and relative_address < vaddr_end) { 315 break sect_contrib.ModuleIndex; 316 } 317 } else { 318 // we have no information to add to the address 319 if (tty_color) { 320 try out_stream.print("???:?:?: "); 321 setTtyColor(TtyColor.Dim); 322 try out_stream.print("0x{x} in ??? (???)", relocated_address); 323 setTtyColor(TtyColor.Reset); 324 try out_stream.print("\n\n\n"); 325 } else { 326 try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", relocated_address); 327 } 328 return; 329 }; 330 331 const mod = &di.modules[mod_index]; 332 try populateModule(di, mod); 333 const obj_basename = os.path.basename(mod.obj_file_name); 334 335 var symbol_i: usize = 0; 336 const symbol_name = while (symbol_i != mod.symbols.len) { 337 const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]); 338 if (prefix.RecordLen < 2) 339 return error.InvalidDebugInfo; 340 switch (prefix.RecordKind) { 341 pdb.SymbolKind.S_LPROC32 => { 342 const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]); 343 const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset; 344 const vaddr_end = vaddr_start + proc_sym.CodeSize; 345 if (relative_address >= vaddr_start and relative_address < vaddr_end) { 346 break mem.toSliceConst(u8, @ptrCast([*]u8, proc_sym) + @sizeOf(pdb.ProcSym)); 347 } 348 }, 349 else => {}, 350 } 351 symbol_i += prefix.RecordLen + @sizeOf(u16); 352 if (symbol_i > mod.symbols.len) 353 return error.InvalidDebugInfo; 354 } else "???"; 355 356 const subsect_info = mod.subsect_info; 357 358 var sect_offset: usize = 0; 359 var skip_len: usize = undefined; 360 const opt_line_info = subsections: { 361 const checksum_offset = mod.checksum_offset orelse break :subsections null; 362 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { 363 const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]); 364 skip_len = subsect_hdr.Length; 365 sect_offset += @sizeOf(pdb.DebugSubsectionHeader); 366 367 switch (subsect_hdr.Kind) { 368 pdb.DebugSubsectionKind.Lines => { 369 var line_index = sect_offset; 370 371 const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]); 372 if (line_hdr.RelocSegment == 0) return error.MissingDebugInfo; 373 line_index += @sizeOf(pdb.LineFragmentHeader); 374 const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset; 375 const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; 376 377 if (relative_address >= frag_vaddr_start and relative_address < frag_vaddr_end) { 378 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) 379 // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in, 380 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. 381 const subsection_end_index = sect_offset + subsect_hdr.Length; 382 383 while (line_index < subsection_end_index) { 384 const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]); 385 line_index += @sizeOf(pdb.LineBlockFragmentHeader); 386 const start_line_index = line_index; 387 388 const has_column = line_hdr.Flags.LF_HaveColumns; 389 390 // All line entries are stored inside their line block by ascending start address. 391 // Heuristic: we want to find the last line entry that has a vaddr_start <= relative_address. 392 // This is done with a simple linear search. 393 var line_i: u32 = 0; 394 while (line_i < block_hdr.NumLines) : (line_i += 1) { 395 const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]); 396 line_index += @sizeOf(pdb.LineNumberEntry); 397 398 const vaddr_start = frag_vaddr_start + line_num_entry.Offset; 399 if (relative_address <= vaddr_start) { 400 break; 401 } 402 } 403 404 // line_i == 0 would mean that no matching LineNumberEntry was found. 405 if (line_i > 0) { 406 const subsect_index = checksum_offset + block_hdr.NameIndex; 407 const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]); 408 const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset; 409 try di.pdb.string_table.seekTo(strtab_offset); 410 const source_file_name = try di.pdb.string_table.readNullTermString(allocator); 411 412 const line_entry_idx = line_i - 1; 413 414 const column = if (has_column) blk: { 415 const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines; 416 const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx; 417 const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[col_index]); 418 break :blk col_num_entry.StartColumn; 419 } else 0; 420 421 const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry); 422 const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[found_line_index]); 423 const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags); 424 425 break :subsections LineInfo{ 426 .allocator = allocator, 427 .file_name = source_file_name, 428 .line = flags.Start, 429 .column = column, 430 }; 431 } 432 } 433 434 // Checking that we are not reading garbage after the (possibly) multiple block fragments. 435 if (line_index != subsection_end_index) { 436 return error.InvalidDebugInfo; 437 } 438 } 439 }, 440 else => {}, 441 } 442 443 if (sect_offset > subsect_info.len) 444 return error.InvalidDebugInfo; 445 } else { 446 break :subsections null; 447 } 448 }; 449 450 if (tty_color) { 451 setTtyColor(TtyColor.White); 452 if (opt_line_info) |li| { 453 try out_stream.print("{}:{}:{}", li.file_name, li.line, li.column); 454 } else { 455 try out_stream.print("???:?:?"); 456 } 457 setTtyColor(TtyColor.Reset); 458 try out_stream.print(": "); 459 setTtyColor(TtyColor.Dim); 460 try out_stream.print("0x{x} in {} ({})", relocated_address, symbol_name, obj_basename); 461 setTtyColor(TtyColor.Reset); 462 463 if (opt_line_info) |line_info| { 464 try out_stream.print("\n"); 465 if (printLineFromFileAnyOs(out_stream, line_info)) { 466 if (line_info.column == 0) { 467 try out_stream.write("\n"); 468 } else { 469 { 470 var col_i: usize = 1; 471 while (col_i < line_info.column) : (col_i += 1) { 472 try out_stream.writeByte(' '); 473 } 474 } 475 setTtyColor(TtyColor.Green); 476 try out_stream.write("^"); 477 setTtyColor(TtyColor.Reset); 478 try out_stream.write("\n"); 479 } 480 } else |err| switch (err) { 481 error.EndOfFile => {}, 482 error.FileNotFound => { 483 setTtyColor(TtyColor.Dim); 484 try out_stream.write("file not found\n\n"); 485 setTtyColor(TtyColor.White); 486 }, 487 else => return err, 488 } 489 } else { 490 try out_stream.print("\n\n\n"); 491 } 492 } else { 493 if (opt_line_info) |li| { 494 try out_stream.print("{}:{}:{}: 0x{x} in {} ({})\n\n\n", li.file_name, li.line, li.column, relocated_address, symbol_name, obj_basename); 495 } else { 496 try out_stream.print("???:?:?: 0x{x} in {} ({})\n\n\n", relocated_address, symbol_name, obj_basename); 497 } 498 } 499 } 500 501 const TtyColor = enum { 502 Red, 503 Green, 504 Cyan, 505 White, 506 Dim, 507 Bold, 508 Reset, 509 }; 510 511 /// TODO this is a special case hack right now. clean it up and maybe make it part of std.fmt 512 fn setTtyColor(tty_color: TtyColor) void { 513 if (os.supportsAnsiEscapeCodes(stderr_file.handle)) { 514 switch (tty_color) { 515 TtyColor.Red => { 516 stderr_file.write(RED) catch return; 517 }, 518 TtyColor.Green => { 519 stderr_file.write(GREEN) catch return; 520 }, 521 TtyColor.Cyan => { 522 stderr_file.write(CYAN) catch return; 523 }, 524 TtyColor.White, TtyColor.Bold => { 525 stderr_file.write(WHITE) catch return; 526 }, 527 TtyColor.Dim => { 528 stderr_file.write(DIM) catch return; 529 }, 530 TtyColor.Reset => { 531 stderr_file.write(RESET) catch return; 532 }, 533 } 534 } else { 535 const S = struct { 536 var attrs: windows.WORD = undefined; 537 var init_attrs = false; 538 }; 539 if (!S.init_attrs) { 540 S.init_attrs = true; 541 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; 542 // TODO handle error 543 _ = windows.GetConsoleScreenBufferInfo(stderr_file.handle, &info); 544 S.attrs = info.wAttributes; 545 } 546 547 // TODO handle errors 548 switch (tty_color) { 549 TtyColor.Red => { 550 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY); 551 }, 552 TtyColor.Green => { 553 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY); 554 }, 555 TtyColor.Cyan => { 556 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY); 557 }, 558 TtyColor.White, TtyColor.Bold => { 559 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY); 560 }, 561 TtyColor.Dim => { 562 _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_INTENSITY); 563 }, 564 TtyColor.Reset => { 565 _ = windows.SetConsoleTextAttribute(stderr_file.handle, S.attrs); 566 }, 567 } 568 } 569 } 570 571 fn populateModule(di: *DebugInfo, mod: *Module) !void { 572 if (mod.populated) 573 return; 574 const allocator = getDebugInfoAllocator(); 575 576 // At most one can be non-zero. 577 if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0) 578 return error.InvalidDebugInfo; 579 580 if (mod.mod_info.C13ByteSize == 0) 581 return; 582 583 const modi = di.pdb.getStreamById(mod.mod_info.ModuleSymStream) orelse return error.MissingDebugInfo; 584 585 const signature = try modi.stream.readIntLittle(u32); 586 if (signature != 4) 587 return error.InvalidDebugInfo; 588 589 mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4); 590 try modi.stream.readNoEof(mod.symbols); 591 592 mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize); 593 try modi.stream.readNoEof(mod.subsect_info); 594 595 var sect_offset: usize = 0; 596 var skip_len: usize = undefined; 597 while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { 598 const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &mod.subsect_info[sect_offset]); 599 skip_len = subsect_hdr.Length; 600 sect_offset += @sizeOf(pdb.DebugSubsectionHeader); 601 602 switch (subsect_hdr.Kind) { 603 pdb.DebugSubsectionKind.FileChecksums => { 604 mod.checksum_offset = sect_offset; 605 break; 606 }, 607 else => {}, 608 } 609 610 if (sect_offset > mod.subsect_info.len) 611 return error.InvalidDebugInfo; 612 } 613 614 mod.populated = true; 615 } 616 617 fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { 618 var min: usize = 0; 619 var max: usize = symbols.len - 1; // Exclude sentinel. 620 while (min < max) { 621 const mid = min + (max - min) / 2; 622 const curr = &symbols[mid]; 623 const next = &symbols[mid + 1]; 624 if (address >= next.address()) { 625 min = mid + 1; 626 } else if (address < curr.address()) { 627 max = mid; 628 } else { 629 return curr; 630 } 631 } 632 return null; 633 } 634 635 fn printSourceAtAddressMacOs(di: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void { 636 const base_addr = std.os.getBaseAddress(); 637 const adjusted_addr = 0x100000000 + (address - base_addr); 638 639 const symbol = machoSearchSymbols(di.symbols, adjusted_addr) orelse { 640 if (tty_color) { 641 try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in ??? (???)" ++ RESET ++ "\n\n\n", address); 642 } else { 643 try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", address); 644 } 645 return; 646 }; 647 648 const symbol_name = mem.toSliceConst(u8, di.strings.ptr + symbol.nlist.n_strx); 649 const compile_unit_name = if (symbol.ofile) |ofile| blk: { 650 const ofile_path = mem.toSliceConst(u8, di.strings.ptr + ofile.n_strx); 651 break :blk os.path.basename(ofile_path); 652 } else "???"; 653 if (getLineNumberInfoMacOs(di, symbol.*, adjusted_addr)) |line_info| { 654 defer line_info.deinit(); 655 try printLineInfo( 656 out_stream, 657 line_info, 658 address, 659 symbol_name, 660 compile_unit_name, 661 tty_color, 662 printLineFromFileAnyOs, 663 ); 664 } else |err| switch (err) { 665 error.MissingDebugInfo, error.InvalidDebugInfo => { 666 if (tty_color) { 667 try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in {} ({})" ++ RESET ++ "\n\n\n", address, symbol_name, compile_unit_name); 668 } else { 669 try out_stream.print("???:?:?: 0x{x} in {} ({})\n\n\n", address, symbol_name, compile_unit_name); 670 } 671 }, 672 else => return err, 673 } 674 } 675 676 /// This function works in freestanding mode. 677 /// fn printLineFromFile(out_stream: var, line_info: LineInfo) !void 678 pub fn printSourceAtAddressDwarf( 679 debug_info: *DwarfInfo, 680 out_stream: var, 681 address: usize, 682 tty_color: bool, 683 comptime printLineFromFile: var, 684 ) !void { 685 const compile_unit = findCompileUnit(debug_info, address) catch { 686 if (tty_color) { 687 try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in ??? (???)" ++ RESET ++ "\n\n\n", address); 688 } else { 689 try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", address); 690 } 691 return; 692 }; 693 const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); 694 if (getLineNumberInfoDwarf(debug_info, compile_unit.*, address)) |line_info| { 695 defer line_info.deinit(); 696 const symbol_name = getSymbolNameDwarf(debug_info, address) orelse "???"; 697 try printLineInfo( 698 out_stream, 699 line_info, 700 address, 701 symbol_name, 702 compile_unit_name, 703 tty_color, 704 printLineFromFile, 705 ); 706 } else |err| switch (err) { 707 error.MissingDebugInfo, error.InvalidDebugInfo => { 708 if (tty_color) { 709 try out_stream.print("???:?:?: " ++ DIM ++ "0x{x} in ??? ({})" ++ RESET ++ "\n\n\n", address, compile_unit_name); 710 } else { 711 try out_stream.print("???:?:?: 0x{x} in ??? ({})\n\n\n", address, compile_unit_name); 712 } 713 }, 714 else => return err, 715 } 716 } 717 718 pub fn printSourceAtAddressLinux(debug_info: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void { 719 return printSourceAtAddressDwarf(debug_info, out_stream, address, tty_color, printLineFromFileAnyOs); 720 } 721 722 fn printLineInfo( 723 out_stream: var, 724 line_info: LineInfo, 725 address: usize, 726 symbol_name: []const u8, 727 compile_unit_name: []const u8, 728 tty_color: bool, 729 comptime printLineFromFile: var, 730 ) !void { 731 if (tty_color) { 732 try out_stream.print( 733 WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ DIM ++ "0x{x} in {} ({})" ++ RESET ++ "\n", 734 line_info.file_name, 735 line_info.line, 736 line_info.column, 737 address, 738 symbol_name, 739 compile_unit_name, 740 ); 741 if (printLineFromFile(out_stream, line_info)) { 742 if (line_info.column == 0) { 743 try out_stream.write("\n"); 744 } else { 745 { 746 var col_i: usize = 1; 747 while (col_i < line_info.column) : (col_i += 1) { 748 try out_stream.writeByte(' '); 749 } 750 } 751 try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); 752 } 753 } else |err| switch (err) { 754 error.EndOfFile => {}, 755 else => return err, 756 } 757 } else { 758 try out_stream.print( 759 "{}:{}:{}: 0x{x} in {} ({})\n", 760 line_info.file_name, 761 line_info.line, 762 line_info.column, 763 address, 764 symbol_name, 765 compile_unit_name, 766 ); 767 } 768 } 769 770 // TODO use this 771 pub const OpenSelfDebugInfoError = error{ 772 MissingDebugInfo, 773 OutOfMemory, 774 UnsupportedOperatingSystem, 775 }; 776 777 pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo { 778 switch (builtin.os) { 779 builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => return openSelfDebugInfoLinux(allocator), 780 builtin.Os.macosx, builtin.Os.ios => return openSelfDebugInfoMacOs(allocator), 781 builtin.Os.windows => return openSelfDebugInfoWindows(allocator), 782 else => return error.UnsupportedOperatingSystem, 783 } 784 } 785 786 fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { 787 const self_file = try os.openSelfExe(); 788 defer self_file.close(); 789 790 const coff_obj = try allocator.create(coff.Coff); 791 coff_obj.* = coff.Coff{ 792 .in_file = self_file, 793 .allocator = allocator, 794 .coff_header = undefined, 795 .pe_header = undefined, 796 .sections = undefined, 797 .guid = undefined, 798 .age = undefined, 799 }; 800 801 var di = DebugInfo{ 802 .coff = coff_obj, 803 .pdb = undefined, 804 .sect_contribs = undefined, 805 .modules = undefined, 806 }; 807 808 try di.coff.loadHeader(); 809 810 var path_buf: [windows.MAX_PATH]u8 = undefined; 811 const len = try di.coff.getPdbPath(path_buf[0..]); 812 const raw_path = path_buf[0..len]; 813 814 const path = try os.path.resolve(allocator, [][]const u8{raw_path}); 815 816 try di.pdb.openFile(di.coff, path); 817 818 var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; 819 const version = try pdb_stream.stream.readIntLittle(u32); 820 const signature = try pdb_stream.stream.readIntLittle(u32); 821 const age = try pdb_stream.stream.readIntLittle(u32); 822 var guid: [16]u8 = undefined; 823 try pdb_stream.stream.readNoEof(guid[0..]); 824 if (!mem.eql(u8, di.coff.guid, guid) or di.coff.age != age) 825 return error.InvalidDebugInfo; 826 // We validated the executable and pdb match. 827 828 const string_table_index = str_tab_index: { 829 const name_bytes_len = try pdb_stream.stream.readIntLittle(u32); 830 const name_bytes = try allocator.alloc(u8, name_bytes_len); 831 try pdb_stream.stream.readNoEof(name_bytes); 832 833 const HashTableHeader = packed struct { 834 Size: u32, 835 Capacity: u32, 836 837 fn maxLoad(cap: u32) u32 { 838 return cap * 2 / 3 + 1; 839 } 840 }; 841 const hash_tbl_hdr = try pdb_stream.stream.readStruct(HashTableHeader); 842 if (hash_tbl_hdr.Capacity == 0) 843 return error.InvalidDebugInfo; 844 845 if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) 846 return error.InvalidDebugInfo; 847 848 const present = try readSparseBitVector(&pdb_stream.stream, allocator); 849 if (present.len != hash_tbl_hdr.Size) 850 return error.InvalidDebugInfo; 851 const deleted = try readSparseBitVector(&pdb_stream.stream, allocator); 852 853 const Bucket = struct { 854 first: u32, 855 second: u32, 856 }; 857 const bucket_list = try allocator.alloc(Bucket, present.len); 858 for (present) |_| { 859 const name_offset = try pdb_stream.stream.readIntLittle(u32); 860 const name_index = try pdb_stream.stream.readIntLittle(u32); 861 const name = mem.toSlice(u8, name_bytes.ptr + name_offset); 862 if (mem.eql(u8, name, "/names")) { 863 break :str_tab_index name_index; 864 } 865 } 866 return error.MissingDebugInfo; 867 }; 868 869 di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.InvalidDebugInfo; 870 di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo; 871 872 const dbi = di.pdb.dbi; 873 874 // Dbi Header 875 const dbi_stream_header = try dbi.stream.readStruct(pdb.DbiStreamHeader); 876 const mod_info_size = dbi_stream_header.ModInfoSize; 877 const section_contrib_size = dbi_stream_header.SectionContributionSize; 878 879 var modules = ArrayList(Module).init(allocator); 880 881 // Module Info Substream 882 var mod_info_offset: usize = 0; 883 while (mod_info_offset != mod_info_size) { 884 const mod_info = try dbi.stream.readStruct(pdb.ModInfo); 885 var this_record_len: usize = @sizeOf(pdb.ModInfo); 886 887 const module_name = try dbi.readNullTermString(allocator); 888 this_record_len += module_name.len + 1; 889 890 const obj_file_name = try dbi.readNullTermString(allocator); 891 this_record_len += obj_file_name.len + 1; 892 893 if (this_record_len % 4 != 0) { 894 const round_to_next_4 = (this_record_len | 0x3) + 1; 895 const march_forward_bytes = round_to_next_4 - this_record_len; 896 try dbi.seekForward(march_forward_bytes); 897 this_record_len += march_forward_bytes; 898 } 899 900 try modules.append(Module{ 901 .mod_info = mod_info, 902 .module_name = module_name, 903 .obj_file_name = obj_file_name, 904 905 .populated = false, 906 .symbols = undefined, 907 .subsect_info = undefined, 908 .checksum_offset = null, 909 }); 910 911 mod_info_offset += this_record_len; 912 if (mod_info_offset > mod_info_size) 913 return error.InvalidDebugInfo; 914 } 915 916 di.modules = modules.toOwnedSlice(); 917 918 // Section Contribution Substream 919 var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator); 920 var sect_cont_offset: usize = 0; 921 if (section_contrib_size != 0) { 922 const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.stream.readIntLittle(u32)); 923 if (ver != pdb.SectionContrSubstreamVersion.Ver60) 924 return error.InvalidDebugInfo; 925 sect_cont_offset += @sizeOf(u32); 926 } 927 while (sect_cont_offset != section_contrib_size) { 928 const entry = try sect_contribs.addOne(); 929 entry.* = try dbi.stream.readStruct(pdb.SectionContribEntry); 930 sect_cont_offset += @sizeOf(pdb.SectionContribEntry); 931 932 if (sect_cont_offset > section_contrib_size) 933 return error.InvalidDebugInfo; 934 } 935 936 di.sect_contribs = sect_contribs.toOwnedSlice(); 937 938 return di; 939 } 940 941 fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize { 942 const num_words = try stream.readIntLittle(u32); 943 var word_i: usize = 0; 944 var list = ArrayList(usize).init(allocator); 945 while (word_i != num_words) : (word_i += 1) { 946 const word = try stream.readIntLittle(u32); 947 var bit_i: u5 = 0; 948 while (true) : (bit_i += 1) { 949 if (word & (u32(1) << bit_i) != 0) { 950 try list.append(word_i * 32 + bit_i); 951 } 952 if (bit_i == maxInt(u5)) break; 953 } 954 } 955 return list.toOwnedSlice(); 956 } 957 958 fn findDwarfSectionFromElf(elf_file: *elf.Elf, name: []const u8) !?DwarfInfo.Section { 959 const elf_header = (try elf_file.findSection(name)) orelse return null; 960 return DwarfInfo.Section{ 961 .offset = elf_header.offset, 962 .size = elf_header.size, 963 }; 964 } 965 966 /// Initialize DWARF info. The caller has the responsibility to initialize most 967 /// the DwarfInfo fields before calling. These fields can be left undefined: 968 /// * abbrev_table_list 969 /// * compile_unit_list 970 pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void { 971 di.abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator); 972 di.compile_unit_list = ArrayList(CompileUnit).init(allocator); 973 di.func_list = ArrayList(Func).init(allocator); 974 try scanAllFunctions(di); 975 try scanAllCompileUnits(di); 976 } 977 978 pub fn openElfDebugInfo( 979 allocator: *mem.Allocator, 980 elf_seekable_stream: *DwarfSeekableStream, 981 elf_in_stream: *DwarfInStream, 982 ) !DwarfInfo { 983 var efile: elf.Elf = undefined; 984 try efile.openStream(allocator, elf_seekable_stream, elf_in_stream); 985 errdefer efile.close(); 986 987 var di = DwarfInfo{ 988 .dwarf_seekable_stream = elf_seekable_stream, 989 .dwarf_in_stream = elf_in_stream, 990 .endian = efile.endian, 991 .debug_info = (try findDwarfSectionFromElf(&efile, ".debug_info")) orelse return error.MissingDebugInfo, 992 .debug_abbrev = (try findDwarfSectionFromElf(&efile, ".debug_abbrev")) orelse return error.MissingDebugInfo, 993 .debug_str = (try findDwarfSectionFromElf(&efile, ".debug_str")) orelse return error.MissingDebugInfo, 994 .debug_line = (try findDwarfSectionFromElf(&efile, ".debug_line")) orelse return error.MissingDebugInfo, 995 .debug_ranges = (try findDwarfSectionFromElf(&efile, ".debug_ranges")), 996 .abbrev_table_list = undefined, 997 .compile_unit_list = undefined, 998 .func_list = undefined, 999 }; 1000 try openDwarfDebugInfo(&di, allocator); 1001 return di; 1002 } 1003 1004 fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DwarfInfo { 1005 const S = struct { 1006 var self_exe_file: os.File = undefined; 1007 var self_exe_mmap_seekable: io.SliceSeekableInStream = undefined; 1008 }; 1009 1010 S.self_exe_file = try os.openSelfExe(); 1011 errdefer S.self_exe_file.close(); 1012 1013 const self_exe_mmap_len = try S.self_exe_file.getEndPos(); 1014 const self_exe_mmap = os.posix.mmap( 1015 null, 1016 self_exe_mmap_len, 1017 os.posix.PROT_READ, 1018 os.posix.MAP_SHARED, 1019 S.self_exe_file.handle, 1020 0, 1021 ); 1022 if (self_exe_mmap == os.posix.MAP_FAILED) return error.OutOfMemory; 1023 errdefer assert(os.posix.munmap(self_exe_mmap, self_exe_mmap_len) == 0); 1024 1025 const file_mmap_slice = @intToPtr([*]const u8, self_exe_mmap)[0..self_exe_mmap_len]; 1026 S.self_exe_mmap_seekable = io.SliceSeekableInStream.init(file_mmap_slice); 1027 1028 return openElfDebugInfo( 1029 allocator, 1030 // TODO https://github.com/ziglang/zig/issues/764 1031 @ptrCast(*DwarfSeekableStream, &S.self_exe_mmap_seekable.seekable_stream), 1032 // TODO https://github.com/ziglang/zig/issues/764 1033 @ptrCast(*DwarfInStream, &S.self_exe_mmap_seekable.stream), 1034 ); 1035 } 1036 1037 fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo { 1038 const hdr = &std.c._mh_execute_header; 1039 assert(hdr.magic == std.macho.MH_MAGIC_64); 1040 1041 const hdr_base = @ptrCast([*]u8, hdr); 1042 var ptr = hdr_base + @sizeOf(macho.mach_header_64); 1043 var ncmd: u32 = hdr.ncmds; 1044 const symtab = while (ncmd != 0) : (ncmd -= 1) { 1045 const lc = @ptrCast(*std.macho.load_command, ptr); 1046 switch (lc.cmd) { 1047 std.macho.LC_SYMTAB => break @ptrCast(*std.macho.symtab_command, ptr), 1048 else => {}, 1049 } 1050 ptr += lc.cmdsize; // TODO https://github.com/ziglang/zig/issues/1403 1051 } else { 1052 return error.MissingDebugInfo; 1053 }; 1054 const syms = @ptrCast([*]macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms]; 1055 const strings = @ptrCast([*]u8, hdr_base + symtab.stroff)[0..symtab.strsize]; 1056 1057 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); 1058 1059 var ofile: ?*macho.nlist_64 = null; 1060 var reloc: u64 = 0; 1061 var symbol_index: usize = 0; 1062 var last_len: u64 = 0; 1063 for (syms) |*sym| { 1064 if (sym.n_type & std.macho.N_STAB != 0) { 1065 switch (sym.n_type) { 1066 std.macho.N_OSO => { 1067 ofile = sym; 1068 reloc = 0; 1069 }, 1070 std.macho.N_FUN => { 1071 if (sym.n_sect == 0) { 1072 last_len = sym.n_value; 1073 } else { 1074 symbols_buf[symbol_index] = MachoSymbol{ 1075 .nlist = sym, 1076 .ofile = ofile, 1077 .reloc = reloc, 1078 }; 1079 symbol_index += 1; 1080 } 1081 }, 1082 std.macho.N_BNSYM => { 1083 if (reloc == 0) { 1084 reloc = sym.n_value; 1085 } 1086 }, 1087 else => continue, 1088 } 1089 } 1090 } 1091 const sentinel = try allocator.create(macho.nlist_64); 1092 sentinel.* = macho.nlist_64{ 1093 .n_strx = 0, 1094 .n_type = 36, 1095 .n_sect = 0, 1096 .n_desc = 0, 1097 .n_value = symbols_buf[symbol_index - 1].nlist.n_value + last_len, 1098 }; 1099 1100 const symbols = allocator.shrink(symbols_buf, symbol_index); 1101 1102 // Even though lld emits symbols in ascending order, this debug code 1103 // should work for programs linked in any valid way. 1104 // This sort is so that we can binary search later. 1105 std.sort.sort(MachoSymbol, symbols, MachoSymbol.addressLessThan); 1106 1107 return DebugInfo{ 1108 .ofiles = DebugInfo.OFileTable.init(allocator), 1109 .symbols = symbols, 1110 .strings = strings, 1111 }; 1112 } 1113 1114 fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { 1115 var f = try os.File.openRead(line_info.file_name); 1116 defer f.close(); 1117 // TODO fstat and make sure that the file has the correct size 1118 1119 var buf: [os.page_size]u8 = undefined; 1120 var line: usize = 1; 1121 var column: usize = 1; 1122 var abs_index: usize = 0; 1123 while (true) { 1124 const amt_read = try f.read(buf[0..]); 1125 const slice = buf[0..amt_read]; 1126 1127 for (slice) |byte| { 1128 if (line == line_info.line) { 1129 try out_stream.writeByte(byte); 1130 if (byte == '\n') { 1131 return; 1132 } 1133 } 1134 if (byte == '\n') { 1135 line += 1; 1136 column = 1; 1137 } else { 1138 column += 1; 1139 } 1140 } 1141 1142 if (amt_read < buf.len) return error.EndOfFile; 1143 } 1144 } 1145 1146 const MachoSymbol = struct { 1147 nlist: *macho.nlist_64, 1148 ofile: ?*macho.nlist_64, 1149 reloc: u64, 1150 1151 /// Returns the address from the macho file 1152 fn address(self: MachoSymbol) u64 { 1153 return self.nlist.n_value; 1154 } 1155 1156 fn addressLessThan(lhs: MachoSymbol, rhs: MachoSymbol) bool { 1157 return lhs.address() < rhs.address(); 1158 } 1159 }; 1160 1161 const MachOFile = struct { 1162 bytes: []align(@alignOf(macho.mach_header_64)) const u8, 1163 sect_debug_info: ?*const macho.section_64, 1164 sect_debug_line: ?*const macho.section_64, 1165 }; 1166 1167 pub const DwarfSeekableStream = io.SeekableStream(anyerror, anyerror); 1168 pub const DwarfInStream = io.InStream(anyerror); 1169 1170 pub const DwarfInfo = struct { 1171 dwarf_seekable_stream: *DwarfSeekableStream, 1172 dwarf_in_stream: *DwarfInStream, 1173 endian: builtin.Endian, 1174 debug_info: Section, 1175 debug_abbrev: Section, 1176 debug_str: Section, 1177 debug_line: Section, 1178 debug_ranges: ?Section, 1179 abbrev_table_list: ArrayList(AbbrevTableHeader), 1180 compile_unit_list: ArrayList(CompileUnit), 1181 func_list: ArrayList(Func), 1182 1183 pub const Section = struct { 1184 offset: u64, 1185 size: u64, 1186 }; 1187 1188 pub fn allocator(self: DwarfInfo) *mem.Allocator { 1189 return self.abbrev_table_list.allocator; 1190 } 1191 1192 pub fn readString(self: *DwarfInfo) ![]u8 { 1193 return readStringRaw(self.allocator(), self.dwarf_in_stream); 1194 } 1195 }; 1196 1197 pub const DebugInfo = switch (builtin.os) { 1198 builtin.Os.macosx, builtin.Os.ios => struct { 1199 symbols: []const MachoSymbol, 1200 strings: []const u8, 1201 ofiles: OFileTable, 1202 1203 const OFileTable = std.HashMap( 1204 *macho.nlist_64, 1205 MachOFile, 1206 std.hash_map.getHashPtrAddrFn(*macho.nlist_64), 1207 std.hash_map.getTrivialEqlFn(*macho.nlist_64), 1208 ); 1209 1210 pub fn allocator(self: DebugInfo) *mem.Allocator { 1211 return self.ofiles.allocator; 1212 } 1213 }, 1214 builtin.Os.uefi, builtin.Os.windows => struct { 1215 pdb: pdb.Pdb, 1216 coff: *coff.Coff, 1217 sect_contribs: []pdb.SectionContribEntry, 1218 modules: []Module, 1219 }, 1220 builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => DwarfInfo, 1221 else => @compileError("Unsupported OS"), 1222 }; 1223 1224 const PcRange = struct { 1225 start: u64, 1226 end: u64, 1227 }; 1228 1229 const CompileUnit = struct { 1230 version: u16, 1231 is_64: bool, 1232 die: *Die, 1233 pc_range: ?PcRange, 1234 }; 1235 1236 const AbbrevTable = ArrayList(AbbrevTableEntry); 1237 1238 const AbbrevTableHeader = struct { 1239 // offset from .debug_abbrev 1240 offset: u64, 1241 table: AbbrevTable, 1242 }; 1243 1244 const AbbrevTableEntry = struct { 1245 has_children: bool, 1246 abbrev_code: u64, 1247 tag_id: u64, 1248 attrs: ArrayList(AbbrevAttr), 1249 }; 1250 1251 const AbbrevAttr = struct { 1252 attr_id: u64, 1253 form_id: u64, 1254 }; 1255 1256 const FormValue = union(enum) { 1257 Address: u64, 1258 Block: []u8, 1259 Const: Constant, 1260 ExprLoc: []u8, 1261 Flag: bool, 1262 SecOffset: u64, 1263 Ref: u64, 1264 RefAddr: u64, 1265 String: []u8, 1266 StrPtr: u64, 1267 }; 1268 1269 const Constant = struct { 1270 payload: u64, 1271 signed: bool, 1272 1273 fn asUnsignedLe(self: *const Constant) !u64 { 1274 if (self.signed) return error.InvalidDebugInfo; 1275 return self.payload; 1276 } 1277 }; 1278 1279 const Die = struct { 1280 tag_id: u64, 1281 has_children: bool, 1282 attrs: ArrayList(Attr), 1283 1284 const Attr = struct { 1285 id: u64, 1286 value: FormValue, 1287 }; 1288 1289 fn getAttr(self: *const Die, id: u64) ?*const FormValue { 1290 for (self.attrs.toSliceConst()) |*attr| { 1291 if (attr.id == id) return &attr.value; 1292 } 1293 return null; 1294 } 1295 1296 fn getAttrAddr(self: *const Die, id: u64) !u64 { 1297 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; 1298 return switch (form_value.*) { 1299 FormValue.Address => |value| value, 1300 else => error.InvalidDebugInfo, 1301 }; 1302 } 1303 1304 fn getAttrSecOffset(self: *const Die, id: u64) !u64 { 1305 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; 1306 return switch (form_value.*) { 1307 FormValue.Const => |value| value.asUnsignedLe(), 1308 FormValue.SecOffset => |value| value, 1309 else => error.InvalidDebugInfo, 1310 }; 1311 } 1312 1313 fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { 1314 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; 1315 return switch (form_value.*) { 1316 FormValue.Const => |value| value.asUnsignedLe(), 1317 else => error.InvalidDebugInfo, 1318 }; 1319 } 1320 1321 fn getAttrRef(self: *const Die, id: u64) !u64 { 1322 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; 1323 return switch (form_value.*) { 1324 FormValue.Ref => |value| value, 1325 else => error.InvalidDebugInfo, 1326 }; 1327 } 1328 1329 fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64) ![]u8 { 1330 const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; 1331 return switch (form_value.*) { 1332 FormValue.String => |value| value, 1333 FormValue.StrPtr => |offset| getString(di, offset), 1334 else => error.InvalidDebugInfo, 1335 }; 1336 } 1337 }; 1338 1339 const FileEntry = struct { 1340 file_name: []const u8, 1341 dir_index: usize, 1342 mtime: usize, 1343 len_bytes: usize, 1344 }; 1345 1346 pub const LineInfo = struct { 1347 line: u64, 1348 column: u64, 1349 file_name: []const u8, 1350 allocator: ?*mem.Allocator, 1351 1352 fn deinit(self: LineInfo) void { 1353 const allocator = self.allocator orelse return; 1354 allocator.free(self.file_name); 1355 } 1356 }; 1357 1358 const LineNumberProgram = struct { 1359 address: usize, 1360 file: usize, 1361 line: i64, 1362 column: u64, 1363 is_stmt: bool, 1364 basic_block: bool, 1365 end_sequence: bool, 1366 1367 target_address: usize, 1368 include_dirs: []const []const u8, 1369 file_entries: *ArrayList(FileEntry), 1370 1371 prev_address: usize, 1372 prev_file: usize, 1373 prev_line: i64, 1374 prev_column: u64, 1375 prev_is_stmt: bool, 1376 prev_basic_block: bool, 1377 prev_end_sequence: bool, 1378 1379 pub fn init(is_stmt: bool, include_dirs: []const []const u8, file_entries: *ArrayList(FileEntry), target_address: usize) LineNumberProgram { 1380 return LineNumberProgram{ 1381 .address = 0, 1382 .file = 1, 1383 .line = 1, 1384 .column = 0, 1385 .is_stmt = is_stmt, 1386 .basic_block = false, 1387 .end_sequence = false, 1388 .include_dirs = include_dirs, 1389 .file_entries = file_entries, 1390 .target_address = target_address, 1391 .prev_address = 0, 1392 .prev_file = undefined, 1393 .prev_line = undefined, 1394 .prev_column = undefined, 1395 .prev_is_stmt = undefined, 1396 .prev_basic_block = undefined, 1397 .prev_end_sequence = undefined, 1398 }; 1399 } 1400 1401 pub fn checkLineMatch(self: *LineNumberProgram) !?LineInfo { 1402 if (self.target_address >= self.prev_address and self.target_address < self.address) { 1403 const file_entry = if (self.prev_file == 0) { 1404 return error.MissingDebugInfo; 1405 } else if (self.prev_file - 1 >= self.file_entries.len) { 1406 return error.InvalidDebugInfo; 1407 } else 1408 &self.file_entries.items[self.prev_file - 1]; 1409 1410 const dir_name = if (file_entry.dir_index >= self.include_dirs.len) { 1411 return error.InvalidDebugInfo; 1412 } else 1413 self.include_dirs[file_entry.dir_index]; 1414 const file_name = try os.path.join(self.file_entries.allocator, [][]const u8{ dir_name, file_entry.file_name }); 1415 errdefer self.file_entries.allocator.free(file_name); 1416 return LineInfo{ 1417 .line = if (self.prev_line >= 0) @intCast(u64, self.prev_line) else 0, 1418 .column = self.prev_column, 1419 .file_name = file_name, 1420 .allocator = self.file_entries.allocator, 1421 }; 1422 } 1423 1424 self.prev_address = self.address; 1425 self.prev_file = self.file; 1426 self.prev_line = self.line; 1427 self.prev_column = self.column; 1428 self.prev_is_stmt = self.is_stmt; 1429 self.prev_basic_block = self.basic_block; 1430 self.prev_end_sequence = self.end_sequence; 1431 return null; 1432 } 1433 }; 1434 1435 fn readStringRaw(allocator: *mem.Allocator, in_stream: var) ![]u8 { 1436 var buf = ArrayList(u8).init(allocator); 1437 while (true) { 1438 const byte = try in_stream.readByte(); 1439 if (byte == 0) break; 1440 try buf.append(byte); 1441 } 1442 return buf.toSlice(); 1443 } 1444 1445 fn getString(di: *DwarfInfo, offset: u64) ![]u8 { 1446 const pos = di.debug_str.offset + offset; 1447 try di.dwarf_seekable_stream.seekTo(pos); 1448 return di.readString(); 1449 } 1450 1451 fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 { 1452 const buf = try allocator.alloc(u8, size); 1453 errdefer allocator.free(buf); 1454 if ((try in_stream.read(buf)) < size) return error.EndOfFile; 1455 return buf; 1456 } 1457 1458 fn parseFormValueBlockLen(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { 1459 const buf = try readAllocBytes(allocator, in_stream, size); 1460 return FormValue{ .Block = buf }; 1461 } 1462 1463 fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { 1464 const block_len = try in_stream.readVarInt(usize, builtin.Endian.Little, size); 1465 return parseFormValueBlockLen(allocator, in_stream, block_len); 1466 } 1467 1468 fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, comptime size: i32) !FormValue { 1469 return FormValue{ 1470 .Const = Constant{ 1471 .signed = signed, 1472 .payload = switch (size) { 1473 1 => try in_stream.readIntLittle(u8), 1474 2 => try in_stream.readIntLittle(u16), 1475 4 => try in_stream.readIntLittle(u32), 1476 8 => try in_stream.readIntLittle(u64), 1477 -1 => if (signed) @bitCast(u64, try leb.readILEB128(i64, in_stream)) else try leb.readULEB128(u64, in_stream), 1478 else => @compileError("Invalid size"), 1479 }, 1480 }, 1481 }; 1482 } 1483 1484 fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 { 1485 return if (is_64) try in_stream.readIntLittle(u64) else u64(try in_stream.readIntLittle(u32)); 1486 } 1487 1488 fn parseFormValueTargetAddrSize(in_stream: var) !u64 { 1489 return if (@sizeOf(usize) == 4) u64(try in_stream.readIntLittle(u32)) else if (@sizeOf(usize) == 8) try in_stream.readIntLittle(u64) else unreachable; 1490 } 1491 1492 fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, size: i32) !FormValue { 1493 return FormValue{ 1494 .Ref = switch (size) { 1495 1 => try in_stream.readIntLittle(u8), 1496 2 => try in_stream.readIntLittle(u16), 1497 4 => try in_stream.readIntLittle(u32), 1498 8 => try in_stream.readIntLittle(u64), 1499 -1 => try leb.readULEB128(u64, in_stream), 1500 else => unreachable, 1501 }, 1502 }; 1503 } 1504 1505 fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) anyerror!FormValue { 1506 return switch (form_id) { 1507 DW.FORM_addr => FormValue{ .Address = try parseFormValueTargetAddrSize(in_stream) }, 1508 DW.FORM_block1 => parseFormValueBlock(allocator, in_stream, 1), 1509 DW.FORM_block2 => parseFormValueBlock(allocator, in_stream, 2), 1510 DW.FORM_block4 => parseFormValueBlock(allocator, in_stream, 4), 1511 DW.FORM_block => x: { 1512 const block_len = try leb.readULEB128(usize, in_stream); 1513 return parseFormValueBlockLen(allocator, in_stream, block_len); 1514 }, 1515 DW.FORM_data1 => parseFormValueConstant(allocator, in_stream, false, 1), 1516 DW.FORM_data2 => parseFormValueConstant(allocator, in_stream, false, 2), 1517 DW.FORM_data4 => parseFormValueConstant(allocator, in_stream, false, 4), 1518 DW.FORM_data8 => parseFormValueConstant(allocator, in_stream, false, 8), 1519 DW.FORM_udata, DW.FORM_sdata => { 1520 const signed = form_id == DW.FORM_sdata; 1521 return parseFormValueConstant(allocator, in_stream, signed, -1); 1522 }, 1523 DW.FORM_exprloc => { 1524 const size = try leb.readULEB128(usize, in_stream); 1525 const buf = try readAllocBytes(allocator, in_stream, size); 1526 return FormValue{ .ExprLoc = buf }; 1527 }, 1528 DW.FORM_flag => FormValue{ .Flag = (try in_stream.readByte()) != 0 }, 1529 DW.FORM_flag_present => FormValue{ .Flag = true }, 1530 DW.FORM_sec_offset => FormValue{ .SecOffset = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, 1531 1532 DW.FORM_ref1 => parseFormValueRef(allocator, in_stream, 1), 1533 DW.FORM_ref2 => parseFormValueRef(allocator, in_stream, 2), 1534 DW.FORM_ref4 => parseFormValueRef(allocator, in_stream, 4), 1535 DW.FORM_ref8 => parseFormValueRef(allocator, in_stream, 8), 1536 DW.FORM_ref_udata => parseFormValueRef(allocator, in_stream, -1), 1537 1538 DW.FORM_ref_addr => FormValue{ .RefAddr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, 1539 DW.FORM_ref_sig8 => FormValue{ .Ref = try in_stream.readIntLittle(u64) }, 1540 1541 DW.FORM_string => FormValue{ .String = try readStringRaw(allocator, in_stream) }, 1542 DW.FORM_strp => FormValue{ .StrPtr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, 1543 DW.FORM_indirect => { 1544 const child_form_id = try leb.readULEB128(u64, in_stream); 1545 return parseFormValue(allocator, in_stream, child_form_id, is_64); 1546 }, 1547 else => error.InvalidDebugInfo, 1548 }; 1549 } 1550 1551 fn parseAbbrevTable(di: *DwarfInfo) !AbbrevTable { 1552 var result = AbbrevTable.init(di.allocator()); 1553 while (true) { 1554 const abbrev_code = try leb.readULEB128(u64, di.dwarf_in_stream); 1555 if (abbrev_code == 0) return result; 1556 try result.append(AbbrevTableEntry{ 1557 .abbrev_code = abbrev_code, 1558 .tag_id = try leb.readULEB128(u64, di.dwarf_in_stream), 1559 .has_children = (try di.dwarf_in_stream.readByte()) == DW.CHILDREN_yes, 1560 .attrs = ArrayList(AbbrevAttr).init(di.allocator()), 1561 }); 1562 const attrs = &result.items[result.len - 1].attrs; 1563 1564 while (true) { 1565 const attr_id = try leb.readULEB128(u64, di.dwarf_in_stream); 1566 const form_id = try leb.readULEB128(u64, di.dwarf_in_stream); 1567 if (attr_id == 0 and form_id == 0) break; 1568 try attrs.append(AbbrevAttr{ 1569 .attr_id = attr_id, 1570 .form_id = form_id, 1571 }); 1572 } 1573 } 1574 } 1575 1576 /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, 1577 /// seeks in the stream and parses it. 1578 fn getAbbrevTable(di: *DwarfInfo, abbrev_offset: u64) !*const AbbrevTable { 1579 for (di.abbrev_table_list.toSlice()) |*header| { 1580 if (header.offset == abbrev_offset) { 1581 return &header.table; 1582 } 1583 } 1584 try di.dwarf_seekable_stream.seekTo(di.debug_abbrev.offset + abbrev_offset); 1585 try di.abbrev_table_list.append(AbbrevTableHeader{ 1586 .offset = abbrev_offset, 1587 .table = try parseAbbrevTable(di), 1588 }); 1589 return &di.abbrev_table_list.items[di.abbrev_table_list.len - 1].table; 1590 } 1591 1592 fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*const AbbrevTableEntry { 1593 for (abbrev_table.toSliceConst()) |*table_entry| { 1594 if (table_entry.abbrev_code == abbrev_code) return table_entry; 1595 } 1596 return null; 1597 } 1598 1599 fn parseDie1(di: *DwarfInfo, abbrev_table: *const AbbrevTable, is_64: bool) !?Die { 1600 const abbrev_code = try leb.readULEB128(u64, di.dwarf_in_stream); 1601 if (abbrev_code == 0) return null; 1602 const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; 1603 1604 var result = Die{ 1605 .tag_id = table_entry.tag_id, 1606 .has_children = table_entry.has_children, 1607 .attrs = ArrayList(Die.Attr).init(di.allocator()), 1608 }; 1609 try result.attrs.resize(table_entry.attrs.len); 1610 for (table_entry.attrs.toSliceConst()) |attr, i| { 1611 result.attrs.items[i] = Die.Attr{ 1612 .id = attr.attr_id, 1613 .value = try parseFormValue(di.allocator(), di.dwarf_in_stream, attr.form_id, is_64), 1614 }; 1615 } 1616 return result; 1617 } 1618 1619 fn parseDie(di: *DwarfInfo, abbrev_table: *const AbbrevTable, is_64: bool) !Die { 1620 const abbrev_code = try leb.readULEB128(u64, di.dwarf_in_stream); 1621 const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; 1622 1623 var result = Die{ 1624 .tag_id = table_entry.tag_id, 1625 .has_children = table_entry.has_children, 1626 .attrs = ArrayList(Die.Attr).init(di.allocator()), 1627 }; 1628 try result.attrs.resize(table_entry.attrs.len); 1629 for (table_entry.attrs.toSliceConst()) |attr, i| { 1630 result.attrs.items[i] = Die.Attr{ 1631 .id = attr.attr_id, 1632 .value = try parseFormValue(di.allocator(), di.dwarf_in_stream, attr.form_id, is_64), 1633 }; 1634 } 1635 return result; 1636 } 1637 1638 fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: usize) !LineInfo { 1639 const ofile = symbol.ofile orelse return error.MissingDebugInfo; 1640 const gop = try di.ofiles.getOrPut(ofile); 1641 const mach_o_file = if (gop.found_existing) &gop.kv.value else blk: { 1642 errdefer _ = di.ofiles.remove(ofile); 1643 const ofile_path = mem.toSliceConst(u8, di.strings.ptr + ofile.n_strx); 1644 1645 gop.kv.value = MachOFile{ 1646 .bytes = try std.io.readFileAllocAligned(di.ofiles.allocator, ofile_path, @alignOf(macho.mach_header_64)), 1647 .sect_debug_info = null, 1648 .sect_debug_line = null, 1649 }; 1650 const hdr = @ptrCast(*const macho.mach_header_64, gop.kv.value.bytes.ptr); 1651 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo; 1652 1653 const hdr_base = @ptrCast([*]const u8, hdr); 1654 var ptr = hdr_base + @sizeOf(macho.mach_header_64); 1655 var ncmd: u32 = hdr.ncmds; 1656 const segcmd = while (ncmd != 0) : (ncmd -= 1) { 1657 const lc = @ptrCast(*const std.macho.load_command, ptr); 1658 switch (lc.cmd) { 1659 std.macho.LC_SEGMENT_64 => break @ptrCast(*const std.macho.segment_command_64, @alignCast(@alignOf(std.macho.segment_command_64), ptr)), 1660 else => {}, 1661 } 1662 ptr += lc.cmdsize; // TODO https://github.com/ziglang/zig/issues/1403 1663 } else { 1664 return error.MissingDebugInfo; 1665 }; 1666 const sections = @ptrCast([*]const macho.section_64, @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)))[0..segcmd.nsects]; 1667 for (sections) |*sect| { 1668 if (sect.flags & macho.SECTION_TYPE == macho.S_REGULAR and 1669 (sect.flags & macho.SECTION_ATTRIBUTES) & macho.S_ATTR_DEBUG == macho.S_ATTR_DEBUG) 1670 { 1671 const sect_name = mem.toSliceConst(u8, §.sectname); 1672 if (mem.eql(u8, sect_name, "__debug_line")) { 1673 gop.kv.value.sect_debug_line = sect; 1674 } else if (mem.eql(u8, sect_name, "__debug_info")) { 1675 gop.kv.value.sect_debug_info = sect; 1676 } 1677 } 1678 } 1679 1680 break :blk &gop.kv.value; 1681 }; 1682 1683 const sect_debug_line = mach_o_file.sect_debug_line orelse return error.MissingDebugInfo; 1684 var ptr = mach_o_file.bytes.ptr + sect_debug_line.offset; 1685 1686 var is_64: bool = undefined; 1687 const unit_length = try readInitialLengthMem(&ptr, &is_64); 1688 if (unit_length == 0) return error.MissingDebugInfo; 1689 1690 const version = readIntMem(&ptr, u16, builtin.Endian.Little); 1691 // TODO support 3 and 5 1692 if (version != 2 and version != 4) return error.InvalidDebugInfo; 1693 1694 const prologue_length = if (is_64) 1695 readIntMem(&ptr, u64, builtin.Endian.Little) 1696 else 1697 readIntMem(&ptr, u32, builtin.Endian.Little); 1698 const prog_start = ptr + prologue_length; 1699 1700 const minimum_instruction_length = readByteMem(&ptr); 1701 if (minimum_instruction_length == 0) return error.InvalidDebugInfo; 1702 1703 if (version >= 4) { 1704 // maximum_operations_per_instruction 1705 ptr += 1; 1706 } 1707 1708 const default_is_stmt = readByteMem(&ptr) != 0; 1709 const line_base = readByteSignedMem(&ptr); 1710 1711 const line_range = readByteMem(&ptr); 1712 if (line_range == 0) return error.InvalidDebugInfo; 1713 1714 const opcode_base = readByteMem(&ptr); 1715 1716 const standard_opcode_lengths = ptr[0 .. opcode_base - 1]; 1717 ptr += opcode_base - 1; 1718 1719 var include_directories = ArrayList([]const u8).init(di.allocator()); 1720 try include_directories.append(""); 1721 while (true) { 1722 const dir = readStringMem(&ptr); 1723 if (dir.len == 0) break; 1724 try include_directories.append(dir); 1725 } 1726 1727 var file_entries = ArrayList(FileEntry).init(di.allocator()); 1728 var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address); 1729 1730 while (true) { 1731 const file_name = readStringMem(&ptr); 1732 if (file_name.len == 0) break; 1733 const dir_index = try leb.readULEB128Mem(usize, &ptr); 1734 const mtime = try leb.readULEB128Mem(usize, &ptr); 1735 const len_bytes = try leb.readULEB128Mem(usize, &ptr); 1736 try file_entries.append(FileEntry{ 1737 .file_name = file_name, 1738 .dir_index = dir_index, 1739 .mtime = mtime, 1740 .len_bytes = len_bytes, 1741 }); 1742 } 1743 1744 ptr = prog_start; 1745 while (true) { 1746 const opcode = readByteMem(&ptr); 1747 1748 if (opcode == DW.LNS_extended_op) { 1749 const op_size = try leb.readULEB128Mem(u64, &ptr); 1750 if (op_size < 1) return error.InvalidDebugInfo; 1751 var sub_op = readByteMem(&ptr); 1752 switch (sub_op) { 1753 DW.LNE_end_sequence => { 1754 prog.end_sequence = true; 1755 if (try prog.checkLineMatch()) |info| return info; 1756 return error.MissingDebugInfo; 1757 }, 1758 DW.LNE_set_address => { 1759 const addr = readIntMem(&ptr, usize, builtin.Endian.Little); 1760 prog.address = symbol.reloc + addr; 1761 }, 1762 DW.LNE_define_file => { 1763 const file_name = readStringMem(&ptr); 1764 const dir_index = try leb.readULEB128Mem(usize, &ptr); 1765 const mtime = try leb.readULEB128Mem(usize, &ptr); 1766 const len_bytes = try leb.readULEB128Mem(usize, &ptr); 1767 try file_entries.append(FileEntry{ 1768 .file_name = file_name, 1769 .dir_index = dir_index, 1770 .mtime = mtime, 1771 .len_bytes = len_bytes, 1772 }); 1773 }, 1774 else => { 1775 ptr += op_size - 1; 1776 }, 1777 } 1778 } else if (opcode >= opcode_base) { 1779 // special opcodes 1780 const adjusted_opcode = opcode - opcode_base; 1781 const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); 1782 const inc_line = i32(line_base) + i32(adjusted_opcode % line_range); 1783 prog.line += inc_line; 1784 prog.address += inc_addr; 1785 if (try prog.checkLineMatch()) |info| return info; 1786 prog.basic_block = false; 1787 } else { 1788 switch (opcode) { 1789 DW.LNS_copy => { 1790 if (try prog.checkLineMatch()) |info| return info; 1791 prog.basic_block = false; 1792 }, 1793 DW.LNS_advance_pc => { 1794 const arg = try leb.readULEB128Mem(usize, &ptr); 1795 prog.address += arg * minimum_instruction_length; 1796 }, 1797 DW.LNS_advance_line => { 1798 const arg = try leb.readILEB128Mem(i64, &ptr); 1799 prog.line += arg; 1800 }, 1801 DW.LNS_set_file => { 1802 const arg = try leb.readULEB128Mem(usize, &ptr); 1803 prog.file = arg; 1804 }, 1805 DW.LNS_set_column => { 1806 const arg = try leb.readULEB128Mem(u64, &ptr); 1807 prog.column = arg; 1808 }, 1809 DW.LNS_negate_stmt => { 1810 prog.is_stmt = !prog.is_stmt; 1811 }, 1812 DW.LNS_set_basic_block => { 1813 prog.basic_block = true; 1814 }, 1815 DW.LNS_const_add_pc => { 1816 const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); 1817 prog.address += inc_addr; 1818 }, 1819 DW.LNS_fixed_advance_pc => { 1820 const arg = readIntMem(&ptr, u16, builtin.Endian.Little); 1821 prog.address += arg; 1822 }, 1823 DW.LNS_set_prologue_end => {}, 1824 else => { 1825 if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; 1826 const len_bytes = standard_opcode_lengths[opcode - 1]; 1827 ptr += len_bytes; 1828 }, 1829 } 1830 } 1831 } 1832 1833 return error.MissingDebugInfo; 1834 } 1835 1836 fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !LineInfo { 1837 const compile_unit_cwd = try compile_unit.die.getAttrString(di, DW.AT_comp_dir); 1838 const line_info_offset = try compile_unit.die.getAttrSecOffset(DW.AT_stmt_list); 1839 1840 assert(line_info_offset < di.debug_line.size); 1841 1842 try di.dwarf_seekable_stream.seekTo(di.debug_line.offset + line_info_offset); 1843 1844 var is_64: bool = undefined; 1845 const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64); 1846 if (unit_length == 0) { 1847 return error.MissingDebugInfo; 1848 } 1849 const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); 1850 1851 const version = try di.dwarf_in_stream.readInt(u16, di.endian); 1852 // TODO support 3 and 5 1853 if (version != 2 and version != 4) return error.InvalidDebugInfo; 1854 1855 const prologue_length = if (is_64) try di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian); 1856 const prog_start_offset = (try di.dwarf_seekable_stream.getPos()) + prologue_length; 1857 1858 const minimum_instruction_length = try di.dwarf_in_stream.readByte(); 1859 if (minimum_instruction_length == 0) return error.InvalidDebugInfo; 1860 1861 if (version >= 4) { 1862 // maximum_operations_per_instruction 1863 _ = try di.dwarf_in_stream.readByte(); 1864 } 1865 1866 const default_is_stmt = (try di.dwarf_in_stream.readByte()) != 0; 1867 const line_base = try di.dwarf_in_stream.readByteSigned(); 1868 1869 const line_range = try di.dwarf_in_stream.readByte(); 1870 if (line_range == 0) return error.InvalidDebugInfo; 1871 1872 const opcode_base = try di.dwarf_in_stream.readByte(); 1873 1874 const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1); 1875 1876 { 1877 var i: usize = 0; 1878 while (i < opcode_base - 1) : (i += 1) { 1879 standard_opcode_lengths[i] = try di.dwarf_in_stream.readByte(); 1880 } 1881 } 1882 1883 var include_directories = ArrayList([]u8).init(di.allocator()); 1884 try include_directories.append(compile_unit_cwd); 1885 while (true) { 1886 const dir = try di.readString(); 1887 if (dir.len == 0) break; 1888 try include_directories.append(dir); 1889 } 1890 1891 var file_entries = ArrayList(FileEntry).init(di.allocator()); 1892 var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address); 1893 1894 while (true) { 1895 const file_name = try di.readString(); 1896 if (file_name.len == 0) break; 1897 const dir_index = try leb.readULEB128(usize, di.dwarf_in_stream); 1898 const mtime = try leb.readULEB128(usize, di.dwarf_in_stream); 1899 const len_bytes = try leb.readULEB128(usize, di.dwarf_in_stream); 1900 try file_entries.append(FileEntry{ 1901 .file_name = file_name, 1902 .dir_index = dir_index, 1903 .mtime = mtime, 1904 .len_bytes = len_bytes, 1905 }); 1906 } 1907 1908 try di.dwarf_seekable_stream.seekTo(prog_start_offset); 1909 1910 while (true) { 1911 const opcode = try di.dwarf_in_stream.readByte(); 1912 1913 if (opcode == DW.LNS_extended_op) { 1914 const op_size = try leb.readULEB128(u64, di.dwarf_in_stream); 1915 if (op_size < 1) return error.InvalidDebugInfo; 1916 var sub_op = try di.dwarf_in_stream.readByte(); 1917 switch (sub_op) { 1918 DW.LNE_end_sequence => { 1919 prog.end_sequence = true; 1920 if (try prog.checkLineMatch()) |info| return info; 1921 return error.MissingDebugInfo; 1922 }, 1923 DW.LNE_set_address => { 1924 const addr = try di.dwarf_in_stream.readInt(usize, di.endian); 1925 prog.address = addr; 1926 }, 1927 DW.LNE_define_file => { 1928 const file_name = try di.readString(); 1929 const dir_index = try leb.readULEB128(usize, di.dwarf_in_stream); 1930 const mtime = try leb.readULEB128(usize, di.dwarf_in_stream); 1931 const len_bytes = try leb.readULEB128(usize, di.dwarf_in_stream); 1932 try file_entries.append(FileEntry{ 1933 .file_name = file_name, 1934 .dir_index = dir_index, 1935 .mtime = mtime, 1936 .len_bytes = len_bytes, 1937 }); 1938 }, 1939 else => { 1940 const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo; 1941 try di.dwarf_seekable_stream.seekForward(fwd_amt); 1942 }, 1943 } 1944 } else if (opcode >= opcode_base) { 1945 // special opcodes 1946 const adjusted_opcode = opcode - opcode_base; 1947 const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); 1948 const inc_line = i32(line_base) + i32(adjusted_opcode % line_range); 1949 prog.line += inc_line; 1950 prog.address += inc_addr; 1951 if (try prog.checkLineMatch()) |info| return info; 1952 prog.basic_block = false; 1953 } else { 1954 switch (opcode) { 1955 DW.LNS_copy => { 1956 if (try prog.checkLineMatch()) |info| return info; 1957 prog.basic_block = false; 1958 }, 1959 DW.LNS_advance_pc => { 1960 const arg = try leb.readULEB128(usize, di.dwarf_in_stream); 1961 prog.address += arg * minimum_instruction_length; 1962 }, 1963 DW.LNS_advance_line => { 1964 const arg = try leb.readILEB128(i64, di.dwarf_in_stream); 1965 prog.line += arg; 1966 }, 1967 DW.LNS_set_file => { 1968 const arg = try leb.readULEB128(usize, di.dwarf_in_stream); 1969 prog.file = arg; 1970 }, 1971 DW.LNS_set_column => { 1972 const arg = try leb.readULEB128(u64, di.dwarf_in_stream); 1973 prog.column = arg; 1974 }, 1975 DW.LNS_negate_stmt => { 1976 prog.is_stmt = !prog.is_stmt; 1977 }, 1978 DW.LNS_set_basic_block => { 1979 prog.basic_block = true; 1980 }, 1981 DW.LNS_const_add_pc => { 1982 const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); 1983 prog.address += inc_addr; 1984 }, 1985 DW.LNS_fixed_advance_pc => { 1986 const arg = try di.dwarf_in_stream.readInt(u16, di.endian); 1987 prog.address += arg; 1988 }, 1989 DW.LNS_set_prologue_end => {}, 1990 else => { 1991 if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; 1992 const len_bytes = standard_opcode_lengths[opcode - 1]; 1993 try di.dwarf_seekable_stream.seekForward(len_bytes); 1994 }, 1995 } 1996 } 1997 } 1998 1999 return error.MissingDebugInfo; 2000 } 2001 2002 const Func = struct { 2003 pc_range: ?PcRange, 2004 name: ?[]u8, 2005 }; 2006 2007 fn getSymbolNameDwarf(di: *DwarfInfo, address: u64) ?[]const u8 { 2008 for (di.func_list.toSliceConst()) |*func| { 2009 if (func.pc_range) |range| { 2010 if (address >= range.start and address < range.end) { 2011 return func.name; 2012 } 2013 } 2014 } 2015 2016 return null; 2017 } 2018 2019 fn scanAllFunctions(di: *DwarfInfo) !void { 2020 const debug_info_end = di.debug_info.offset + di.debug_info.size; 2021 var this_unit_offset = di.debug_info.offset; 2022 2023 while (this_unit_offset < debug_info_end) { 2024 try di.dwarf_seekable_stream.seekTo(this_unit_offset); 2025 2026 var is_64: bool = undefined; 2027 const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64); 2028 if (unit_length == 0) return; 2029 const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); 2030 2031 const version = try di.dwarf_in_stream.readInt(u16, di.endian); 2032 if (version < 2 or version > 5) return error.InvalidDebugInfo; 2033 2034 const debug_abbrev_offset = if (is_64) try di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian); 2035 2036 const address_size = try di.dwarf_in_stream.readByte(); 2037 if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; 2038 2039 const compile_unit_pos = try di.dwarf_seekable_stream.getPos(); 2040 const abbrev_table = try getAbbrevTable(di, debug_abbrev_offset); 2041 2042 try di.dwarf_seekable_stream.seekTo(compile_unit_pos); 2043 2044 const next_unit_pos = this_unit_offset + next_offset; 2045 2046 while ((try di.dwarf_seekable_stream.getPos()) < next_unit_pos) { 2047 const die_obj = (try parseDie1(di, abbrev_table, is_64)) orelse continue; 2048 const after_die_offset = try di.dwarf_seekable_stream.getPos(); 2049 2050 switch (die_obj.tag_id) { 2051 DW.TAG_subprogram, DW.TAG_inlined_subroutine, DW.TAG_subroutine, DW.TAG_entry_point => { 2052 const fn_name = x: { 2053 var depth: i32 = 3; 2054 var this_die_obj = die_obj; 2055 // Prenvent endless loops 2056 while (depth > 0) : (depth -= 1) { 2057 if (this_die_obj.getAttr(DW.AT_name)) |_| { 2058 const name = try this_die_obj.getAttrString(di, DW.AT_name); 2059 break :x name; 2060 } else if (this_die_obj.getAttr(DW.AT_abstract_origin)) |ref| { 2061 // Follow the DIE it points to and repeat 2062 const ref_offset = try this_die_obj.getAttrRef(DW.AT_abstract_origin); 2063 if (ref_offset > next_offset) return error.InvalidDebugInfo; 2064 try di.dwarf_seekable_stream.seekTo(this_unit_offset + ref_offset); 2065 this_die_obj = (try parseDie1(di, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; 2066 } else if (this_die_obj.getAttr(DW.AT_specification)) |ref| { 2067 // Follow the DIE it points to and repeat 2068 const ref_offset = try this_die_obj.getAttrRef(DW.AT_specification); 2069 if (ref_offset > next_offset) return error.InvalidDebugInfo; 2070 try di.dwarf_seekable_stream.seekTo(this_unit_offset + ref_offset); 2071 this_die_obj = (try parseDie1(di, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; 2072 } else { 2073 break :x null; 2074 } 2075 } 2076 2077 break :x null; 2078 }; 2079 2080 const pc_range = x: { 2081 if (die_obj.getAttrAddr(DW.AT_low_pc)) |low_pc| { 2082 if (die_obj.getAttr(DW.AT_high_pc)) |high_pc_value| { 2083 const pc_end = switch (high_pc_value.*) { 2084 FormValue.Address => |value| value, 2085 FormValue.Const => |value| b: { 2086 const offset = try value.asUnsignedLe(); 2087 break :b (low_pc + offset); 2088 }, 2089 else => return error.InvalidDebugInfo, 2090 }; 2091 break :x PcRange{ 2092 .start = low_pc, 2093 .end = pc_end, 2094 }; 2095 } else { 2096 break :x null; 2097 } 2098 } else |err| { 2099 if (err != error.MissingDebugInfo) return err; 2100 break :x null; 2101 } 2102 }; 2103 2104 try di.func_list.append(Func{ 2105 .name = fn_name, 2106 .pc_range = pc_range, 2107 }); 2108 }, 2109 else => { 2110 continue; 2111 }, 2112 } 2113 2114 try di.dwarf_seekable_stream.seekTo(after_die_offset); 2115 } 2116 2117 this_unit_offset += next_offset; 2118 } 2119 } 2120 2121 fn scanAllCompileUnits(di: *DwarfInfo) !void { 2122 const debug_info_end = di.debug_info.offset + di.debug_info.size; 2123 var this_unit_offset = di.debug_info.offset; 2124 2125 while (this_unit_offset < debug_info_end) { 2126 try di.dwarf_seekable_stream.seekTo(this_unit_offset); 2127 2128 var is_64: bool = undefined; 2129 const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64); 2130 if (unit_length == 0) return; 2131 const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); 2132 2133 const version = try di.dwarf_in_stream.readInt(u16, di.endian); 2134 if (version < 2 or version > 5) return error.InvalidDebugInfo; 2135 2136 const debug_abbrev_offset = if (is_64) try di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian); 2137 2138 const address_size = try di.dwarf_in_stream.readByte(); 2139 if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; 2140 2141 const compile_unit_pos = try di.dwarf_seekable_stream.getPos(); 2142 const abbrev_table = try getAbbrevTable(di, debug_abbrev_offset); 2143 2144 try di.dwarf_seekable_stream.seekTo(compile_unit_pos); 2145 2146 const compile_unit_die = try di.allocator().create(Die); 2147 compile_unit_die.* = try parseDie(di, abbrev_table, is_64); 2148 2149 if (compile_unit_die.tag_id != DW.TAG_compile_unit) return error.InvalidDebugInfo; 2150 2151 const pc_range = x: { 2152 if (compile_unit_die.getAttrAddr(DW.AT_low_pc)) |low_pc| { 2153 if (compile_unit_die.getAttr(DW.AT_high_pc)) |high_pc_value| { 2154 const pc_end = switch (high_pc_value.*) { 2155 FormValue.Address => |value| value, 2156 FormValue.Const => |value| b: { 2157 const offset = try value.asUnsignedLe(); 2158 break :b (low_pc + offset); 2159 }, 2160 else => return error.InvalidDebugInfo, 2161 }; 2162 break :x PcRange{ 2163 .start = low_pc, 2164 .end = pc_end, 2165 }; 2166 } else { 2167 break :x null; 2168 } 2169 } else |err| { 2170 if (err != error.MissingDebugInfo) return err; 2171 break :x null; 2172 } 2173 }; 2174 2175 try di.compile_unit_list.append(CompileUnit{ 2176 .version = version, 2177 .is_64 = is_64, 2178 .pc_range = pc_range, 2179 .die = compile_unit_die, 2180 }); 2181 2182 this_unit_offset += next_offset; 2183 } 2184 } 2185 2186 fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit { 2187 for (di.compile_unit_list.toSlice()) |*compile_unit| { 2188 if (compile_unit.pc_range) |range| { 2189 if (target_address >= range.start and target_address < range.end) return compile_unit; 2190 } 2191 if (compile_unit.die.getAttrSecOffset(DW.AT_ranges)) |ranges_offset| { 2192 var base_address: usize = 0; 2193 if (di.debug_ranges) |debug_ranges| { 2194 try di.dwarf_seekable_stream.seekTo(debug_ranges.offset + ranges_offset); 2195 while (true) { 2196 const begin_addr = try di.dwarf_in_stream.readIntLittle(usize); 2197 const end_addr = try di.dwarf_in_stream.readIntLittle(usize); 2198 if (begin_addr == 0 and end_addr == 0) { 2199 break; 2200 } 2201 if (begin_addr == maxInt(usize)) { 2202 base_address = begin_addr; 2203 continue; 2204 } 2205 if (target_address >= begin_addr and target_address < end_addr) { 2206 return compile_unit; 2207 } 2208 } 2209 } 2210 } else |err| { 2211 if (err != error.MissingDebugInfo) return err; 2212 continue; 2213 } 2214 } 2215 return error.MissingDebugInfo; 2216 } 2217 2218 fn readIntMem(ptr: *[*]const u8, comptime T: type, endian: builtin.Endian) T { 2219 // TODO https://github.com/ziglang/zig/issues/863 2220 const result = mem.readIntSlice(T, ptr.*[0..@sizeOf(T)], endian); 2221 ptr.* += @sizeOf(T); 2222 return result; 2223 } 2224 2225 fn readByteMem(ptr: *[*]const u8) u8 { 2226 const result = ptr.*[0]; 2227 ptr.* += 1; 2228 return result; 2229 } 2230 2231 fn readByteSignedMem(ptr: *[*]const u8) i8 { 2232 return @bitCast(i8, readByteMem(ptr)); 2233 } 2234 2235 fn readInitialLengthMem(ptr: *[*]const u8, is_64: *bool) !u64 { 2236 // TODO this code can be improved with https://github.com/ziglang/zig/issues/863 2237 const first_32_bits = mem.readIntSliceLittle(u32, ptr.*[0..4]); 2238 is_64.* = (first_32_bits == 0xffffffff); 2239 if (is_64.*) { 2240 ptr.* += 4; 2241 const result = mem.readIntSliceLittle(u64, ptr.*[0..8]); 2242 ptr.* += 8; 2243 return result; 2244 } else { 2245 if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo; 2246 ptr.* += 4; 2247 return u64(first_32_bits); 2248 } 2249 } 2250 2251 fn readStringMem(ptr: *[*]const u8) []const u8 { 2252 const result = mem.toSliceConst(u8, ptr.*); 2253 ptr.* += result.len + 1; 2254 return result; 2255 } 2256 2257 fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) !u64 { 2258 const first_32_bits = try in_stream.readIntLittle(u32); 2259 is_64.* = (first_32_bits == 0xffffffff); 2260 if (is_64.*) { 2261 return in_stream.readIntLittle(u64); 2262 } else { 2263 if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo; 2264 return u64(first_32_bits); 2265 } 2266 } 2267 2268 /// This should only be used in temporary test programs. 2269 pub const global_allocator = &global_fixed_allocator.allocator; 2270 var global_fixed_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(global_allocator_mem[0..]); 2271 var global_allocator_mem: [100 * 1024]u8 = undefined; 2272 2273 /// TODO multithreaded awareness 2274 var debug_info_allocator: ?*mem.Allocator = null; 2275 var debug_info_direct_allocator: std.heap.DirectAllocator = undefined; 2276 var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; 2277 fn getDebugInfoAllocator() *mem.Allocator { 2278 if (debug_info_allocator) |a| return a; 2279 2280 debug_info_direct_allocator = std.heap.DirectAllocator.init(); 2281 debug_info_arena_allocator = std.heap.ArenaAllocator.init(&debug_info_direct_allocator.allocator); 2282 debug_info_allocator = &debug_info_arena_allocator.allocator; 2283 return &debug_info_arena_allocator.allocator; 2284 }