Commit 461fb499f3

kcbanner <kcbanner@gmail.com>
2022-09-25 03:50:15
windows: rework DebugInfo to use less file operations and fix some memory management issues
1 parent fcee1bf
Changed files (4)
lib/std/os/windows/kernel32.zig
@@ -66,6 +66,7 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE;
 const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
 const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
 const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
+const MODULEENTRY32 = windows.MODULEENTRY32;
 
 pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque;
 pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
@@ -132,6 +133,8 @@ pub extern "kernel32" fn CreateIoCompletionPort(FileHandle: HANDLE, ExistingComp
 
 pub extern "kernel32" fn CreateThread(lpThreadAttributes: ?*SECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?*DWORD) callconv(WINAPI) ?HANDLE;
 
+pub extern "kernel32" fn CreateToolhelp32Snapshot(dwFlags: DWORD, th32ProcessID: DWORD) callconv(WINAPI) HANDLE;
+
 pub extern "kernel32" fn DeviceIoControl(
     h: HANDLE,
     dwIoControlCode: DWORD,
@@ -265,6 +268,10 @@ pub extern "kernel32" fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASI
 
 pub extern "kernel32" fn LocalFree(hMem: HLOCAL) callconv(WINAPI) ?HLOCAL;
 
+pub extern "kernel32" fn Module32First(hSnapshot: HANDLE, lpme: *MODULEENTRY32) callconv(WINAPI) BOOL;
+
+pub extern "kernel32" fn Module32Next(hSnapshot: HANDLE, lpme: *MODULEENTRY32) callconv(WINAPI) BOOL;
+
 pub extern "kernel32" fn MoveFileExW(
     lpExistingFileName: [*:0]const u16,
     lpNewFileName: [*:0]const u16,
lib/std/os/windows.zig
@@ -3801,6 +3801,26 @@ pub const PEB_LDR_DATA = extern struct {
     ShutdownThreadId: HANDLE,
 };
 
+/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including:
+///  - https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data
+///  - https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm
+pub const LDR_DATA_TABLE_ENTRY = extern struct {
+    Reserved1: [2]PVOID,
+    InMemoryOrderLinks: LIST_ENTRY,
+    Reserved2: [2]PVOID,
+    DllBase: PVOID,
+    EntryPoint: PVOID,
+    SizeOfImage: ULONG,
+    FullDllName: UNICODE_STRING,
+    Reserved4: [8]BYTE,
+    Reserved5: [3]PVOID,
+    DUMMYUNIONNAME: extern union {
+        CheckSum: ULONG,
+        Reserved6: PVOID,
+    },
+    TimeDateStamp: ULONG,
+};
+
 pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
     AllocationSize: ULONG,
     Size: ULONG,
@@ -4349,3 +4369,25 @@ pub fn IsProcessorFeaturePresent(feature: PF) bool {
     if (@enumToInt(feature) >= PROCESSOR_FEATURE_MAX) return false;
     return SharedUserData.ProcessorFeatures[@enumToInt(feature)] == 1;
 }
+
+pub const TH32CS_SNAPHEAPLIST = 0x00000001;
+pub const TH32CS_SNAPPROCESS = 0x00000002;
+pub const TH32CS_SNAPTHREAD = 0x00000004;
+pub const TH32CS_SNAPMODULE = 0x00000008;
+pub const TH32CS_SNAPMODULE32 = 0x00000010;
+pub const TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE;
+pub const TH32CS_INHERIT = 0x80000000;
+
+pub const MAX_MODULE_NAME32 = 255;
+pub const MODULEENTRY32 = extern struct {
+    dwSize: DWORD,
+    th32ModuleID: DWORD,
+    th32ProcessID: DWORD,
+    GlblcntUsage: DWORD,
+    ProccntUsage: DWORD,
+    modBaseAddr: *BYTE,
+    modBaseSize: DWORD,
+    hModule: HMODULE,
+    szModule: [MAX_MODULE_NAME32 + 1]CHAR,
+    szExePath: [MAX_PATH]CHAR,
+};
lib/std/coff.zig
@@ -1061,65 +1061,55 @@ pub const CoffError = error{
 
 // Official documentation of the format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
 pub const Coff = struct {
-    allocator: mem.Allocator,
-    data: []const u8 = undefined,
-    is_image: bool = false,
-    coff_header_offset: usize = 0,
+    data: []const u8,
+    is_image: bool,
+    coff_header_offset: usize,
 
     guid: [16]u8 = undefined,
     age: u32 = undefined,
 
-    pub fn deinit(self: *Coff) void {
-        self.allocator.free(self.data);
-    }
-
-    /// Takes ownership of `data`.
-    pub fn parse(self: *Coff, data: []const u8) !void {
-        self.data = data;
-
+    // The lifetime of `data` must be longer than the lifetime of the returned Coff
+    pub fn init(data: []const u8) !Coff {
         const pe_pointer_offset = 0x3C;
         const pe_magic = "PE\x00\x00";
 
-        var stream = std.io.fixedBufferStream(self.data);
+        var stream = std.io.fixedBufferStream(data);
         const reader = stream.reader();
         try stream.seekTo(pe_pointer_offset);
-        const coff_header_offset = try reader.readIntLittle(u32);
+        var coff_header_offset = try reader.readIntLittle(u32);
         try stream.seekTo(coff_header_offset);
         var buf: [4]u8 = undefined;
         try reader.readNoEof(&buf);
-        self.is_image = mem.eql(u8, pe_magic, &buf);
+        const is_image = mem.eql(u8, pe_magic, &buf);
+
+        var coff = @This(){
+            .data = data,
+            .is_image = is_image,
+            .coff_header_offset = coff_header_offset,
+        };
 
         // Do some basic validation upfront
-        if (self.is_image) {
-            self.coff_header_offset = coff_header_offset + 4;
-            const coff_header = self.getCoffHeader();
+        if (is_image) {
+            coff.coff_header_offset = coff.coff_header_offset + 4;
+            const coff_header = coff.getCoffHeader();
             if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;
         }
 
         // JK: we used to check for architecture here and throw an error if not x86 or derivative.
         // However I am willing to take a leap of faith and let aarch64 have a shot also.
+
+        return coff;
     }
 
     pub fn getPdbPath(self: *Coff, buffer: []u8) !usize {
         assert(self.is_image);
 
-        const header = blk: {
-            if (self.getSectionByName(".buildid")) |hdr| {
-                break :blk hdr;
-            } else if (self.getSectionByName(".rdata")) |hdr| {
-                break :blk hdr;
-            } else {
-                return error.MissingCoffSection;
-            }
-        };
-
         const data_dirs = self.getDataDirectories();
         const debug_dir = data_dirs[@enumToInt(DirectoryEntry.DEBUG)];
-        const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data;
 
         var stream = std.io.fixedBufferStream(self.data);
         const reader = stream.reader();
-        try stream.seekTo(file_offset);
+        try stream.seekTo(debug_dir.virtual_address);
 
         // Find the correct DebugDirectoryEntry, and where its data is stored.
         // It can be in any section.
@@ -1128,16 +1118,8 @@ pub const Coff = struct {
         blk: while (i < debug_dir_entry_count) : (i += 1) {
             const debug_dir_entry = try reader.readStruct(DebugDirectoryEntry);
             if (debug_dir_entry.type == .CODEVIEW) {
-                for (self.getSectionHeaders()) |*section| {
-                    const section_start = section.virtual_address;
-                    const section_size = section.virtual_size;
-                    const rva = debug_dir_entry.address_of_raw_data;
-                    const offset = rva - section_start;
-                    if (section_start <= rva and offset < section_size and debug_dir_entry.size_of_data <= section_size - offset) {
-                        try stream.seekTo(section.pointer_to_raw_data + offset);
-                        break :blk;
-                    }
-                }
+                try stream.seekTo(debug_dir_entry.address_of_raw_data);
+                break :blk;
             }
         }
 
@@ -1238,6 +1220,16 @@ pub const Coff = struct {
         return @ptrCast([*]align(1) const SectionHeader, self.data.ptr + offset)[0..coff_header.number_of_sections];
     }
 
+    pub fn getSectionHeadersAlloc(self: *const Coff, allocator: mem.Allocator) ![]SectionHeader {
+        const section_headers = self.getSectionHeaders();
+        const out_buff = try allocator.alloc(SectionHeader, section_headers.len);
+        for (out_buff) |*section_header, i| {
+            section_header.* = section_headers[i];
+        }
+
+        return out_buff;
+    }
+
     pub fn getSectionName(self: *const Coff, sect_hdr: *align(1) const SectionHeader) []const u8 {
         const name = sect_hdr.getName() orelse blk: {
             const strtab = self.getStrtab().?;
@@ -1256,12 +1248,15 @@ pub const Coff = struct {
         return null;
     }
 
+    pub fn getSectionData(self: *const Coff, comptime name: []const u8) ![]const u8 {
+        const sec = self.getSectionByName(name) orelse return error.MissingCoffSection;
+        return self.data[sec.pointer_to_raw_data..][0..sec.virtual_size];
+    }
+
     // Return an owned slice full of the section data
     pub fn getSectionDataAlloc(self: *const Coff, comptime name: []const u8, allocator: mem.Allocator) ![]u8 {
-        const sec = self.getSectionByName(name) orelse return error.MissingCoffSection;
-        const out_buff = try allocator.alloc(u8, sec.virtual_size);
-        mem.copy(u8, out_buff, self.data[sec.pointer_to_raw_data..][0..sec.virtual_size]);
-        return out_buff;
+        const section_data = try self.getSectionData(name);
+        return allocator.dupe(u8, section_data);
     }
 };
 
lib/std/debug.zig
@@ -811,7 +811,7 @@ fn printLineInfo(
 pub const OpenSelfDebugInfoError = error{
     MissingDebugInfo,
     UnsupportedOperatingSystem,
-};
+} || @typeInfo(@typeInfo(@TypeOf(DebugInfo.init)).Fn.return_type.?).ErrorUnion.error_set;
 
 pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugInfo {
     nosuspend {
@@ -827,60 +827,56 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI
             .dragonfly,
             .openbsd,
             .macos,
-            .windows,
             .solaris,
-            => return DebugInfo.init(allocator),
+            .windows,
+            => return try DebugInfo.init(allocator),
             else => return error.UnsupportedOperatingSystem,
         }
     }
 }
 
-/// This takes ownership of coff_file: users of this function should not close
-/// it themselves, even on error.
-/// TODO it's weird to take ownership even on error, rework this code.
-fn readCoffDebugInfo(allocator: mem.Allocator, coff_file: File) !ModuleDebugInfo {
+fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDebugInfo {
     nosuspend {
-        defer coff_file.close();
-
         const coff_obj = try allocator.create(coff.Coff);
         errdefer allocator.destroy(coff_obj);
-        coff_obj.* = .{ .allocator = allocator };
+        coff_obj.* = try coff.Coff.init(coff_bytes);
 
         var di = ModuleDebugInfo{
             .base_address = undefined,
-            .coff = coff_obj,
+            .coff_image_base = coff_obj.getImageBase(),
+            .coff_section_headers = undefined,
             .debug_data = undefined,
         };
 
-        // TODO convert to Windows' memory-mapped file API
-        const file_len = math.cast(usize, try coff_file.getEndPos()) orelse math.maxInt(usize);
-        const data = try coff_file.readToEndAlloc(allocator, file_len);
-        try di.coff.parse(data);
-
-        if (di.coff.getSectionByName(".debug_info")) |sec| {
+        if (coff_obj.getSectionByName(".debug_info")) |sec| {
             // This coff file has embedded DWARF debug info
             _ = sec;
-            // TODO: free the section data slices
-            const debug_info = di.coff.getSectionDataAlloc(".debug_info", allocator) catch null;
-            const debug_abbrev = di.coff.getSectionDataAlloc(".debug_abbrev", allocator) catch null;
-            const debug_str = di.coff.getSectionDataAlloc(".debug_str", allocator) catch null;
-            const debug_str_offsets = di.coff.getSectionDataAlloc(".debug_str_offsets", allocator) catch null;
-            const debug_line = di.coff.getSectionDataAlloc(".debug_line", allocator) catch null;
-            const debug_line_str = di.coff.getSectionDataAlloc(".debug_line_str", allocator) catch null;
-            const debug_ranges = di.coff.getSectionDataAlloc(".debug_ranges", allocator) catch null;
-            const debug_loclists = di.coff.getSectionDataAlloc(".debug_loclists", allocator) catch null;
-            const debug_rnglists = di.coff.getSectionDataAlloc(".debug_rnglists", allocator) catch null;
-            const debug_addr = di.coff.getSectionDataAlloc(".debug_addr", allocator) catch null;
-            const debug_names = di.coff.getSectionDataAlloc(".debug_names", allocator) catch null;
-            const debug_frame = di.coff.getSectionDataAlloc(".debug_frame", allocator) catch null;
+
+            const debug_info = coff_obj.getSectionDataAlloc(".debug_info", allocator) catch return error.MissingDebugInfo;
+            errdefer allocator.free(debug_info);
+            const debug_abbrev = coff_obj.getSectionDataAlloc(".debug_abbrev", allocator) catch return error.MissingDebugInfo;
+            errdefer allocator.free(debug_abbrev);
+            const debug_str = coff_obj.getSectionDataAlloc(".debug_str", allocator) catch return error.MissingDebugInfo;
+            errdefer allocator.free(debug_str);
+            const debug_line = coff_obj.getSectionDataAlloc(".debug_line", allocator) catch return error.MissingDebugInfo;
+            errdefer allocator.free(debug_line);
+
+            const debug_str_offsets = coff_obj.getSectionDataAlloc(".debug_str_offsets", allocator) catch null;
+            const debug_line_str = coff_obj.getSectionDataAlloc(".debug_line_str", allocator) catch null;
+            const debug_ranges = coff_obj.getSectionDataAlloc(".debug_ranges", allocator) catch null;
+            const debug_loclists = coff_obj.getSectionDataAlloc(".debug_loclists", allocator) catch null;
+            const debug_rnglists = coff_obj.getSectionDataAlloc(".debug_rnglists", allocator) catch null;
+            const debug_addr = coff_obj.getSectionDataAlloc(".debug_addr", allocator) catch null;
+            const debug_names = coff_obj.getSectionDataAlloc(".debug_names", allocator) catch null;
+            const debug_frame = coff_obj.getSectionDataAlloc(".debug_frame", allocator) catch null;
 
             var dwarf = DW.DwarfInfo{
                 .endian = native_endian,
-                .debug_info = debug_info orelse return error.MissingDebugInfo,
-                .debug_abbrev = debug_abbrev orelse return error.MissingDebugInfo,
-                .debug_str = debug_str orelse return error.MissingDebugInfo,
+                .debug_info = debug_info,
+                .debug_abbrev = debug_abbrev,
+                .debug_str = debug_str,
                 .debug_str_offsets = debug_str_offsets,
-                .debug_line = debug_line orelse return error.MissingDebugInfo,
+                .debug_line = debug_line,
                 .debug_line_str = debug_line_str,
                 .debug_ranges = debug_ranges,
                 .debug_loclists = debug_loclists,
@@ -889,13 +885,28 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_file: File) !ModuleDebugInfo
                 .debug_names = debug_names,
                 .debug_frame = debug_frame,
             };
-            try DW.openDwarfDebugInfo(&dwarf, allocator);
+
+            DW.openDwarfDebugInfo(&dwarf, allocator) catch |err| {
+                if (debug_str_offsets) |d| allocator.free(d);
+                if (debug_line_str) |d| allocator.free(d);
+                if (debug_ranges) |d| allocator.free(d);
+                if (debug_loclists) |d| allocator.free(d);
+                if (debug_rnglists) |d| allocator.free(d);
+                if (debug_addr) |d| allocator.free(d);
+                if (debug_names) |d| allocator.free(d);
+                if (debug_frame) |d| allocator.free(d);
+                return err;
+            };
+
             di.debug_data = PdbOrDwarf{ .dwarf = dwarf };
             return di;
         }
 
+        // Only used by pdb path
+        di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
+
         var path_buf: [windows.MAX_PATH]u8 = undefined;
-        const len = try di.coff.getPdbPath(path_buf[0..]);
+        const len = try coff_obj.getPdbPath(path_buf[0..]);
         const raw_path = path_buf[0..len];
 
         const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path});
@@ -909,7 +920,7 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_file: File) !ModuleDebugInfo
         try di.debug_data.pdb.parseInfoStream();
         try di.debug_data.pdb.parseDbiStream();
 
-        if (!mem.eql(u8, &di.coff.guid, &di.debug_data.pdb.guid) or di.coff.age != di.debug_data.pdb.age)
+        if (!mem.eql(u8, &coff_obj.guid, &di.debug_data.pdb.guid) or coff_obj.age != di.debug_data.pdb.age)
             return error.InvalidDebugInfo;
 
         return di;
@@ -1225,15 +1236,49 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
     }
 }
 
+pub const ModuleInfo = struct {
+    base_address: usize,
+    size: u32,
+};
+
 pub const DebugInfo = struct {
     allocator: mem.Allocator,
     address_map: std.AutoHashMap(usize, *ModuleDebugInfo),
+    modules: if (native_os == .windows) std.ArrayListUnmanaged(ModuleInfo) else void,
 
-    pub fn init(allocator: mem.Allocator) DebugInfo {
-        return DebugInfo{
+    pub fn init(allocator: mem.Allocator) !DebugInfo {
+        var debug_info = DebugInfo{
             .allocator = allocator,
             .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator),
+            .modules = if (native_os == .windows) .{} else {},
         };
+
+        if (native_os == .windows) {
+            const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
+            if (handle == windows.INVALID_HANDLE_VALUE) {
+                switch (windows.kernel32.GetLastError()) {
+                    else => |err| return windows.unexpectedError(err),
+                }
+            }
+
+            defer windows.CloseHandle(handle);
+
+            var module_entry: windows.MODULEENTRY32 = undefined;
+            module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
+            if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
+                return error.MissingDebugInfo;
+            }
+
+            var module_valid = true;
+            while (module_valid) {
+                const module_info = try debug_info.modules.addOne(allocator);
+                module_info.base_address = @ptrToInt(module_entry.modBaseAddr);
+                module_info.size = module_entry.modBaseSize;
+                module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
+            }
+        }
+
+        return debug_info;
     }
 
     pub fn deinit(self: *DebugInfo) void {
@@ -1322,79 +1367,20 @@ pub const DebugInfo = struct {
     }
 
     fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
-        const process_handle = windows.kernel32.GetCurrentProcess();
-
-        // Find how many modules are actually loaded
-        var dummy: windows.HMODULE = undefined;
-        var bytes_needed: windows.DWORD = undefined;
-        if (windows.kernel32.K32EnumProcessModules(
-            process_handle,
-            @ptrCast([*]windows.HMODULE, &dummy),
-            0,
-            &bytes_needed,
-        ) == 0)
-            return error.MissingDebugInfo;
-
-        const needed_modules = bytes_needed / @sizeOf(windows.HMODULE);
-
-        // Fetch the complete module list
-        var modules = try self.allocator.alloc(windows.HMODULE, needed_modules);
-        defer self.allocator.free(modules);
-        if (windows.kernel32.K32EnumProcessModules(
-            process_handle,
-            modules.ptr,
-            math.cast(windows.DWORD, modules.len * @sizeOf(windows.HMODULE)) orelse return error.Overflow,
-            &bytes_needed,
-        ) == 0)
-            return error.MissingDebugInfo;
-
-        // There's an unavoidable TOCTOU problem here, the module list may have
-        // changed between the two EnumProcessModules call.
-        // Pick the smallest amount of elements to avoid processing garbage.
-        const needed_modules_after = bytes_needed / @sizeOf(windows.HMODULE);
-        const loaded_modules = math.min(needed_modules, needed_modules_after);
-
-        for (modules[0..loaded_modules]) |module| {
-            var info: windows.MODULEINFO = undefined;
-            if (windows.kernel32.K32GetModuleInformation(
-                process_handle,
-                module,
-                &info,
-                @sizeOf(@TypeOf(info)),
-            ) == 0)
-                return error.MissingDebugInfo;
-
-            const seg_start = @ptrToInt(info.lpBaseOfDll);
-            const seg_end = seg_start + info.SizeOfImage;
-
-            if (address >= seg_start and address < seg_end) {
-                if (self.address_map.get(seg_start)) |obj_di| {
+        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;
                 }
 
-                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 len = windows.kernel32.K32GetModuleFileNameExW(
-                    process_handle,
-                    module,
-                    @ptrCast(windows.LPWSTR, &name_buffer[4]),
-                    windows.PATH_MAX_WIDE,
-                );
-                assert(len > 0);
-
+                const mapped_module = @intToPtr([*]const u8, module.base_address)[0..module.size];
                 const obj_di = try self.allocator.create(ModuleDebugInfo);
                 errdefer self.allocator.destroy(obj_di);
 
-                const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
-                    error.FileNotFound => return error.MissingDebugInfo,
-                    else => return err,
-                };
-                obj_di.* = try readCoffDebugInfo(self.allocator, coff_file);
-                obj_di.base_address = seg_start;
-
-                try self.address_map.putNoClobber(seg_start, obj_di);
+                obj_di.* = try readCoffDebugInfo(self.allocator, mapped_module);
+                obj_di.base_address = module.base_address;
 
+                try self.address_map.putNoClobber(module.base_address, obj_di);
                 return obj_di;
             }
         }
@@ -1727,12 +1713,31 @@ pub const ModuleDebugInfo = switch (native_os) {
     .uefi, .windows => struct {
         base_address: usize,
         debug_data: PdbOrDwarf,
-        coff: *coff.Coff,
+        coff_image_base: u64,
+        coff_section_headers: []coff.SectionHeader,
 
         fn deinit(self: *@This(), allocator: mem.Allocator) void {
+            switch (self.debug_data) {
+                .dwarf => |*dwarf| {
+                    allocator.free(dwarf.debug_info);
+                    allocator.free(dwarf.debug_abbrev);
+                    allocator.free(dwarf.debug_str);
+                    allocator.free(dwarf.debug_line);
+                    if (dwarf.debug_str_offsets) |d| allocator.free(d);
+                    if (dwarf.debug_line_str) |d| allocator.free(d);
+                    if (dwarf.debug_ranges) |d| allocator.free(d);
+                    if (dwarf.debug_loclists) |d| allocator.free(d);
+                    if (dwarf.debug_rnglists) |d| allocator.free(d);
+                    if (dwarf.debug_addr) |d| allocator.free(d);
+                    if (dwarf.debug_names) |d| allocator.free(d);
+                    if (dwarf.debug_frame) |d| allocator.free(d);
+                },
+                .pdb => {
+                    allocator.free(self.coff_section_headers);
+                },
+            }
+
             self.debug_data.deinit(allocator);
-            self.coff.deinit();
-            allocator.destroy(self.coff);
         }
 
         pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
@@ -1741,7 +1746,7 @@ pub const ModuleDebugInfo = switch (native_os) {
 
             switch (self.debug_data) {
                 .dwarf => |*dwarf| {
-                    const dwarf_address = relocated_address + self.coff.getImageBase();
+                    const dwarf_address = relocated_address + self.coff_image_base;
                     return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
                 },
                 .pdb => {
@@ -1751,10 +1756,9 @@ pub const ModuleDebugInfo = switch (native_os) {
 
             var coff_section: *align(1) const coff.SectionHeader = undefined;
             const mod_index = for (self.debug_data.pdb.sect_contribs) |sect_contrib| {
-                const sections = self.coff.getSectionHeaders();
-                if (sect_contrib.Section > sections.len) continue;
+                if (sect_contrib.Section > self.coff_section_headers.len) continue;
                 // Remember that SectionContribEntry.Section is 1-based.
-                coff_section = &sections[sect_contrib.Section - 1];
+                coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
 
                 const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
                 const vaddr_end = vaddr_start + sect_contrib.Size;