Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Module

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;
                    // Remember that SectionContribEntry.Section is 1-based.
                    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 {
                    // we have no information to add to the address
                    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; // bad module index
                };

                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| {
                        // Filter out duplicate inline sites. Tools like llvm-addr2line output
                        // 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;

                        // If our address points into this site, get the source location(s) it
                        // 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;

                            // If we aren't trying to resolve inline callers, and we've matched a
                            // 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;
                            }

                            // Only resolve the name if we're resolving inline callers, otherwise
                            // 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) {
                        // Inline sites are stored in the pdb in reverse order, so we reverse the
                        // 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| {
                        // If we aren't resolving inline callers, then all results will have the
                        // 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 there's room for another symbol, add the actual proc
                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();

        // 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.
        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].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
            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(
                &section_handle,
                .{
                    .SPECIFIC = .{ .SECTION = .{
                        .QUERY = true,
                        .MAP_READ = true,
                    } },
                    .STANDARD = .{ .RIGHTS = .REQUIRED },
                },
                null,
                null,
                .{ .READONLY = true },
                // 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.
                .{ .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(&section_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,
        };
    }
}