feature. See also
. The project being documented here (as the example) is the Zig library itself.
Windows.Module
const Module = struct
File
Code
const Module = struct {
entry: *const LDR.DATA_TABLE_ENTRY,
name: ?[]const u8,
di: ?(Error!DebugInfo),
const DebugInfo = struct {
arena: std.heap.ArenaAllocator.State,
coff_image_base: u64,
mapped_file: ?MappedFile,
dwarf: ?Dwarf,
pdb: ?Pdb,
coff_section_headers: []coff.SectionHeader,
const MappedFile = struct {
file: Io.File,
section_handle: windows.HANDLE,
section_view: []const u8,
fn deinit(mf: *const MappedFile, io: Io) void {
const process_handle = windows.GetCurrentProcess();
switch (windows.ntdll.NtUnmapViewOfSection(
process_handle,
@constCast(mf.section_view.ptr),
)) {
.SUCCESS => {},
else => |status| windows.unexpectedStatus(status) catch {},
}
windows.CloseHandle(mf.section_handle);
mf.file.close(io);
}
};
fn deinit(di: *DebugInfo, gpa: Allocator, io: Io) void {
if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
if (di.pdb) |*pdb| {
pdb.file_reader.file.close(io);
pdb.deinit();
}
if (di.mapped_file) |*mf| mf.deinit(io);
var arena = di.arena.promote(gpa);
arena.deinit();
}
fn getSymbols(
di: *DebugInfo,
symbol_allocator: Allocator,
text_arena: Allocator,
vaddr: usize,
resolve_inline_callers: bool,
symbols: *std.ArrayList(std.debug.Symbol),
) Error!void {
pdb: {
const pdb = &(di.pdb orelse break :pdb);
var coff_section: *align(1) const coff.SectionHeader = undefined;
const mod_index = for (pdb.sect_contribs) |sect_contrib| {
if (sect_contrib.section > di.coff_section_headers.len) continue;
coff_section = &di.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;
if (vaddr >= vaddr_start and vaddr < vaddr_end) {
break sect_contrib.module_index;
}
} else {
break :pdb;
};
const module = pdb.getModule(mod_index) catch |err| switch (err) {
error.InvalidDebugInfo,
error.MissingDebugInfo,
error.OutOfMemory,
=> |e| return e,
error.ReadFailed,
error.EndOfStream,
=> return error.InvalidDebugInfo,
} orelse {
return error.InvalidDebugInfo;
};
const addr = vaddr - coff_section.virtual_address;
const maybe_proc = pdb.getProcSym(module, addr);
const compile_unit_name = fs.path.basename(module.obj_file_name);
const symbols_top = symbols.items.len;
if (maybe_proc) |proc| {
const offset_in_func = addr - proc.code_offset;
var last_inlinee: ?u32 = null;
var iter = pdb.getInlinees(module, proc);
while (iter.next(module)) |inline_site| {
// duplicate sites in the same cases as us if we elide this check,
// implying that they exist in the underlying data and are not indicative
// of a parser bug. No useful information is lost here since an inline site
// can't actually reference itself.
if (inline_site.inlinee == last_inlinee) continue;
// points at
var line_iter = pdb.getInlineeSourceLines(module, inline_site.inlinee);
while (line_iter.next()) |inlinee_src_line| {
const maybe_loc = pdb.getInlineSiteSourceLocation(
text_arena,
module,
inline_site,
inlinee_src_line,
offset_in_func,
) catch continue;
const loc = maybe_loc orelse continue;
// new inline site, we want to overwrite the previously appended
// results.
if (!resolve_inline_callers and inline_site.inlinee != last_inlinee) {
symbols.items.len = symbols_top;
}
// wait until we're done to avoid duplicated work.
const name = if (resolve_inline_callers)
pdb.findInlineeName(inline_site.inlinee)
else
null;
try symbols.append(symbol_allocator, .{
.name = name,
.compile_unit_name = compile_unit_name,
.source_location = loc,
});
last_inlinee = inline_site.inlinee;
}
}
if (resolve_inline_callers) {
// matching sites here. We could alternatively use the parent fields to
// determine the order, but this would introduce seemingly unecessary
// complexity.
std.mem.reverse(std.debug.Symbol, symbols.items);
} else if (last_inlinee) |inlinee| {
// same inline site, and we resolve its name once at the end.
const name = pdb.findInlineeName(inlinee);
for (symbols.items) |*symbol| symbol.name = name;
}
}
if (resolve_inline_callers or symbols.items.len == 0) {
try symbols.append(symbol_allocator, .{
.name = if (maybe_proc) |proc| pdb.getSymbolName(proc) else null,
.compile_unit_name = compile_unit_name,
.source_location = pdb.getLineNumberInfo(text_arena, module, addr) catch null,
});
}
return;
}
dwarf: {
const dwarf = &(di.dwarf orelse break :dwarf);
const addr = vaddr + di.coff_image_base;
return dwarf.getSymbols(
symbol_allocator,
text_arena,
native_endian,
addr,
resolve_inline_callers,
symbols,
);
}
return error.MissingDebugInfo;
}
};
fn deinit(module: *Module, gpa: Allocator, io: Io) void {
if (module.name) |name| gpa.free(name);
if (module.di) |*di_or_err| if (di_or_err.*) |*di| di.deinit(gpa, io) else |_| {};
module.* = undefined;
}
fn getDebugInfo(module: *Module, gpa: Allocator, io: Io) Error!*DebugInfo {
if (module.di == null) module.di = loadDebugInfo(module, gpa, io);
return if (module.di.?) |*di| di else |err| err;
}
fn loadDebugInfo(module: *const Module, gpa: Allocator, io: Io) Error!DebugInfo {
const mapped_ptr: [*]const u8 = @ptrCast(module.entry.DllBase);
const mapped = mapped_ptr[0..module.entry.SizeOfImage];
var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
var arena_instance: std.heap.ArenaAllocator = .init(gpa);
errdefer arena_instance.deinit();
const arena = arena_instance.allocator();
// 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.
const mapped_file: ?DebugInfo.MappedFile = mapped: {
if (!coff_obj.strtabRequired()) break :mapped null;
var path_buffer: [4 + windows.PATH_MAX_WIDE]u16 = undefined;
path_buffer[0..4].* = .{ '\\', '?', '?', '\\' };
const path_slice = module.entry.FullDllName.slice();
@memcpy(path_buffer[4..][0..path_slice.len], path_slice);
const coff_file = Io.Threaded.dirOpenFileWtf16(
null,
path_buffer[0 .. 4 + path_slice.len],
.{},
) catch |err| switch (err) {
error.Canceled => |e| return e,
error.Unexpected => |e| return e,
error.FileNotFound => return error.MissingDebugInfo,
error.FileTooBig,
error.IsDir,
error.NotDir,
error.SymLinkLoop,
error.NameTooLong,
error.BadPathName,
=> return error.InvalidDebugInfo,
error.SystemResources,
error.WouldBlock,
error.AccessDenied,
error.PermissionDenied,
error.NoSpaceLeft,
error.DeviceBusy,
error.NoDevice,
error.PathAlreadyExists,
error.PipeBusy,
error.NetworkNotFound,
error.AntivirusInterference,
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
error.FileLocksUnsupported,
error.FileBusy,
error.ReadOnlyFileSystem,
=> return error.ReadFailed,
};
errdefer coff_file.close(io);
var section_handle: windows.HANDLE = undefined;
const create_section_rc = windows.ntdll.NtCreateSection(
§ion_handle,
.{
.SPECIFIC = .{ .SECTION = .{
.QUERY = true,
.MAP_READ = true,
} },
.STANDARD = .{ .RIGHTS = .REQUIRED },
},
null,
null,
.{ .READONLY = true },
// then SEC_COMMIT is the default.
// In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
.{ .COMMIT = true },
coff_file.handle,
);
if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
errdefer windows.CloseHandle(section_handle);
var coff_len: usize = 0;
var section_view_ptr: ?[*]const u8 = null;
const process_handle = windows.GetCurrentProcess();
const map_section_rc = windows.ntdll.NtMapViewOfSection(
section_handle,
process_handle,
@ptrCast(§ion_view_ptr),
null,
0,
null,
&coff_len,
.Unmap,
.{},
.{ .READONLY = true },
);
if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
errdefer switch (windows.ntdll.NtUnmapViewOfSection(
process_handle,
@constCast(section_view_ptr.?),
)) {
.SUCCESS => {},
else => |status| windows.unexpectedStatus(status) catch {},
};
const section_view = section_view_ptr.?[0..coff_len];
coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
break :mapped .{
.file = coff_file,
.section_handle = section_handle,
.section_view = section_view,
};
};
errdefer if (mapped_file) |*mf| mf.deinit(io);
const coff_image_base = coff_obj.getImageBase();
var opt_dwarf: ?Dwarf = dwarf: {
if (coff_obj.getSectionByName(".debug_info") == null) break :dwarf null;
var sections: Dwarf.SectionArray = undefined;
inline for (@typeInfo(Dwarf.Section.Id).@"enum".field_names, 0..) |section_name, i| {
sections[i] = if (coff_obj.getSectionByName("." ++ section_name)) |section_header| .{
.data = try coff_obj.getSectionDataAlloc(section_header, arena),
.owned = false,
} else null;
}
break :dwarf .{ .sections = sections };
};
errdefer if (opt_dwarf) |*dwarf| dwarf.deinit(gpa);
if (opt_dwarf) |*dwarf| {
dwarf.open(gpa, native_endian) catch |err| switch (err) {
error.Overflow,
error.EndOfStream,
error.StreamTooLong,
error.ReadFailed,
=> return error.InvalidDebugInfo,
error.InvalidDebugInfo,
error.MissingDebugInfo,
error.OutOfMemory,
=> |e| return e,
};
}
var opt_pdb: ?Pdb = pdb: {
const path = coff_obj.getPdbPath() catch {
return error.InvalidDebugInfo;
} orelse {
break :pdb null;
};
const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: {
break :res Io.Dir.cwd().openFile(io, path, .{});
} else res: {
const self_dir = std.process.executableDirPathAlloc(io, gpa) catch |err| switch (err) {
error.OutOfMemory, error.Unexpected => |e| return e,
else => return error.ReadFailed,
};
defer gpa.free(self_dir);
const abs_path = try fs.path.join(gpa, &.{ self_dir, path });
defer gpa.free(abs_path);
break :res Io.Dir.cwd().openFile(io, abs_path, .{});
};
const pdb_file = pdb_file_open_result catch |err| switch (err) {
error.FileNotFound, error.IsDir => break :pdb null,
else => return error.ReadFailed,
};
errdefer pdb_file.close(io);
const pdb_reader = try arena.create(Io.File.Reader);
pdb_reader.* = pdb_file.reader(io, try arena.alloc(u8, 4096));
var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) {
error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e,
else => return error.InvalidDebugInfo,
};
errdefer pdb.deinit();
pdb.parseInfoStream() catch |err| switch (err) {
error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
error.EndOfStream => return error.InvalidDebugInfo,
error.InvalidDebugInfo,
error.MissingDebugInfo,
error.OutOfMemory,
error.ReadFailed,
=> |e| return e,
};
pdb.parseDbiStream() catch |err| switch (err) {
error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
error.EndOfStream,
error.EOF,
error.StreamTooLong,
error.WriteFailed,
=> return error.InvalidDebugInfo,
error.InvalidDebugInfo,
error.OutOfMemory,
error.ReadFailed,
=> |e| return e,
};
pdb.parseIpiStream() catch |err| switch (err) {
error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
error.EndOfStream,
=> return error.InvalidDebugInfo,
error.OutOfMemory,
error.ReadFailed,
=> |e| return e,
};
if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age)
return error.InvalidDebugInfo;
break :pdb pdb;
};
errdefer if (opt_pdb) |*pdb| {
pdb.file_reader.file.close(io);
pdb.deinit();
};
const coff_section_headers: []coff.SectionHeader = if (opt_pdb != null) csh: {
break :csh try coff_obj.getSectionHeadersAlloc(arena);
} else &.{};
return .{
.arena = arena_instance.state,
.coff_image_base = coff_image_base,
.mapped_file = mapped_file,
.dwarf = opt_dwarf,
.pdb = opt_pdb,
.coff_section_headers = coff_section_headers,
};
}
}