authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-01 16:31:03-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-01 22:11:35-07:00
log290966c2497dc9d212bf9d4bd0fecee4988091a5
treec71be0f5dbb8bc9a0008425a0f4c3cfba1d2a8e4
parentab0253f6620f5b87f06fdbddfc8876263c2a10a2

std.debug: rename Info to SelfInfo

This code has the hard-coded goal of supporting the executable's own debug information and makes design choices along that goal, such as memory-mapping the inputs, using dl_iterate_phdr, and doing conditional compilation on the host target. A more general-purpose implementation of debug information may be able to share code with this, but there are some fundamental incompatibilities. For example, the "SelfInfo" implementation wants to avoid bloating the binary with PDB on POSIX systems, and likewise DWARF on Windows systems, while a general-purpose implementation needs to support both PDB and DWARF from the same binary. It might, for example, inspect the debug information from a cross-compiled binary. `SourceLocation` now lives at `std.debug.SourceLocation` and is documented. Deprecate `std.debug.runtime_safety` because it returns the optimization mode of the standard library, when the caller probably wants to use the optimization mode of their own module. `std.pdb.Pdb` is moved to `std.debug.Pdb`, mirroring the recent extraction of `std.debug.Dwarf` from `std.dwarf`. I have no idea why we have both Module (with a Windows-specific definition) and WindowsModule. I left some passive aggressive doc comments to express my frustration.

6 files changed, 2009 insertions(+), 2000 deletions(-)

lib/std/debug.zig+33-24
......@@ -6,11 +6,6 @@ const io = std.io;
66const posix = std.posix;
77const fs = std.fs;
88const testing = std.testing;
9const elf = std.elf;
10const DW = std.dwarf;
11const macho = std.macho;
12const coff = std.coff;
13const pdb = std.pdb;
149const root = @import("root");
1510const File = std.fs.File;
1611const windows = std.os.windows;
......@@ -19,8 +14,22 @@ const native_os = builtin.os.tag;
1914const native_endian = native_arch.endian();
2015
2116pub const Dwarf = @import("debug/Dwarf.zig");
22pub const Info = @import("debug/Info.zig");
17pub const Pdb = @import("debug/Pdb.zig");
18pub const SelfInfo = @import("debug/SelfInfo.zig");
19
20/// Unresolved source locations can be represented with a single `usize` that
21/// corresponds to a virtual memory address of the program counter. Combined
22/// with debug information, those values can be converted into a resolved
23/// source location, including file, line, and column.
24pub const SourceLocation = struct {
25 line: u64,
26 column: u64,
27 file_name: []const u8,
28};
2329
30/// Deprecated because it returns the optimization mode of the standard
31/// library, when the caller probably wants to use the optimization mode of
32/// their own module.
2433pub const runtime_safety = switch (builtin.mode) {
2534 .Debug, .ReleaseSafe => true,
2635 .ReleaseFast, .ReleaseSmall => false,
......@@ -72,13 +81,13 @@ pub fn getStderrMutex() *std.Thread.Mutex {
7281}
7382
7483/// TODO multithreaded awareness
75var self_debug_info: ?Info = null;
84var self_debug_info: ?SelfInfo = null;
7685
77pub fn getSelfDebugInfo() !*Info {
86pub fn getSelfDebugInfo() !*SelfInfo {
7887 if (self_debug_info) |*info| {
7988 return info;
8089 } else {
81 self_debug_info = try Info.openSelf(getDebugInfoAllocator());
90 self_debug_info = try SelfInfo.openSelf(getDebugInfoAllocator());
8291 return &self_debug_info.?;
8392 }
8493}
......@@ -316,7 +325,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
316325 stack_trace.index = slice.len;
317326 } else {
318327 // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required).
319 // A new path for loading Info needs to be created which will only attempt to parse in-memory sections, because
328 // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because
320329 // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding.
321330 var it = StackIterator.init(first_address, null);
322331 defer it.deinit();
......@@ -494,7 +503,7 @@ pub fn writeStackTrace(
494503 stack_trace: std.builtin.StackTrace,
495504 out_stream: anytype,
496505 allocator: mem.Allocator,
497 debug_info: *Info,
506 debug_info: *SelfInfo,
498507 tty_config: io.tty.Config,
499508) !void {
500509 _ = allocator;
......@@ -531,11 +540,11 @@ pub const StackIterator = struct {
531540 fp: usize,
532541 ma: MemoryAccessor = MemoryAccessor.init,
533542
534 // When Info and a register context is available, this iterator can unwind
543 // When SelfInfo and a register context is available, this iterator can unwind
535544 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),
536545 // using DWARF and MachO unwind info.
537546 unwind_state: if (have_ucontext) ?struct {
538 debug_info: *Info,
547 debug_info: *SelfInfo,
539548 dwarf_context: Dwarf.UnwindContext,
540549 last_error: ?UnwindError = null,
541550 failed: bool = false,
......@@ -560,7 +569,7 @@ pub const StackIterator = struct {
560569 };
561570 }
562571
563 pub fn initWithContext(first_address: ?usize, debug_info: *Info, context: *const posix.ucontext_t) !StackIterator {
572 pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *const posix.ucontext_t) !StackIterator {
564573 // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
565574 // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
566575 if (comptime builtin.target.isDarwin() and native_arch == .aarch64) {
......@@ -820,7 +829,7 @@ const have_msync = switch (native_os) {
820829
821830pub fn writeCurrentStackTrace(
822831 out_stream: anytype,
823 debug_info: *Info,
832 debug_info: *SelfInfo,
824833 tty_config: io.tty.Config,
825834 start_addr: ?usize,
826835) !void {
......@@ -906,7 +915,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w
906915
907916pub fn writeStackTraceWindows(
908917 out_stream: anytype,
909 debug_info: *Info,
918 debug_info: *SelfInfo,
910919 tty_config: io.tty.Config,
911920 context: *const windows.CONTEXT,
912921 start_addr: ?usize,
......@@ -925,7 +934,7 @@ pub fn writeStackTraceWindows(
925934 }
926935}
927936
928fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
937fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
929938 const module_name = debug_info.getModuleNameForAddress(address);
930939 return printLineInfo(
931940 out_stream,
......@@ -938,14 +947,14 @@ fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tt
938947 );
939948}
940949
941fn printLastUnwindError(it: *StackIterator, debug_info: *Info, out_stream: anytype, tty_config: io.tty.Config) void {
950fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void {
942951 if (!have_ucontext) return;
943952 if (it.getLastError()) |unwind_error| {
944953 printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {};
945954 }
946955}
947956
948fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
957fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
949958 const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
950959 try tty_config.setColor(out_stream, .dim);
951960 if (err == error.MissingDebugInfo) {
......@@ -956,7 +965,7 @@ fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err:
956965 try tty_config.setColor(out_stream, .reset);
957966}
958967
959pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
968pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
960969 const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
961970 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
962971 else => return err,
......@@ -981,7 +990,7 @@ pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usi
981990
982991fn printLineInfo(
983992 out_stream: anytype,
984 line_info: ?Info.SourceLocation,
993 line_info: ?SourceLocation,
985994 address: usize,
986995 symbol_name: []const u8,
987996 compile_unit_name: []const u8,
......@@ -1027,7 +1036,7 @@ fn printLineInfo(
10271036 }
10281037}
10291038
1030fn printLineFromFileAnyOs(out_stream: anytype, line_info: Info.SourceLocation) !void {
1039fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void {
10311040 // Need this to always block even in async I/O mode, because this could potentially
10321041 // be called from e.g. the event loop code crashing.
10331042 var f = try fs.cwd().openFile(line_info.file_name, .{});
......@@ -1093,7 +1102,7 @@ test printLineFromFileAnyOs {
10931102
10941103 var test_dir = std.testing.tmpDir(.{});
10951104 defer test_dir.cleanup();
1096 // Relies on testing.tmpDir internals which is not ideal, but Info.SourceLocation requires paths.
1105 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
10971106 const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
10981107 defer allocator.free(test_dir_path);
10991108
......@@ -1439,7 +1448,7 @@ test "manage resources correctly" {
14391448 }
14401449
14411450 const writer = std.io.null_writer;
1442 var di = try Info.openSelf(testing.allocator);
1451 var di = try SelfInfo.openSelf(testing.allocator);
14431452 defer di.deinit();
14441453 try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr()));
14451454}
lib/std/debug/Dwarf.zig+3-3
......@@ -1353,7 +1353,7 @@ pub fn getLineNumberInfo(
13531353 allocator: Allocator,
13541354 compile_unit: CompileUnit,
13551355 target_address: u64,
1356) !std.debug.Info.SourceLocation {
1356) !std.debug.SourceLocation {
13571357 const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit);
13581358 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
13591359
......@@ -2084,7 +2084,7 @@ const LineNumberProgram = struct {
20842084 self: *LineNumberProgram,
20852085 allocator: Allocator,
20862086 file_entries: []const FileEntry,
2087 ) !?std.debug.Info.SourceLocation {
2087 ) !?std.debug.SourceLocation {
20882088 if (self.prev_valid and
20892089 self.target_address >= self.prev_address and
20902090 self.target_address < self.address)
......@@ -2104,7 +2104,7 @@ const LineNumberProgram = struct {
21042104 dir_name, file_entry.path,
21052105 });
21062106
2107 return std.debug.Info.SourceLocation{
2107 return std.debug.SourceLocation{
21082108 .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0,
21092109 .column = self.prev_column,
21102110 .file_name = file_name,
lib/std/debug/Info.zig deleted-1377
......@@ -1,1377 +0,0 @@
1//! Cross-platform abstraction for debug information.
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const native_endian = native_arch.endian();
6const native_arch = builtin.cpu.arch;
7
8const std = @import("../std.zig");
9const mem = std.mem;
10const Allocator = std.mem.Allocator;
11const windows = std.os.windows;
12const macho = std.macho;
13const fs = std.fs;
14const coff = std.coff;
15const pdb = std.pdb;
16const assert = std.debug.assert;
17const posix = std.posix;
18const elf = std.elf;
19const Dwarf = std.debug.Dwarf;
20const File = std.fs.File;
21const math = std.math;
22const testing = std.testing;
23
24const Info = @This();
25
26const root = @import("root");
27
28allocator: Allocator,
29address_map: std.AutoHashMap(usize, *Module),
30modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void,
31
32pub const OpenSelfError = error{
33 MissingDebugInfo,
34 UnsupportedOperatingSystem,
35} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set;
36
37pub fn openSelf(allocator: Allocator) OpenSelfError!Info {
38 nosuspend {
39 if (builtin.strip_debug_info)
40 return error.MissingDebugInfo;
41 if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
42 return root.os.debug.openSelfDebugInfo(allocator);
43 }
44 switch (native_os) {
45 .linux,
46 .freebsd,
47 .netbsd,
48 .dragonfly,
49 .openbsd,
50 .macos,
51 .solaris,
52 .illumos,
53 .windows,
54 => return try Info.init(allocator),
55 else => return error.UnsupportedOperatingSystem,
56 }
57 }
58}
59
60pub fn init(allocator: Allocator) !Info {
61 var debug_info: Info = .{
62 .allocator = allocator,
63 .address_map = std.AutoHashMap(usize, *Module).init(allocator),
64 .modules = if (native_os == .windows) .{} else {},
65 };
66
67 if (native_os == .windows) {
68 errdefer debug_info.modules.deinit(allocator);
69
70 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
71 if (handle == windows.INVALID_HANDLE_VALUE) {
72 switch (windows.GetLastError()) {
73 else => |err| return windows.unexpectedError(err),
74 }
75 }
76 defer windows.CloseHandle(handle);
77
78 var module_entry: windows.MODULEENTRY32 = undefined;
79 module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
80 if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
81 return error.MissingDebugInfo;
82 }
83
84 var module_valid = true;
85 while (module_valid) {
86 const module_info = try debug_info.modules.addOne(allocator);
87 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
88 errdefer allocator.free(name);
89
90 module_info.* = .{
91 .base_address = @intFromPtr(module_entry.modBaseAddr),
92 .size = module_entry.modBaseSize,
93 .name = name,
94 .handle = module_entry.hModule,
95 };
96
97 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
98 }
99 }
100
101 return debug_info;
102}
103
104pub fn deinit(self: *Info) void {
105 var it = self.address_map.iterator();
106 while (it.next()) |entry| {
107 const mdi = entry.value_ptr.*;
108 mdi.deinit(self.allocator);
109 self.allocator.destroy(mdi);
110 }
111 self.address_map.deinit();
112 if (native_os == .windows) {
113 for (self.modules.items) |module| {
114 self.allocator.free(module.name);
115 if (module.mapped_file) |mapped_file| mapped_file.deinit();
116 }
117 self.modules.deinit(self.allocator);
118 }
119}
120
121pub fn getModuleForAddress(self: *Info, address: usize) !*Module {
122 if (comptime builtin.target.isDarwin()) {
123 return self.lookupModuleDyld(address);
124 } else if (native_os == .windows) {
125 return self.lookupModuleWin32(address);
126 } else if (native_os == .haiku) {
127 return self.lookupModuleHaiku(address);
128 } else if (comptime builtin.target.isWasm()) {
129 return self.lookupModuleWasm(address);
130 } else {
131 return self.lookupModuleDl(address);
132 }
133}
134
135// Returns the module name for a given address.
136// This can be called when getModuleForAddress fails, so implementations should provide
137// a path that doesn't rely on any side-effects of a prior successful module lookup.
138pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 {
139 if (comptime builtin.target.isDarwin()) {
140 return self.lookupModuleNameDyld(address);
141 } else if (native_os == .windows) {
142 return self.lookupModuleNameWin32(address);
143 } else if (native_os == .haiku) {
144 return null;
145 } else if (comptime builtin.target.isWasm()) {
146 return null;
147 } else {
148 return self.lookupModuleNameDl(address);
149 }
150}
151
152fn lookupModuleDyld(self: *Info, address: usize) !*Module {
153 const image_count = std.c._dyld_image_count();
154
155 var i: u32 = 0;
156 while (i < image_count) : (i += 1) {
157 const header = std.c._dyld_get_image_header(i) orelse continue;
158 const base_address = @intFromPtr(header);
159 if (address < base_address) continue;
160 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
161
162 var it = macho.LoadCommandIterator{
163 .ncmds = header.ncmds,
164 .buffer = @alignCast(@as(
165 [*]u8,
166 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
167 )[0..header.sizeofcmds]),
168 };
169
170 var unwind_info: ?[]const u8 = null;
171 var eh_frame: ?[]const u8 = null;
172 while (it.next()) |cmd| switch (cmd.cmd()) {
173 .SEGMENT_64 => {
174 const segment_cmd = cmd.cast(macho.segment_command_64).?;
175 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
176
177 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
178 const seg_end = seg_start + segment_cmd.vmsize;
179 if (address >= seg_start and address < seg_end) {
180 if (self.address_map.get(base_address)) |obj_di| {
181 return obj_di;
182 }
183
184 for (cmd.getSections()) |sect| {
185 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
186 unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
187 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
188 eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
189 }
190 }
191
192 const obj_di = try self.allocator.create(Module);
193 errdefer self.allocator.destroy(obj_di);
194
195 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
196 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
197 error.FileNotFound => return error.MissingDebugInfo,
198 else => return err,
199 };
200 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
201 obj_di.base_address = base_address;
202 obj_di.vmaddr_slide = vmaddr_slide;
203 obj_di.unwind_info = unwind_info;
204 obj_di.eh_frame = eh_frame;
205
206 try self.address_map.putNoClobber(base_address, obj_di);
207
208 return obj_di;
209 }
210 },
211 else => {},
212 };
213 }
214
215 return error.MissingDebugInfo;
216}
217
218fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 {
219 _ = self;
220 const image_count = std.c._dyld_image_count();
221
222 var i: u32 = 0;
223 while (i < image_count) : (i += 1) {
224 const header = std.c._dyld_get_image_header(i) orelse continue;
225 const base_address = @intFromPtr(header);
226 if (address < base_address) continue;
227 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
228
229 var it = macho.LoadCommandIterator{
230 .ncmds = header.ncmds,
231 .buffer = @alignCast(@as(
232 [*]u8,
233 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
234 )[0..header.sizeofcmds]),
235 };
236
237 while (it.next()) |cmd| switch (cmd.cmd()) {
238 .SEGMENT_64 => {
239 const segment_cmd = cmd.cast(macho.segment_command_64).?;
240 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
241
242 const original_address = address - vmaddr_slide;
243 const seg_start = segment_cmd.vmaddr;
244 const seg_end = seg_start + segment_cmd.vmsize;
245 if (original_address >= seg_start and original_address < seg_end) {
246 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
247 }
248 },
249 else => {},
250 };
251 }
252
253 return null;
254}
255
256fn lookupModuleWin32(self: *Info, address: usize) !*Module {
257 for (self.modules.items) |*module| {
258 if (address >= module.base_address and address < module.base_address + module.size) {
259 if (self.address_map.get(module.base_address)) |obj_di| {
260 return obj_di;
261 }
262
263 const obj_di = try self.allocator.create(Module);
264 errdefer self.allocator.destroy(obj_di);
265
266 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
267 var coff_obj = try coff.Coff.init(mapped_module, true);
268
269 // The string table is not mapped into memory by the loader, so if a section name is in the
270 // string table then we have to map the full image file from disk. This can happen when
271 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
272 if (coff_obj.strtabRequired()) {
273 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
274 // openFileAbsoluteW requires the prefix to be present
275 @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
276
277 const process_handle = windows.GetCurrentProcess();
278 const len = windows.kernel32.GetModuleFileNameExW(
279 process_handle,
280 module.handle,
281 @ptrCast(&name_buffer[4]),
282 windows.PATH_MAX_WIDE,
283 );
284
285 if (len == 0) return error.MissingDebugInfo;
286 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
287 error.FileNotFound => return error.MissingDebugInfo,
288 else => return err,
289 };
290 errdefer coff_file.close();
291
292 var section_handle: windows.HANDLE = undefined;
293 const create_section_rc = windows.ntdll.NtCreateSection(
294 &section_handle,
295 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
296 null,
297 null,
298 windows.PAGE_READONLY,
299 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
300 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
301 windows.SEC_COMMIT,
302 coff_file.handle,
303 );
304 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
305 errdefer windows.CloseHandle(section_handle);
306
307 var coff_len: usize = 0;
308 var base_ptr: usize = 0;
309 const map_section_rc = windows.ntdll.NtMapViewOfSection(
310 section_handle,
311 process_handle,
312 @ptrCast(&base_ptr),
313 null,
314 0,
315 null,
316 &coff_len,
317 .ViewUnmap,
318 0,
319 windows.PAGE_READONLY,
320 );
321 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
322 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
323
324 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
325 coff_obj = try coff.Coff.init(section_view, false);
326
327 module.mapped_file = .{
328 .file = coff_file,
329 .section_handle = section_handle,
330 .section_view = section_view,
331 };
332 }
333 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
334
335 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
336 obj_di.base_address = module.base_address;
337
338 try self.address_map.putNoClobber(module.base_address, obj_di);
339 return obj_di;
340 }
341 }
342
343 return error.MissingDebugInfo;
344}
345
346fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 {
347 for (self.modules.items) |module| {
348 if (address >= module.base_address and address < module.base_address + module.size) {
349 return module.name;
350 }
351 }
352 return null;
353}
354
355fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 {
356 _ = self;
357
358 var ctx: struct {
359 // Input
360 address: usize,
361 // Output
362 name: []const u8 = "",
363 } = .{ .address = address };
364 const CtxTy = @TypeOf(ctx);
365
366 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
367 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
368 _ = size;
369 if (context.address < info.addr) return;
370 const phdrs = info.phdr[0..info.phnum];
371 for (phdrs) |*phdr| {
372 if (phdr.p_type != elf.PT_LOAD) continue;
373
374 const seg_start = info.addr +% phdr.p_vaddr;
375 const seg_end = seg_start + phdr.p_memsz;
376 if (context.address >= seg_start and context.address < seg_end) {
377 context.name = mem.sliceTo(info.name, 0) orelse "";
378 break;
379 }
380 } else return;
381
382 return error.Found;
383 }
384 }.callback)) {
385 return null;
386 } else |err| switch (err) {
387 error.Found => return fs.path.basename(ctx.name),
388 }
389
390 return null;
391}
392
393fn lookupModuleDl(self: *Info, address: usize) !*Module {
394 var ctx: struct {
395 // Input
396 address: usize,
397 // Output
398 base_address: usize = undefined,
399 name: []const u8 = undefined,
400 build_id: ?[]const u8 = null,
401 gnu_eh_frame: ?[]const u8 = null,
402 } = .{ .address = address };
403 const CtxTy = @TypeOf(ctx);
404
405 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
406 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
407 _ = size;
408 // The base address is too high
409 if (context.address < info.addr)
410 return;
411
412 const phdrs = info.phdr[0..info.phnum];
413 for (phdrs) |*phdr| {
414 if (phdr.p_type != elf.PT_LOAD) continue;
415
416 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
417 const seg_start = info.addr +% phdr.p_vaddr;
418 const seg_end = seg_start + phdr.p_memsz;
419 if (context.address >= seg_start and context.address < seg_end) {
420 // Android libc uses NULL instead of an empty string to mark the
421 // main program
422 context.name = mem.sliceTo(info.name, 0) orelse "";
423 context.base_address = info.addr;
424 break;
425 }
426 } else return;
427
428 for (info.phdr[0..info.phnum]) |phdr| {
429 switch (phdr.p_type) {
430 elf.PT_NOTE => {
431 // Look for .note.gnu.build-id
432 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
433 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
434 if (name_size != 4) continue;
435 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
436 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
437 if (note_type != elf.NT_GNU_BUILD_ID) continue;
438 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
439 context.build_id = note_bytes[16..][0..desc_size];
440 },
441 elf.PT_GNU_EH_FRAME => {
442 context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
443 },
444 else => {},
445 }
446 }
447
448 // Stop the iteration
449 return error.Found;
450 }
451 }.callback)) {
452 return error.MissingDebugInfo;
453 } else |err| switch (err) {
454 error.Found => {},
455 }
456
457 if (self.address_map.get(ctx.base_address)) |obj_di| {
458 return obj_di;
459 }
460
461 const obj_di = try self.allocator.create(Module);
462 errdefer self.allocator.destroy(obj_di);
463
464 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
465 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
466 // This is a special case - pointer offsets inside .eh_frame_hdr
467 // are encoded relative to its base address, so we must use the
468 // version that is already memory mapped, and not the one that
469 // will be mapped separately from the ELF file.
470 sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{
471 .data = eh_frame_hdr,
472 .owned = false,
473 };
474 }
475
476 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
477 obj_di.base_address = ctx.base_address;
478
479 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
480 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
481
482 try self.address_map.putNoClobber(ctx.base_address, obj_di);
483
484 return obj_di;
485}
486
487fn lookupModuleHaiku(self: *Info, address: usize) !*Module {
488 _ = self;
489 _ = address;
490 @panic("TODO implement lookup module for Haiku");
491}
492
493fn lookupModuleWasm(self: *Info, address: usize) !*Module {
494 _ = self;
495 _ = address;
496 @panic("TODO implement lookup module for Wasm");
497}
498
499pub const Module = switch (native_os) {
500 .macos, .ios, .watchos, .tvos, .visionos => struct {
501 base_address: usize,
502 vmaddr_slide: usize,
503 mapped_memory: []align(mem.page_size) const u8,
504 symbols: []const MachoSymbol,
505 strings: [:0]const u8,
506 ofiles: OFileTable,
507
508 // Backed by the in-memory sections mapped by the loader
509 unwind_info: ?[]const u8 = null,
510 eh_frame: ?[]const u8 = null,
511
512 const OFileTable = std.StringHashMap(OFileInfo);
513 const OFileInfo = struct {
514 di: Dwarf,
515 addr_table: std.StringHashMap(u64),
516 };
517
518 pub fn deinit(self: *@This(), allocator: Allocator) void {
519 var it = self.ofiles.iterator();
520 while (it.next()) |entry| {
521 const ofile = entry.value_ptr;
522 ofile.di.deinit(allocator);
523 ofile.addr_table.deinit();
524 }
525 self.ofiles.deinit();
526 allocator.free(self.symbols);
527 posix.munmap(self.mapped_memory);
528 }
529
530 fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo {
531 const o_file = try fs.cwd().openFile(o_file_path, .{});
532 const mapped_mem = try mapWholeFile(o_file);
533
534 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
535 if (hdr.magic != std.macho.MH_MAGIC_64)
536 return error.InvalidDebugInfo;
537
538 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
539 var symtabcmd: ?macho.symtab_command = null;
540 var it = macho.LoadCommandIterator{
541 .ncmds = hdr.ncmds,
542 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
543 };
544 while (it.next()) |cmd| switch (cmd.cmd()) {
545 .SEGMENT_64 => segcmd = cmd,
546 .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
547 else => {},
548 };
549
550 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
551
552 // Parse symbols
553 const strtab = @as(
554 [*]const u8,
555 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
556 )[0 .. symtabcmd.?.strsize - 1 :0];
557 const symtab = @as(
558 [*]const macho.nlist_64,
559 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
560 )[0..symtabcmd.?.nsyms];
561
562 // TODO handle tentative (common) symbols
563 var addr_table = std.StringHashMap(u64).init(allocator);
564 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
565 for (symtab) |sym| {
566 if (sym.n_strx == 0) continue;
567 if (sym.undf() or sym.tentative() or sym.abs()) continue;
568 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
569 // TODO is it possible to have a symbol collision?
570 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
571 }
572
573 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
574 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
575 .data = eh_frame,
576 .owned = false,
577 };
578
579 for (segcmd.?.getSections()) |sect| {
580 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
581
582 var section_index: ?usize = null;
583 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
584 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
585 }
586 if (section_index == null) continue;
587
588 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
589 sections[section_index.?] = .{
590 .data = section_bytes,
591 .virtual_address = sect.addr,
592 .owned = false,
593 };
594 }
595
596 const missing_debug_info =
597 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
598 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
599 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
600 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
601 if (missing_debug_info) return error.MissingDebugInfo;
602
603 var di = Dwarf{
604 .endian = .little,
605 .sections = sections,
606 .is_macho = true,
607 };
608
609 try Dwarf.open(&di, allocator);
610 const info = OFileInfo{
611 .di = di,
612 .addr_table = addr_table,
613 };
614
615 // Add the debug info to the cache
616 const result = try self.ofiles.getOrPut(o_file_path);
617 assert(!result.found_existing);
618 result.value_ptr.* = info;
619
620 return result.value_ptr;
621 }
622
623 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
624 nosuspend {
625 const result = try self.getOFileInfoForAddress(allocator, address);
626 if (result.symbol == null) return .{};
627
628 // Take the symbol name from the N_FUN STAB entry, we're going to
629 // use it if we fail to find the DWARF infos
630 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
631 if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
632
633 // Translate again the address, this time into an address inside the
634 // .o file
635 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
636 .symbol_name = "???",
637 };
638
639 const addr_off = result.relocated_address - result.symbol.?.addr;
640 const o_file_di = &result.o_file_info.?.di;
641 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
642 return SymbolInfo{
643 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
644 .compile_unit_name = compile_unit.die.getAttrString(
645 o_file_di,
646 std.dwarf.AT.name,
647 o_file_di.section(.debug_str),
648 compile_unit.*,
649 ) catch |err| switch (err) {
650 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
651 },
652 .line_info = o_file_di.getLineNumberInfo(
653 allocator,
654 compile_unit.*,
655 relocated_address_o + addr_off,
656 ) catch |err| switch (err) {
657 error.MissingDebugInfo, error.InvalidDebugInfo => null,
658 else => return err,
659 },
660 };
661 } else |err| switch (err) {
662 error.MissingDebugInfo, error.InvalidDebugInfo => {
663 return SymbolInfo{ .symbol_name = stab_symbol };
664 },
665 else => return err,
666 }
667 }
668 }
669
670 pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct {
671 relocated_address: usize,
672 symbol: ?*const MachoSymbol = null,
673 o_file_info: ?*OFileInfo = null,
674 } {
675 nosuspend {
676 // Translate the VA into an address into this object
677 const relocated_address = address - self.vmaddr_slide;
678
679 // Find the .o file where this symbol is defined
680 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
681 .relocated_address = relocated_address,
682 };
683
684 // Check if its debug infos are already in the cache
685 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
686 const o_file_info = self.ofiles.getPtr(o_file_path) orelse
687 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
688 error.FileNotFound,
689 error.MissingDebugInfo,
690 error.InvalidDebugInfo,
691 => return .{
692 .relocated_address = relocated_address,
693 .symbol = symbol,
694 },
695 else => return err,
696 });
697
698 return .{
699 .relocated_address = relocated_address,
700 .symbol = symbol,
701 .o_file_info = o_file_info,
702 };
703 }
704 }
705
706 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
707 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
708 }
709 },
710 .uefi, .windows => struct {
711 base_address: usize,
712 pdb: ?pdb.Pdb = null,
713 dwarf: ?Dwarf = null,
714 coff_image_base: u64,
715
716 /// Only used if pdb is non-null
717 coff_section_headers: []coff.SectionHeader,
718
719 pub fn deinit(self: *@This(), allocator: Allocator) void {
720 if (self.dwarf) |*dwarf| {
721 dwarf.deinit(allocator);
722 }
723
724 if (self.pdb) |*p| {
725 p.deinit();
726 allocator.free(self.coff_section_headers);
727 }
728 }
729
730 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo {
731 var coff_section: *align(1) const coff.SectionHeader = undefined;
732 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
733 if (sect_contrib.Section > self.coff_section_headers.len) continue;
734 // Remember that SectionContribEntry.Section is 1-based.
735 coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
736
737 const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
738 const vaddr_end = vaddr_start + sect_contrib.Size;
739 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
740 break sect_contrib.ModuleIndex;
741 }
742 } else {
743 // we have no information to add to the address
744 return null;
745 };
746
747 const module = (try self.pdb.?.getModule(mod_index)) orelse
748 return error.InvalidDebugInfo;
749 const obj_basename = fs.path.basename(module.obj_file_name);
750
751 const symbol_name = self.pdb.?.getSymbolName(
752 module,
753 relocated_address - coff_section.virtual_address,
754 ) orelse "???";
755 const opt_line_info = try self.pdb.?.getLineNumberInfo(
756 module,
757 relocated_address - coff_section.virtual_address,
758 );
759
760 return SymbolInfo{
761 .symbol_name = symbol_name,
762 .compile_unit_name = obj_basename,
763 .line_info = opt_line_info,
764 };
765 }
766
767 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
768 // Translate the VA into an address into this object
769 const relocated_address = address - self.base_address;
770
771 if (self.pdb != null) {
772 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
773 }
774
775 if (self.dwarf) |*dwarf| {
776 const dwarf_address = relocated_address + self.coff_image_base;
777 return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
778 }
779
780 return SymbolInfo{};
781 }
782
783 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
784 _ = allocator;
785 _ = address;
786
787 return switch (self.debug_data) {
788 .dwarf => |*dwarf| dwarf,
789 else => null,
790 };
791 }
792 },
793 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
794 base_address: usize,
795 dwarf: Dwarf,
796 mapped_memory: []align(mem.page_size) const u8,
797 external_mapped_memory: ?[]align(mem.page_size) const u8,
798
799 pub fn deinit(self: *@This(), allocator: Allocator) void {
800 self.dwarf.deinit(allocator);
801 posix.munmap(self.mapped_memory);
802 if (self.external_mapped_memory) |m| posix.munmap(m);
803 }
804
805 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
806 // Translate the VA into an address into this object
807 const relocated_address = address - self.base_address;
808 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
809 }
810
811 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
812 _ = allocator;
813 _ = address;
814 return &self.dwarf;
815 }
816 },
817 .wasi, .emscripten => struct {
818 pub fn deinit(self: *@This(), allocator: Allocator) void {
819 _ = self;
820 _ = allocator;
821 }
822
823 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
824 _ = self;
825 _ = allocator;
826 _ = address;
827 return SymbolInfo{};
828 }
829
830 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
831 _ = self;
832 _ = allocator;
833 _ = address;
834 return null;
835 }
836 },
837 else => Dwarf,
838};
839
840pub const WindowsModuleInfo = struct {
841 base_address: usize,
842 size: u32,
843 name: []const u8,
844 handle: windows.HMODULE,
845
846 // Set when the image file needed to be mapped from disk
847 mapped_file: ?struct {
848 file: File,
849 section_handle: windows.HANDLE,
850 section_view: []const u8,
851
852 pub fn deinit(self: @This()) void {
853 const process_handle = windows.GetCurrentProcess();
854 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
855 windows.CloseHandle(self.section_handle);
856 self.file.close();
857 }
858 } = null,
859};
860
861/// This takes ownership of macho_file: users of this function should not close
862/// it themselves, even on error.
863/// TODO it's weird to take ownership even on error, rework this code.
864fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
865 const mapped_mem = try mapWholeFile(macho_file);
866
867 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
868 if (hdr.magic != macho.MH_MAGIC_64)
869 return error.InvalidDebugInfo;
870
871 var it = macho.LoadCommandIterator{
872 .ncmds = hdr.ncmds,
873 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
874 };
875 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
876 .SYMTAB => break cmd.cast(macho.symtab_command).?,
877 else => {},
878 } else return error.MissingDebugInfo;
879
880 const syms = @as(
881 [*]const macho.nlist_64,
882 @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
883 )[0..symtab.nsyms];
884 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
885
886 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
887
888 var ofile: u32 = undefined;
889 var last_sym: MachoSymbol = undefined;
890 var symbol_index: usize = 0;
891 var state: enum {
892 init,
893 oso_open,
894 oso_close,
895 bnsym,
896 fun_strx,
897 fun_size,
898 ensym,
899 } = .init;
900
901 for (syms) |*sym| {
902 if (!sym.stab()) continue;
903
904 // TODO handle globals N_GSYM, and statics N_STSYM
905 switch (sym.n_type) {
906 macho.N_OSO => {
907 switch (state) {
908 .init, .oso_close => {
909 state = .oso_open;
910 ofile = sym.n_strx;
911 },
912 else => return error.InvalidDebugInfo,
913 }
914 },
915 macho.N_BNSYM => {
916 switch (state) {
917 .oso_open, .ensym => {
918 state = .bnsym;
919 last_sym = .{
920 .strx = 0,
921 .addr = sym.n_value,
922 .size = 0,
923 .ofile = ofile,
924 };
925 },
926 else => return error.InvalidDebugInfo,
927 }
928 },
929 macho.N_FUN => {
930 switch (state) {
931 .bnsym => {
932 state = .fun_strx;
933 last_sym.strx = sym.n_strx;
934 },
935 .fun_strx => {
936 state = .fun_size;
937 last_sym.size = @as(u32, @intCast(sym.n_value));
938 },
939 else => return error.InvalidDebugInfo,
940 }
941 },
942 macho.N_ENSYM => {
943 switch (state) {
944 .fun_size => {
945 state = .ensym;
946 symbols_buf[symbol_index] = last_sym;
947 symbol_index += 1;
948 },
949 else => return error.InvalidDebugInfo,
950 }
951 },
952 macho.N_SO => {
953 switch (state) {
954 .init, .oso_close => {},
955 .oso_open, .ensym => {
956 state = .oso_close;
957 },
958 else => return error.InvalidDebugInfo,
959 }
960 },
961 else => {},
962 }
963 }
964
965 switch (state) {
966 .init => return error.MissingDebugInfo,
967 .oso_close => {},
968 else => return error.InvalidDebugInfo,
969 }
970
971 const symbols = try allocator.realloc(symbols_buf, symbol_index);
972
973 // Even though lld emits symbols in ascending order, this debug code
974 // should work for programs linked in any valid way.
975 // This sort is so that we can binary search later.
976 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
977
978 return .{
979 .base_address = undefined,
980 .vmaddr_slide = undefined,
981 .mapped_memory = mapped_mem,
982 .ofiles = Module.OFileTable.init(allocator),
983 .symbols = symbols,
984 .strings = strings,
985 };
986}
987
988fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
989 nosuspend {
990 var di: Module = .{
991 .base_address = undefined,
992 .coff_image_base = coff_obj.getImageBase(),
993 .coff_section_headers = undefined,
994 };
995
996 if (coff_obj.getSectionByName(".debug_info")) |_| {
997 // This coff file has embedded DWARF debug info
998 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
999 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1000
1001 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1002 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1003 break :blk .{
1004 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1005 .virtual_address = section_header.virtual_address,
1006 .owned = true,
1007 };
1008 } else null;
1009 }
1010
1011 var dwarf = Dwarf{
1012 .endian = native_endian,
1013 .sections = sections,
1014 .is_macho = false,
1015 };
1016
1017 try Dwarf.open(&dwarf, allocator);
1018 di.dwarf = dwarf;
1019 }
1020
1021 const raw_path = try coff_obj.getPdbPath() orelse return di;
1022 const path = blk: {
1023 if (fs.path.isAbsolute(raw_path)) {
1024 break :blk raw_path;
1025 } else {
1026 const self_dir = try fs.selfExeDirPathAlloc(allocator);
1027 defer allocator.free(self_dir);
1028 break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
1029 }
1030 };
1031 defer if (path.ptr != raw_path.ptr) allocator.free(path);
1032
1033 di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) {
1034 error.FileNotFound, error.IsDir => {
1035 if (di.dwarf == null) return error.MissingDebugInfo;
1036 return di;
1037 },
1038 else => return err,
1039 };
1040 try di.pdb.?.parseInfoStream();
1041 try di.pdb.?.parseDbiStream();
1042
1043 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
1044 return error.InvalidDebugInfo;
1045
1046 // Only used by the pdb path
1047 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1048 errdefer allocator.free(di.coff_section_headers);
1049
1050 return di;
1051 }
1052}
1053
1054/// Reads debug info from an ELF file, or the current binary if none in specified.
1055/// If the required sections aren't present but a reference to external debug info is,
1056/// then this this function will recurse to attempt to load the debug sections from
1057/// an external file.
1058pub fn readElfDebugInfo(
1059 allocator: Allocator,
1060 elf_filename: ?[]const u8,
1061 build_id: ?[]const u8,
1062 expected_crc: ?u32,
1063 parent_sections: *Dwarf.SectionArray,
1064 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1065) !Module {
1066 nosuspend {
1067 const elf_file = (if (elf_filename) |filename| blk: {
1068 break :blk fs.cwd().openFile(filename, .{});
1069 } else fs.openSelfExe(.{})) catch |err| switch (err) {
1070 error.FileNotFound => return error.MissingDebugInfo,
1071 else => return err,
1072 };
1073
1074 const mapped_mem = try mapWholeFile(elf_file);
1075 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
1076
1077 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
1078 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
1079 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
1080
1081 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
1082 elf.ELFDATA2LSB => .little,
1083 elf.ELFDATA2MSB => .big,
1084 else => return error.InvalidElfEndian,
1085 };
1086 assert(endian == native_endian); // this is our own debug info
1087
1088 const shoff = hdr.e_shoff;
1089 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1090 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1091 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1092 const shdrs = @as(
1093 [*]const elf.Shdr,
1094 @ptrCast(@alignCast(&mapped_mem[shoff])),
1095 )[0..hdr.e_shnum];
1096
1097 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1098
1099 // Combine section list. This takes ownership over any owned sections from the parent scope.
1100 for (parent_sections, &sections) |*parent, *section| {
1101 if (parent.*) |*p| {
1102 section.* = p.*;
1103 p.owned = false;
1104 }
1105 }
1106 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1107
1108 var separate_debug_filename: ?[]const u8 = null;
1109 var separate_debug_crc: ?u32 = null;
1110
1111 for (shdrs) |*shdr| {
1112 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1113 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1114
1115 if (mem.eql(u8, name, ".gnu_debuglink")) {
1116 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1117 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1118 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1119 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1120 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
1121 separate_debug_filename = debug_filename;
1122 continue;
1123 }
1124
1125 var section_index: ?usize = null;
1126 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1127 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1128 }
1129 if (section_index == null) continue;
1130 if (sections[section_index.?] != null) continue;
1131
1132 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1133 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1134 var section_stream = std.io.fixedBufferStream(section_bytes);
1135 var section_reader = section_stream.reader();
1136 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1137 if (chdr.ch_type != .ZLIB) continue;
1138
1139 var zlib_stream = std.compress.zlib.decompressor(section_stream.reader());
1140
1141 const decompressed_section = try allocator.alloc(u8, chdr.ch_size);
1142 errdefer allocator.free(decompressed_section);
1143
1144 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
1145 assert(read == decompressed_section.len);
1146
1147 break :blk .{
1148 .data = decompressed_section,
1149 .virtual_address = shdr.sh_addr,
1150 .owned = true,
1151 };
1152 } else .{
1153 .data = section_bytes,
1154 .virtual_address = shdr.sh_addr,
1155 .owned = false,
1156 };
1157 }
1158
1159 const missing_debug_info =
1160 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
1161 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1162 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1163 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
1164
1165 // Attempt to load debug info from an external file
1166 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1167 if (missing_debug_info) {
1168
1169 // Only allow one level of debug info nesting
1170 if (parent_mapped_mem) |_| {
1171 return error.MissingDebugInfo;
1172 }
1173
1174 const global_debug_directories = [_][]const u8{
1175 "/usr/lib/debug",
1176 };
1177
1178 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1179 if (build_id) |id| blk: {
1180 if (id.len < 3) break :blk;
1181
1182 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1183 const extension = ".debug";
1184 var id_prefix_buf: [2]u8 = undefined;
1185 var filename_buf: [38 + extension.len]u8 = undefined;
1186
1187 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
1188 const filename = std.fmt.bufPrint(
1189 &filename_buf,
1190 "{s}" ++ extension,
1191 .{std.fmt.fmtSliceHexLower(id[1..])},
1192 ) catch break :blk;
1193
1194 for (global_debug_directories) |global_directory| {
1195 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
1196 defer allocator.free(path);
1197
1198 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1199 }
1200 }
1201
1202 // use the path from .gnu_debuglink, in the same search order as gdb
1203 if (separate_debug_filename) |separate_filename| blk: {
1204 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
1205
1206 // <cwd>/<gnu_debuglink>
1207 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1208
1209 // <cwd>/.debug/<gnu_debuglink>
1210 {
1211 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
1212 defer allocator.free(path);
1213
1214 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1215 }
1216
1217 var cwd_buf: [fs.max_path_bytes]u8 = undefined;
1218 const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk;
1219
1220 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1221 for (global_debug_directories) |global_directory| {
1222 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
1223 defer allocator.free(path);
1224 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1225 }
1226 }
1227
1228 return error.MissingDebugInfo;
1229 }
1230
1231 var di = Dwarf{
1232 .endian = endian,
1233 .sections = sections,
1234 .is_macho = false,
1235 };
1236
1237 try Dwarf.open(&di, allocator);
1238
1239 return .{
1240 .base_address = undefined,
1241 .dwarf = di,
1242 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1243 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1244 };
1245 }
1246}
1247
1248const MachoSymbol = struct {
1249 strx: u32,
1250 addr: u64,
1251 size: u32,
1252 ofile: u32,
1253
1254 /// Returns the address from the macho file
1255 fn address(self: MachoSymbol) u64 {
1256 return self.addr;
1257 }
1258
1259 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1260 _ = context;
1261 return lhs.addr < rhs.addr;
1262 }
1263};
1264
1265/// Takes ownership of file, even on error.
1266/// TODO it's weird to take ownership even on error, rework this code.
1267fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
1268 nosuspend {
1269 defer file.close();
1270
1271 const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
1272 const mapped_mem = try posix.mmap(
1273 null,
1274 file_len,
1275 posix.PROT.READ,
1276 .{ .TYPE = .SHARED },
1277 file.handle,
1278 0,
1279 );
1280 errdefer posix.munmap(mapped_mem);
1281
1282 return mapped_mem;
1283 }
1284}
1285
1286fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1287 const start = math.cast(usize, offset) orelse return error.Overflow;
1288 const end = start + (math.cast(usize, size) orelse return error.Overflow);
1289 return ptr[start..end];
1290}
1291
1292pub const SymbolInfo = struct {
1293 symbol_name: []const u8 = "???",
1294 compile_unit_name: []const u8 = "???",
1295 line_info: ?SourceLocation = null,
1296
1297 pub fn deinit(self: SymbolInfo, allocator: Allocator) void {
1298 if (self.line_info) |li| {
1299 li.deinit(allocator);
1300 }
1301 }
1302};
1303
1304pub const SourceLocation = struct {
1305 line: u64,
1306 column: u64,
1307 file_name: []const u8,
1308
1309 pub fn deinit(self: SourceLocation, allocator: Allocator) void {
1310 allocator.free(self.file_name);
1311 }
1312};
1313
1314fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
1315 var min: usize = 0;
1316 var max: usize = symbols.len - 1;
1317 while (min < max) {
1318 const mid = min + (max - min) / 2;
1319 const curr = &symbols[mid];
1320 const next = &symbols[mid + 1];
1321 if (address >= next.address()) {
1322 min = mid + 1;
1323 } else if (address < curr.address()) {
1324 max = mid;
1325 } else {
1326 return curr;
1327 }
1328 }
1329
1330 const max_sym = &symbols[symbols.len - 1];
1331 if (address >= max_sym.address())
1332 return max_sym;
1333
1334 return null;
1335}
1336
1337test machoSearchSymbols {
1338 const symbols = [_]MachoSymbol{
1339 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
1340 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
1341 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
1342 };
1343
1344 try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
1345 try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
1346 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
1347 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
1348 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
1349
1350 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
1351 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
1352 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
1353
1354 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
1355 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
1356 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1357}
1358
1359fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo {
1360 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
1361 return SymbolInfo{
1362 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
1363 .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
1364 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
1365 },
1366 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
1367 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1368 else => return err,
1369 },
1370 };
1371 } else |err| switch (err) {
1372 error.MissingDebugInfo, error.InvalidDebugInfo => {
1373 return SymbolInfo{};
1374 },
1375 else => return err,
1376 }
1377}
lib/std/debug/Pdb.zig created+591
......@@ -0,0 +1,591 @@
1const std = @import("../std.zig");
2const File = std.fs.File;
3const Allocator = std.mem.Allocator;
4const pdb = std.pdb;
5
6const Pdb = @This();
7
8in_file: File,
9msf: Msf,
10allocator: Allocator,
11string_table: ?*MsfStream,
12dbi: ?*MsfStream,
13modules: []Module,
14sect_contribs: []pdb.SectionContribEntry,
15guid: [16]u8,
16age: u32,
17
18pub const Module = struct {
19 mod_info: pdb.ModInfo,
20 module_name: []u8,
21 obj_file_name: []u8,
22 // The fields below are filled on demand.
23 populated: bool,
24 symbols: []u8,
25 subsect_info: []u8,
26 checksum_offset: ?usize,
27
28 pub fn deinit(self: *Module, allocator: Allocator) void {
29 allocator.free(self.module_name);
30 allocator.free(self.obj_file_name);
31 if (self.populated) {
32 allocator.free(self.symbols);
33 allocator.free(self.subsect_info);
34 }
35 }
36};
37
38pub fn init(allocator: Allocator, path: []const u8) !Pdb {
39 const file = try std.fs.cwd().openFile(path, .{});
40 errdefer file.close();
41
42 return .{
43 .in_file = file,
44 .allocator = allocator,
45 .string_table = null,
46 .dbi = null,
47 .msf = try Msf.init(allocator, file),
48 .modules = &[_]Module{},
49 .sect_contribs = &[_]pdb.SectionContribEntry{},
50 .guid = undefined,
51 .age = undefined,
52 };
53}
54
55pub fn deinit(self: *Pdb) void {
56 self.in_file.close();
57 self.msf.deinit(self.allocator);
58 for (self.modules) |*module| {
59 module.deinit(self.allocator);
60 }
61 self.allocator.free(self.modules);
62 self.allocator.free(self.sect_contribs);
63}
64
65pub fn parseDbiStream(self: *Pdb) !void {
66 var stream = self.getStream(pdb.StreamType.Dbi) orelse
67 return error.InvalidDebugInfo;
68 const reader = stream.reader();
69
70 const header = try reader.readStruct(std.pdb.DbiStreamHeader);
71 if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team
72 return error.UnknownPDBVersion;
73 // if (header.Age != age)
74 // return error.UnmatchingPDB;
75
76 const mod_info_size = header.ModInfoSize;
77 const section_contrib_size = header.SectionContributionSize;
78
79 var modules = std.ArrayList(Module).init(self.allocator);
80 errdefer modules.deinit();
81
82 // Module Info Substream
83 var mod_info_offset: usize = 0;
84 while (mod_info_offset != mod_info_size) {
85 const mod_info = try reader.readStruct(pdb.ModInfo);
86 var this_record_len: usize = @sizeOf(pdb.ModInfo);
87
88 const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
89 errdefer self.allocator.free(module_name);
90 this_record_len += module_name.len + 1;
91
92 const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
93 errdefer self.allocator.free(obj_file_name);
94 this_record_len += obj_file_name.len + 1;
95
96 if (this_record_len % 4 != 0) {
97 const round_to_next_4 = (this_record_len | 0x3) + 1;
98 const march_forward_bytes = round_to_next_4 - this_record_len;
99 try stream.seekBy(@as(isize, @intCast(march_forward_bytes)));
100 this_record_len += march_forward_bytes;
101 }
102
103 try modules.append(Module{
104 .mod_info = mod_info,
105 .module_name = module_name,
106 .obj_file_name = obj_file_name,
107
108 .populated = false,
109 .symbols = undefined,
110 .subsect_info = undefined,
111 .checksum_offset = null,
112 });
113
114 mod_info_offset += this_record_len;
115 if (mod_info_offset > mod_info_size)
116 return error.InvalidDebugInfo;
117 }
118
119 // Section Contribution Substream
120 var sect_contribs = std.ArrayList(pdb.SectionContribEntry).init(self.allocator);
121 errdefer sect_contribs.deinit();
122
123 var sect_cont_offset: usize = 0;
124 if (section_contrib_size != 0) {
125 const version = reader.readEnum(std.pdb.SectionContrSubstreamVersion, .little) catch |err| switch (err) {
126 error.InvalidValue => return error.InvalidDebugInfo,
127 else => |e| return e,
128 };
129 _ = version;
130 sect_cont_offset += @sizeOf(u32);
131 }
132 while (sect_cont_offset != section_contrib_size) {
133 const entry = try sect_contribs.addOne();
134 entry.* = try reader.readStruct(pdb.SectionContribEntry);
135 sect_cont_offset += @sizeOf(pdb.SectionContribEntry);
136
137 if (sect_cont_offset > section_contrib_size)
138 return error.InvalidDebugInfo;
139 }
140
141 self.modules = try modules.toOwnedSlice();
142 self.sect_contribs = try sect_contribs.toOwnedSlice();
143}
144
145pub fn parseInfoStream(self: *Pdb) !void {
146 var stream = self.getStream(pdb.StreamType.Pdb) orelse
147 return error.InvalidDebugInfo;
148 const reader = stream.reader();
149
150 // Parse the InfoStreamHeader.
151 const version = try reader.readInt(u32, .little);
152 const signature = try reader.readInt(u32, .little);
153 _ = signature;
154 const age = try reader.readInt(u32, .little);
155 const guid = try reader.readBytesNoEof(16);
156
157 if (version != 20000404) // VC70, only value observed by LLVM team
158 return error.UnknownPDBVersion;
159
160 self.guid = guid;
161 self.age = age;
162
163 // Find the string table.
164 const string_table_index = str_tab_index: {
165 const name_bytes_len = try reader.readInt(u32, .little);
166 const name_bytes = try self.allocator.alloc(u8, name_bytes_len);
167 defer self.allocator.free(name_bytes);
168 try reader.readNoEof(name_bytes);
169
170 const HashTableHeader = extern struct {
171 Size: u32,
172 Capacity: u32,
173
174 fn maxLoad(cap: u32) u32 {
175 return cap * 2 / 3 + 1;
176 }
177 };
178 const hash_tbl_hdr = try reader.readStruct(HashTableHeader);
179 if (hash_tbl_hdr.Capacity == 0)
180 return error.InvalidDebugInfo;
181
182 if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
183 return error.InvalidDebugInfo;
184
185 const present = try readSparseBitVector(&reader, self.allocator);
186 defer self.allocator.free(present);
187 if (present.len != hash_tbl_hdr.Size)
188 return error.InvalidDebugInfo;
189 const deleted = try readSparseBitVector(&reader, self.allocator);
190 defer self.allocator.free(deleted);
191
192 for (present) |_| {
193 const name_offset = try reader.readInt(u32, .little);
194 const name_index = try reader.readInt(u32, .little);
195 if (name_offset > name_bytes.len)
196 return error.InvalidDebugInfo;
197 const name = std.mem.sliceTo(name_bytes[name_offset..], 0);
198 if (std.mem.eql(u8, name, "/names")) {
199 break :str_tab_index name_index;
200 }
201 }
202 return error.MissingDebugInfo;
203 };
204
205 self.string_table = self.getStreamById(string_table_index) orelse
206 return error.MissingDebugInfo;
207}
208
209pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 {
210 _ = self;
211 std.debug.assert(module.populated);
212
213 var symbol_i: usize = 0;
214 while (symbol_i != module.symbols.len) {
215 const prefix = @as(*align(1) pdb.RecordPrefix, @ptrCast(&module.symbols[symbol_i]));
216 if (prefix.RecordLen < 2)
217 return null;
218 switch (prefix.RecordKind) {
219 .S_LPROC32, .S_GPROC32 => {
220 const proc_sym = @as(*align(1) pdb.ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]));
221 if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) {
222 return std.mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0);
223 }
224 },
225 else => {},
226 }
227 symbol_i += prefix.RecordLen + @sizeOf(u16);
228 }
229
230 return null;
231}
232
233pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {
234 std.debug.assert(module.populated);
235 const subsect_info = module.subsect_info;
236
237 var sect_offset: usize = 0;
238 var skip_len: usize = undefined;
239 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
240 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
241 const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset]));
242 skip_len = subsect_hdr.Length;
243 sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
244
245 switch (subsect_hdr.Kind) {
246 .Lines => {
247 var line_index = sect_offset;
248
249 const line_hdr = @as(*align(1) pdb.LineFragmentHeader, @ptrCast(&subsect_info[line_index]));
250 if (line_hdr.RelocSegment == 0)
251 return error.MissingDebugInfo;
252 line_index += @sizeOf(pdb.LineFragmentHeader);
253 const frag_vaddr_start = line_hdr.RelocOffset;
254 const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
255
256 if (address >= frag_vaddr_start and address < frag_vaddr_end) {
257 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
258 // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in,
259 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
260 const subsection_end_index = sect_offset + subsect_hdr.Length;
261
262 while (line_index < subsection_end_index) {
263 const block_hdr = @as(*align(1) pdb.LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index]));
264 line_index += @sizeOf(pdb.LineBlockFragmentHeader);
265 const start_line_index = line_index;
266
267 const has_column = line_hdr.Flags.LF_HaveColumns;
268
269 // All line entries are stored inside their line block by ascending start address.
270 // Heuristic: we want to find the last line entry
271 // that has a vaddr_start <= address.
272 // This is done with a simple linear search.
273 var line_i: u32 = 0;
274 while (line_i < block_hdr.NumLines) : (line_i += 1) {
275 const line_num_entry = @as(*align(1) pdb.LineNumberEntry, @ptrCast(&subsect_info[line_index]));
276 line_index += @sizeOf(pdb.LineNumberEntry);
277
278 const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
279 if (address < vaddr_start) {
280 break;
281 }
282 }
283
284 // line_i == 0 would mean that no matching pdb.LineNumberEntry was found.
285 if (line_i > 0) {
286 const subsect_index = checksum_offset + block_hdr.NameIndex;
287 const chksum_hdr = @as(*align(1) pdb.FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index]));
288 const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.FileNameOffset;
289 try self.string_table.?.seekTo(strtab_offset);
290 const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024);
291
292 const line_entry_idx = line_i - 1;
293
294 const column = if (has_column) blk: {
295 const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines;
296 const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx;
297 const col_num_entry = @as(*align(1) pdb.ColumnNumberEntry, @ptrCast(&subsect_info[col_index]));
298 break :blk col_num_entry.StartColumn;
299 } else 0;
300
301 const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry);
302 const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
303 const flags: *align(1) pdb.LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags);
304
305 return .{
306 .file_name = source_file_name,
307 .line = flags.Start,
308 .column = column,
309 };
310 }
311 }
312
313 // Checking that we are not reading garbage after the (possibly) multiple block fragments.
314 if (line_index != subsection_end_index) {
315 return error.InvalidDebugInfo;
316 }
317 }
318 },
319 else => {},
320 }
321
322 if (sect_offset > subsect_info.len)
323 return error.InvalidDebugInfo;
324 }
325
326 return error.MissingDebugInfo;
327}
328
329pub fn getModule(self: *Pdb, index: usize) !?*Module {
330 if (index >= self.modules.len)
331 return null;
332
333 const mod = &self.modules[index];
334 if (mod.populated)
335 return mod;
336
337 // At most one can be non-zero.
338 if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
339 return error.InvalidDebugInfo;
340 if (mod.mod_info.C13ByteSize == 0)
341 return error.InvalidDebugInfo;
342
343 const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse
344 return error.MissingDebugInfo;
345 const reader = stream.reader();
346
347 const signature = try reader.readInt(u32, .little);
348 if (signature != 4)
349 return error.InvalidDebugInfo;
350
351 mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
352 errdefer self.allocator.free(mod.symbols);
353 try reader.readNoEof(mod.symbols);
354
355 mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize);
356 errdefer self.allocator.free(mod.subsect_info);
357 try reader.readNoEof(mod.subsect_info);
358
359 var sect_offset: usize = 0;
360 var skip_len: usize = undefined;
361 while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
362 const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset]));
363 skip_len = subsect_hdr.Length;
364 sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
365
366 switch (subsect_hdr.Kind) {
367 .FileChecksums => {
368 mod.checksum_offset = sect_offset;
369 break;
370 },
371 else => {},
372 }
373
374 if (sect_offset > mod.subsect_info.len)
375 return error.InvalidDebugInfo;
376 }
377
378 mod.populated = true;
379 return mod;
380}
381
382pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {
383 if (id >= self.msf.streams.len)
384 return null;
385 return &self.msf.streams[id];
386}
387
388pub fn getStream(self: *Pdb, stream: pdb.StreamType) ?*MsfStream {
389 const id = @intFromEnum(stream);
390 return self.getStreamById(id);
391}
392
393/// https://llvm.org/docs/PDB/MsfFile.html
394const Msf = struct {
395 directory: MsfStream,
396 streams: []MsfStream,
397
398 fn init(allocator: Allocator, file: File) !Msf {
399 const in = file.reader();
400
401 const superblock = try in.readStruct(pdb.SuperBlock);
402
403 // Sanity checks
404 if (!std.mem.eql(u8, &superblock.FileMagic, pdb.SuperBlock.file_magic))
405 return error.InvalidDebugInfo;
406 if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2)
407 return error.InvalidDebugInfo;
408 const file_len = try file.getEndPos();
409 if (superblock.NumBlocks * superblock.BlockSize != file_len)
410 return error.InvalidDebugInfo;
411 switch (superblock.BlockSize) {
412 // llvm only supports 4096 but we can handle any of these values
413 512, 1024, 2048, 4096 => {},
414 else => return error.InvalidDebugInfo,
415 }
416
417 const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize);
418 if (dir_block_count > superblock.BlockSize / @sizeOf(u32))
419 return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment.
420
421 try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr);
422 const dir_blocks = try allocator.alloc(u32, dir_block_count);
423 for (dir_blocks) |*b| {
424 b.* = try in.readInt(u32, .little);
425 }
426 var directory = MsfStream.init(
427 superblock.BlockSize,
428 file,
429 dir_blocks,
430 );
431
432 const begin = directory.pos;
433 const stream_count = try directory.reader().readInt(u32, .little);
434 const stream_sizes = try allocator.alloc(u32, stream_count);
435 defer allocator.free(stream_sizes);
436
437 // Microsoft's implementation uses @as(u32, -1) for inexistent streams.
438 // These streams are not used, but still participate in the file
439 // and must be taken into account when resolving stream indices.
440 const Nil = 0xFFFFFFFF;
441 for (stream_sizes) |*s| {
442 const size = try directory.reader().readInt(u32, .little);
443 s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);
444 }
445
446 const streams = try allocator.alloc(MsfStream, stream_count);
447 for (streams, 0..) |*stream, i| {
448 const size = stream_sizes[i];
449 if (size == 0) {
450 stream.* = MsfStream{
451 .blocks = &[_]u32{},
452 };
453 } else {
454 var blocks = try allocator.alloc(u32, size);
455 var j: u32 = 0;
456 while (j < size) : (j += 1) {
457 const block_id = try directory.reader().readInt(u32, .little);
458 const n = (block_id % superblock.BlockSize);
459 // 0 is for pdb.SuperBlock, 1 and 2 for FPMs.
460 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len)
461 return error.InvalidBlockIndex;
462 blocks[j] = block_id;
463 }
464
465 stream.* = MsfStream.init(
466 superblock.BlockSize,
467 file,
468 blocks,
469 );
470 }
471 }
472
473 const end = directory.pos;
474 if (end - begin != superblock.NumDirectoryBytes)
475 return error.InvalidStreamDirectory;
476
477 return Msf{
478 .directory = directory,
479 .streams = streams,
480 };
481 }
482
483 fn deinit(self: *Msf, allocator: Allocator) void {
484 allocator.free(self.directory.blocks);
485 for (self.streams) |*stream| {
486 allocator.free(stream.blocks);
487 }
488 allocator.free(self.streams);
489 }
490};
491
492const MsfStream = struct {
493 in_file: File = undefined,
494 pos: u64 = undefined,
495 blocks: []u32 = undefined,
496 block_size: u32 = undefined,
497
498 pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set;
499
500 fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
501 const stream = MsfStream{
502 .in_file = file,
503 .pos = 0,
504 .blocks = blocks,
505 .block_size = block_size,
506 };
507
508 return stream;
509 }
510
511 fn read(self: *MsfStream, buffer: []u8) !usize {
512 var block_id = @as(usize, @intCast(self.pos / self.block_size));
513 if (block_id >= self.blocks.len) return 0; // End of Stream
514 var block = self.blocks[block_id];
515 var offset = self.pos % self.block_size;
516
517 try self.in_file.seekTo(block * self.block_size + offset);
518 const in = self.in_file.reader();
519
520 var size: usize = 0;
521 var rem_buffer = buffer;
522 while (size < buffer.len) {
523 const size_to_read = @min(self.block_size - offset, rem_buffer.len);
524 size += try in.read(rem_buffer[0..size_to_read]);
525 rem_buffer = buffer[size..];
526 offset += size_to_read;
527
528 // If we're at the end of a block, go to the next one.
529 if (offset == self.block_size) {
530 offset = 0;
531 block_id += 1;
532 if (block_id >= self.blocks.len) break; // End of Stream
533 block = self.blocks[block_id];
534 try self.in_file.seekTo(block * self.block_size);
535 }
536 }
537
538 self.pos += buffer.len;
539 return buffer.len;
540 }
541
542 pub fn seekBy(self: *MsfStream, len: i64) !void {
543 self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len));
544 if (self.pos >= self.blocks.len * self.block_size)
545 return error.EOF;
546 }
547
548 pub fn seekTo(self: *MsfStream, len: u64) !void {
549 self.pos = len;
550 if (self.pos >= self.blocks.len * self.block_size)
551 return error.EOF;
552 }
553
554 fn getSize(self: *const MsfStream) u64 {
555 return self.blocks.len * self.block_size;
556 }
557
558 fn getFilePos(self: MsfStream) u64 {
559 const block_id = self.pos / self.block_size;
560 const block = self.blocks[block_id];
561 const offset = self.pos % self.block_size;
562
563 return block * self.block_size + offset;
564 }
565
566 pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) {
567 return .{ .context = self };
568 }
569};
570
571fn readSparseBitVector(stream: anytype, allocator: Allocator) ![]u32 {
572 const num_words = try stream.readInt(u32, .little);
573 var list = std.ArrayList(u32).init(allocator);
574 errdefer list.deinit();
575 var word_i: u32 = 0;
576 while (word_i != num_words) : (word_i += 1) {
577 const word = try stream.readInt(u32, .little);
578 var bit_i: u5 = 0;
579 while (true) : (bit_i += 1) {
580 if (word & (@as(u32, 1) << bit_i) != 0) {
581 try list.append(word_i * 32 + bit_i);
582 }
583 if (bit_i == std.math.maxInt(u5)) break;
584 }
585 }
586 return try list.toOwnedSlice();
587}
588
589fn blockCountFromSize(size: u32, block_size: u32) u32 {
590 return (size + block_size - 1) / block_size;
591}
lib/std/debug/SelfInfo.zig created+1371
......@@ -0,0 +1,1371 @@
1//! Cross-platform abstraction for this binary's own debug information, with a
2//! goal of minimal code bloat and compilation speed penalty.
3
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6const native_endian = native_arch.endian();
7const native_arch = builtin.cpu.arch;
8
9const std = @import("../std.zig");
10const mem = std.mem;
11const Allocator = std.mem.Allocator;
12const windows = std.os.windows;
13const macho = std.macho;
14const fs = std.fs;
15const coff = std.coff;
16const pdb = std.pdb;
17const assert = std.debug.assert;
18const posix = std.posix;
19const elf = std.elf;
20const Dwarf = std.debug.Dwarf;
21const Pdb = std.debug.Pdb;
22const File = std.fs.File;
23const math = std.math;
24const testing = std.testing;
25
26const SelfInfo = @This();
27
28const root = @import("root");
29
30allocator: Allocator,
31address_map: std.AutoHashMap(usize, *Module),
32modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,
33
34pub const OpenSelfError = error{
35 MissingDebugInfo,
36 UnsupportedOperatingSystem,
37} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).Fn.return_type.?).ErrorUnion.error_set;
38
39pub fn openSelf(allocator: Allocator) OpenSelfError!SelfInfo {
40 nosuspend {
41 if (builtin.strip_debug_info)
42 return error.MissingDebugInfo;
43 if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
44 return root.os.debug.openSelfDebugInfo(allocator);
45 }
46 switch (native_os) {
47 .linux,
48 .freebsd,
49 .netbsd,
50 .dragonfly,
51 .openbsd,
52 .macos,
53 .solaris,
54 .illumos,
55 .windows,
56 => return try SelfInfo.init(allocator),
57 else => return error.UnsupportedOperatingSystem,
58 }
59 }
60}
61
62pub fn init(allocator: Allocator) !SelfInfo {
63 var debug_info: SelfInfo = .{
64 .allocator = allocator,
65 .address_map = std.AutoHashMap(usize, *Module).init(allocator),
66 .modules = if (native_os == .windows) .{} else {},
67 };
68
69 if (native_os == .windows) {
70 errdefer debug_info.modules.deinit(allocator);
71
72 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
73 if (handle == windows.INVALID_HANDLE_VALUE) {
74 switch (windows.GetLastError()) {
75 else => |err| return windows.unexpectedError(err),
76 }
77 }
78 defer windows.CloseHandle(handle);
79
80 var module_entry: windows.MODULEENTRY32 = undefined;
81 module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
82 if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
83 return error.MissingDebugInfo;
84 }
85
86 var module_valid = true;
87 while (module_valid) {
88 const module_info = try debug_info.modules.addOne(allocator);
89 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
90 errdefer allocator.free(name);
91
92 module_info.* = .{
93 .base_address = @intFromPtr(module_entry.modBaseAddr),
94 .size = module_entry.modBaseSize,
95 .name = name,
96 .handle = module_entry.hModule,
97 };
98
99 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
100 }
101 }
102
103 return debug_info;
104}
105
106pub fn deinit(self: *SelfInfo) void {
107 var it = self.address_map.iterator();
108 while (it.next()) |entry| {
109 const mdi = entry.value_ptr.*;
110 mdi.deinit(self.allocator);
111 self.allocator.destroy(mdi);
112 }
113 self.address_map.deinit();
114 if (native_os == .windows) {
115 for (self.modules.items) |module| {
116 self.allocator.free(module.name);
117 if (module.mapped_file) |mapped_file| mapped_file.deinit();
118 }
119 self.modules.deinit(self.allocator);
120 }
121}
122
123pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
124 if (comptime builtin.target.isDarwin()) {
125 return self.lookupModuleDyld(address);
126 } else if (native_os == .windows) {
127 return self.lookupModuleWin32(address);
128 } else if (native_os == .haiku) {
129 return self.lookupModuleHaiku(address);
130 } else if (comptime builtin.target.isWasm()) {
131 return self.lookupModuleWasm(address);
132 } else {
133 return self.lookupModuleDl(address);
134 }
135}
136
137// Returns the module name for a given address.
138// This can be called when getModuleForAddress fails, so implementations should provide
139// a path that doesn't rely on any side-effects of a prior successful module lookup.
140pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 {
141 if (comptime builtin.target.isDarwin()) {
142 return self.lookupModuleNameDyld(address);
143 } else if (native_os == .windows) {
144 return self.lookupModuleNameWin32(address);
145 } else if (native_os == .haiku) {
146 return null;
147 } else if (comptime builtin.target.isWasm()) {
148 return null;
149 } else {
150 return self.lookupModuleNameDl(address);
151 }
152}
153
154fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module {
155 const image_count = std.c._dyld_image_count();
156
157 var i: u32 = 0;
158 while (i < image_count) : (i += 1) {
159 const header = std.c._dyld_get_image_header(i) orelse continue;
160 const base_address = @intFromPtr(header);
161 if (address < base_address) continue;
162 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
163
164 var it = macho.LoadCommandIterator{
165 .ncmds = header.ncmds,
166 .buffer = @alignCast(@as(
167 [*]u8,
168 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
169 )[0..header.sizeofcmds]),
170 };
171
172 var unwind_info: ?[]const u8 = null;
173 var eh_frame: ?[]const u8 = null;
174 while (it.next()) |cmd| switch (cmd.cmd()) {
175 .SEGMENT_64 => {
176 const segment_cmd = cmd.cast(macho.segment_command_64).?;
177 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
178
179 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
180 const seg_end = seg_start + segment_cmd.vmsize;
181 if (address >= seg_start and address < seg_end) {
182 if (self.address_map.get(base_address)) |obj_di| {
183 return obj_di;
184 }
185
186 for (cmd.getSections()) |sect| {
187 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
188 unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
189 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
190 eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
191 }
192 }
193
194 const obj_di = try self.allocator.create(Module);
195 errdefer self.allocator.destroy(obj_di);
196
197 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
198 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
199 error.FileNotFound => return error.MissingDebugInfo,
200 else => return err,
201 };
202 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
203 obj_di.base_address = base_address;
204 obj_di.vmaddr_slide = vmaddr_slide;
205 obj_di.unwind_info = unwind_info;
206 obj_di.eh_frame = eh_frame;
207
208 try self.address_map.putNoClobber(base_address, obj_di);
209
210 return obj_di;
211 }
212 },
213 else => {},
214 };
215 }
216
217 return error.MissingDebugInfo;
218}
219
220fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 {
221 _ = self;
222 const image_count = std.c._dyld_image_count();
223
224 var i: u32 = 0;
225 while (i < image_count) : (i += 1) {
226 const header = std.c._dyld_get_image_header(i) orelse continue;
227 const base_address = @intFromPtr(header);
228 if (address < base_address) continue;
229 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
230
231 var it = macho.LoadCommandIterator{
232 .ncmds = header.ncmds,
233 .buffer = @alignCast(@as(
234 [*]u8,
235 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
236 )[0..header.sizeofcmds]),
237 };
238
239 while (it.next()) |cmd| switch (cmd.cmd()) {
240 .SEGMENT_64 => {
241 const segment_cmd = cmd.cast(macho.segment_command_64).?;
242 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
243
244 const original_address = address - vmaddr_slide;
245 const seg_start = segment_cmd.vmaddr;
246 const seg_end = seg_start + segment_cmd.vmsize;
247 if (original_address >= seg_start and original_address < seg_end) {
248 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
249 }
250 },
251 else => {},
252 };
253 }
254
255 return null;
256}
257
258fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module {
259 for (self.modules.items) |*module| {
260 if (address >= module.base_address and address < module.base_address + module.size) {
261 if (self.address_map.get(module.base_address)) |obj_di| {
262 return obj_di;
263 }
264
265 const obj_di = try self.allocator.create(Module);
266 errdefer self.allocator.destroy(obj_di);
267
268 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
269 var coff_obj = try coff.Coff.init(mapped_module, true);
270
271 // The string table is not mapped into memory by the loader, so if a section name is in the
272 // string table then we have to map the full image file from disk. This can happen when
273 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
274 if (coff_obj.strtabRequired()) {
275 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
276 // openFileAbsoluteW requires the prefix to be present
277 @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
278
279 const process_handle = windows.GetCurrentProcess();
280 const len = windows.kernel32.GetModuleFileNameExW(
281 process_handle,
282 module.handle,
283 @ptrCast(&name_buffer[4]),
284 windows.PATH_MAX_WIDE,
285 );
286
287 if (len == 0) return error.MissingDebugInfo;
288 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
289 error.FileNotFound => return error.MissingDebugInfo,
290 else => return err,
291 };
292 errdefer coff_file.close();
293
294 var section_handle: windows.HANDLE = undefined;
295 const create_section_rc = windows.ntdll.NtCreateSection(
296 &section_handle,
297 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
298 null,
299 null,
300 windows.PAGE_READONLY,
301 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
302 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
303 windows.SEC_COMMIT,
304 coff_file.handle,
305 );
306 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
307 errdefer windows.CloseHandle(section_handle);
308
309 var coff_len: usize = 0;
310 var base_ptr: usize = 0;
311 const map_section_rc = windows.ntdll.NtMapViewOfSection(
312 section_handle,
313 process_handle,
314 @ptrCast(&base_ptr),
315 null,
316 0,
317 null,
318 &coff_len,
319 .ViewUnmap,
320 0,
321 windows.PAGE_READONLY,
322 );
323 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
324 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
325
326 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
327 coff_obj = try coff.Coff.init(section_view, false);
328
329 module.mapped_file = .{
330 .file = coff_file,
331 .section_handle = section_handle,
332 .section_view = section_view,
333 };
334 }
335 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
336
337 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
338 obj_di.base_address = module.base_address;
339
340 try self.address_map.putNoClobber(module.base_address, obj_di);
341 return obj_di;
342 }
343 }
344
345 return error.MissingDebugInfo;
346}
347
348fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 {
349 for (self.modules.items) |module| {
350 if (address >= module.base_address and address < module.base_address + module.size) {
351 return module.name;
352 }
353 }
354 return null;
355}
356
357fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 {
358 _ = self;
359
360 var ctx: struct {
361 // Input
362 address: usize,
363 // Output
364 name: []const u8 = "",
365 } = .{ .address = address };
366 const CtxTy = @TypeOf(ctx);
367
368 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
369 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
370 _ = size;
371 if (context.address < info.addr) return;
372 const phdrs = info.phdr[0..info.phnum];
373 for (phdrs) |*phdr| {
374 if (phdr.p_type != elf.PT_LOAD) continue;
375
376 const seg_start = info.addr +% phdr.p_vaddr;
377 const seg_end = seg_start + phdr.p_memsz;
378 if (context.address >= seg_start and context.address < seg_end) {
379 context.name = mem.sliceTo(info.name, 0) orelse "";
380 break;
381 }
382 } else return;
383
384 return error.Found;
385 }
386 }.callback)) {
387 return null;
388 } else |err| switch (err) {
389 error.Found => return fs.path.basename(ctx.name),
390 }
391
392 return null;
393}
394
395fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
396 var ctx: struct {
397 // Input
398 address: usize,
399 // Output
400 base_address: usize = undefined,
401 name: []const u8 = undefined,
402 build_id: ?[]const u8 = null,
403 gnu_eh_frame: ?[]const u8 = null,
404 } = .{ .address = address };
405 const CtxTy = @TypeOf(ctx);
406
407 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
408 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
409 _ = size;
410 // The base address is too high
411 if (context.address < info.addr)
412 return;
413
414 const phdrs = info.phdr[0..info.phnum];
415 for (phdrs) |*phdr| {
416 if (phdr.p_type != elf.PT_LOAD) continue;
417
418 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
419 const seg_start = info.addr +% phdr.p_vaddr;
420 const seg_end = seg_start + phdr.p_memsz;
421 if (context.address >= seg_start and context.address < seg_end) {
422 // Android libc uses NULL instead of an empty string to mark the
423 // main program
424 context.name = mem.sliceTo(info.name, 0) orelse "";
425 context.base_address = info.addr;
426 break;
427 }
428 } else return;
429
430 for (info.phdr[0..info.phnum]) |phdr| {
431 switch (phdr.p_type) {
432 elf.PT_NOTE => {
433 // Look for .note.gnu.build-id
434 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
435 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
436 if (name_size != 4) continue;
437 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
438 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
439 if (note_type != elf.NT_GNU_BUILD_ID) continue;
440 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
441 context.build_id = note_bytes[16..][0..desc_size];
442 },
443 elf.PT_GNU_EH_FRAME => {
444 context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
445 },
446 else => {},
447 }
448 }
449
450 // Stop the iteration
451 return error.Found;
452 }
453 }.callback)) {
454 return error.MissingDebugInfo;
455 } else |err| switch (err) {
456 error.Found => {},
457 }
458
459 if (self.address_map.get(ctx.base_address)) |obj_di| {
460 return obj_di;
461 }
462
463 const obj_di = try self.allocator.create(Module);
464 errdefer self.allocator.destroy(obj_di);
465
466 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
467 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
468 // This is a special case - pointer offsets inside .eh_frame_hdr
469 // are encoded relative to its base address, so we must use the
470 // version that is already memory mapped, and not the one that
471 // will be mapped separately from the ELF file.
472 sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{
473 .data = eh_frame_hdr,
474 .owned = false,
475 };
476 }
477
478 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
479 obj_di.base_address = ctx.base_address;
480
481 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
482 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
483
484 try self.address_map.putNoClobber(ctx.base_address, obj_di);
485
486 return obj_di;
487}
488
489fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module {
490 _ = self;
491 _ = address;
492 @panic("TODO implement lookup module for Haiku");
493}
494
495fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module {
496 _ = self;
497 _ = address;
498 @panic("TODO implement lookup module for Wasm");
499}
500
501pub const Module = switch (native_os) {
502 .macos, .ios, .watchos, .tvos, .visionos => struct {
503 base_address: usize,
504 vmaddr_slide: usize,
505 mapped_memory: []align(mem.page_size) const u8,
506 symbols: []const MachoSymbol,
507 strings: [:0]const u8,
508 ofiles: OFileTable,
509
510 // Backed by the in-memory sections mapped by the loader
511 unwind_info: ?[]const u8 = null,
512 eh_frame: ?[]const u8 = null,
513
514 const OFileTable = std.StringHashMap(OFileInfo);
515 const OFileInfo = struct {
516 di: Dwarf,
517 addr_table: std.StringHashMap(u64),
518 };
519
520 pub fn deinit(self: *@This(), allocator: Allocator) void {
521 var it = self.ofiles.iterator();
522 while (it.next()) |entry| {
523 const ofile = entry.value_ptr;
524 ofile.di.deinit(allocator);
525 ofile.addr_table.deinit();
526 }
527 self.ofiles.deinit();
528 allocator.free(self.symbols);
529 posix.munmap(self.mapped_memory);
530 }
531
532 fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo {
533 const o_file = try fs.cwd().openFile(o_file_path, .{});
534 const mapped_mem = try mapWholeFile(o_file);
535
536 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
537 if (hdr.magic != std.macho.MH_MAGIC_64)
538 return error.InvalidDebugInfo;
539
540 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
541 var symtabcmd: ?macho.symtab_command = null;
542 var it = macho.LoadCommandIterator{
543 .ncmds = hdr.ncmds,
544 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
545 };
546 while (it.next()) |cmd| switch (cmd.cmd()) {
547 .SEGMENT_64 => segcmd = cmd,
548 .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
549 else => {},
550 };
551
552 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
553
554 // Parse symbols
555 const strtab = @as(
556 [*]const u8,
557 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
558 )[0 .. symtabcmd.?.strsize - 1 :0];
559 const symtab = @as(
560 [*]const macho.nlist_64,
561 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
562 )[0..symtabcmd.?.nsyms];
563
564 // TODO handle tentative (common) symbols
565 var addr_table = std.StringHashMap(u64).init(allocator);
566 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
567 for (symtab) |sym| {
568 if (sym.n_strx == 0) continue;
569 if (sym.undf() or sym.tentative() or sym.abs()) continue;
570 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
571 // TODO is it possible to have a symbol collision?
572 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
573 }
574
575 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
576 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
577 .data = eh_frame,
578 .owned = false,
579 };
580
581 for (segcmd.?.getSections()) |sect| {
582 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
583
584 var section_index: ?usize = null;
585 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
586 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
587 }
588 if (section_index == null) continue;
589
590 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
591 sections[section_index.?] = .{
592 .data = section_bytes,
593 .virtual_address = sect.addr,
594 .owned = false,
595 };
596 }
597
598 const missing_debug_info =
599 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
600 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
601 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
602 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
603 if (missing_debug_info) return error.MissingDebugInfo;
604
605 var di = Dwarf{
606 .endian = .little,
607 .sections = sections,
608 .is_macho = true,
609 };
610
611 try Dwarf.open(&di, allocator);
612 const info = OFileInfo{
613 .di = di,
614 .addr_table = addr_table,
615 };
616
617 // Add the debug info to the cache
618 const result = try self.ofiles.getOrPut(o_file_path);
619 assert(!result.found_existing);
620 result.value_ptr.* = info;
621
622 return result.value_ptr;
623 }
624
625 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
626 nosuspend {
627 const result = try self.getOFileInfoForAddress(allocator, address);
628 if (result.symbol == null) return .{};
629
630 // Take the symbol name from the N_FUN STAB entry, we're going to
631 // use it if we fail to find the DWARF infos
632 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
633 if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
634
635 // Translate again the address, this time into an address inside the
636 // .o file
637 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
638 .symbol_name = "???",
639 };
640
641 const addr_off = result.relocated_address - result.symbol.?.addr;
642 const o_file_di = &result.o_file_info.?.di;
643 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
644 return SymbolInfo{
645 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
646 .compile_unit_name = compile_unit.die.getAttrString(
647 o_file_di,
648 std.dwarf.AT.name,
649 o_file_di.section(.debug_str),
650 compile_unit.*,
651 ) catch |err| switch (err) {
652 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
653 },
654 .line_info = o_file_di.getLineNumberInfo(
655 allocator,
656 compile_unit.*,
657 relocated_address_o + addr_off,
658 ) catch |err| switch (err) {
659 error.MissingDebugInfo, error.InvalidDebugInfo => null,
660 else => return err,
661 },
662 };
663 } else |err| switch (err) {
664 error.MissingDebugInfo, error.InvalidDebugInfo => {
665 return SymbolInfo{ .symbol_name = stab_symbol };
666 },
667 else => return err,
668 }
669 }
670 }
671
672 pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct {
673 relocated_address: usize,
674 symbol: ?*const MachoSymbol = null,
675 o_file_info: ?*OFileInfo = null,
676 } {
677 nosuspend {
678 // Translate the VA into an address into this object
679 const relocated_address = address - self.vmaddr_slide;
680
681 // Find the .o file where this symbol is defined
682 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
683 .relocated_address = relocated_address,
684 };
685
686 // Check if its debug infos are already in the cache
687 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
688 const o_file_info = self.ofiles.getPtr(o_file_path) orelse
689 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
690 error.FileNotFound,
691 error.MissingDebugInfo,
692 error.InvalidDebugInfo,
693 => return .{
694 .relocated_address = relocated_address,
695 .symbol = symbol,
696 },
697 else => return err,
698 });
699
700 return .{
701 .relocated_address = relocated_address,
702 .symbol = symbol,
703 .o_file_info = o_file_info,
704 };
705 }
706 }
707
708 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
709 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
710 }
711 },
712 .uefi, .windows => struct {
713 base_address: usize,
714 pdb: ?Pdb = null,
715 dwarf: ?Dwarf = null,
716 coff_image_base: u64,
717
718 /// Only used if pdb is non-null
719 coff_section_headers: []coff.SectionHeader,
720
721 pub fn deinit(self: *@This(), allocator: Allocator) void {
722 if (self.dwarf) |*dwarf| {
723 dwarf.deinit(allocator);
724 }
725
726 if (self.pdb) |*p| {
727 p.deinit();
728 allocator.free(self.coff_section_headers);
729 }
730 }
731
732 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo {
733 var coff_section: *align(1) const coff.SectionHeader = undefined;
734 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
735 if (sect_contrib.Section > self.coff_section_headers.len) continue;
736 // Remember that SectionContribEntry.Section is 1-based.
737 coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
738
739 const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
740 const vaddr_end = vaddr_start + sect_contrib.Size;
741 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
742 break sect_contrib.ModuleIndex;
743 }
744 } else {
745 // we have no information to add to the address
746 return null;
747 };
748
749 const module = (try self.pdb.?.getModule(mod_index)) orelse
750 return error.InvalidDebugInfo;
751 const obj_basename = fs.path.basename(module.obj_file_name);
752
753 const symbol_name = self.pdb.?.getSymbolName(
754 module,
755 relocated_address - coff_section.virtual_address,
756 ) orelse "???";
757 const opt_line_info = try self.pdb.?.getLineNumberInfo(
758 module,
759 relocated_address - coff_section.virtual_address,
760 );
761
762 return SymbolInfo{
763 .symbol_name = symbol_name,
764 .compile_unit_name = obj_basename,
765 .line_info = opt_line_info,
766 };
767 }
768
769 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
770 // Translate the VA into an address into this object
771 const relocated_address = address - self.base_address;
772
773 if (self.pdb != null) {
774 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
775 }
776
777 if (self.dwarf) |*dwarf| {
778 const dwarf_address = relocated_address + self.coff_image_base;
779 return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
780 }
781
782 return SymbolInfo{};
783 }
784
785 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
786 _ = allocator;
787 _ = address;
788
789 return switch (self.debug_data) {
790 .dwarf => |*dwarf| dwarf,
791 else => null,
792 };
793 }
794 },
795 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
796 base_address: usize,
797 dwarf: Dwarf,
798 mapped_memory: []align(mem.page_size) const u8,
799 external_mapped_memory: ?[]align(mem.page_size) const u8,
800
801 pub fn deinit(self: *@This(), allocator: Allocator) void {
802 self.dwarf.deinit(allocator);
803 posix.munmap(self.mapped_memory);
804 if (self.external_mapped_memory) |m| posix.munmap(m);
805 }
806
807 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
808 // Translate the VA into an address into this object
809 const relocated_address = address - self.base_address;
810 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
811 }
812
813 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
814 _ = allocator;
815 _ = address;
816 return &self.dwarf;
817 }
818 },
819 .wasi, .emscripten => struct {
820 pub fn deinit(self: *@This(), allocator: Allocator) void {
821 _ = self;
822 _ = allocator;
823 }
824
825 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
826 _ = self;
827 _ = allocator;
828 _ = address;
829 return SymbolInfo{};
830 }
831
832 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
833 _ = self;
834 _ = allocator;
835 _ = address;
836 return null;
837 }
838 },
839 else => Dwarf,
840};
841
842/// How is this different than `Module` when the host is Windows?
843/// Why are both stored in the `SelfInfo` struct?
844/// Boy, it sure would be nice if someone added documentation comments for this
845/// struct explaining it.
846pub const WindowsModule = struct {
847 base_address: usize,
848 size: u32,
849 name: []const u8,
850 handle: windows.HMODULE,
851
852 // Set when the image file needed to be mapped from disk
853 mapped_file: ?struct {
854 file: File,
855 section_handle: windows.HANDLE,
856 section_view: []const u8,
857
858 pub fn deinit(self: @This()) void {
859 const process_handle = windows.GetCurrentProcess();
860 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
861 windows.CloseHandle(self.section_handle);
862 self.file.close();
863 }
864 } = null,
865};
866
867/// This takes ownership of macho_file: users of this function should not close
868/// it themselves, even on error.
869/// TODO it's weird to take ownership even on error, rework this code.
870fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
871 const mapped_mem = try mapWholeFile(macho_file);
872
873 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
874 if (hdr.magic != macho.MH_MAGIC_64)
875 return error.InvalidDebugInfo;
876
877 var it = macho.LoadCommandIterator{
878 .ncmds = hdr.ncmds,
879 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
880 };
881 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
882 .SYMTAB => break cmd.cast(macho.symtab_command).?,
883 else => {},
884 } else return error.MissingDebugInfo;
885
886 const syms = @as(
887 [*]const macho.nlist_64,
888 @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
889 )[0..symtab.nsyms];
890 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
891
892 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
893
894 var ofile: u32 = undefined;
895 var last_sym: MachoSymbol = undefined;
896 var symbol_index: usize = 0;
897 var state: enum {
898 init,
899 oso_open,
900 oso_close,
901 bnsym,
902 fun_strx,
903 fun_size,
904 ensym,
905 } = .init;
906
907 for (syms) |*sym| {
908 if (!sym.stab()) continue;
909
910 // TODO handle globals N_GSYM, and statics N_STSYM
911 switch (sym.n_type) {
912 macho.N_OSO => {
913 switch (state) {
914 .init, .oso_close => {
915 state = .oso_open;
916 ofile = sym.n_strx;
917 },
918 else => return error.InvalidDebugInfo,
919 }
920 },
921 macho.N_BNSYM => {
922 switch (state) {
923 .oso_open, .ensym => {
924 state = .bnsym;
925 last_sym = .{
926 .strx = 0,
927 .addr = sym.n_value,
928 .size = 0,
929 .ofile = ofile,
930 };
931 },
932 else => return error.InvalidDebugInfo,
933 }
934 },
935 macho.N_FUN => {
936 switch (state) {
937 .bnsym => {
938 state = .fun_strx;
939 last_sym.strx = sym.n_strx;
940 },
941 .fun_strx => {
942 state = .fun_size;
943 last_sym.size = @as(u32, @intCast(sym.n_value));
944 },
945 else => return error.InvalidDebugInfo,
946 }
947 },
948 macho.N_ENSYM => {
949 switch (state) {
950 .fun_size => {
951 state = .ensym;
952 symbols_buf[symbol_index] = last_sym;
953 symbol_index += 1;
954 },
955 else => return error.InvalidDebugInfo,
956 }
957 },
958 macho.N_SO => {
959 switch (state) {
960 .init, .oso_close => {},
961 .oso_open, .ensym => {
962 state = .oso_close;
963 },
964 else => return error.InvalidDebugInfo,
965 }
966 },
967 else => {},
968 }
969 }
970
971 switch (state) {
972 .init => return error.MissingDebugInfo,
973 .oso_close => {},
974 else => return error.InvalidDebugInfo,
975 }
976
977 const symbols = try allocator.realloc(symbols_buf, symbol_index);
978
979 // Even though lld emits symbols in ascending order, this debug code
980 // should work for programs linked in any valid way.
981 // This sort is so that we can binary search later.
982 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
983
984 return .{
985 .base_address = undefined,
986 .vmaddr_slide = undefined,
987 .mapped_memory = mapped_mem,
988 .ofiles = Module.OFileTable.init(allocator),
989 .symbols = symbols,
990 .strings = strings,
991 };
992}
993
994fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
995 nosuspend {
996 var di: Module = .{
997 .base_address = undefined,
998 .coff_image_base = coff_obj.getImageBase(),
999 .coff_section_headers = undefined,
1000 };
1001
1002 if (coff_obj.getSectionByName(".debug_info")) |_| {
1003 // This coff file has embedded DWARF debug info
1004 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1005 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1006
1007 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1008 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1009 break :blk .{
1010 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1011 .virtual_address = section_header.virtual_address,
1012 .owned = true,
1013 };
1014 } else null;
1015 }
1016
1017 var dwarf = Dwarf{
1018 .endian = native_endian,
1019 .sections = sections,
1020 .is_macho = false,
1021 };
1022
1023 try Dwarf.open(&dwarf, allocator);
1024 di.dwarf = dwarf;
1025 }
1026
1027 const raw_path = try coff_obj.getPdbPath() orelse return di;
1028 const path = blk: {
1029 if (fs.path.isAbsolute(raw_path)) {
1030 break :blk raw_path;
1031 } else {
1032 const self_dir = try fs.selfExeDirPathAlloc(allocator);
1033 defer allocator.free(self_dir);
1034 break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
1035 }
1036 };
1037 defer if (path.ptr != raw_path.ptr) allocator.free(path);
1038
1039 di.pdb = Pdb.init(allocator, path) catch |err| switch (err) {
1040 error.FileNotFound, error.IsDir => {
1041 if (di.dwarf == null) return error.MissingDebugInfo;
1042 return di;
1043 },
1044 else => return err,
1045 };
1046 try di.pdb.?.parseInfoStream();
1047 try di.pdb.?.parseDbiStream();
1048
1049 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
1050 return error.InvalidDebugInfo;
1051
1052 // Only used by the pdb path
1053 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1054 errdefer allocator.free(di.coff_section_headers);
1055
1056 return di;
1057 }
1058}
1059
1060/// Reads debug info from an ELF file, or the current binary if none in specified.
1061/// If the required sections aren't present but a reference to external debug info is,
1062/// then this this function will recurse to attempt to load the debug sections from
1063/// an external file.
1064pub fn readElfDebugInfo(
1065 allocator: Allocator,
1066 elf_filename: ?[]const u8,
1067 build_id: ?[]const u8,
1068 expected_crc: ?u32,
1069 parent_sections: *Dwarf.SectionArray,
1070 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1071) !Module {
1072 nosuspend {
1073 const elf_file = (if (elf_filename) |filename| blk: {
1074 break :blk fs.cwd().openFile(filename, .{});
1075 } else fs.openSelfExe(.{})) catch |err| switch (err) {
1076 error.FileNotFound => return error.MissingDebugInfo,
1077 else => return err,
1078 };
1079
1080 const mapped_mem = try mapWholeFile(elf_file);
1081 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
1082
1083 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
1084 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
1085 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
1086
1087 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
1088 elf.ELFDATA2LSB => .little,
1089 elf.ELFDATA2MSB => .big,
1090 else => return error.InvalidElfEndian,
1091 };
1092 assert(endian == native_endian); // this is our own debug info
1093
1094 const shoff = hdr.e_shoff;
1095 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1096 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1097 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1098 const shdrs = @as(
1099 [*]const elf.Shdr,
1100 @ptrCast(@alignCast(&mapped_mem[shoff])),
1101 )[0..hdr.e_shnum];
1102
1103 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1104
1105 // Combine section list. This takes ownership over any owned sections from the parent scope.
1106 for (parent_sections, &sections) |*parent, *section| {
1107 if (parent.*) |*p| {
1108 section.* = p.*;
1109 p.owned = false;
1110 }
1111 }
1112 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1113
1114 var separate_debug_filename: ?[]const u8 = null;
1115 var separate_debug_crc: ?u32 = null;
1116
1117 for (shdrs) |*shdr| {
1118 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1119 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1120
1121 if (mem.eql(u8, name, ".gnu_debuglink")) {
1122 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1123 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1124 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1125 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1126 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
1127 separate_debug_filename = debug_filename;
1128 continue;
1129 }
1130
1131 var section_index: ?usize = null;
1132 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1133 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1134 }
1135 if (section_index == null) continue;
1136 if (sections[section_index.?] != null) continue;
1137
1138 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1139 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1140 var section_stream = std.io.fixedBufferStream(section_bytes);
1141 var section_reader = section_stream.reader();
1142 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1143 if (chdr.ch_type != .ZLIB) continue;
1144
1145 var zlib_stream = std.compress.zlib.decompressor(section_stream.reader());
1146
1147 const decompressed_section = try allocator.alloc(u8, chdr.ch_size);
1148 errdefer allocator.free(decompressed_section);
1149
1150 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
1151 assert(read == decompressed_section.len);
1152
1153 break :blk .{
1154 .data = decompressed_section,
1155 .virtual_address = shdr.sh_addr,
1156 .owned = true,
1157 };
1158 } else .{
1159 .data = section_bytes,
1160 .virtual_address = shdr.sh_addr,
1161 .owned = false,
1162 };
1163 }
1164
1165 const missing_debug_info =
1166 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
1167 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1168 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1169 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
1170
1171 // Attempt to load debug info from an external file
1172 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1173 if (missing_debug_info) {
1174
1175 // Only allow one level of debug info nesting
1176 if (parent_mapped_mem) |_| {
1177 return error.MissingDebugInfo;
1178 }
1179
1180 const global_debug_directories = [_][]const u8{
1181 "/usr/lib/debug",
1182 };
1183
1184 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1185 if (build_id) |id| blk: {
1186 if (id.len < 3) break :blk;
1187
1188 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1189 const extension = ".debug";
1190 var id_prefix_buf: [2]u8 = undefined;
1191 var filename_buf: [38 + extension.len]u8 = undefined;
1192
1193 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
1194 const filename = std.fmt.bufPrint(
1195 &filename_buf,
1196 "{s}" ++ extension,
1197 .{std.fmt.fmtSliceHexLower(id[1..])},
1198 ) catch break :blk;
1199
1200 for (global_debug_directories) |global_directory| {
1201 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
1202 defer allocator.free(path);
1203
1204 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1205 }
1206 }
1207
1208 // use the path from .gnu_debuglink, in the same search order as gdb
1209 if (separate_debug_filename) |separate_filename| blk: {
1210 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
1211
1212 // <cwd>/<gnu_debuglink>
1213 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1214
1215 // <cwd>/.debug/<gnu_debuglink>
1216 {
1217 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
1218 defer allocator.free(path);
1219
1220 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1221 }
1222
1223 var cwd_buf: [fs.max_path_bytes]u8 = undefined;
1224 const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk;
1225
1226 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1227 for (global_debug_directories) |global_directory| {
1228 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
1229 defer allocator.free(path);
1230 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1231 }
1232 }
1233
1234 return error.MissingDebugInfo;
1235 }
1236
1237 var di = Dwarf{
1238 .endian = endian,
1239 .sections = sections,
1240 .is_macho = false,
1241 };
1242
1243 try Dwarf.open(&di, allocator);
1244
1245 return .{
1246 .base_address = undefined,
1247 .dwarf = di,
1248 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1249 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1250 };
1251 }
1252}
1253
1254const MachoSymbol = struct {
1255 strx: u32,
1256 addr: u64,
1257 size: u32,
1258 ofile: u32,
1259
1260 /// Returns the address from the macho file
1261 fn address(self: MachoSymbol) u64 {
1262 return self.addr;
1263 }
1264
1265 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1266 _ = context;
1267 return lhs.addr < rhs.addr;
1268 }
1269};
1270
1271/// Takes ownership of file, even on error.
1272/// TODO it's weird to take ownership even on error, rework this code.
1273fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
1274 nosuspend {
1275 defer file.close();
1276
1277 const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
1278 const mapped_mem = try posix.mmap(
1279 null,
1280 file_len,
1281 posix.PROT.READ,
1282 .{ .TYPE = .SHARED },
1283 file.handle,
1284 0,
1285 );
1286 errdefer posix.munmap(mapped_mem);
1287
1288 return mapped_mem;
1289 }
1290}
1291
1292fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1293 const start = math.cast(usize, offset) orelse return error.Overflow;
1294 const end = start + (math.cast(usize, size) orelse return error.Overflow);
1295 return ptr[start..end];
1296}
1297
1298pub const SymbolInfo = struct {
1299 symbol_name: []const u8 = "???",
1300 compile_unit_name: []const u8 = "???",
1301 line_info: ?std.debug.SourceLocation = null,
1302
1303 pub fn deinit(self: SymbolInfo, allocator: Allocator) void {
1304 if (self.line_info) |li| allocator.free(li.file_name);
1305 }
1306};
1307
1308fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
1309 var min: usize = 0;
1310 var max: usize = symbols.len - 1;
1311 while (min < max) {
1312 const mid = min + (max - min) / 2;
1313 const curr = &symbols[mid];
1314 const next = &symbols[mid + 1];
1315 if (address >= next.address()) {
1316 min = mid + 1;
1317 } else if (address < curr.address()) {
1318 max = mid;
1319 } else {
1320 return curr;
1321 }
1322 }
1323
1324 const max_sym = &symbols[symbols.len - 1];
1325 if (address >= max_sym.address())
1326 return max_sym;
1327
1328 return null;
1329}
1330
1331test machoSearchSymbols {
1332 const symbols = [_]MachoSymbol{
1333 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
1334 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
1335 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
1336 };
1337
1338 try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
1339 try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
1340 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
1341 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
1342 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
1343
1344 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
1345 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
1346 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
1347
1348 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
1349 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
1350 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1351}
1352
1353fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo {
1354 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
1355 return SymbolInfo{
1356 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
1357 .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
1358 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
1359 },
1360 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
1361 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1362 else => return err,
1363 },
1364 };
1365 } else |err| switch (err) {
1366 error.MissingDebugInfo, error.InvalidDebugInfo => {
1367 return SymbolInfo{};
1368 },
1369 else => return err,
1370 }
1371}
lib/std/pdb.zig+11-596
......@@ -1,3 +1,12 @@
1//! Program Data Base debugging information format.
2//!
3//! This namespace contains unopinionated types and data definitions only. For
4//! an implementation of parsing and caching PDB information, see
5//! `std.debug.Pdb`.
6//!
7//! Most of this is based on information gathered from LLVM source code,
8//! documentation and/or contributors.
9
110const std = @import("std.zig");
211const io = std.io;
312const math = std.math;
......@@ -9,10 +18,7 @@ const debug = std.debug;
918
1019const ArrayList = std.ArrayList;
1120
12// Note: most of this is based on information gathered from LLVM source code,
13// documentation and/or contributors.
14
15// https://llvm.org/docs/PDB/DbiStream.html#stream-header
21/// https://llvm.org/docs/PDB/DbiStream.html#stream-header
1622pub const DbiStreamHeader = extern struct {
1723 VersionSignature: i32,
1824 VersionHeader: u32,
......@@ -415,10 +421,8 @@ pub const ColumnNumberEntry = extern struct {
415421pub const FileChecksumEntryHeader = extern struct {
416422 /// Byte offset of filename in global string table.
417423 FileNameOffset: u32,
418
419424 /// Number of bytes of checksum.
420425 ChecksumSize: u8,
421
422426 /// FileChecksumKind
423427 ChecksumKind: u8,
424428};
......@@ -451,525 +455,15 @@ pub const DebugSubsectionHeader = extern struct {
451455 Length: u32,
452456};
453457
454pub const PDBStringTableHeader = extern struct {
458pub const StringTableHeader = extern struct {
455459 /// PDBStringTableSignature
456460 Signature: u32,
457
458461 /// 1 or 2
459462 HashVersion: u32,
460
461463 /// Number of bytes of names buffer.
462464 ByteSize: u32,
463465};
464466
465fn readSparseBitVector(stream: anytype, allocator: mem.Allocator) ![]u32 {
466 const num_words = try stream.readInt(u32, .little);
467 var list = ArrayList(u32).init(allocator);
468 errdefer list.deinit();
469 var word_i: u32 = 0;
470 while (word_i != num_words) : (word_i += 1) {
471 const word = try stream.readInt(u32, .little);
472 var bit_i: u5 = 0;
473 while (true) : (bit_i += 1) {
474 if (word & (@as(u32, 1) << bit_i) != 0) {
475 try list.append(word_i * 32 + bit_i);
476 }
477 if (bit_i == std.math.maxInt(u5)) break;
478 }
479 }
480 return try list.toOwnedSlice();
481}
482
483pub const Pdb = struct {
484 in_file: File,
485 msf: Msf,
486 allocator: mem.Allocator,
487 string_table: ?*MsfStream,
488 dbi: ?*MsfStream,
489 modules: []Module,
490 sect_contribs: []SectionContribEntry,
491 guid: [16]u8,
492 age: u32,
493
494 pub const Module = struct {
495 mod_info: ModInfo,
496 module_name: []u8,
497 obj_file_name: []u8,
498 // The fields below are filled on demand.
499 populated: bool,
500 symbols: []u8,
501 subsect_info: []u8,
502 checksum_offset: ?usize,
503
504 pub fn deinit(self: *Module, allocator: mem.Allocator) void {
505 allocator.free(self.module_name);
506 allocator.free(self.obj_file_name);
507 if (self.populated) {
508 allocator.free(self.symbols);
509 allocator.free(self.subsect_info);
510 }
511 }
512 };
513
514 pub fn init(allocator: mem.Allocator, path: []const u8) !Pdb {
515 const file = try fs.cwd().openFile(path, .{});
516 errdefer file.close();
517
518 return Pdb{
519 .in_file = file,
520 .allocator = allocator,
521 .string_table = null,
522 .dbi = null,
523 .msf = try Msf.init(allocator, file),
524 .modules = &[_]Module{},
525 .sect_contribs = &[_]SectionContribEntry{},
526 .guid = undefined,
527 .age = undefined,
528 };
529 }
530
531 pub fn deinit(self: *Pdb) void {
532 self.in_file.close();
533 self.msf.deinit(self.allocator);
534 for (self.modules) |*module| {
535 module.deinit(self.allocator);
536 }
537 self.allocator.free(self.modules);
538 self.allocator.free(self.sect_contribs);
539 }
540
541 pub fn parseDbiStream(self: *Pdb) !void {
542 var stream = self.getStream(StreamType.Dbi) orelse
543 return error.InvalidDebugInfo;
544 const reader = stream.reader();
545
546 const header = try reader.readStruct(DbiStreamHeader);
547 if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team
548 return error.UnknownPDBVersion;
549 // if (header.Age != age)
550 // return error.UnmatchingPDB;
551
552 const mod_info_size = header.ModInfoSize;
553 const section_contrib_size = header.SectionContributionSize;
554
555 var modules = ArrayList(Module).init(self.allocator);
556 errdefer modules.deinit();
557
558 // Module Info Substream
559 var mod_info_offset: usize = 0;
560 while (mod_info_offset != mod_info_size) {
561 const mod_info = try reader.readStruct(ModInfo);
562 var this_record_len: usize = @sizeOf(ModInfo);
563
564 const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
565 errdefer self.allocator.free(module_name);
566 this_record_len += module_name.len + 1;
567
568 const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
569 errdefer self.allocator.free(obj_file_name);
570 this_record_len += obj_file_name.len + 1;
571
572 if (this_record_len % 4 != 0) {
573 const round_to_next_4 = (this_record_len | 0x3) + 1;
574 const march_forward_bytes = round_to_next_4 - this_record_len;
575 try stream.seekBy(@as(isize, @intCast(march_forward_bytes)));
576 this_record_len += march_forward_bytes;
577 }
578
579 try modules.append(Module{
580 .mod_info = mod_info,
581 .module_name = module_name,
582 .obj_file_name = obj_file_name,
583
584 .populated = false,
585 .symbols = undefined,
586 .subsect_info = undefined,
587 .checksum_offset = null,
588 });
589
590 mod_info_offset += this_record_len;
591 if (mod_info_offset > mod_info_size)
592 return error.InvalidDebugInfo;
593 }
594
595 // Section Contribution Substream
596 var sect_contribs = ArrayList(SectionContribEntry).init(self.allocator);
597 errdefer sect_contribs.deinit();
598
599 var sect_cont_offset: usize = 0;
600 if (section_contrib_size != 0) {
601 const version = reader.readEnum(SectionContrSubstreamVersion, .little) catch |err| switch (err) {
602 error.InvalidValue => return error.InvalidDebugInfo,
603 else => |e| return e,
604 };
605 _ = version;
606 sect_cont_offset += @sizeOf(u32);
607 }
608 while (sect_cont_offset != section_contrib_size) {
609 const entry = try sect_contribs.addOne();
610 entry.* = try reader.readStruct(SectionContribEntry);
611 sect_cont_offset += @sizeOf(SectionContribEntry);
612
613 if (sect_cont_offset > section_contrib_size)
614 return error.InvalidDebugInfo;
615 }
616
617 self.modules = try modules.toOwnedSlice();
618 self.sect_contribs = try sect_contribs.toOwnedSlice();
619 }
620
621 pub fn parseInfoStream(self: *Pdb) !void {
622 var stream = self.getStream(StreamType.Pdb) orelse
623 return error.InvalidDebugInfo;
624 const reader = stream.reader();
625
626 // Parse the InfoStreamHeader.
627 const version = try reader.readInt(u32, .little);
628 const signature = try reader.readInt(u32, .little);
629 _ = signature;
630 const age = try reader.readInt(u32, .little);
631 const guid = try reader.readBytesNoEof(16);
632
633 if (version != 20000404) // VC70, only value observed by LLVM team
634 return error.UnknownPDBVersion;
635
636 self.guid = guid;
637 self.age = age;
638
639 // Find the string table.
640 const string_table_index = str_tab_index: {
641 const name_bytes_len = try reader.readInt(u32, .little);
642 const name_bytes = try self.allocator.alloc(u8, name_bytes_len);
643 defer self.allocator.free(name_bytes);
644 try reader.readNoEof(name_bytes);
645
646 const HashTableHeader = extern struct {
647 Size: u32,
648 Capacity: u32,
649
650 fn maxLoad(cap: u32) u32 {
651 return cap * 2 / 3 + 1;
652 }
653 };
654 const hash_tbl_hdr = try reader.readStruct(HashTableHeader);
655 if (hash_tbl_hdr.Capacity == 0)
656 return error.InvalidDebugInfo;
657
658 if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
659 return error.InvalidDebugInfo;
660
661 const present = try readSparseBitVector(&reader, self.allocator);
662 defer self.allocator.free(present);
663 if (present.len != hash_tbl_hdr.Size)
664 return error.InvalidDebugInfo;
665 const deleted = try readSparseBitVector(&reader, self.allocator);
666 defer self.allocator.free(deleted);
667
668 for (present) |_| {
669 const name_offset = try reader.readInt(u32, .little);
670 const name_index = try reader.readInt(u32, .little);
671 if (name_offset > name_bytes.len)
672 return error.InvalidDebugInfo;
673 const name = mem.sliceTo(name_bytes[name_offset..], 0);
674 if (mem.eql(u8, name, "/names")) {
675 break :str_tab_index name_index;
676 }
677 }
678 return error.MissingDebugInfo;
679 };
680
681 self.string_table = self.getStreamById(string_table_index) orelse
682 return error.MissingDebugInfo;
683 }
684
685 pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 {
686 _ = self;
687 std.debug.assert(module.populated);
688
689 var symbol_i: usize = 0;
690 while (symbol_i != module.symbols.len) {
691 const prefix = @as(*align(1) RecordPrefix, @ptrCast(&module.symbols[symbol_i]));
692 if (prefix.RecordLen < 2)
693 return null;
694 switch (prefix.RecordKind) {
695 .S_LPROC32, .S_GPROC32 => {
696 const proc_sym = @as(*align(1) ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(RecordPrefix)]));
697 if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) {
698 return mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0);
699 }
700 },
701 else => {},
702 }
703 symbol_i += prefix.RecordLen + @sizeOf(u16);
704 }
705
706 return null;
707 }
708
709 pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.Info.SourceLocation {
710 std.debug.assert(module.populated);
711 const subsect_info = module.subsect_info;
712
713 var sect_offset: usize = 0;
714 var skip_len: usize = undefined;
715 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
716 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
717 const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset]));
718 skip_len = subsect_hdr.Length;
719 sect_offset += @sizeOf(DebugSubsectionHeader);
720
721 switch (subsect_hdr.Kind) {
722 .Lines => {
723 var line_index = sect_offset;
724
725 const line_hdr = @as(*align(1) LineFragmentHeader, @ptrCast(&subsect_info[line_index]));
726 if (line_hdr.RelocSegment == 0)
727 return error.MissingDebugInfo;
728 line_index += @sizeOf(LineFragmentHeader);
729 const frag_vaddr_start = line_hdr.RelocOffset;
730 const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
731
732 if (address >= frag_vaddr_start and address < frag_vaddr_end) {
733 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
734 // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in,
735 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
736 const subsection_end_index = sect_offset + subsect_hdr.Length;
737
738 while (line_index < subsection_end_index) {
739 const block_hdr = @as(*align(1) LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index]));
740 line_index += @sizeOf(LineBlockFragmentHeader);
741 const start_line_index = line_index;
742
743 const has_column = line_hdr.Flags.LF_HaveColumns;
744
745 // All line entries are stored inside their line block by ascending start address.
746 // Heuristic: we want to find the last line entry
747 // that has a vaddr_start <= address.
748 // This is done with a simple linear search.
749 var line_i: u32 = 0;
750 while (line_i < block_hdr.NumLines) : (line_i += 1) {
751 const line_num_entry = @as(*align(1) LineNumberEntry, @ptrCast(&subsect_info[line_index]));
752 line_index += @sizeOf(LineNumberEntry);
753
754 const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
755 if (address < vaddr_start) {
756 break;
757 }
758 }
759
760 // line_i == 0 would mean that no matching LineNumberEntry was found.
761 if (line_i > 0) {
762 const subsect_index = checksum_offset + block_hdr.NameIndex;
763 const chksum_hdr = @as(*align(1) FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index]));
764 const strtab_offset = @sizeOf(PDBStringTableHeader) + chksum_hdr.FileNameOffset;
765 try self.string_table.?.seekTo(strtab_offset);
766 const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024);
767
768 const line_entry_idx = line_i - 1;
769
770 const column = if (has_column) blk: {
771 const start_col_index = start_line_index + @sizeOf(LineNumberEntry) * block_hdr.NumLines;
772 const col_index = start_col_index + @sizeOf(ColumnNumberEntry) * line_entry_idx;
773 const col_num_entry = @as(*align(1) ColumnNumberEntry, @ptrCast(&subsect_info[col_index]));
774 break :blk col_num_entry.StartColumn;
775 } else 0;
776
777 const found_line_index = start_line_index + line_entry_idx * @sizeOf(LineNumberEntry);
778 const line_num_entry: *align(1) LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
779 const flags: *align(1) LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags);
780
781 return debug.Info.SourceLocation{
782 .file_name = source_file_name,
783 .line = flags.Start,
784 .column = column,
785 };
786 }
787 }
788
789 // Checking that we are not reading garbage after the (possibly) multiple block fragments.
790 if (line_index != subsection_end_index) {
791 return error.InvalidDebugInfo;
792 }
793 }
794 },
795 else => {},
796 }
797
798 if (sect_offset > subsect_info.len)
799 return error.InvalidDebugInfo;
800 }
801
802 return error.MissingDebugInfo;
803 }
804
805 pub fn getModule(self: *Pdb, index: usize) !?*Module {
806 if (index >= self.modules.len)
807 return null;
808
809 const mod = &self.modules[index];
810 if (mod.populated)
811 return mod;
812
813 // At most one can be non-zero.
814 if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
815 return error.InvalidDebugInfo;
816 if (mod.mod_info.C13ByteSize == 0)
817 return error.InvalidDebugInfo;
818
819 const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse
820 return error.MissingDebugInfo;
821 const reader = stream.reader();
822
823 const signature = try reader.readInt(u32, .little);
824 if (signature != 4)
825 return error.InvalidDebugInfo;
826
827 mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
828 errdefer self.allocator.free(mod.symbols);
829 try reader.readNoEof(mod.symbols);
830
831 mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize);
832 errdefer self.allocator.free(mod.subsect_info);
833 try reader.readNoEof(mod.subsect_info);
834
835 var sect_offset: usize = 0;
836 var skip_len: usize = undefined;
837 while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
838 const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset]));
839 skip_len = subsect_hdr.Length;
840 sect_offset += @sizeOf(DebugSubsectionHeader);
841
842 switch (subsect_hdr.Kind) {
843 .FileChecksums => {
844 mod.checksum_offset = sect_offset;
845 break;
846 },
847 else => {},
848 }
849
850 if (sect_offset > mod.subsect_info.len)
851 return error.InvalidDebugInfo;
852 }
853
854 mod.populated = true;
855 return mod;
856 }
857
858 pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {
859 if (id >= self.msf.streams.len)
860 return null;
861 return &self.msf.streams[id];
862 }
863
864 pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream {
865 const id = @intFromEnum(stream);
866 return self.getStreamById(id);
867 }
868};
869
870// see https://llvm.org/docs/PDB/MsfFile.html
871const Msf = struct {
872 directory: MsfStream,
873 streams: []MsfStream,
874
875 fn init(allocator: mem.Allocator, file: File) !Msf {
876 const in = file.reader();
877
878 const superblock = try in.readStruct(SuperBlock);
879
880 // Sanity checks
881 if (!mem.eql(u8, &superblock.FileMagic, SuperBlock.file_magic))
882 return error.InvalidDebugInfo;
883 if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2)
884 return error.InvalidDebugInfo;
885 const file_len = try file.getEndPos();
886 if (superblock.NumBlocks * superblock.BlockSize != file_len)
887 return error.InvalidDebugInfo;
888 switch (superblock.BlockSize) {
889 // llvm only supports 4096 but we can handle any of these values
890 512, 1024, 2048, 4096 => {},
891 else => return error.InvalidDebugInfo,
892 }
893
894 const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize);
895 if (dir_block_count > superblock.BlockSize / @sizeOf(u32))
896 return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment.
897
898 try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr);
899 const dir_blocks = try allocator.alloc(u32, dir_block_count);
900 for (dir_blocks) |*b| {
901 b.* = try in.readInt(u32, .little);
902 }
903 var directory = MsfStream.init(
904 superblock.BlockSize,
905 file,
906 dir_blocks,
907 );
908
909 const begin = directory.pos;
910 const stream_count = try directory.reader().readInt(u32, .little);
911 const stream_sizes = try allocator.alloc(u32, stream_count);
912 defer allocator.free(stream_sizes);
913
914 // Microsoft's implementation uses @as(u32, -1) for inexistent streams.
915 // These streams are not used, but still participate in the file
916 // and must be taken into account when resolving stream indices.
917 const Nil = 0xFFFFFFFF;
918 for (stream_sizes) |*s| {
919 const size = try directory.reader().readInt(u32, .little);
920 s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);
921 }
922
923 const streams = try allocator.alloc(MsfStream, stream_count);
924 for (streams, 0..) |*stream, i| {
925 const size = stream_sizes[i];
926 if (size == 0) {
927 stream.* = MsfStream{
928 .blocks = &[_]u32{},
929 };
930 } else {
931 var blocks = try allocator.alloc(u32, size);
932 var j: u32 = 0;
933 while (j < size) : (j += 1) {
934 const block_id = try directory.reader().readInt(u32, .little);
935 const n = (block_id % superblock.BlockSize);
936 // 0 is for SuperBlock, 1 and 2 for FPMs.
937 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len)
938 return error.InvalidBlockIndex;
939 blocks[j] = block_id;
940 }
941
942 stream.* = MsfStream.init(
943 superblock.BlockSize,
944 file,
945 blocks,
946 );
947 }
948 }
949
950 const end = directory.pos;
951 if (end - begin != superblock.NumDirectoryBytes)
952 return error.InvalidStreamDirectory;
953
954 return Msf{
955 .directory = directory,
956 .streams = streams,
957 };
958 }
959
960 fn deinit(self: *Msf, allocator: mem.Allocator) void {
961 allocator.free(self.directory.blocks);
962 for (self.streams) |*stream| {
963 allocator.free(stream.blocks);
964 }
965 allocator.free(self.streams);
966 }
967};
968
969fn blockCountFromSize(size: u32, block_size: u32) u32 {
970 return (size + block_size - 1) / block_size;
971}
972
973467// https://llvm.org/docs/PDB/MsfFile.html#the-superblock
974468pub const SuperBlock = extern struct {
975469 /// The LLVM docs list a space between C / C++ but empirically this is not the case.
......@@ -1016,82 +510,3 @@ pub const SuperBlock = extern struct {
1016510 // implement it so we're kind of safe making this assumption for now.
1017511 BlockMapAddr: u32,
1018512};
1019
1020const MsfStream = struct {
1021 in_file: File = undefined,
1022 pos: u64 = undefined,
1023 blocks: []u32 = undefined,
1024 block_size: u32 = undefined,
1025
1026 pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set;
1027
1028 fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
1029 const stream = MsfStream{
1030 .in_file = file,
1031 .pos = 0,
1032 .blocks = blocks,
1033 .block_size = block_size,
1034 };
1035
1036 return stream;
1037 }
1038
1039 fn read(self: *MsfStream, buffer: []u8) !usize {
1040 var block_id = @as(usize, @intCast(self.pos / self.block_size));
1041 if (block_id >= self.blocks.len) return 0; // End of Stream
1042 var block = self.blocks[block_id];
1043 var offset = self.pos % self.block_size;
1044
1045 try self.in_file.seekTo(block * self.block_size + offset);
1046 const in = self.in_file.reader();
1047
1048 var size: usize = 0;
1049 var rem_buffer = buffer;
1050 while (size < buffer.len) {
1051 const size_to_read = @min(self.block_size - offset, rem_buffer.len);
1052 size += try in.read(rem_buffer[0..size_to_read]);
1053 rem_buffer = buffer[size..];
1054 offset += size_to_read;
1055
1056 // If we're at the end of a block, go to the next one.
1057 if (offset == self.block_size) {
1058 offset = 0;
1059 block_id += 1;
1060 if (block_id >= self.blocks.len) break; // End of Stream
1061 block = self.blocks[block_id];
1062 try self.in_file.seekTo(block * self.block_size);
1063 }
1064 }
1065
1066 self.pos += buffer.len;
1067 return buffer.len;
1068 }
1069
1070 pub fn seekBy(self: *MsfStream, len: i64) !void {
1071 self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len));
1072 if (self.pos >= self.blocks.len * self.block_size)
1073 return error.EOF;
1074 }
1075
1076 pub fn seekTo(self: *MsfStream, len: u64) !void {
1077 self.pos = len;
1078 if (self.pos >= self.blocks.len * self.block_size)
1079 return error.EOF;
1080 }
1081
1082 fn getSize(self: *const MsfStream) u64 {
1083 return self.blocks.len * self.block_size;
1084 }
1085
1086 fn getFilePos(self: MsfStream) u64 {
1087 const block_id = self.pos / self.block_size;
1088 const block = self.blocks[block_id];
1089 const offset = self.pos % self.block_size;
1090
1091 return block * self.block_size + offset;
1092 }
1093
1094 pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) {
1095 return .{ .context = self };
1096 }
1097};