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
This commit is contained in:
kcbanner 2023-06-28 20:37:45 -04:00
parent caa334712f
commit f991b9dc05
4 changed files with 169 additions and 15 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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));

View File

@ -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,