diff options
| author | kcbanner <kcbanner@gmail.com> | 2023-06-28 20:37:45 -0400 |
|---|---|---|
| committer | kcbanner <kcbanner@gmail.com> | 2023-07-20 22:58:14 -0400 |
| commit | f991b9dc05706613839743f970a32d516085f182 (patch) | |
| tree | cd78cc290863b47a88c5e2892f78695be9d97010 /lib/std/debug.zig | |
| parent | caa334712fc8f540349e54fa3008aa3fdef64e13 (diff) | |
| download | zig-f991b9dc05706613839743f970a32d516085f182.tar.gz zig-f991b9dc05706613839743f970a32d516085f182.zip | |
debug: fix reading -gdwarf generated debug sections in COFF files
I had accidentally regressed support for -gdwarf in 461fb499f3cff9038a427eae120fb34defc9ab38 when I changed the logic to
use the already-mapped exe/dll image instead of loading it from disk. The string table is mapped as all zeroes by the loader,
so if a section header's name is longer than 8 bytes (like the ones generated by -gdwarf), then the name can't be read.
Now, if any section headers require the string table, the file is mapped from disk.
windows: Add NtCreateSection/NtMapViewOfSection/NtUnmapViewOfSection
Diffstat (limited to 'lib/std/debug.zig')
| -rw-r--r-- | lib/std/debug.zig | 124 |
1 files changed, 109 insertions, 15 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 7b74be24af..ddef223a02 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -887,12 +887,8 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI } } -fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDebugInfo { +fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo { nosuspend { - const coff_obj = try allocator.create(coff.Coff); - defer allocator.destroy(coff_obj); - coff_obj.* = try coff.Coff.init(coff_bytes); - var di = ModuleDebugInfo{ .base_address = undefined, .coff_image_base = coff_obj.getImageBase(), @@ -908,9 +904,14 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { - sections[i] = .{ - .data = try coff_obj.getSectionDataAlloc("." ++ section.name, allocator), - .owned = true, + sections[i] = if (coff_obj.getSectionDataAlloc("." ++ section.name, allocator)) |data| blk: { + break :blk .{ + .data = data, + .owned = true, + }; + } else |err| blk: { + if (err == error.MissingCoffSection) break :blk null; + return err; }; } @@ -920,7 +921,7 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe .is_macho = false, }; - try DW.openDwarfDebugInfo(&dwarf, allocator, coff_bytes); + try DW.openDwarfDebugInfo(&dwarf, allocator, coff_obj.data); di.debug_data = PdbOrDwarf{ .dwarf = dwarf }; return di; } @@ -1358,6 +1359,21 @@ pub const WindowsModuleInfo = struct { base_address: usize, size: u32, name: []const u8, + handle: windows.HMODULE, + + // Set when the image file needed to be mapped from disk + mapped_file: ?struct { + file: File, + section_handle: windows.HANDLE, + section_view: []const u8, + + pub fn deinit(self: @This()) void { + const process_handle = windows.kernel32.GetCurrentProcess(); + assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS); + windows.CloseHandle(self.section_handle); + self.file.close(); + } + } = null, }; pub const DebugInfo = struct { @@ -1373,6 +1389,8 @@ pub const DebugInfo = struct { }; if (native_os == .windows) { + errdefer debug_info.modules.deinit(allocator); + const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); if (handle == windows.INVALID_HANDLE_VALUE) { switch (windows.kernel32.GetLastError()) { @@ -1390,9 +1408,16 @@ pub const DebugInfo = struct { var module_valid = true; while (module_valid) { const module_info = try debug_info.modules.addOne(allocator); - module_info.base_address = @intFromPtr(module_entry.modBaseAddr); - module_info.size = module_entry.modBaseSize; - module_info.name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; + const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; + errdefer allocator.free(name); + + module_info.* = .{ + .base_address = @intFromPtr(module_entry.modBaseAddr), + .size = module_entry.modBaseSize, + .name = name, + .handle = module_entry.hModule, + }; + module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; } } @@ -1411,6 +1436,7 @@ pub const DebugInfo = struct { if (native_os == .windows) { for (self.modules.items) |module| { self.allocator.free(module.name); + if (module.mapped_file) |mapped_file| mapped_file.deinit(); } self.modules.deinit(self.allocator); } @@ -1500,17 +1526,85 @@ pub const DebugInfo = struct { } fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo { - for (self.modules.items) |module| { + for (self.modules.items) |*module| { if (address >= module.base_address and address < module.base_address + module.size) { if (self.address_map.get(module.base_address)) |obj_di| { return obj_di; } - const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; const obj_di = try self.allocator.create(ModuleDebugInfo); errdefer self.allocator.destroy(obj_di); - obj_di.* = try readCoffDebugInfo(self.allocator, mapped_module); + const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; + var coff_obj = try coff.Coff.init(mapped_module); + + // The string table is not mapped into memory by the loader, so if a section name is in the + // string table then we have to map the full image file from disk. This can happen when + // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. + if (coff_obj.strtabRequired()) { + var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; + // openFileAbsoluteW requires the prefix to be present + mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); + + const process_handle = windows.kernel32.GetCurrentProcess(); + const len = windows.kernel32.K32GetModuleFileNameExW( + process_handle, + module.handle, + @ptrCast(&name_buffer[4]), + windows.PATH_MAX_WIDE, + ); + + if (len == 0) return error.MissingDebugInfo; + const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return err, + }; + errdefer coff_file.close(); + + var section_handle: windows.HANDLE = undefined; + const create_section_rc = windows.ntdll.NtCreateSection( + §ion_handle, + windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, + null, + null, + windows.PAGE_READONLY, + // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. + // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. + windows.SEC_COMMIT, + coff_file.handle, + ); + if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; + errdefer windows.CloseHandle(section_handle); + + var coff_len: usize = 0; + var base_ptr: usize = 0; + const map_section_rc = windows.ntdll.NtMapViewOfSection( + section_handle, + process_handle, + @ptrCast(&base_ptr), + null, + 0, + null, + &coff_len, + .ViewUnmap, + 0, + windows.PAGE_READONLY, + ); + if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; + errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); + + const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; + coff_obj = try coff.Coff.init(section_view); + + module.mapped_file = .{ + .file = coff_file, + .section_handle = section_handle, + .section_view = section_view, + }; + } + errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); + + obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); obj_di.base_address = module.base_address; try self.address_map.putNoClobber(module.base_address, obj_di); |
