| ... | @@ -26,10 +26,13 @@ const regValueNative = Dwarf.abi.regValueNative; | ... | @@ -26,10 +26,13 @@ const regValueNative = Dwarf.abi.regValueNative; |
| 26 | | 26 | |
| 27 | const SelfInfo = @This(); | 27 | const SelfInfo = @This(); |
| 28 | | 28 | |
| 29 | /// MLUGG TODO: what if this field had a less stupid name... | 29 | modules: std.AutoHashMapUnmanaged(usize, struct { |
| 30 | address_map: std.AutoHashMapUnmanaged(usize, Module.DebugInfo), | 30 | di: Module.DebugInfo, |
| 31 | | 31 | loaded_debug: bool, |
| 32 | module_cache: if (native_os == .windows) std.ArrayListUnmanaged(windows.MODULEENTRY32) else void, | 32 | loaded_unwind: bool, |
| | 33 | const init: @This() = .{ .di = .init, .loaded_debug = false, .loaded_unwind = false }; |
| | 34 | }), |
| | 35 | lookup_cache: Module.LookupCache, |
| 33 | | 36 | |
| 34 | pub const target_supported: bool = switch (native_os) { | 37 | pub const target_supported: bool = switch (native_os) { |
| 35 | .linux, | 38 | .linux, |
| ... | @@ -46,19 +49,19 @@ pub const target_supported: bool = switch (native_os) { | ... | @@ -46,19 +49,19 @@ pub const target_supported: bool = switch (native_os) { |
| 46 | }; | 49 | }; |
| 47 | | 50 | |
| 48 | pub const init: SelfInfo = .{ | 51 | pub const init: SelfInfo = .{ |
| 49 | .address_map = .empty, | 52 | .modules = .empty, |
| 50 | .module_cache = if (native_os == .windows) .empty, | 53 | .lookup_cache = if (Module.LookupCache != void) .init, |
| 51 | }; | 54 | }; |
| 52 | | 55 | |
| 53 | pub fn deinit(self: *SelfInfo) void { | 56 | pub fn deinit(self: *SelfInfo) void { |
| 54 | // MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...???? | 57 | // MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...???? |
| 55 | var it = self.address_map.iterator(); | 58 | var it = self.modules.iterator(); |
| 56 | while (it.next()) |entry| { | 59 | while (it.next()) |entry| { |
| 57 | const mdi = entry.value_ptr.*; | 60 | const mdi = entry.value_ptr.*; |
| 58 | mdi.deinit(self.allocator); | 61 | mdi.deinit(self.allocator); |
| 59 | self.allocator.destroy(mdi); | 62 | self.allocator.destroy(mdi); |
| 60 | } | 63 | } |
| 61 | self.address_map.deinit(self.allocator); | 64 | self.modules.deinit(self.allocator); |
| 62 | if (native_os == .windows) { | 65 | if (native_os == .windows) { |
| 63 | for (self.modules.items) |module| { | 66 | for (self.modules.items) |module| { |
| 64 | self.allocator.free(module.name); | 67 | self.allocator.free(module.name); |
| ... | @@ -68,94 +71,26 @@ pub fn deinit(self: *SelfInfo) void { | ... | @@ -68,94 +71,26 @@ pub fn deinit(self: *SelfInfo) void { |
| 68 | } | 71 | } |
| 69 | } | 72 | } |
| 70 | | 73 | |
| 71 | fn lookupModuleForAddress(self: *SelfInfo, gpa: Allocator, address: usize) !Module { | | |
| 72 | if (builtin.target.os.tag.isDarwin()) { | | |
| 73 | return self.lookupModuleDyld(address); | | |
| 74 | } else if (native_os == .windows) { | | |
| 75 | return self.lookupModuleWin32(gpa, address); | | |
| 76 | } else if (native_os == .haiku) { | | |
| 77 | @panic("TODO implement lookup module for Haiku"); | | |
| 78 | } else if (builtin.target.cpu.arch.isWasm()) { | | |
| 79 | @panic("TODO implement lookup module for Wasm"); | | |
| 80 | } else { | | |
| 81 | return self.lookupModuleDl(address); | | |
| 82 | } | | |
| 83 | } | | |
| 84 | | | |
| 85 | fn loadModuleDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void { | | |
| 86 | // MLUGG TODO: this should totally just go into the `Module` impl or something, right? lol | | |
| 87 | if (builtin.target.os.tag.isDarwin()) { | | |
| 88 | try loadMachODebugInfo(gpa, module, di); | | |
| 89 | } else if (native_os == .windows) { | | |
| 90 | // MLUGG TODO: deal with 'already loaded' properly | | |
| 91 | try readCoffDebugInfo(gpa, module, di); | | |
| 92 | } else if (native_os == .haiku) { | | |
| 93 | unreachable; | | |
| 94 | } else if (builtin.target.cpu.arch.isWasm()) { | | |
| 95 | unreachable; | | |
| 96 | } else { | | |
| 97 | if (di.mapped_memory != null) return; // already loaded | | |
| 98 | const filename: ?[]const u8 = if (module.name.len > 0) module.name else null; | | |
| 99 | const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) { | | |
| 100 | error.FileNotFound => return error.MissingDebugInfo, | | |
| 101 | error.FileTooBig => return error.InvalidDebugInfo, | | |
| 102 | else => |e| return e, | | |
| 103 | }; | | |
| 104 | errdefer posix.munmap(mapped_mem); | | |
| 105 | try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename); | | |
| 106 | assert(di.mapped_memory != null); | | |
| 107 | } | | |
| 108 | } | | |
| 109 | | | |
| 110 | fn loadModuleUnwindInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void { | | |
| 111 | if (builtin.target.os.tag.isDarwin()) { | | |
| 112 | // MLUGG TODO HACKHACK | | |
| 113 | try loadMachODebugInfo(gpa, module, di); | | |
| 114 | } else if (native_os == .windows) { | | |
| 115 | comptime unreachable; // not supported | | |
| 116 | } else if (native_os == .haiku) { | | |
| 117 | comptime unreachable; // not supported | | |
| 118 | } else if (builtin.target.cpu.arch.isWasm()) { | | |
| 119 | comptime unreachable; // not supported | | |
| 120 | } else { | | |
| 121 | eh_frame: { | | |
| 122 | if (di.unwind.eh_frame != null) break :eh_frame; // already loaded | | |
| 123 | const eh_frame_hdr_bytes = module.gnu_eh_frame orelse break :eh_frame; | | |
| 124 | const eh_frame_hdr: Dwarf.Unwind.EhFrameHeader = try .parse( | | |
| 125 | @intFromPtr(eh_frame_hdr_bytes.ptr) - module.load_offset, | | |
| 126 | eh_frame_hdr_bytes, | | |
| 127 | @sizeOf(usize), | | |
| 128 | native_endian, | | |
| 129 | ); | | |
| 130 | const eh_frame_addr = module.load_offset + @as(usize, @intCast(eh_frame_hdr.eh_frame_vaddr)); | | |
| 131 | try di.unwind.scanEhFrame( | | |
| 132 | gpa, | | |
| 133 | eh_frame_hdr, | | |
| 134 | @ptrFromInt(eh_frame_addr), | | |
| 135 | null, | | |
| 136 | @sizeOf(usize), | | |
| 137 | native_endian, | | |
| 138 | ); | | |
| 139 | } | | |
| 140 | } | | |
| 141 | } | | |
| 142 | | | |
| 143 | pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize { | 74 | pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize { |
| 144 | comptime assert(target_supported); | 75 | comptime assert(target_supported); |
| 145 | const module = try self.lookupModuleForAddress(gpa, context.pc); | 76 | const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); // MLUGG TODO: don't take gpa |
| 146 | const gop = try self.address_map.getOrPut(gpa, module.load_offset); | 77 | const gop = try self.modules.getOrPut(gpa, module.load_offset); |
| 147 | if (!gop.found_existing) gop.value_ptr.* = .init; | 78 | if (!gop.found_existing) gop.value_ptr.* = .init; |
| 148 | try loadModuleUnwindInfo(gpa, &module, gop.value_ptr); | 79 | if (!gop.value_ptr.loaded_unwind) { |
| | 80 | try module.loadUnwindInfo(gpa, &gop.value_ptr.di); |
| | 81 | gop.value_ptr.loaded_unwind = true; |
| | 82 | } |
| | 83 | // MLUGG TODO: the stuff below is impl! |
| 149 | if (native_os.isDarwin()) { | 84 | if (native_os.isDarwin()) { |
| 150 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding | 85 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding |
| 151 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. | 86 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. |
| 152 | if (gop.value_ptr.unwind_info) |unwind_info| { | 87 | if (gop.value_ptr.di.unwind_info) |unwind_info| { |
| 153 | if (unwindFrameMachO( | 88 | if (unwindFrameMachO( |
| 154 | module.text_base, | 89 | module.text_base, |
| 155 | module.load_offset, | 90 | module.load_offset, |
| 156 | context, | 91 | context, |
| 157 | unwind_info, | 92 | unwind_info, |
| 158 | gop.value_ptr.eh_frame, | 93 | gop.value_ptr.di.eh_frame, |
| 159 | )) |return_address| { | 94 | )) |return_address| { |
| 160 | return return_address; | 95 | return return_address; |
| 161 | } else |err| { | 96 | } else |err| { |
| ... | @@ -164,7 +99,7 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us | ... | @@ -164,7 +99,7 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us |
| 164 | } | 99 | } |
| 165 | return error.MissingUnwindInfo; | 100 | return error.MissingUnwindInfo; |
| 166 | } | 101 | } |
| 167 | if (try gop.value_ptr.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| { | 102 | if (try gop.value_ptr.di.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| { |
| 168 | return unwindFrameDwarf(unwind, module.load_offset, context, null); | 103 | return unwindFrameDwarf(unwind, module.load_offset, context, null); |
| 169 | } | 104 | } |
| 170 | return error.MissingDebugInfo; | 105 | return error.MissingDebugInfo; |
| ... | @@ -172,11 +107,15 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us | ... | @@ -172,11 +107,15 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us |
| 172 | | 107 | |
| 173 | pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol { | 108 | pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol { |
| 174 | comptime assert(target_supported); | 109 | comptime assert(target_supported); |
| 175 | const module = try self.lookupModuleForAddress(gpa, address); | 110 | const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa |
| 176 | const gop = try self.address_map.getOrPut(gpa, module.key()); | 111 | const gop = try self.modules.getOrPut(gpa, module.key()); |
| 177 | if (!gop.found_existing) gop.value_ptr.* = .init; | 112 | if (!gop.found_existing) gop.value_ptr.* = .init; |
| 178 | try loadModuleDebugInfo(gpa, &module, gop.value_ptr); | 113 | if (!gop.value_ptr.loaded_debug) { |
| 179 | return module.getSymbolAtAddress(gpa, gop.value_ptr, address); | 114 | // MLUGG TODO: this overloads the name 'debug info' with including vs excluding unwind info |
| | 115 | // figure out a better name for one or the other (i think the inner one is maybe 'symbol info' or something idk) |
| | 116 | try module.loadDebugInfo(gpa, &gop.value_ptr.di); |
| | 117 | } |
| | 118 | return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address); |
| 180 | } | 119 | } |
| 181 | | 120 | |
| 182 | /// Returns the module name for a given address. | 121 | /// Returns the module name for a given address. |
| ... | @@ -184,278 +123,12 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std. | ... | @@ -184,278 +123,12 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std. |
| 184 | /// a path that doesn't rely on any side-effects of a prior successful module lookup. | 123 | /// a path that doesn't rely on any side-effects of a prior successful module lookup. |
| 185 | pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 { | 124 | pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 { |
| 186 | comptime assert(target_supported); | 125 | comptime assert(target_supported); |
| 187 | const module = try self.lookupModuleForAddress(gpa, address); | 126 | const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa |
| 188 | return module.name; | 127 | return module.name; |
| 189 | } | 128 | } |
| 190 | | 129 | |
| 191 | fn lookupModuleDl(self: *SelfInfo, address: usize) !Module { | | |
| 192 | _ = self; // MLUGG | | |
| 193 | const DlIterContext = struct { | | |
| 194 | /// input | | |
| 195 | address: usize, | | |
| 196 | /// output | | |
| 197 | module: Module, | | |
| 198 | | | |
| 199 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void { | | |
| 200 | _ = size; | | |
| 201 | // The base address is too high | | |
| 202 | if (context.address < info.addr) | | |
| 203 | return; | | |
| 204 | | | |
| 205 | const phdrs = info.phdr[0..info.phnum]; | | |
| 206 | for (phdrs) |*phdr| { | | |
| 207 | if (phdr.p_type != elf.PT_LOAD) continue; | | |
| 208 | | | |
| 209 | // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 | | |
| 210 | const seg_start = info.addr +% phdr.p_vaddr; | | |
| 211 | const seg_end = seg_start + phdr.p_memsz; | | |
| 212 | if (context.address >= seg_start and context.address < seg_end) { | | |
| 213 | context.module = .{ | | |
| 214 | .load_offset = info.addr, | | |
| 215 | // Android libc uses NULL instead of "" to mark the main program | | |
| 216 | .name = mem.sliceTo(info.name, 0) orelse "", | | |
| 217 | .build_id = null, | | |
| 218 | .gnu_eh_frame = null, | | |
| 219 | }; | | |
| 220 | break; | | |
| 221 | } | | |
| 222 | } else return; | | |
| 223 | | | |
| 224 | for (info.phdr[0..info.phnum]) |phdr| { | | |
| 225 | switch (phdr.p_type) { | | |
| 226 | elf.PT_NOTE => { | | |
| 227 | // Look for .note.gnu.build-id | | |
| 228 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | | |
| 229 | var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]); | | |
| 230 | const name_size = r.takeInt(u32, native_endian) catch continue; | | |
| 231 | const desc_size = r.takeInt(u32, native_endian) catch continue; | | |
| 232 | const note_type = r.takeInt(u32, native_endian) catch continue; | | |
| 233 | const name = r.take(name_size) catch continue; | | |
| 234 | if (note_type != elf.NT_GNU_BUILD_ID) continue; | | |
| 235 | if (!mem.eql(u8, name, "GNU\x00")) continue; | | |
| 236 | const desc = r.take(desc_size) catch continue; | | |
| 237 | context.module.build_id = desc; | | |
| 238 | }, | | |
| 239 | elf.PT_GNU_EH_FRAME => { | | |
| 240 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); | | |
| 241 | context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz]; | | |
| 242 | }, | | |
| 243 | else => {}, | | |
| 244 | } | | |
| 245 | } | | |
| 246 | | | |
| 247 | // Stop the iteration | | |
| 248 | return error.Found; | | |
| 249 | } | | |
| 250 | }; | | |
| 251 | var ctx: DlIterContext = .{ | | |
| 252 | .address = address, | | |
| 253 | .module = undefined, | | |
| 254 | }; | | |
| 255 | posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) { | | |
| 256 | error.Found => return ctx.module, | | |
| 257 | }; | | |
| 258 | return error.MissingDebugInfo; | | |
| 259 | } | | |
| 260 | | | |
| 261 | fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module { | | |
| 262 | _ = self; // MLUGG | | |
| 263 | const image_count = std.c._dyld_image_count(); | | |
| 264 | for (0..image_count) |image_idx| { | | |
| 265 | const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue; | | |
| 266 | const text_base = @intFromPtr(header); | | |
| 267 | if (address < text_base) continue; | | |
| 268 | const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx)); | | |
| 269 | | | |
| 270 | // Find the __TEXT segment | | |
| 271 | var it: macho.LoadCommandIterator = .{ | | |
| 272 | .ncmds = header.ncmds, | | |
| 273 | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], | | |
| 274 | }; | | |
| 275 | const text_segment_cmd, const text_sections = while (it.next()) |load_cmd| { | | |
| 276 | if (load_cmd.cmd() != .SEGMENT_64) continue; | | |
| 277 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; | | |
| 278 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; | | |
| 279 | break .{ segment_cmd, load_cmd.getSections() }; | | |
| 280 | } else continue; | | |
| 281 | | | |
| 282 | const seg_start = load_offset + text_segment_cmd.vmaddr; | | |
| 283 | assert(seg_start == text_base); | | |
| 284 | const seg_end = seg_start + text_segment_cmd.vmsize; | | |
| 285 | if (address < seg_start or address >= seg_end) continue; | | |
| 286 | | | |
| 287 | // We've found the matching __TEXT segment. This is the image we need, but we must look | | |
| 288 | // for unwind info in it before returning. | | |
| 289 | | | |
| 290 | var result: Module = .{ | | |
| 291 | .text_base = text_base, | | |
| 292 | .load_offset = load_offset, | | |
| 293 | .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))), | | |
| 294 | .unwind_info = null, | | |
| 295 | .eh_frame = null, | | |
| 296 | }; | | |
| 297 | for (text_sections) |sect| { | | |
| 298 | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { | | |
| 299 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr))); | | |
| 300 | result.unwind_info = sect_ptr[0..@intCast(sect.size)]; | | |
| 301 | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { | | |
| 302 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr))); | | |
| 303 | result.eh_frame = sect_ptr[0..@intCast(sect.size)]; | | |
| 304 | } | | |
| 305 | } | | |
| 306 | return result; | | |
| 307 | } | | |
| 308 | return error.MissingDebugInfo; | | |
| 309 | } | | |
| 310 | | | |
| 311 | fn lookupModuleWin32(self: *SelfInfo, gpa: Allocator, address: usize) !Module { | | |
| 312 | if (self.lookupModuleWin32Cache(address)) |m| return m; | | |
| 313 | | | |
| 314 | { | | |
| 315 | // Check a new module hasn't been loaded | | |
| 316 | self.module_cache.clearRetainingCapacity(); | | |
| 317 | | | |
| 318 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | | |
| 319 | if (handle == windows.INVALID_HANDLE_VALUE) { | | |
| 320 | return windows.unexpectedError(windows.GetLastError()); | | |
| 321 | } | | |
| 322 | defer windows.CloseHandle(handle); | | |
| 323 | | | |
| 324 | var entry: windows.MODULEENTRY32 = undefined; | | |
| 325 | entry.dwSize = @sizeOf(windows.MODULEENTRY32); | | |
| 326 | if (windows.kernel32.Module32First(handle, &entry) != 0) { | | |
| 327 | try self.module_cache.append(gpa, entry); | | |
| 328 | while (windows.kernel32.Module32Next(handle, &entry) != 0) { | | |
| 329 | try self.module_cache.append(gpa, entry); | | |
| 330 | } | | |
| 331 | } | | |
| 332 | } | | |
| 333 | | | |
| 334 | if (self.lookupModuleWin32Cache(address)) |m| return m; | | |
| 335 | return error.MissingDebugInfo; | | |
| 336 | } | | |
| 337 | fn lookupModuleWin32Cache(self: *SelfInfo, address: usize) ?Module { | | |
| 338 | for (self.module_cache.items) |*entry| { | | |
| 339 | const base_address = @intFromPtr(entry.modBaseAddr); | | |
| 340 | if (address >= base_address and address < base_address + entry.modBaseSize) { | | |
| 341 | return .{ | | |
| 342 | .base_address = base_address, | | |
| 343 | .size = entry.modBaseSize, | | |
| 344 | .name = std.mem.sliceTo(&entry.szModule, 0), | | |
| 345 | .handle = entry.hModule, | | |
| 346 | }; | | |
| 347 | } | | |
| 348 | } | | |
| 349 | return null; | | |
| 350 | } | | |
| 351 | | | |
| 352 | fn readCoffDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void { | | |
| 353 | const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address); | | |
| 354 | const mapped = mapped_ptr[0..module.size]; | | |
| 355 | var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo; | | |
| 356 | // The string table is not mapped into memory by the loader, so if a section name is in the | | |
| 357 | // string table then we have to map the full image file from disk. This can happen when | | |
| 358 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | | |
| 359 | if (coff_obj.strtabRequired()) { | | |
| 360 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | | |
| 361 | name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present | | |
| 362 | const process_handle = windows.GetCurrentProcess(); | | |
| 363 | const len = windows.kernel32.GetModuleFileNameExW( | | |
| 364 | process_handle, | | |
| 365 | module.handle, | | |
| 366 | name_buffer[4..], | | |
| 367 | windows.PATH_MAX_WIDE, | | |
| 368 | ); | | |
| 369 | if (len == 0) return error.MissingDebugInfo; | | |
| 370 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | | |
| 371 | error.FileNotFound => return error.MissingDebugInfo, | | |
| 372 | else => |e| return e, | | |
| 373 | }; | | |
| 374 | errdefer coff_file.close(); | | |
| 375 | var section_handle: windows.HANDLE = undefined; | | |
| 376 | const create_section_rc = windows.ntdll.NtCreateSection( | | |
| 377 | &section_handle, | | |
| 378 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | | |
| 379 | null, | | |
| 380 | null, | | |
| 381 | windows.PAGE_READONLY, | | |
| 382 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | | |
| 383 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | | |
| 384 | windows.SEC_COMMIT, | | |
| 385 | coff_file.handle, | | |
| 386 | ); | | |
| 387 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | | |
| 388 | errdefer windows.CloseHandle(section_handle); | | |
| 389 | var coff_len: usize = 0; | | |
| 390 | var section_view_ptr: [*]const u8 = undefined; | | |
| 391 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | | |
| 392 | section_handle, | | |
| 393 | process_handle, | | |
| 394 | @ptrCast(&section_view_ptr), | | |
| 395 | null, | | |
| 396 | 0, | | |
| 397 | null, | | |
| 398 | &coff_len, | | |
| 399 | .ViewUnmap, | | |
| 400 | 0, | | |
| 401 | windows.PAGE_READONLY, | | |
| 402 | ); | | |
| 403 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | | |
| 404 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS); | | |
| 405 | const section_view = section_view_ptr[0..coff_len]; | | |
| 406 | coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; | | |
| 407 | di.mapped_file = .{ | | |
| 408 | .file = coff_file, | | |
| 409 | .section_handle = section_handle, | | |
| 410 | .section_view = section_view, | | |
| 411 | }; | | |
| 412 | } | | |
| 413 | di.coff_image_base = coff_obj.getImageBase(); | | |
| 414 | | | |
| 415 | if (coff_obj.getSectionByName(".debug_info")) |_| { | | |
| 416 | di.dwarf = .{}; | | |
| 417 | | | |
| 418 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | | |
| 419 | di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { | | |
| 420 | break :blk .{ | | |
| 421 | .data = try coff_obj.getSectionDataAlloc(section_header, gpa), | | |
| 422 | .virtual_address = section_header.virtual_address, | | |
| 423 | .owned = true, | | |
| 424 | }; | | |
| 425 | } else null; | | |
| 426 | } | | |
| 427 | | | |
| 428 | try di.dwarf.?.open(gpa, native_endian); | | |
| 429 | } | | |
| 430 | | | |
| 431 | if (try coff_obj.getPdbPath()) |raw_path| pdb: { | | |
| 432 | const path = blk: { | | |
| 433 | if (fs.path.isAbsolute(raw_path)) { | | |
| 434 | break :blk raw_path; | | |
| 435 | } else { | | |
| 436 | const self_dir = try fs.selfExeDirPathAlloc(gpa); | | |
| 437 | defer gpa.free(self_dir); | | |
| 438 | break :blk try fs.path.join(gpa, &.{ self_dir, raw_path }); | | |
| 439 | } | | |
| 440 | }; | | |
| 441 | defer if (path.ptr != raw_path.ptr) gpa.free(path); | | |
| 442 | | | |
| 443 | di.pdb = Pdb.init(gpa, path) catch |err| switch (err) { | | |
| 444 | error.FileNotFound, error.IsDir => break :pdb, | | |
| 445 | else => return err, | | |
| 446 | }; | | |
| 447 | try di.pdb.?.parseInfoStream(); | | |
| 448 | try di.pdb.?.parseDbiStream(); | | |
| 449 | | | |
| 450 | if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) | | |
| 451 | return error.InvalidDebugInfo; | | |
| 452 | | | |
| 453 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa); | | |
| 454 | } | | |
| 455 | } | | |
| 456 | | | |
| 457 | const Module = switch (native_os) { | 130 | const Module = switch (native_os) { |
| 458 | else => "MLUGG TODO", // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm... | 131 | else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm... |
| 459 | .macos, .ios, .watchos, .tvos, .visionos => struct { | 132 | .macos, .ios, .watchos, .tvos, .visionos => struct { |
| 460 | /// The runtime address where __TEXT is loaded. | 133 | /// The runtime address where __TEXT is loaded. |
| 461 | text_base: usize, | 134 | text_base: usize, |
| ... | @@ -466,6 +139,63 @@ const Module = switch (native_os) { | ... | @@ -466,6 +139,63 @@ const Module = switch (native_os) { |
| 466 | fn key(m: *const Module) usize { | 139 | fn key(m: *const Module) usize { |
| 467 | return m.text_base; | 140 | return m.text_base; |
| 468 | } | 141 | } |
| | 142 | fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module { |
| | 143 | _ = cache; |
| | 144 | _ = gpa; |
| | 145 | const image_count = std.c._dyld_image_count(); |
| | 146 | for (0..image_count) |image_idx| { |
| | 147 | const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue; |
| | 148 | const text_base = @intFromPtr(header); |
| | 149 | if (address < text_base) continue; |
| | 150 | const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx)); |
| | 151 | |
| | 152 | // Find the __TEXT segment |
| | 153 | var it: macho.LoadCommandIterator = .{ |
| | 154 | .ncmds = header.ncmds, |
| | 155 | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], |
| | 156 | }; |
| | 157 | const text_segment_cmd, const text_sections = while (it.next()) |load_cmd| { |
| | 158 | if (load_cmd.cmd() != .SEGMENT_64) continue; |
| | 159 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; |
| | 160 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; |
| | 161 | break .{ segment_cmd, load_cmd.getSections() }; |
| | 162 | } else continue; |
| | 163 | |
| | 164 | const seg_start = load_offset + text_segment_cmd.vmaddr; |
| | 165 | assert(seg_start == text_base); |
| | 166 | const seg_end = seg_start + text_segment_cmd.vmsize; |
| | 167 | if (address < seg_start or address >= seg_end) continue; |
| | 168 | |
| | 169 | // We've found the matching __TEXT segment. This is the image we need, but we must look |
| | 170 | // for unwind info in it before returning. |
| | 171 | |
| | 172 | var result: Module = .{ |
| | 173 | .text_base = text_base, |
| | 174 | .load_offset = load_offset, |
| | 175 | .name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))), |
| | 176 | .unwind_info = null, |
| | 177 | .eh_frame = null, |
| | 178 | }; |
| | 179 | for (text_sections) |sect| { |
| | 180 | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { |
| | 181 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr))); |
| | 182 | result.unwind_info = sect_ptr[0..@intCast(sect.size)]; |
| | 183 | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { |
| | 184 | const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr))); |
| | 185 | result.eh_frame = sect_ptr[0..@intCast(sect.size)]; |
| | 186 | } |
| | 187 | } |
| | 188 | return result; |
| | 189 | } |
| | 190 | return error.MissingDebugInfo; |
| | 191 | } |
| | 192 | fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void { |
| | 193 | return loadMachODebugInfo(gpa, module, di); |
| | 194 | } |
| | 195 | fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void { |
| | 196 | // MLUGG TODO HACKHACK |
| | 197 | try loadMachODebugInfo(gpa, module, di); |
| | 198 | } |
| 469 | fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { | 199 | fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { |
| 470 | const vaddr = address - module.load_offset; | 200 | const vaddr = address - module.load_offset; |
| 471 | const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null? | 201 | const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null? |
| ... | @@ -524,8 +254,8 @@ const Module = switch (native_os) { | ... | @@ -524,8 +254,8 @@ const Module = switch (native_os) { |
| 524 | }, | 254 | }, |
| 525 | }; | 255 | }; |
| 526 | } | 256 | } |
| | 257 | const LookupCache = void; |
| 527 | const DebugInfo = struct { | 258 | const DebugInfo = struct { |
| 528 | // MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them! | | |
| 529 | mapped_memory: []align(std.heap.page_size_min) const u8, | 259 | mapped_memory: []align(std.heap.page_size_min) const u8, |
| 530 | symbols: []const MachoSymbol, | 260 | symbols: []const MachoSymbol, |
| 531 | strings: [:0]const u8, | 261 | strings: [:0]const u8, |
| ... | @@ -533,6 +263,7 @@ const Module = switch (native_os) { | ... | @@ -533,6 +263,7 @@ const Module = switch (native_os) { |
| 533 | ofiles: std.StringArrayHashMapUnmanaged(OFile), | 263 | ofiles: std.StringArrayHashMapUnmanaged(OFile), |
| 534 | | 264 | |
| 535 | // Backed by the in-memory sections mapped by the loader | 265 | // Backed by the in-memory sections mapped by the loader |
| | 266 | // MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them! |
| 536 | unwind_info: ?[]const u8, | 267 | unwind_info: ?[]const u8, |
| 537 | eh_frame: ?[]const u8, | 268 | eh_frame: ?[]const u8, |
| 538 | | 269 | |
| ... | @@ -642,28 +373,137 @@ const Module = switch (native_os) { | ... | @@ -642,28 +373,137 @@ const Module = switch (native_os) { |
| 642 | }; | 373 | }; |
| 643 | }, | 374 | }, |
| 644 | .wasi, .emscripten => struct { | 375 | .wasi, .emscripten => struct { |
| | 376 | const LookupCache = void; |
| 645 | const DebugInfo = struct { | 377 | const DebugInfo = struct { |
| 646 | const init: DebugInfo = .{}; | 378 | const init: DebugInfo = .{}; |
| 647 | fn getSymbolAtAddress(di: *DebugInfo, gpa: Allocator, base_address: usize, address: usize) !std.debug.Symbol { | | |
| 648 | _ = di; | | |
| 649 | _ = gpa; | | |
| 650 | _ = base_address; | | |
| 651 | _ = address; | | |
| 652 | unreachable; | | |
| 653 | } | | |
| 654 | }; | 379 | }; |
| | 380 | fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module { |
| | 381 | _ = cache; |
| | 382 | _ = gpa; |
| | 383 | _ = address; |
| | 384 | @panic("TODO implement lookup module for Wasm"); |
| | 385 | } |
| | 386 | fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { |
| | 387 | _ = module; |
| | 388 | _ = gpa; |
| | 389 | _ = di; |
| | 390 | _ = address; |
| | 391 | unreachable; |
| | 392 | } |
| | 393 | fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void { |
| | 394 | _ = module; |
| | 395 | _ = gpa; |
| | 396 | _ = di; |
| | 397 | unreachable; |
| | 398 | } |
| | 399 | fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void { |
| | 400 | _ = module; |
| | 401 | _ = gpa; |
| | 402 | _ = di; |
| | 403 | unreachable; |
| | 404 | } |
| 655 | }, | 405 | }, |
| 656 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { | 406 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { |
| 657 | load_offset: usize, | 407 | load_offset: usize, |
| 658 | name: []const u8, | 408 | name: []const u8, |
| 659 | build_id: ?[]const u8, | 409 | build_id: ?[]const u8, |
| 660 | gnu_eh_frame: ?[]const u8, | 410 | gnu_eh_frame: ?[]const u8, |
| | 411 | const LookupCache = void; |
| | 412 | const DebugInfo = Dwarf.ElfModule; |
| 661 | fn key(m: Module) usize { | 413 | fn key(m: Module) usize { |
| 662 | return m.load_offset; // MLUGG TODO: is this technically valid? idk | 414 | return m.load_offset; // MLUGG TODO: is this technically valid? idk |
| 663 | } | 415 | } |
| 664 | const DebugInfo = Dwarf.ElfModule; | 416 | fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module { |
| 665 | fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { | 417 | _ = cache; |
| 666 | return di.getSymbolAtAddress(gpa, native_endian, mod.load_offset, address); | 418 | _ = gpa; |
| | 419 | if (native_os == .haiku) @panic("TODO implement lookup module for Haiku"); |
| | 420 | const DlIterContext = struct { |
| | 421 | /// input |
| | 422 | address: usize, |
| | 423 | /// output |
| | 424 | module: Module, |
| | 425 | |
| | 426 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void { |
| | 427 | _ = size; |
| | 428 | // The base address is too high |
| | 429 | if (context.address < info.addr) |
| | 430 | return; |
| | 431 | |
| | 432 | const phdrs = info.phdr[0..info.phnum]; |
| | 433 | for (phdrs) |*phdr| { |
| | 434 | if (phdr.p_type != elf.PT_LOAD) continue; |
| | 435 | |
| | 436 | // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 |
| | 437 | const seg_start = info.addr +% phdr.p_vaddr; |
| | 438 | const seg_end = seg_start + phdr.p_memsz; |
| | 439 | if (context.address >= seg_start and context.address < seg_end) { |
| | 440 | context.module = .{ |
| | 441 | .load_offset = info.addr, |
| | 442 | // Android libc uses NULL instead of "" to mark the main program |
| | 443 | .name = mem.sliceTo(info.name, 0) orelse "", |
| | 444 | .build_id = null, |
| | 445 | .gnu_eh_frame = null, |
| | 446 | }; |
| | 447 | break; |
| | 448 | } |
| | 449 | } else return; |
| | 450 | |
| | 451 | for (info.phdr[0..info.phnum]) |phdr| { |
| | 452 | switch (phdr.p_type) { |
| | 453 | elf.PT_NOTE => { |
| | 454 | // Look for .note.gnu.build-id |
| | 455 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); |
| | 456 | var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]); |
| | 457 | const name_size = r.takeInt(u32, native_endian) catch continue; |
| | 458 | const desc_size = r.takeInt(u32, native_endian) catch continue; |
| | 459 | const note_type = r.takeInt(u32, native_endian) catch continue; |
| | 460 | const name = r.take(name_size) catch continue; |
| | 461 | if (note_type != elf.NT_GNU_BUILD_ID) continue; |
| | 462 | if (!mem.eql(u8, name, "GNU\x00")) continue; |
| | 463 | const desc = r.take(desc_size) catch continue; |
| | 464 | context.module.build_id = desc; |
| | 465 | }, |
| | 466 | elf.PT_GNU_EH_FRAME => { |
| | 467 | const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); |
| | 468 | context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz]; |
| | 469 | }, |
| | 470 | else => {}, |
| | 471 | } |
| | 472 | } |
| | 473 | |
| | 474 | // Stop the iteration |
| | 475 | return error.Found; |
| | 476 | } |
| | 477 | }; |
| | 478 | var ctx: DlIterContext = .{ |
| | 479 | .address = address, |
| | 480 | .module = undefined, |
| | 481 | }; |
| | 482 | posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) { |
| | 483 | error.Found => return ctx.module, |
| | 484 | }; |
| | 485 | return error.MissingDebugInfo; |
| | 486 | } |
| | 487 | fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void { |
| | 488 | const filename: ?[]const u8 = if (module.name.len > 0) module.name else null; |
| | 489 | const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) { |
| | 490 | error.FileNotFound => return error.MissingDebugInfo, |
| | 491 | error.FileTooBig => return error.InvalidDebugInfo, |
| | 492 | else => |e| return e, |
| | 493 | }; |
| | 494 | errdefer posix.munmap(mapped_mem); |
| | 495 | try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename); |
| | 496 | assert(di.mapped_memory != null); |
| | 497 | } |
| | 498 | fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void { |
| | 499 | const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file |
| | 500 | const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset; |
| | 501 | const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian); |
| | 502 | try di.unwind.loadFromEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr)); |
| | 503 | try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian); |
| | 504 | } |
| | 505 | fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { |
| | 506 | return di.getSymbolAtAddress(gpa, native_endian, module.load_offset, address); |
| 667 | } | 507 | } |
| 668 | }, | 508 | }, |
| 669 | .uefi, .windows => struct { | 509 | .uefi, .windows => struct { |
| ... | @@ -674,6 +514,152 @@ const Module = switch (native_os) { | ... | @@ -674,6 +514,152 @@ const Module = switch (native_os) { |
| 674 | fn key(m: Module) usize { | 514 | fn key(m: Module) usize { |
| 675 | return m.base_address; | 515 | return m.base_address; |
| 676 | } | 516 | } |
| | 517 | fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module { |
| | 518 | if (lookupInCache(cache, address)) |m| return m; |
| | 519 | { |
| | 520 | // Check a new module hasn't been loaded |
| | 521 | cache.modules.clearRetainingCapacity(); |
| | 522 | |
| | 523 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); |
| | 524 | if (handle == windows.INVALID_HANDLE_VALUE) { |
| | 525 | return windows.unexpectedError(windows.GetLastError()); |
| | 526 | } |
| | 527 | defer windows.CloseHandle(handle); |
| | 528 | |
| | 529 | var entry: windows.MODULEENTRY32 = undefined; |
| | 530 | entry.dwSize = @sizeOf(windows.MODULEENTRY32); |
| | 531 | if (windows.kernel32.Module32First(handle, &entry) != 0) { |
| | 532 | try cache.modules.append(gpa, entry); |
| | 533 | while (windows.kernel32.Module32Next(handle, &entry) != 0) { |
| | 534 | try cache.modules.append(gpa, entry); |
| | 535 | } |
| | 536 | } |
| | 537 | } |
| | 538 | if (lookupInCache(cache, address)) |m| return m; |
| | 539 | return error.MissingDebugInfo; |
| | 540 | } |
| | 541 | fn lookupInCache(cache: *const LookupCache, address: usize) ?Module { |
| | 542 | for (cache.modules.items) |*entry| { |
| | 543 | const base_address = @intFromPtr(entry.modBaseAddr); |
| | 544 | if (address >= base_address and address < base_address + entry.modBaseSize) { |
| | 545 | return .{ |
| | 546 | .base_address = base_address, |
| | 547 | .size = entry.modBaseSize, |
| | 548 | .name = std.mem.sliceTo(&entry.szModule, 0), |
| | 549 | .handle = entry.hModule, |
| | 550 | }; |
| | 551 | } |
| | 552 | } |
| | 553 | return null; |
| | 554 | } |
| | 555 | fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void { |
| | 556 | const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address); |
| | 557 | const mapped = mapped_ptr[0..module.size]; |
| | 558 | var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo; |
| | 559 | // The string table is not mapped into memory by the loader, so if a section name is in the |
| | 560 | // string table then we have to map the full image file from disk. This can happen when |
| | 561 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. |
| | 562 | if (coff_obj.strtabRequired()) { |
| | 563 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; |
| | 564 | name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present |
| | 565 | const process_handle = windows.GetCurrentProcess(); |
| | 566 | const len = windows.kernel32.GetModuleFileNameExW( |
| | 567 | process_handle, |
| | 568 | module.handle, |
| | 569 | name_buffer[4..], |
| | 570 | windows.PATH_MAX_WIDE, |
| | 571 | ); |
| | 572 | if (len == 0) return error.MissingDebugInfo; |
| | 573 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { |
| | 574 | error.FileNotFound => return error.MissingDebugInfo, |
| | 575 | else => |e| return e, |
| | 576 | }; |
| | 577 | errdefer coff_file.close(); |
| | 578 | var section_handle: windows.HANDLE = undefined; |
| | 579 | const create_section_rc = windows.ntdll.NtCreateSection( |
| | 580 | &section_handle, |
| | 581 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, |
| | 582 | null, |
| | 583 | null, |
| | 584 | windows.PAGE_READONLY, |
| | 585 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. |
| | 586 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. |
| | 587 | windows.SEC_COMMIT, |
| | 588 | coff_file.handle, |
| | 589 | ); |
| | 590 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; |
| | 591 | errdefer windows.CloseHandle(section_handle); |
| | 592 | var coff_len: usize = 0; |
| | 593 | var section_view_ptr: [*]const u8 = undefined; |
| | 594 | const map_section_rc = windows.ntdll.NtMapViewOfSection( |
| | 595 | section_handle, |
| | 596 | process_handle, |
| | 597 | @ptrCast(&section_view_ptr), |
| | 598 | null, |
| | 599 | 0, |
| | 600 | null, |
| | 601 | &coff_len, |
| | 602 | .ViewUnmap, |
| | 603 | 0, |
| | 604 | windows.PAGE_READONLY, |
| | 605 | ); |
| | 606 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; |
| | 607 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS); |
| | 608 | const section_view = section_view_ptr[0..coff_len]; |
| | 609 | coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; |
| | 610 | di.mapped_file = .{ |
| | 611 | .file = coff_file, |
| | 612 | .section_handle = section_handle, |
| | 613 | .section_view = section_view, |
| | 614 | }; |
| | 615 | } |
| | 616 | di.coff_image_base = coff_obj.getImageBase(); |
| | 617 | |
| | 618 | if (coff_obj.getSectionByName(".debug_info")) |_| { |
| | 619 | di.dwarf = .{}; |
| | 620 | |
| | 621 | inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { |
| | 622 | di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { |
| | 623 | break :blk .{ |
| | 624 | .data = try coff_obj.getSectionDataAlloc(section_header, gpa), |
| | 625 | .virtual_address = section_header.virtual_address, |
| | 626 | .owned = true, |
| | 627 | }; |
| | 628 | } else null; |
| | 629 | } |
| | 630 | |
| | 631 | try di.dwarf.?.open(gpa, native_endian); |
| | 632 | } |
| | 633 | |
| | 634 | if (try coff_obj.getPdbPath()) |raw_path| pdb: { |
| | 635 | const path = blk: { |
| | 636 | if (fs.path.isAbsolute(raw_path)) { |
| | 637 | break :blk raw_path; |
| | 638 | } else { |
| | 639 | const self_dir = try fs.selfExeDirPathAlloc(gpa); |
| | 640 | defer gpa.free(self_dir); |
| | 641 | break :blk try fs.path.join(gpa, &.{ self_dir, raw_path }); |
| | 642 | } |
| | 643 | }; |
| | 644 | defer if (path.ptr != raw_path.ptr) gpa.free(path); |
| | 645 | |
| | 646 | di.pdb = Pdb.init(gpa, path) catch |err| switch (err) { |
| | 647 | error.FileNotFound, error.IsDir => break :pdb, |
| | 648 | else => return err, |
| | 649 | }; |
| | 650 | try di.pdb.?.parseInfoStream(); |
| | 651 | try di.pdb.?.parseDbiStream(); |
| | 652 | |
| | 653 | if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) |
| | 654 | return error.InvalidDebugInfo; |
| | 655 | |
| | 656 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa); |
| | 657 | } |
| | 658 | } |
| | 659 | const LookupCache = struct { |
| | 660 | modules: std.ArrayListUnmanaged(windows.MODULEENTRY32), |
| | 661 | const init: LookupCache = .{ .modules = .empty }; |
| | 662 | }; |
| 677 | const DebugInfo = struct { | 663 | const DebugInfo = struct { |
| 678 | coff_image_base: u64, | 664 | coff_image_base: u64, |
| 679 | mapped_file: ?struct { | 665 | mapped_file: ?struct { |
| ... | @@ -747,9 +733,9 @@ const Module = switch (native_os) { | ... | @@ -747,9 +733,9 @@ const Module = switch (native_os) { |
| 747 | } | 733 | } |
| 748 | }; | 734 | }; |
| 749 | | 735 | |
| 750 | fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { | 736 | fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol { |
| 751 | // Translate the runtime address into a virtual address into the module | 737 | // Translate the runtime address into a virtual address into the module |
| 752 | const vaddr = address - mod.base_address; | 738 | const vaddr = address - module.base_address; |
| 753 | | 739 | |
| 754 | if (di.pdb != null) { | 740 | if (di.pdb != null) { |
| 755 | if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol; | 741 | if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol; |
| ... | @@ -1091,12 +1077,12 @@ fn unwindFrameDwarf( | ... | @@ -1091,12 +1077,12 @@ fn unwindFrameDwarf( |
| 1091 | | 1077 | |
| 1092 | const pc_vaddr = context.pc - load_offset; | 1078 | const pc_vaddr = context.pc - load_offset; |
| 1093 | | 1079 | |
| 1094 | const fde_offset = explicit_fde_offset orelse try unwind.findFdeOffset( | 1080 | const fde_offset = explicit_fde_offset orelse try unwind.lookupPc( |
| 1095 | pc_vaddr, | 1081 | pc_vaddr, |
| 1096 | @sizeOf(usize), | 1082 | @sizeOf(usize), |
| 1097 | native_endian, | 1083 | native_endian, |
| 1098 | ) orelse return error.MissingDebugInfo; | 1084 | ) orelse return error.MissingDebugInfo; |
| 1099 | const format, const cie, const fde = try unwind.loadFde(fde_offset, @sizeOf(usize), native_endian); | 1085 | const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian); |
| 1100 | | 1086 | |
| 1101 | // Check if this FDE *actually* includes the address. | 1087 | // Check if this FDE *actually* includes the address. |
| 1102 | if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo; | 1088 | if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo; |
| ... | @@ -1294,7 +1280,7 @@ fn unwindFrameMachO( | ... | @@ -1294,7 +1280,7 @@ fn unwindFrameMachO( |
| 1294 | load_offset: usize, | 1280 | load_offset: usize, |
| 1295 | context: *UnwindContext, | 1281 | context: *UnwindContext, |
| 1296 | unwind_info: []const u8, | 1282 | unwind_info: []const u8, |
| 1297 | eh_frame: ?[]const u8, | 1283 | opt_eh_frame: ?[]const u8, |
| 1298 | ) !usize { | 1284 | ) !usize { |
| 1299 | if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo; | 1285 | if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo; |
| 1300 | const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info); | 1286 | const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info); |
| ... | @@ -1304,20 +1290,6 @@ fn unwindFrameMachO( | ... | @@ -1304,20 +1290,6 @@ fn unwindFrameMachO( |
| 1304 | const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]); | 1290 | const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]); |
| 1305 | if (indices.len == 0) return error.MissingUnwindInfo; | 1291 | if (indices.len == 0) return error.MissingUnwindInfo; |
| 1306 | | 1292 | |
| 1307 | // MLUGG TODO HACKHACK -- Unwind needs a slight refactor to make this work well | | |
| 1308 | const opt_dwarf_unwind: ?Dwarf.Unwind = if (eh_frame) |eh_frame_data| .{ | | |
| 1309 | .debug_frame = null, | | |
| 1310 | .eh_frame = .{ | | |
| 1311 | .header = .{ | | |
| 1312 | .vaddr = undefined, | | |
| 1313 | .eh_frame_vaddr = @intFromPtr(eh_frame_data.ptr) - load_offset, | | |
| 1314 | .search_table = null, | | |
| 1315 | }, | | |
| 1316 | .eh_frame_data = eh_frame_data, | | |
| 1317 | .sorted_fdes = null, | | |
| 1318 | }, | | |
| 1319 | } else null; | | |
| 1320 | | | |
| 1321 | // offset of the PC into the `__TEXT` segment | 1293 | // offset of the PC into the `__TEXT` segment |
| 1322 | const pc_text_offset = context.pc - text_base; | 1294 | const pc_text_offset = context.pc - text_base; |
| 1323 | | 1295 | |
| ... | @@ -1533,8 +1505,11 @@ fn unwindFrameMachO( | ... | @@ -1533,8 +1505,11 @@ fn unwindFrameMachO( |
| 1533 | break :ip new_ip; | 1505 | break :ip new_ip; |
| 1534 | }, | 1506 | }, |
| 1535 | .DWARF => { | 1507 | .DWARF => { |
| 1536 | const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame); | 1508 | const eh_frame = opt_eh_frame orelse return error.MissingEhFrame; |
| 1537 | return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf)); | 1509 | const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset; |
| | 1510 | var dwarf_unwind: Dwarf.Unwind = .init; |
| | 1511 | dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame); |
| | 1512 | return unwindFrameDwarf(&dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf)); |
| 1538 | }, | 1513 | }, |
| 1539 | }, | 1514 | }, |
| 1540 | .aarch64, .aarch64_be => switch (encoding.mode.arm64) { | 1515 | .aarch64, .aarch64_be => switch (encoding.mode.arm64) { |
| ... | @@ -1547,7 +1522,10 @@ fn unwindFrameMachO( | ... | @@ -1547,7 +1522,10 @@ fn unwindFrameMachO( |
| 1547 | break :ip new_ip; | 1522 | break :ip new_ip; |
| 1548 | }, | 1523 | }, |
| 1549 | .DWARF => { | 1524 | .DWARF => { |
| 1550 | const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame); | 1525 | const eh_frame = opt_eh_frame orelse return error.MissingEhFrame; |
| | 1526 | const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset; |
| | 1527 | var dwarf_unwind: Dwarf.Unwind = .init; |
| | 1528 | dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame); |
| 1551 | return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf)); | 1529 | return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf)); |
| 1552 | }, | 1530 | }, |
| 1553 | .FRAME => ip: { | 1531 | .FRAME => ip: { |