Commit f991b9dc05

kcbanner <kcbanner@gmail.com>
2023-06-29 02:37:45
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
1 parent caa3347
Changed files (4)
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,
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));
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;
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(
+                        &section_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);