From f991b9dc05706613839743f970a32d516085f182 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Wed, 28 Jun 2023 20:37:45 -0400 Subject: [PATCH] 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 --- lib/std/coff.zig | 5 ++ lib/std/debug.zig | 124 ++++++++++++++++++++++++++++++----- lib/std/os/windows.zig | 29 ++++++++ lib/std/os/windows/ntdll.zig | 26 ++++++++ 4 files changed, 169 insertions(+), 15 deletions(-) diff --git a/lib/std/coff.zig b/lib/std/coff.zig index a08c2c514d..706f888fbc 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -1214,6 +1214,11 @@ pub const Coff = struct { return Strtab{ .buffer = self.data[offset..][0..size] }; } + pub fn strtabRequired(self: *const Coff) bool { + for (self.getSectionHeaders()) |*sect_hdr| if (sect_hdr.getName() == null) return true; + return false; + } + pub fn getSectionHeaders(self: *const Coff) []align(1) const SectionHeader { const coff_header = self.getCoffHeader(); const offset = self.coff_header_offset + @sizeOf(CoffHeader) + coff_header.size_of_optional_header; 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); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 1337efdd34..9f8aa326a9 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3301,6 +3301,35 @@ pub const REGSAM = ACCESS_MASK; pub const ACCESS_MASK = DWORD; pub const LSTATUS = LONG; +pub const SECTION_INHERIT = enum(c_int) { + ViewShare = 0, + ViewUnmap = 1, +}; + +pub const SECTION_QUERY = 0x0001; +pub const SECTION_MAP_WRITE = 0x0002; +pub const SECTION_MAP_READ = 0x0004; +pub const SECTION_MAP_EXECUTE = 0x0008; +pub const SECTION_EXTEND_SIZE = 0x0010; +pub const SECTION_ALL_ACCESS = + STANDARD_RIGHTS_REQUIRED | + SECTION_QUERY | + SECTION_MAP_WRITE | + SECTION_MAP_READ | + SECTION_MAP_EXECUTE | + SECTION_EXTEND_SIZE; + +pub const SEC_64K_PAGES = 0x80000; +pub const SEC_FILE = 0x800000; +pub const SEC_IMAGE = 0x1000000; +pub const SEC_PROTECTED_IMAGE = 0x2000000; +pub const SEC_RESERVE = 0x4000000; +pub const SEC_COMMIT = 0x8000000; +pub const SEC_IMAGE_NO_EXECUTE = SEC_IMAGE | SEC_NOCACHE; +pub const SEC_NOCACHE = 0x10000000; +pub const SEC_WRITECOMBINE = 0x40000000; +pub const SEC_LARGE_PAGES = 0x80000000; + pub const HKEY = *opaque {}; pub const HKEY_LOCAL_MACHINE: HKEY = @as(HKEY, @ptrFromInt(0x80000002)); diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 328ecb80f5..8c14f1fad9 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -36,6 +36,7 @@ const THREADINFOCLASS = windows.THREADINFOCLASS; const PROCESSINFOCLASS = windows.PROCESSINFOCLASS; const LPVOID = windows.LPVOID; const LPCVOID = windows.LPCVOID; +const SECTION_INHERIT = windows.SECTION_INHERIT; pub extern "ntdll" fn NtQueryInformationProcess( ProcessHandle: HANDLE, @@ -125,6 +126,31 @@ pub extern "ntdll" fn NtCreateFile( EaBuffer: ?*anyopaque, EaLength: ULONG, ) callconv(WINAPI) NTSTATUS; +pub extern "ntdll" fn NtCreateSection( + SectionHandle: *HANDLE, + DesiredAccess: ACCESS_MASK, + ObjectAttributes: ?*OBJECT_ATTRIBUTES, + MaximumSize: ?*LARGE_INTEGER, + SectionPageProtection: ULONG, + AllocationAttributes: ULONG, + FileHandle: ?HANDLE, +) callconv(WINAPI) NTSTATUS; +pub extern "ntdll" fn NtMapViewOfSection( + SectionHandle: HANDLE, + ProcessHandle: HANDLE, + BaseAddress: *PVOID, + ZeroBits: ?*ULONG, + CommitSize: SIZE_T, + SectionOffset: ?*LARGE_INTEGER, + ViewSize: *SIZE_T, + InheritDispostion: SECTION_INHERIT, + AllocationType: ULONG, + Win32Protect: ULONG, +) callconv(WINAPI) NTSTATUS; +pub extern "ntdll" fn NtUnmapViewOfSection( + ProcessHandle: HANDLE, + BaseAddress: PVOID, +) callconv(WINAPI) NTSTATUS; pub extern "ntdll" fn NtDeviceIoControlFile( FileHandle: HANDLE, Event: ?HANDLE,