diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e753b5137c47406bd24c139d070fccc939e1361..0d8ace6a614889119b8aa6cf998137c747e10fd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -444,6 +444,7 @@ set(ZIG_STD_FILES "c/index.zig" "c/linux.zig" "c/windows.zig" + "coff.zig" "crypto/blake2.zig" "crypto/hmac.zig" "crypto/index.zig" @@ -583,6 +584,7 @@ set(ZIG_STD_FILES "os/windows/user32.zig" "os/windows/util.zig" "os/zen.zig" + "pdb.zig" "rand/index.zig" "rand/ziggurat.zig" "segmented_list.zig" diff --git a/std/coff.zig b/std/coff.zig new file mode 100644 index 0000000000000000000000000000000000000000..475b4fcbc17cad03a82edd9a8b983597f16a6524 --- /dev/null +++ b/std/coff.zig @@ -0,0 +1,238 @@ +const builtin = @import("builtin"); +const std = @import("index.zig"); +const io = std.io; +const mem = std.mem; +const os = std.os; + +const ArrayList = std.ArrayList; + +// CoffHeader.machine values +// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680313(v=vs.85).aspx +const IMAGE_FILE_MACHINE_I386 = 0x014c; +const IMAGE_FILE_MACHINE_IA64 = 0x0200; +const IMAGE_FILE_MACHINE_AMD64 = 0x8664; + +// OptionalHeader.magic values +// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx +const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; +const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; + +const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; +const DEBUG_DIRECTORY = 6; + +pub const CoffError = error { + InvalidPEMagic, + InvalidPEHeader, + InvalidMachine, + MissingCoffSection, +}; + +pub const Coff = struct { + in_file: os.File, + allocator: *mem.Allocator, + + coff_header: CoffHeader, + pe_header: OptionalHeader, + sections: ArrayList(Section), + + guid: [16]u8, + age: u32, + + pub fn loadHeader(self: *Coff) !void { + const pe_pointer_offset = 0x3C; + + var file_stream = io.FileInStream.init(&self.in_file); + const in = &file_stream.stream; + + var magic: [2]u8 = undefined; + try in.readNoEof(magic[0..]); + if (!mem.eql(u8, magic, "MZ")) + return error.InvalidPEMagic; + + // Seek to PE File Header (coff header) + try self.in_file.seekTo(pe_pointer_offset); + const pe_magic_offset = try in.readIntLe(u32); + try self.in_file.seekTo(pe_magic_offset); + + var pe_header_magic: [4]u8 = undefined; + try in.readNoEof(pe_header_magic[0..]); + if (!mem.eql(u8, pe_header_magic, []u8{'P', 'E', 0, 0})) + return error.InvalidPEHeader; + + self.coff_header = CoffHeader { + .machine = try in.readIntLe(u16), + .number_of_sections = try in.readIntLe(u16), + .timedate_stamp = try in.readIntLe(u32), + .pointer_to_symbol_table = try in.readIntLe(u32), + .number_of_symbols = try in.readIntLe(u32), + .size_of_optional_header = try in.readIntLe(u16), + .characteristics = try in.readIntLe(u16), + }; + + switch (self.coff_header.machine) { + IMAGE_FILE_MACHINE_I386, + IMAGE_FILE_MACHINE_AMD64, + IMAGE_FILE_MACHINE_IA64 + => {}, + else => return error.InvalidMachine, + } + + try self.loadOptionalHeader(&file_stream); + } + + fn loadOptionalHeader(self: *Coff, file_stream: *io.FileInStream) !void { + const in = &file_stream.stream; + self.pe_header.magic = try in.readIntLe(u16); + std.debug.warn("reading pe optional\n"); + // For now we're only interested in finding the reference to the .pdb, + // so we'll skip most of this header, which size is different in 32 + // 64 bits by the way. + var skip_size: u16 = undefined; + if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 18 * @sizeOf(u32); + } + else if (self.pe_header.magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + skip_size = 2 * @sizeOf(u8) + 8 * @sizeOf(u16) + 12 * @sizeOf(u32) + 5 * @sizeOf(u64); + } + else + return error.InvalidPEMagic; + + std.debug.warn("skipping {}\n", skip_size); + try self.in_file.seekForward(skip_size); + + const number_of_rva_and_sizes = try in.readIntLe(u32); + //std.debug.warn("indicating {} data dirs\n", number_of_rva_and_sizes); + if (number_of_rva_and_sizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + return error.InvalidPEHeader; + + for (self.pe_header.data_directory) |*data_dir| { + data_dir.* = OptionalHeader.DataDirectory { + .virtual_address = try in.readIntLe(u32), + .size = try in.readIntLe(u32), + }; + //std.debug.warn("data_dir @ {x}, size {}\n", data_dir.virtual_address, data_dir.size); + } + std.debug.warn("loaded data directories\n"); + } + + pub fn getPdbPath(self: *Coff, buffer: []u8) !usize { + try self.loadSections(); + const header = (self.getSection(".rdata") orelse return error.MissingCoffSection).header; + + // The linker puts a chunk that contains the .pdb path right after the + // debug_directory. + const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY]; + const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data; + std.debug.warn("file offset {x}\n", file_offset); + try self.in_file.seekTo(file_offset + debug_dir.size); + + var file_stream = io.FileInStream.init(&self.in_file); + const in = &file_stream.stream; + + var cv_signature: [4]u8 = undefined; // CodeView signature + try in.readNoEof(cv_signature[0..]); + // 'RSDS' indicates PDB70 format, used by lld. + if (!mem.eql(u8, cv_signature, "RSDS")) + return error.InvalidPEMagic; + std.debug.warn("cv_signature {}\n", cv_signature); + try in.readNoEof(self.guid[0..]); + self.age = try in.readIntLe(u32); + + // Finally read the null-terminated string. + var byte = try in.readByte(); + var i: usize = 0; + while (byte != 0 and i < buffer.len) : (i += 1) { + buffer[i] = byte; + byte = try in.readByte(); + } + + if (byte != 0 and i == buffer.len) + return error.NameTooLong; + + return i; + } + + pub fn loadSections(self: *Coff) !void { + if (self.sections.len != 0) + return; + + self.sections = ArrayList(Section).init(self.allocator); + + var file_stream = io.FileInStream.init(&self.in_file); + const in = &file_stream.stream; + + var name: [8]u8 = undefined; + + var i: u16 = 0; + while (i < self.coff_header.number_of_sections) : (i += 1) { + try in.readNoEof(name[0..]); + try self.sections.append(Section { + .header = SectionHeader { + .name = name, + .misc = SectionHeader.Misc { .physical_address = try in.readIntLe(u32) }, + .virtual_address = try in.readIntLe(u32), + .size_of_raw_data = try in.readIntLe(u32), + .pointer_to_raw_data = try in.readIntLe(u32), + .pointer_to_relocations = try in.readIntLe(u32), + .pointer_to_line_numbers = try in.readIntLe(u32), + .number_of_relocations = try in.readIntLe(u16), + .number_of_line_numbers = try in.readIntLe(u16), + .characteristics = try in.readIntLe(u32), + }, + }); + } + std.debug.warn("loaded {} sections\n", self.coff_header.number_of_sections); + } + + pub fn getSection(self: *Coff, comptime name: []const u8) ?*Section { + for (self.sections.toSlice()) |*sec| { + if (mem.eql(u8, sec.header.name[0..name.len], name)) { + return sec; + } + } + return null; + } + +}; + +const CoffHeader = struct { + machine: u16, + number_of_sections: u16, + timedate_stamp: u32, + pointer_to_symbol_table: u32, + number_of_symbols: u32, + size_of_optional_header: u16, + characteristics: u16 +}; + +const OptionalHeader = struct { + const DataDirectory = struct { + virtual_address: u32, + size: u32 + }; + + magic: u16, + data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory, +}; + +const Section = struct { + header: SectionHeader, +}; + +const SectionHeader = struct { + const Misc = union { + physical_address: u32, + virtual_size: u32 + }; + + name: [8]u8, + misc: Misc, + virtual_address: u32, + size_of_raw_data: u32, + pointer_to_raw_data: u32, + pointer_to_relocations: u32, + pointer_to_line_numbers: u32, + number_of_relocations: u16, + number_of_line_numbers: u16, + characteristics: u32, +}; \ No newline at end of file diff --git a/std/debug/index.zig b/std/debug/index.zig index 39c41d4bc13d60af98c32f7da8b4c3aaa1f506bc..2828f42a8bc6f79e116e86b390b977ca9d99fe97 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -4,8 +4,11 @@ const mem = std.mem; const io = std.io; const os = std.os; const elf = std.elf; -const macho = std.macho; const DW = std.dwarf; +const macho = std.macho; +const coff = std.coff; +const pdb = std.pdb; +const windows = os.windows; const ArrayList = std.ArrayList; const builtin = @import("builtin"); @@ -228,14 +231,19 @@ pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: us switch (builtin.os) { builtin.Os.macosx => return printSourceAtAddressMacOs(debug_info, out_stream, address, tty_color), builtin.Os.linux => return printSourceAtAddressLinux(debug_info, out_stream, address, tty_color), - builtin.Os.windows => { - // TODO https://github.com/ziglang/zig/issues/721 - return error.UnsupportedOperatingSystem; - }, + builtin.Os.windows => return printSourceAtAddressWindows(debug_info, out_stream, address, tty_color), else => return error.UnsupportedOperatingSystem, } } +fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void { + const base_address = @ptrToInt(windows.GetModuleHandleW(null)); // returned HMODULE points to our executable file in memory + const relative_address = address - base_address; + std.debug.warn("{x} - {x} => {x}\n", address, base_address, relative_address); + try di.pdb.getSourceLine(relative_address); + return error.UnsupportedDebugInfo; +} + fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { var min: usize = 0; var max: usize = symbols.len - 1; // Exclude sentinel. @@ -372,14 +380,44 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo { switch (builtin.os) { builtin.Os.linux => return openSelfDebugInfoLinux(allocator), builtin.Os.macosx, builtin.Os.ios => return openSelfDebugInfoMacOs(allocator), - builtin.Os.windows => { - // TODO: https://github.com/ziglang/zig/issues/721 - return error.UnsupportedOperatingSystem; - }, + builtin.Os.windows => return openSelfDebugInfoWindows(allocator), else => return error.UnsupportedOperatingSystem, } } +fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { + var coff_file: coff.Coff = undefined; + coff_file.in_file = try os.openSelfExe(); + coff_file.allocator = allocator; + defer coff_file.in_file.close(); + + try coff_file.loadHeader(); + + var path: [windows.MAX_PATH]u8 = undefined; + const len = try coff_file.getPdbPath(path[0..]); + std.debug.warn("pdb path {}\n", path[0..len]); + + var di = DebugInfo{ + .pdb = undefined, + }; + + try di.pdb.openFile(allocator, path[0..len]); + + var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; + std.debug.warn("pdb real filepos {}\n", pdb_stream.getFilePos()); + const version = try pdb_stream.stream.readIntLe(u32); + const signature = try pdb_stream.stream.readIntLe(u32); + const age = try pdb_stream.stream.readIntLe(u32); + var guid: [16]u8 = undefined; + try pdb_stream.stream.readNoEof(guid[0..]); + if (!mem.eql(u8, coff_file.guid, guid) or coff_file.age != age) + return error.InvalidDebugInfo; + std.debug.warn("v {} s {} a {}\n", version, signature, age); + // We validated the executable and pdb match. + + return di; +} + fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo { var di = DebugInfo{ .self_exe_file = undefined, @@ -578,7 +616,10 @@ pub const DebugInfo = switch (builtin.os) { return self.ofiles.allocator; } }, - else => struct { + builtin.Os.windows => struct { + pdb: pdb.Pdb, + }, + builtin.Os.linux => struct { self_exe_file: os.File, elf: elf.Elf, debug_info: *elf.SectionHeader, @@ -604,6 +645,7 @@ pub const DebugInfo = switch (builtin.os) { self.elf.close(); } }, + else => @compileError("Unsupported OS"), }; const PcRange = struct { diff --git a/std/index.zig b/std/index.zig index 8dfc59b1d24a122b32ffa4cba80ba365e4ad171d..cf61ff53b5e4be154191cee38833e2cf192fa8a1 100644 --- a/std/index.zig +++ b/std/index.zig @@ -15,6 +15,7 @@ pub const atomic = @import("atomic/index.zig"); pub const base64 = @import("base64.zig"); pub const build = @import("build.zig"); pub const c = @import("c/index.zig"); +pub const coff = @import("coff.zig"); pub const crypto = @import("crypto/index.zig"); pub const cstr = @import("cstr.zig"); pub const debug = @import("debug/index.zig"); @@ -33,6 +34,7 @@ pub const math = @import("math/index.zig"); pub const mem = @import("mem.zig"); pub const net = @import("net.zig"); pub const os = @import("os/index.zig"); +pub const pdb = @import("pdb.zig"); pub const rand = @import("rand/index.zig"); pub const rb = @import("rb.zig"); pub const sort = @import("sort.zig"); @@ -56,6 +58,7 @@ test "std" { _ = @import("base64.zig"); _ = @import("build.zig"); _ = @import("c/index.zig"); + _ = @import("coff.zig"); _ = @import("crypto/index.zig"); _ = @import("cstr.zig"); _ = @import("debug/index.zig"); @@ -74,6 +77,7 @@ test "std" { _ = @import("heap.zig"); _ = @import("os/index.zig"); _ = @import("rand/index.zig"); + _ = @import("pdb.zig"); _ = @import("sort.zig"); _ = @import("unicode.zig"); _ = @import("zig/index.zig"); diff --git a/std/os/file.zig b/std/os/file.zig index 1f5ce7cf9dcb633e799ba669e58c41d5b8481590..d63cb1deaad80ddf6ec8a5da8a9e822bfac7bb1e 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -48,18 +48,23 @@ pub const File = struct { return openReadC(&path_c); } if (is_windows) { - const handle = try os.windowsOpen( - path, - windows.GENERIC_READ, - windows.FILE_SHARE_READ, - windows.OPEN_EXISTING, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); + const path_w = try windows_util.sliceToPrefixedFileW(path); + return openReadW(&path_w); } @compileError("Unsupported OS"); } + pub fn openReadW(path_w: [*]const u16) OpenError!File { + const handle = try os.windowsOpenW( + path_w, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + /// Calls `openWriteMode` with os.File.default_mode for the mode. pub fn openWrite(path: []const u8) OpenError!File { return openWriteMode(path, os.File.default_mode); @@ -74,19 +79,24 @@ pub const File = struct { const fd = try os.posixOpen(path, flags, file_mode); return openHandle(fd); } else if (is_windows) { - const handle = try os.windowsOpen( - path, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - windows.CREATE_ALWAYS, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); + const path_w = try windows_util.sliceToPrefixedFileW(path); + return openWriteModeW(&path_w, file_mode); } else { @compileError("TODO implement openWriteMode for this OS"); } } + pub fn openWriteModeW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try os.windowsOpenW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + windows.CREATE_ALWAYS, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + /// If the path does not exist it will be created. /// If a file already exists in the destination this returns OpenError.PathAlreadyExists /// Call close to clean up. @@ -96,19 +106,24 @@ pub const File = struct { const fd = try os.posixOpen(path, flags, file_mode); return openHandle(fd); } else if (is_windows) { - const handle = try os.windowsOpen( - path, - windows.GENERIC_WRITE, - windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, - windows.CREATE_NEW, - windows.FILE_ATTRIBUTE_NORMAL, - ); - return openHandle(handle); + const path_w = try windows_util.sliceToPrefixedFileW(path); + return openWriteNoClobberW(&path_w, file_mode); } else { @compileError("TODO implement openWriteMode for this OS"); } } + pub fn openWriteNoClobberW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try os.windowsOpenW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + windows.CREATE_NEW, + windows.FILE_ATTRIBUTE_NORMAL, + ); + return openHandle(handle); + } + pub fn openHandle(handle: os.FileHandle) File { return File{ .handle = handle }; } diff --git a/std/os/index.zig b/std/os/index.zig index 29d887e214519f1d586052157047095170b587dc..74f94dce5a9b40139536065bf16a55147d6b1c8f 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -57,6 +57,7 @@ pub const windowsWaitSingle = windows_util.windowsWaitSingle; pub const windowsWrite = windows_util.windowsWrite; pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty; pub const windowsOpen = windows_util.windowsOpen; +pub const windowsOpenW = windows_util.windowsOpenW; pub const windowsLoadDll = windows_util.windowsLoadDll; pub const windowsUnloadDll = windows_util.windowsUnloadDll; pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; @@ -2103,17 +2104,35 @@ pub fn openSelfExe() !os.File { buf[self_exe_path.len] = 0; return os.File.openReadC(self_exe_path.ptr); }, + Os.windows => { + var buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try selfExePathW(&buf); + return os.File.openReadW(wide_slice.ptr); + }, else => @compileError("Unsupported OS"), } } test "openSelfExe" { switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(), - else => return error.SkipZigTest, // Unsupported OS + Os.linux, Os.macosx, Os.ios, Os.windows => (try openSelfExe()).close(), + else => return error.SkipZigTest, // Unsupported OS. } } +pub fn selfExePathW(out_buffer: *[windows_util.PATH_MAX_WIDE]u16) ![]u16 { + const casted_len = @intCast(windows.DWORD, out_buffer.len); // TODO shouldn't need this cast + const rc = windows.GetModuleFileNameW(null, out_buffer, casted_len); + assert(rc <= out_buffer.len); + if (rc == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), + } + } + return out_buffer[0..rc]; +} + /// Get the path to the current executable. /// If you only need the directory, use selfExeDirPath. /// If you only want an open file handle, use openSelfExe. @@ -2128,17 +2147,7 @@ pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { switch (builtin.os) { Os.linux => return readLink(out_buffer, "/proc/self/exe"), Os.windows => { - var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; - const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast - const rc = windows.GetModuleFileNameW(null, &utf16le_buf, casted_len); - assert(rc <= utf16le_buf.len); - if (rc == 0) { - const err = windows.GetLastError(); - switch (err) { - else => return unexpectedErrorWindows(err), - } - } - const utf16le_slice = utf16le_buf[0..rc]; + const utf16le_slice = try selfExePathW(&utf16le_buf); // Trust that Windows gives us valid UTF-16LE. const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable; return out_buffer[0..end_index]; diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 66b529118952cbfaf8a20b7fb789bb4e99c4f44e..65f10a5a2a2b1d6ac9bf9763b214ff37ad20e4b9 100644 --- a/std/os/windows/kernel32.zig +++ b/std/os/windows/kernel32.zig @@ -92,6 +92,8 @@ pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: [*]u8, nSize: DWORD) DWORD; pub extern "kernel32" stdcallcc fn GetModuleFileNameW(hModule: ?HMODULE, lpFilename: [*]u16, nSize: DWORD) DWORD; +pub extern "kernel32" stdcallcc fn GetModuleHandleW(lpModuleName: ?[*]const WCHAR) HMODULE; + pub extern "kernel32" stdcallcc fn GetLastError() DWORD; pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx( diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index 72de89699607843f4f0d3522146728e3192f7588..168f6c386180b4a692ee8e5c10caaa812ede1b8c 100644 --- a/std/os/windows/util.zig +++ b/std/os/windows/util.zig @@ -118,16 +118,14 @@ pub const OpenError = error{ Unexpected, }; -pub fn windowsOpen( - file_path: []const u8, +pub fn windowsOpenW( + file_path_w: [*]const u16, desired_access: windows.DWORD, share_mode: windows.DWORD, creation_disposition: windows.DWORD, flags_and_attrs: windows.DWORD, ) OpenError!windows.HANDLE { - const file_path_w = try sliceToPrefixedFileW(file_path); - - const result = windows.CreateFileW(&file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); + const result = windows.CreateFileW(file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); if (result == windows.INVALID_HANDLE_VALUE) { const err = windows.GetLastError(); @@ -146,6 +144,17 @@ pub fn windowsOpen( return result; } +pub fn windowsOpen( + file_path: []const u8, + desired_access: windows.DWORD, + share_mode: windows.DWORD, + creation_disposition: windows.DWORD, + flags_and_attrs: windows.DWORD, +) OpenError!windows.HANDLE { + const file_path_w = try sliceToPrefixedFileW(file_path); + return windowsOpenW(&file_path_w, desired_access, share_mode, creation_disposition, flags_and_attrs); +} + /// Caller must free result. pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u8 { // count bytes needed diff --git a/std/pdb.zig b/std/pdb.zig new file mode 100644 index 0000000000000000000000000000000000000000..08c1d25f65def1fc0d728887d5503d315e70d2b9 --- /dev/null +++ b/std/pdb.zig @@ -0,0 +1,265 @@ +const builtin = @import("builtin"); +const std = @import("index.zig"); +const io = std.io; +const math = std.math; +const mem = std.mem; +const os = std.os; +const warn = std.debug.warn; + +const ArrayList = std.ArrayList; + +pub const PdbError = error { + InvalidPdbMagic, + CorruptedFile, +}; + +pub const StreamType = enum(u16) { + Pdb = 1, + Tpi = 2, + Dbi = 3, + Ipi = 4, +}; + +pub const Pdb = struct { + in_file: os.File, + allocator: *mem.Allocator, + + msf: Msf, + + pub fn openFile(self: *Pdb, allocator: *mem.Allocator, file_name: []u8) !void { + self.in_file = try os.File.openRead(file_name[0..]); + self.allocator = allocator; + + try self.msf.openFile(allocator, &self.in_file); + } + + pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream { + const id = @enumToInt(stream); + if (id < self.msf.streams.len) + return &self.msf.streams.items[id]; + return null; + } + + pub fn getSourceLine(self: *Pdb, address: usize) !void { + const dbi = self.getStream(StreamType.Dbi) orelse return error.CorruptedFile; + + // Dbi Header + try dbi.seekForward(@sizeOf(u32) * 3 + @sizeOf(u16) * 6); + warn("dbi stream at {} (file offset)\n", dbi.getFilePos()); + const module_info_size = try dbi.stream.readIntLe(u32); + const section_contribution_size = try dbi.stream.readIntLe(u32); + const section_map_size = try dbi.stream.readIntLe(u32); + const source_info_size = try dbi.stream.readIntLe(u32); + warn("module_info_size: {}\n", module_info_size); + warn("section_contribution_size: {}\n", section_contribution_size); + warn("section_map_size: {}\n", section_map_size); + warn("source_info_size: {}\n", source_info_size); + try dbi.seekForward(@sizeOf(u32) * 5 + @sizeOf(u16) * 2); + warn("after header dbi stream at {} (file offset)\n", dbi.getFilePos()); + + // Module Info Substream + try dbi.seekForward(@sizeOf(u32) + @sizeOf(u16) + @sizeOf(u8) * 2); + const offset = try dbi.stream.readIntLe(u32); + const size = try dbi.stream.readIntLe(u32); + try dbi.seekForward(@sizeOf(u32)); + const module_index = try dbi.stream.readIntLe(u16); + warn("module {} of size {} at {}\n", module_index, size, offset); + + // TODO: locate corresponding source line information + } +}; + +// see https://llvm.org/docs/PDB/MsfFile.html +const Msf = struct { + superblock: SuperBlock, + directory: MsfStream, + streams: ArrayList(MsfStream), + + fn openFile(self: *Msf, allocator: *mem.Allocator, file: *os.File) !void { + var file_stream = io.FileInStream.init(file); + const in = &file_stream.stream; + + var magic: SuperBlock.FileMagicBuffer = undefined; + try in.readNoEof(magic[0..]); + warn("magic: '{}'\n", magic); + + if (!mem.eql(u8, magic, SuperBlock.FileMagic)) + return error.InvalidPdbMagic; + + self.superblock = SuperBlock { + .block_size = try in.readIntLe(u32), + .free_block_map_block = try in.readIntLe(u32), + .num_blocks = try in.readIntLe(u32), + .num_directory_bytes = try in.readIntLe(u32), + .unknown = try in.readIntLe(u32), + .block_map_addr = try in.readIntLe(u32), + }; + + switch (self.superblock.block_size) { + 512, 1024, 2048, 4096 => {}, // llvm only uses 4096 + else => return error.InvalidPdbMagic + } + + if (self.superblock.fileSize() != try file.getEndPos()) + return error.CorruptedFile; // Should always stand. + + self.directory = try MsfStream.init( + self.superblock.block_size, + self.superblock.blocksOccupiedByDirectoryStream(), + self.superblock.blockMapAddr(), + file, + allocator + ); + + const stream_count = try self.directory.stream.readIntLe(u32); + warn("stream count {}\n", stream_count); + + var stream_sizes = ArrayList(u32).init(allocator); + try stream_sizes.resize(stream_count); + for (stream_sizes.toSlice()) |*s| { + const size = try self.directory.stream.readIntLe(u32); + s.* = blockCountFromSize(size, self.superblock.block_size); + warn("stream {}B {} blocks\n", size, s.*); + } + + self.streams = ArrayList(MsfStream).init(allocator); + try self.streams.resize(stream_count); + for (self.streams.toSlice()) |*ss, i| { + ss.* = try MsfStream.init( + self.superblock.block_size, + stream_sizes.items[i], + try file.getPos(), // We're reading the jagged array of block indices when creating streams so the file is always at the right position. + file, + allocator + ); + } + } +}; + +fn blockCountFromSize(size: u32, block_size: u32) u32 { + return (size + block_size - 1) / block_size; +} + +const SuperBlock = struct { + const FileMagic = "Microsoft C/C++ MSF 7.00\r\n" ++ []u8 { 0x1A, 'D', 'S', 0, 0, 0}; + const FileMagicBuffer = @typeOf(FileMagic); + + block_size: u32, + free_block_map_block: u32, + num_blocks: u32, + num_directory_bytes: u32, + unknown: u32, + block_map_addr: u32, + + fn fileSize(self: *const SuperBlock) usize { + return self.num_blocks * self.block_size; + } + + fn blockMapAddr(self: *const SuperBlock) usize { + return self.block_size * self.block_map_addr; + } + + fn blocksOccupiedByDirectoryStream(self: *const SuperBlock) u32 { + return blockCountFromSize(self.num_directory_bytes, self.block_size); + } +}; + +const MsfStream = struct { + in_file: *os.File, + pos: usize, + blocks: ArrayList(u32), + block_size: u32, + + fn init(block_size: u32, block_count: u32, pos: usize, file: *os.File, allocator: *mem.Allocator) !MsfStream { + var stream = MsfStream { + .in_file = file, + .pos = 0, + .blocks = ArrayList(u32).init(allocator), + .block_size = block_size, + .stream = Stream { + .readFn = readFn, + }, + }; + + try stream.blocks.resize(block_count); + + var file_stream = io.FileInStream.init(file); + const in = &file_stream.stream; + try file.seekTo(pos); + + warn("stream with blocks"); + var i: u32 = 0; + while (i < block_count) : (i += 1) { + stream.blocks.items[i] = try in.readIntLe(u32); + warn(" {}", stream.blocks.items[i]); + } + warn("\n"); + + return stream; + } + + fn read(self: *MsfStream, buffer: []u8) !usize { + var block_id = self.pos / self.block_size; + var block = self.blocks.items[block_id]; + var offset = self.pos % self.block_size; + + try self.in_file.seekTo(block * self.block_size + offset); + var file_stream = io.FileInStream.init(self.in_file); + const in = &file_stream.stream; + + var size: usize = 0; + for (buffer) |*byte| { + byte.* = try in.readByte(); + + offset += 1; + size += 1; + + // If we're at the end of a block, go to the next one. + if (offset == self.block_size) + { + offset = 0; + block_id += 1; + block = self.blocks.items[block_id]; + try self.in_file.seekTo(block * self.block_size); + } + } + + self.pos += size; + return size; + } + + fn seekForward(self: *MsfStream, len: usize) !void { + self.pos += len; + if (self.pos >= self.blocks.len * self.block_size) + return error.EOF; + } + + fn seekTo(self: *MsfStream, len: usize) !void { + self.pos = len; + if (self.pos >= self.blocks.len * self.block_size) + return error.EOF; + } + + fn getSize(self: *const MsfStream) usize { + return self.blocks.len * self.block_size; + } + + fn getFilePos(self: *const MsfStream) usize { + const block_id = self.pos / self.block_size; + const block = self.blocks.items[block_id]; + const offset = self.pos % self.block_size; + + return block * self.block_size + offset; + } + + /// Implementation of InStream trait for Pdb.MsfStream + pub const Error = @typeOf(read).ReturnType.ErrorSet; + pub const Stream = io.InStream(Error); + + stream: Stream, + + fn readFn(in_stream: *Stream, buffer: []u8) Error!usize { + const self = @fieldParentPtr(MsfStream, "stream", in_stream); + return self.read(buffer); + } +};