diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e753b5137c4..5664f1db198e 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" @@ -577,12 +578,14 @@ set(ZIG_STD_FILES "os/windows/error.zig" "os/windows/index.zig" "os/windows/kernel32.zig" + "os/windows/ntdll.zig" "os/windows/ole32.zig" "os/windows/shell32.zig" "os/windows/shlwapi.zig" "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/doc/docgen.zig b/doc/docgen.zig index 3145c4483e62..c1158dc03f57 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -40,11 +40,11 @@ pub fn main() !void { var out_file = try os.File.openWrite(out_file_name); defer out_file.close(); - var file_in_stream = io.FileInStream.init(&in_file); + var file_in_stream = io.FileInStream.init(in_file); const input_file_bytes = try file_in_stream.stream.readAllAlloc(allocator, max_doc_file_size); - var file_out_stream = io.FileOutStream.init(&out_file); + var file_out_stream = io.FileOutStream.init(out_file); var buffered_out_stream = io.BufferedOutStream(io.FileOutStream.Error).init(&file_out_stream.stream); var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig index bed132b25c67..062f93e7f7fa 100644 --- a/example/guess_number/main.zig +++ b/example/guess_number/main.zig @@ -6,7 +6,7 @@ const os = std.os; pub fn main() !void { var stdout_file = try io.getStdOut(); - var stdout_file_stream = io.FileOutStream.init(&stdout_file); + var stdout_file_stream = io.FileOutStream.init(stdout_file); const stdout = &stdout_file_stream.stream; try stdout.print("Welcome to the Guess Number Game in Zig.\n"); diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig index 028c2e21747c..6cf29b94413c 100644 --- a/src-self-hosted/errmsg.zig +++ b/src-self-hosted/errmsg.zig @@ -272,7 +272,7 @@ pub const Msg = struct { try stream.write("\n"); } - pub fn printToFile(msg: *const Msg, file: *os.File, color: Color) !void { + pub fn printToFile(msg: *const Msg, file: os.File, color: Color) !void { const color_on = switch (color) { Color.Auto => file.isTty(), Color.On => true, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 64c55a24e802..6a450030ca12 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -55,11 +55,11 @@ pub fn main() !void { const allocator = std.heap.c_allocator; var stdout_file = try std.io.getStdOut(); - var stdout_out_stream = std.io.FileOutStream.init(&stdout_file); + var stdout_out_stream = std.io.FileOutStream.init(stdout_file); stdout = &stdout_out_stream.stream; stderr_file = try std.io.getStdErr(); - var stderr_out_stream = std.io.FileOutStream.init(&stderr_file); + var stderr_out_stream = std.io.FileOutStream.init(stderr_file); stderr = &stderr_out_stream.stream; const args = try os.argsAlloc(allocator); @@ -491,7 +491,7 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { stderr.print("Build {} compile errors:\n", count) catch os.exit(1); for (msgs) |msg| { defer msg.destroy(); - msg.printToFile(&stderr_file, color) catch os.exit(1); + msg.printToFile(stderr_file, color) catch os.exit(1); } }, } @@ -619,7 +619,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { } var stdin_file = try io.getStdIn(); - var stdin = io.FileInStream.init(&stdin_file); + var stdin = io.FileInStream.init(stdin_file); const source_code = try stdin.stream.readAllAlloc(allocator, max_src_size); defer allocator.free(source_code); @@ -635,7 +635,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, ""); defer msg.destroy(); - try msg.printToFile(&stderr_file, color); + try msg.printToFile(stderr_file, color); } if (tree.errors.len != 0) { os.exit(1); @@ -772,7 +772,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8) FmtError!void { const msg = try errmsg.Msg.createFromParseError(fmt.loop.allocator, parse_error, &tree, file_path); defer fmt.loop.allocator.destroy(msg); - try msg.printToFile(&stderr_file, fmt.color); + try msg.printToFile(stderr_file, fmt.color); } if (tree.errors.len != 0) { fmt.any_error = true; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index d4a45e7a044b..4f377d42476d 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -185,7 +185,7 @@ pub const TestContext = struct { try stderr.write("build incorrectly failed:\n"); for (msgs) |msg| { defer msg.destroy(); - try msg.printToFile(&stderr, errmsg.Color.Auto); + try msg.printToFile(stderr, errmsg.Color.Auto); } }, } @@ -234,7 +234,7 @@ pub const TestContext = struct { var stderr = try std.io.getStdErr(); for (msgs) |msg| { defer msg.destroy(); - try msg.printToFile(&stderr, errmsg.Color.Auto); + try msg.printToFile(stderr, errmsg.Color.Auto); } std.debug.warn("============\n"); return error.TestFailed; diff --git a/std/coff.zig b/std/coff.zig new file mode 100644 index 000000000000..379fd1af42fc --- /dev/null +++ b/std/coff.zig @@ -0,0 +1,230 @@ +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); + // 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; + + try self.in_file.seekForward(skip_size); + + const number_of_rva_and_sizes = try in.readIntLe(u32); + 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), + }; + } + } + + 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; + 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; + 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), + }, + }); + } + } + + 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, +}; + +pub 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, +}; diff --git a/std/debug/index.zig b/std/debug/index.zig index 39c41d4bc13d..8db7c75d2c61 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"); @@ -17,6 +20,17 @@ pub const runtime_safety = switch (builtin.mode) { builtin.Mode.ReleaseFast, builtin.Mode.ReleaseSmall => false, }; +const Module = struct { + mod_info: pdb.ModInfo, + module_name: []u8, + obj_file_name: []u8, + + populated: bool, + symbols: []u8, + subsect_info: []u8, + checksum_offset: ?usize, +}; + /// Tries to write to stderr, unbuffered, and ignores any error returned. /// Does not append a newline. var stderr_file: os.File = undefined; @@ -37,7 +51,7 @@ pub fn getStderrStream() !*io.OutStream(io.FileOutStream.Error) { return st; } else { stderr_file = try io.getStdErr(); - stderr_file_out_stream = io.FileOutStream.init(&stderr_file); + stderr_file_out_stream = io.FileOutStream.init(stderr_file); const st = &stderr_file_out_stream.stream; stderr_stream = st; return st; @@ -70,7 +84,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return; return; }; - writeCurrentStackTrace(stderr, getDebugInfoAllocator(), debug_info, wantTtyColor(), start_addr) catch |err| { + writeCurrentStackTrace(stderr, debug_info, wantTtyColor(), start_addr) catch |err| { stderr.print("Unable to dump stack trace: {}\n", @errorName(err)) catch return; return; }; @@ -191,7 +205,11 @@ pub inline fn getReturnAddress(frame_count: usize) usize { return @intToPtr(*const usize, fp + @sizeOf(usize)).*; } -pub fn writeCurrentStackTrace(out_stream: var, allocator: *mem.Allocator, debug_info: *DebugInfo, tty_color: bool, start_addr: ?usize) !void { +pub fn writeCurrentStackTrace(out_stream: var, debug_info: *DebugInfo, tty_color: bool, start_addr: ?usize) !void { + switch (builtin.os) { + builtin.Os.windows => return writeCurrentStackTraceWindows(out_stream, debug_info, tty_color, start_addr), + else => {}, + } const AddressState = union(enum) { NotLookingForStartAddress, LookingForStartAddress: usize, @@ -224,18 +242,296 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: *mem.Allocator, debug_ } } +pub fn writeCurrentStackTraceWindows(out_stream: var, debug_info: *DebugInfo, + tty_color: bool, start_addr: ?usize) !void +{ + var addr_buf: [1024]usize = undefined; + const casted_len = @intCast(u32, addr_buf.len); // TODO shouldn't need this cast + const n = windows.RtlCaptureStackBackTrace(0, casted_len, @ptrCast(**c_void, &addr_buf), null); + const addrs = addr_buf[0..n]; + var start_i: usize = if (start_addr) |saddr| blk: { + for (addrs) |addr, i| { + if (addr == saddr) break :blk i; + } + return; + } else 0; + for (addrs[start_i..]) |addr| { + try printSourceAtAddress(debug_info, out_stream, addr, tty_color); + } +} + pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_color: bool) !void { 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, relocated_address: usize, tty_color: bool) !void { + const allocator = getDebugInfoAllocator(); + const base_address = os.getBaseAddress(); + const relative_address = relocated_address - base_address; + + var coff_section: *coff.Section = undefined; + const mod_index = for (di.sect_contribs) |sect_contrib| { + if (sect_contrib.Section >= di.coff.sections.len) continue; + coff_section = &di.coff.sections.toSlice()[sect_contrib.Section]; + + const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset; + const vaddr_end = vaddr_start + sect_contrib.Size; + if (relative_address >= vaddr_start and relative_address < vaddr_end) { + break sect_contrib.ModuleIndex; + } + } else { + // we have no information to add to the address + if (tty_color) { + try out_stream.print("???:?:?: "); + setTtyColor(TtyColor.Dim); + try out_stream.print("0x{x} in ??? (???)", relocated_address); + setTtyColor(TtyColor.Reset); + try out_stream.print("\n\n\n"); + } else { + try out_stream.print("???:?:?: 0x{x} in ??? (???)\n\n\n", relocated_address); + } + return; + }; + + const mod = &di.modules[mod_index]; + try populateModule(di, mod); + const obj_basename = os.path.basename(mod.obj_file_name); + + var symbol_i: usize = 0; + const symbol_name = while (symbol_i != mod.symbols.len) { + const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]); + if (prefix.RecordLen < 2) + return error.InvalidDebugInfo; + switch (prefix.RecordKind) { + pdb.SymbolKind.S_LPROC32 => { + const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]); + const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset; + const vaddr_end = vaddr_start + proc_sym.CodeSize; + if (relative_address >= vaddr_start and relative_address < vaddr_end) { + break mem.toSliceConst(u8, @ptrCast([*]u8, proc_sym) + @sizeOf(pdb.ProcSym)); + } + }, + else => {}, + } + symbol_i += prefix.RecordLen + @sizeOf(u16); + if (symbol_i > mod.symbols.len) + return error.InvalidDebugInfo; + } else "???"; + + const subsect_info = mod.subsect_info; + + var sect_offset: usize = 0; + var skip_len: usize = undefined; + const opt_line_info = subsections: { + const checksum_offset = mod.checksum_offset orelse break :subsections null; + while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { + const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]); + skip_len = subsect_hdr.Length; + sect_offset += @sizeOf(pdb.DebugSubsectionHeader); + + switch (subsect_hdr.Kind) { + pdb.DebugSubsectionKind.Lines => { + var line_index: usize = sect_offset; + + const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]); + if (line_hdr.RelocSegment == 0) return error.MissingDebugInfo; + line_index += @sizeOf(pdb.LineFragmentHeader); + + const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]); + line_index += @sizeOf(pdb.LineBlockFragmentHeader); + + const has_column = line_hdr.Flags.LF_HaveColumns; + + const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset; + const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; + if (relative_address >= frag_vaddr_start and relative_address < frag_vaddr_end) { + var line_i: usize = 0; + const start_line_index = line_index; + while (line_i < block_hdr.NumLines) : (line_i += 1) { + const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]); + line_index += @sizeOf(pdb.LineNumberEntry); + const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags); + const vaddr_start = frag_vaddr_start + line_num_entry.Offset; + const vaddr_end = if (flags.End == 0) frag_vaddr_end else vaddr_start + flags.End; + if (relative_address >= vaddr_start and relative_address < vaddr_end) { + const subsect_index = checksum_offset + block_hdr.NameIndex; + const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]); + const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset; + try di.pdb.string_table.seekTo(strtab_offset); + const source_file_name = try di.pdb.string_table.readNullTermString(allocator); + const line = flags.Start; + const column = if (has_column) blk: { + line_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines; + line_index += @sizeOf(pdb.ColumnNumberEntry) * line_i; + const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[line_index]); + break :blk col_num_entry.StartColumn; + } else 0; + break :subsections LineInfo{ + .allocator = allocator, + .file_name = source_file_name, + .line = line, + .column = column, + }; + } + } + break :subsections null; + } + }, + else => {}, + } + + if (sect_offset > subsect_info.len) + return error.InvalidDebugInfo; + } else { + break :subsections null; + } + }; + + if (tty_color) { + setTtyColor(TtyColor.White); + if (opt_line_info) |li| { + try out_stream.print("{}:{}:{}", li.file_name, li.line, li.column); + } else { + try out_stream.print("???:?:?"); + } + setTtyColor(TtyColor.Reset); + try out_stream.print(": "); + setTtyColor(TtyColor.Dim); + try out_stream.print("0x{x} in {} ({})", relocated_address, symbol_name, obj_basename); + setTtyColor(TtyColor.Reset); + + if (opt_line_info) |line_info| { + try out_stream.print("\n"); + if (printLineFromFile(out_stream, line_info)) { + if (line_info.column == 0) { + try out_stream.write("\n"); + } else { + { + var col_i: usize = 1; + while (col_i < line_info.column) : (col_i += 1) { + try out_stream.writeByte(' '); + } + } + setTtyColor(TtyColor.Green); + try out_stream.write("^"); + setTtyColor(TtyColor.Reset); + try out_stream.write("\n"); + } + } else |err| switch (err) { + error.EndOfFile => {}, + else => return err, + } + } else { + try out_stream.print("\n\n\n"); + } + } else { + if (opt_line_info) |li| { + try out_stream.print("{}:{}:{}: 0x{x} in {} ({})\n\n\n", li.file_name, li.line, li.column, relocated_address, symbol_name, obj_basename); + } else { + try out_stream.print("???:?:?: 0x{x} in {} ({})\n\n\n", relocated_address, symbol_name, obj_basename); + } + } +} + +const TtyColor = enum{ + Red, + Green, + Cyan, + White, + Dim, + Bold, + Reset, +}; + +/// TODO this is a special case hack right now. clean it up and maybe make it part of std.fmt +fn setTtyColor(tty_color: TtyColor) void { + const S = struct { + var attrs: windows.WORD = undefined; + var init_attrs = false; + }; + if (!S.init_attrs) { + S.init_attrs = true; + var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + // TODO handle error + _ = windows.GetConsoleScreenBufferInfo(stderr_file.handle, &info); + S.attrs = info.wAttributes; + } + + // TODO handle errors + switch (tty_color) { + TtyColor.Red => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_RED|windows.FOREGROUND_INTENSITY); + }, + TtyColor.Green => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_GREEN|windows.FOREGROUND_INTENSITY); + }, + TtyColor.Cyan => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, + windows.FOREGROUND_GREEN|windows.FOREGROUND_BLUE|windows.FOREGROUND_INTENSITY); + }, + TtyColor.White, TtyColor.Bold => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, + windows.FOREGROUND_RED|windows.FOREGROUND_GREEN|windows.FOREGROUND_BLUE|windows.FOREGROUND_INTENSITY); + }, + TtyColor.Dim => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, windows.FOREGROUND_INTENSITY); + }, + TtyColor.Reset => { + _ = windows.SetConsoleTextAttribute(stderr_file.handle, S.attrs); + }, + } +} + +fn populateModule(di: *DebugInfo, mod: *Module) !void { + if (mod.populated) + return; + const allocator = getDebugInfoAllocator(); + + if (mod.mod_info.C11ByteSize != 0) + return error.InvalidDebugInfo; + + if (mod.mod_info.C13ByteSize == 0) + return error.MissingDebugInfo; + + const modi = di.pdb.getStreamById(mod.mod_info.ModuleSymStream) orelse return error.MissingDebugInfo; + + const signature = try modi.stream.readIntLe(u32); + if (signature != 4) + return error.InvalidDebugInfo; + + mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4); + try modi.stream.readNoEof(mod.symbols); + + mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize); + try modi.stream.readNoEof(mod.subsect_info); + + var sect_offset: usize = 0; + var skip_len: usize = undefined; + while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { + const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &mod.subsect_info[sect_offset]); + skip_len = subsect_hdr.Length; + sect_offset += @sizeOf(pdb.DebugSubsectionHeader); + + switch (subsect_hdr.Kind) { + pdb.DebugSubsectionKind.FileChecksums => { + mod.checksum_offset = sect_offset; + break; + }, + else => {}, + } + + if (sect_offset > mod.subsect_info.len) + return error.InvalidDebugInfo; + } + + mod.populated = true; +} + fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { var min: usize = 0; var max: usize = symbols.len - 1; // Exclude sentinel. @@ -372,14 +668,185 @@ 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 { + const self_file = try os.openSelfExe(); + defer self_file.close(); + + const coff_obj = try allocator.createOne(coff.Coff); + coff_obj.* = coff.Coff{ + .in_file = self_file, + .allocator = allocator, + .coff_header = undefined, + .pe_header = undefined, + .sections = undefined, + .guid = undefined, + .age = undefined, + }; + + var di = DebugInfo{ + .coff = coff_obj, + .pdb = undefined, + .sect_contribs = undefined, + .modules = undefined, + }; + + try di.coff.loadHeader(); + + var path_buf: [windows.MAX_PATH]u8 = undefined; + const len = try di.coff.getPdbPath(path_buf[0..]); + const raw_path = path_buf[0..len]; + + const path = try os.path.resolve(allocator, raw_path); + + try di.pdb.openFile(di.coff, path); + + var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; + 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, di.coff.guid, guid) or di.coff.age != age) + return error.InvalidDebugInfo; + // We validated the executable and pdb match. + + const string_table_index = str_tab_index: { + const name_bytes_len = try pdb_stream.stream.readIntLe(u32); + const name_bytes = try allocator.alloc(u8, name_bytes_len); + try pdb_stream.stream.readNoEof(name_bytes); + + const HashTableHeader = packed struct { + Size: u32, + Capacity: u32, + + fn maxLoad(cap: u32) u32 { + return cap * 2 / 3 + 1; + } + }; + var hash_tbl_hdr: HashTableHeader = undefined; + try pdb_stream.stream.readStruct(HashTableHeader, &hash_tbl_hdr); + if (hash_tbl_hdr.Capacity == 0) + return error.InvalidDebugInfo; + + if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) + return error.InvalidDebugInfo; + + const present = try readSparseBitVector(&pdb_stream.stream, allocator); + if (present.len != hash_tbl_hdr.Size) + return error.InvalidDebugInfo; + const deleted = try readSparseBitVector(&pdb_stream.stream, allocator); + + const Bucket = struct { + first: u32, + second: u32, + }; + const bucket_list = try allocator.alloc(Bucket, present.len); + for (present) |_| { + const name_offset = try pdb_stream.stream.readIntLe(u32); + const name_index = try pdb_stream.stream.readIntLe(u32); + const name = mem.toSlice(u8, name_bytes.ptr + name_offset); + if (mem.eql(u8, name, "/names")) { + break :str_tab_index name_index; + } + } + return error.MissingDebugInfo; + }; + + di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.InvalidDebugInfo; + di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo; + + const dbi = di.pdb.dbi; + + // Dbi Header + var dbi_stream_header: pdb.DbiStreamHeader = undefined; + try dbi.stream.readStruct(pdb.DbiStreamHeader, &dbi_stream_header); + const mod_info_size = dbi_stream_header.ModInfoSize; + const section_contrib_size = dbi_stream_header.SectionContributionSize; + + var modules = ArrayList(Module).init(allocator); + + // Module Info Substream + var mod_info_offset: usize = 0; + while (mod_info_offset != mod_info_size) { + var mod_info: pdb.ModInfo = undefined; + try dbi.stream.readStruct(pdb.ModInfo, &mod_info); + var this_record_len: usize = @sizeOf(pdb.ModInfo); + + const module_name = try dbi.readNullTermString(allocator); + this_record_len += module_name.len + 1; + + const obj_file_name = try dbi.readNullTermString(allocator); + this_record_len += obj_file_name.len + 1; + + const march_forward_bytes = this_record_len % 4; + if (march_forward_bytes != 0) { + try dbi.seekForward(march_forward_bytes); + this_record_len += march_forward_bytes; + } + + try modules.append(Module{ + .mod_info = mod_info, + .module_name = module_name, + .obj_file_name = obj_file_name, + + .populated = false, + .symbols = undefined, + .subsect_info = undefined, + .checksum_offset = null, + }); + + mod_info_offset += this_record_len; + if (mod_info_offset > mod_info_size) + return error.InvalidDebugInfo; + } + + di.modules = modules.toOwnedSlice(); + + // Section Contribution Substream + var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator); + var sect_cont_offset: usize = 0; + if (section_contrib_size != 0) { + const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.stream.readIntLe(u32)); + if (ver != pdb.SectionContrSubstreamVersion.Ver60) + return error.InvalidDebugInfo; + sect_cont_offset += @sizeOf(u32); + } + while (sect_cont_offset != section_contrib_size) { + const entry = try sect_contribs.addOne(); + try dbi.stream.readStruct(pdb.SectionContribEntry, entry); + sect_cont_offset += @sizeOf(pdb.SectionContribEntry); + + if (sect_cont_offset > section_contrib_size) + return error.InvalidDebugInfo; + } + + di.sect_contribs = sect_contribs.toOwnedSlice(); + + return di; +} + +fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize { + const num_words = try stream.readIntLe(u32); + var word_i: usize = 0; + var list = ArrayList(usize).init(allocator); + while (word_i != num_words) : (word_i += 1) { + const word = try stream.readIntLe(u32); + var bit_i: u5 = 0; + while (true) : (bit_i += 1) { + if (word & (u32(1) << bit_i) != 0) { + try list.append(word_i * 32 + bit_i); + } + if (bit_i == @maxValue(u5)) break; + } + } + return list.toOwnedSlice(); +} + fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo { var di = DebugInfo{ .self_exe_file = undefined, @@ -395,7 +862,7 @@ fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo { di.self_exe_file = try os.openSelfExe(); errdefer di.self_exe_file.close(); - try di.elf.openFile(allocator, &di.self_exe_file); + try di.elf.openFile(allocator, di.self_exe_file); errdefer di.elf.close(); di.debug_info = (try di.elf.findSection(".debug_info")) orelse return error.MissingDebugInfo; @@ -578,7 +1045,13 @@ pub const DebugInfo = switch (builtin.os) { return self.ofiles.allocator; } }, - else => struct { + builtin.Os.windows => struct { + pdb: pdb.Pdb, + coff: *coff.Coff, + sect_contribs: []pdb.SectionContribEntry, + modules: []Module, + }, + builtin.Os.linux => struct { self_exe_file: os.File, elf: elf.Elf, debug_info: *elf.SectionHeader, @@ -594,7 +1067,7 @@ pub const DebugInfo = switch (builtin.os) { } pub fn readString(self: *DebugInfo) ![]u8 { - var in_file_stream = io.FileInStream.init(&self.self_exe_file); + var in_file_stream = io.FileInStream.init(self.self_exe_file); const in_stream = &in_file_stream.stream; return readStringRaw(self.allocator(), in_stream); } @@ -604,6 +1077,7 @@ pub const DebugInfo = switch (builtin.os) { self.elf.close(); } }, + else => @compileError("Unsupported OS"), }; const PcRange = struct { @@ -929,7 +1403,7 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64 } fn parseAbbrevTable(st: *DebugInfo) !AbbrevTable { - const in_file = &st.self_exe_file; + const in_file = st.self_exe_file; var in_file_stream = io.FileInStream.init(in_file); const in_stream = &in_file_stream.stream; var result = AbbrevTable.init(st.allocator()); @@ -980,7 +1454,7 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con } fn parseDie(st: *DebugInfo, abbrev_table: *const AbbrevTable, is_64: bool) !Die { - const in_file = &st.self_exe_file; + const in_file = st.self_exe_file; var in_file_stream = io.FileInStream.init(in_file); const in_stream = &in_file_stream.stream; const abbrev_code = try readULeb128(in_stream); @@ -1202,7 +1676,7 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, target_address: usize) !LineInfo { const compile_unit_cwd = try compile_unit.die.getAttrString(di, DW.AT_comp_dir); - const in_file = &di.self_exe_file; + const in_file = di.self_exe_file; const debug_line_end = di.debug_line.offset + di.debug_line.size; var this_offset = di.debug_line.offset; var this_index: usize = 0; @@ -1382,7 +1856,7 @@ fn scanAllCompileUnits(st: *DebugInfo) !void { var this_unit_offset = st.debug_info.offset; var cu_index: usize = 0; - var in_file_stream = io.FileInStream.init(&st.self_exe_file); + var in_file_stream = io.FileInStream.init(st.self_exe_file); const in_stream = &in_file_stream.stream; while (this_unit_offset < debug_info_end) { @@ -1448,7 +1922,7 @@ fn scanAllCompileUnits(st: *DebugInfo) !void { } fn findCompileUnit(st: *DebugInfo, target_address: u64) !*const CompileUnit { - var in_file_stream = io.FileInStream.init(&st.self_exe_file); + var in_file_stream = io.FileInStream.init(st.self_exe_file); const in_stream = &in_file_stream.stream; for (st.compile_unit_list.toSlice()) |*compile_unit| { if (compile_unit.pc_range) |range| { diff --git a/std/elf.zig b/std/elf.zig index 3d815553195f..a3a72dc72803 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -353,7 +353,7 @@ pub const SectionHeader = struct { }; pub const Elf = struct { - in_file: *os.File, + in_file: os.File, auto_close_stream: bool, is_64: bool, endian: builtin.Endian, @@ -376,7 +376,7 @@ pub const Elf = struct { } /// Call close when done. - pub fn openFile(elf: *Elf, allocator: *mem.Allocator, file: *os.File) !void { + pub fn openFile(elf: *Elf, allocator: *mem.Allocator, file: os.File) !void { elf.allocator = allocator; elf.in_file = file; elf.auto_close_stream = false; diff --git a/std/event/tcp.zig b/std/event/tcp.zig index d8b97659a9aa..491acab39d96 100644 --- a/std/event/tcp.zig +++ b/std/event/tcp.zig @@ -145,11 +145,11 @@ test "listen on a port, send bytes, receive bytes" { cancel @handle(); } } - async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: *const std.os.File) !void { + async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: std.os.File) !void { const addr = _addr.*; // TODO https://github.com/ziglang/zig/issues/733 - var socket = _socket.*; // TODO https://github.com/ziglang/zig/issues/733 + var socket = _socket; // TODO https://github.com/ziglang/zig/issues/733 - var adapter = std.io.FileOutStream.init(&socket); + var adapter = std.io.FileOutStream.init(socket); var stream = &adapter.stream; try stream.print("hello from server\n"); } diff --git a/std/index.zig b/std/index.zig index 8dfc59b1d24a..cf61ff53b5e4 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/io.zig b/std/io.zig index 369f6eede300..2b31bc054861 100644 --- a/std/io.zig +++ b/std/io.zig @@ -34,13 +34,13 @@ pub fn getStdIn() GetStdIoErrs!File { /// Implementation of InStream trait for File pub const FileInStream = struct { - file: *File, + file: File, stream: Stream, pub const Error = @typeOf(File.read).ReturnType.ErrorSet; pub const Stream = InStream(Error); - pub fn init(file: *File) FileInStream { + pub fn init(file: File) FileInStream { return FileInStream{ .file = file, .stream = Stream{ .readFn = readFn }, @@ -55,13 +55,13 @@ pub const FileInStream = struct { /// Implementation of OutStream trait for File pub const FileOutStream = struct { - file: *File, + file: File, stream: Stream, pub const Error = File.WriteError; pub const Stream = OutStream(Error); - pub fn init(file: *File) FileOutStream { + pub fn init(file: File) FileOutStream { return FileOutStream{ .file = file, .stream = Stream{ .writeFn = writeFn }, @@ -210,7 +210,7 @@ pub fn InStream(comptime ReadError: type) type { pub fn readStruct(self: *Self, comptime T: type, ptr: *T) !void { // Only extern and packed structs have defined in-memory layout. - assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto); + comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto); return self.readNoEof(@sliceToBytes((*[1]T)(ptr)[0..])); } }; @@ -280,7 +280,7 @@ pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptim const buf = try allocator.alignedAlloc(u8, A, size); errdefer allocator.free(buf); - var adapter = FileInStream.init(&file); + var adapter = FileInStream.init(file); try adapter.stream.readNoEof(buf[0..size]); return buf; } @@ -592,7 +592,7 @@ pub const BufferedAtomicFile = struct { self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.File.default_mode); errdefer self.atomic_file.deinit(); - self.file_stream = FileOutStream.init(&self.atomic_file.file); + self.file_stream = FileOutStream.init(self.atomic_file.file); self.buffered_stream = BufferedOutStream(FileOutStream.Error).init(&self.file_stream.stream); return self; } @@ -622,7 +622,7 @@ test "import io tests" { pub fn readLine(buf: []u8) !usize { var stdin = getStdIn() catch return error.StdInUnavailable; - var adapter = FileInStream.init(&stdin); + var adapter = FileInStream.init(stdin); var stream = &adapter.stream; var index: usize = 0; while (true) { diff --git a/std/io_test.zig b/std/io_test.zig index 7a4403267369..7403c9699436 100644 --- a/std/io_test.zig +++ b/std/io_test.zig @@ -19,7 +19,7 @@ test "write a file, read it, then delete it" { var file = try os.File.openWrite(tmp_file_name); defer file.close(); - var file_out_stream = io.FileOutStream.init(&file); + var file_out_stream = io.FileOutStream.init(file); var buf_stream = io.BufferedOutStream(io.FileOutStream.Error).init(&file_out_stream.stream); const st = &buf_stream.stream; try st.print("begin"); @@ -35,7 +35,7 @@ test "write a file, read it, then delete it" { const expected_file_size = "begin".len + data.len + "end".len; assert(file_size == expected_file_size); - var file_in_stream = io.FileInStream.init(&file); + var file_in_stream = io.FileInStream.init(file); var buf_stream = io.BufferedInStream(io.FileInStream.Error).init(&file_in_stream.stream); const st = &buf_stream.stream; const contents = try st.readAllAlloc(allocator, 2 * 1024); diff --git a/std/os/child_process.zig b/std/os/child_process.zig index b79a8de16feb..decd8d04fa75 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -209,8 +209,8 @@ pub const ChildProcess = struct { defer Buffer.deinit(&stdout); defer Buffer.deinit(&stderr); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size); try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size); diff --git a/std/os/file.zig b/std/os/file.zig index 1f5ce7cf9dcb..020a5dca54b7 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 }; } @@ -190,17 +205,16 @@ pub const File = struct { /// Upon success, the stream is in an uninitialized state. To continue using it, /// you must use the open() function. - pub fn close(self: *File) void { + pub fn close(self: File) void { os.close(self.handle); - self.handle = undefined; } /// Calls `os.isTty` on `self.handle`. - pub fn isTty(self: *File) bool { + pub fn isTty(self: File) bool { return os.isTty(self.handle); } - pub fn seekForward(self: *File, amount: isize) !void { + pub fn seekForward(self: File, amount: isize) !void { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { const result = posix.lseek(self.handle, amount, posix.SEEK_CUR); @@ -231,7 +245,7 @@ pub const File = struct { } } - pub fn seekTo(self: *File, pos: usize) !void { + pub fn seekTo(self: File, pos: usize) !void { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { const ipos = try math.cast(isize, pos); @@ -256,6 +270,7 @@ pub const File = struct { const err = windows.GetLastError(); return switch (err) { windows.ERROR.INVALID_PARAMETER => unreachable, + windows.ERROR.INVALID_HANDLE => unreachable, else => os.unexpectedErrorWindows(err), }; } @@ -264,7 +279,7 @@ pub const File = struct { } } - pub fn getPos(self: *File) !usize { + pub fn getPos(self: File) !usize { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { const result = posix.lseek(self.handle, 0, posix.SEEK_CUR); @@ -300,7 +315,7 @@ pub const File = struct { } } - pub fn getEndPos(self: *File) !usize { + pub fn getEndPos(self: File) !usize { if (is_posix) { const stat = try os.posixFStat(self.handle); return @intCast(usize, stat.size); @@ -325,7 +340,7 @@ pub const File = struct { Unexpected, }; - pub fn mode(self: *File) ModeError!Mode { + pub fn mode(self: File) ModeError!Mode { if (is_posix) { var stat: posix.Stat = undefined; const err = posix.getErrno(posix.fstat(self.handle, &stat)); @@ -359,7 +374,7 @@ pub const File = struct { Unexpected, }; - pub fn read(self: *File, buffer: []u8) ReadError!usize { + pub fn read(self: File, buffer: []u8) ReadError!usize { if (is_posix) { var index: usize = 0; while (index < buffer.len) { @@ -407,7 +422,7 @@ pub const File = struct { pub const WriteError = os.WindowsWriteError || os.PosixWriteError; - pub fn write(self: *File, bytes: []const u8) WriteError!void { + pub fn write(self: File, bytes: []const u8) WriteError!void { if (is_posix) { try os.posixWrite(self.handle, bytes); } else if (is_windows) { diff --git a/std/os/index.zig b/std/os/index.zig index 29d887e21451..9b49a0506789 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; @@ -660,6 +661,7 @@ pub fn getBaseAddress() usize { return phdr - @sizeOf(ElfHeader); }, builtin.Os.macosx => return @ptrToInt(&std.c._mh_execute_header), + builtin.Os.windows => return @ptrToInt(windows.GetModuleHandleW(null)), else => @compileError("Unsupported OS"), } } @@ -2068,7 +2070,7 @@ fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []cons } // TODO make this a build variable that you can set -const unexpected_error_tracing = false; +const unexpected_error_tracing = true; const UnexpectedError = error{ /// The Operating System returned an undocumented error code. Unexpected, @@ -2087,8 +2089,9 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError { /// Call this when you made a windows DLL call or something that does SetLastError /// and you get an unexpected error. pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError { - if (true) { + if (unexpected_error_tracing) { debug.warn("unexpected GetLastError(): {}\n", err); + @breakpoint(); debug.dumpCurrentStackTrace(null); } return error.Unexpected; @@ -2103,15 +2106,33 @@ 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. @@ -2129,16 +2150,7 @@ pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { 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/index.zig b/std/os/windows/index.zig index 5c68176c5ac6..9286b7d09027 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -3,6 +3,7 @@ const assert = std.debug.assert; pub use @import("advapi32.zig"); pub use @import("kernel32.zig"); +pub use @import("ntdll.zig"); pub use @import("ole32.zig"); pub use @import("shell32.zig"); pub use @import("shlwapi.zig"); @@ -14,6 +15,7 @@ test "import" { pub const ERROR = @import("error.zig"); +pub const SHORT = c_short; pub const BOOL = c_int; pub const BOOLEAN = BYTE; pub const BYTE = u8; @@ -363,3 +365,15 @@ pub const FILE_FLAG_RANDOM_ACCESS = 0x10000000; pub const FILE_FLAG_SESSION_AWARE = 0x00800000; pub const FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; pub const FILE_FLAG_WRITE_THROUGH = 0x80000000; + +pub const SMALL_RECT = extern struct { + Left: SHORT, + Top: SHORT, + Right: SHORT, + Bottom: SHORT, +}; + +pub const COORD = extern struct { + X: SHORT, + Y: SHORT, +}; diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 66b529118952..ffa44227609b 100644 --- a/std/os/windows/kernel32.zig +++ b/std/os/windows/kernel32.zig @@ -72,6 +72,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR; pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL; +pub extern "kernel32" stdcallcc fn GetConsoleScreenBufferInfo(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) BOOL; + pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: DWORD, lpBuffer: ?[*]CHAR) DWORD; pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD; @@ -92,6 +94,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( @@ -177,6 +181,8 @@ pub extern "kernel32" stdcallcc fn ReadFile( pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL; +pub extern "kernel32" stdcallcc fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) BOOL; + pub extern "kernel32" stdcallcc fn SetFilePointerEx( in_fFile: HANDLE, in_liDistanceToMove: LARGE_INTEGER, @@ -232,3 +238,17 @@ pub const FILE_NOTIFY_CHANGE_LAST_WRITE = 16; pub const FILE_NOTIFY_CHANGE_DIR_NAME = 2; pub const FILE_NOTIFY_CHANGE_FILE_NAME = 1; pub const FILE_NOTIFY_CHANGE_ATTRIBUTES = 4; + + +pub const CONSOLE_SCREEN_BUFFER_INFO = extern struct { + dwSize: COORD, + dwCursorPosition: COORD, + wAttributes: WORD, + srWindow: SMALL_RECT, + dwMaximumWindowSize: COORD, +}; + +pub const FOREGROUND_BLUE = 1; +pub const FOREGROUND_GREEN = 2; +pub const FOREGROUND_RED = 4; +pub const FOREGROUND_INTENSITY = 8; diff --git a/std/os/windows/ntdll.zig b/std/os/windows/ntdll.zig new file mode 100644 index 000000000000..acb78a59f4d7 --- /dev/null +++ b/std/os/windows/ntdll.zig @@ -0,0 +1,3 @@ +use @import("index.zig"); + +pub extern "NtDll" stdcallcc fn RtlCaptureStackBackTrace(FramesToSkip: DWORD, FramesToCapture: DWORD, BackTrace: **c_void, BackTraceHash: ?*DWORD) WORD; diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index 72de89699607..168f6c386180 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 000000000000..907eddff0446 --- /dev/null +++ b/std/pdb.zig @@ -0,0 +1,646 @@ +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 coff = std.coff; + +const ArrayList = std.ArrayList; + +// https://llvm.org/docs/PDB/DbiStream.html#stream-header +pub const DbiStreamHeader = packed struct { + VersionSignature: i32, + VersionHeader: u32, + Age: u32, + GlobalStreamIndex: u16, + BuildNumber: u16, + PublicStreamIndex: u16, + PdbDllVersion: u16, + SymRecordStream: u16, + PdbDllRbld: u16, + ModInfoSize: u32, + SectionContributionSize: u32, + SectionMapSize: u32, + SourceInfoSize: i32, + TypeServerSize: i32, + MFCTypeServerIndex: u32, + OptionalDbgHeaderSize: i32, + ECSubstreamSize: i32, + Flags: u16, + Machine: u16, + Padding: u32, +}; + +pub const SectionContribEntry = packed struct { + Section: u16, + Padding1: [2]u8, + Offset: u32, + Size: u32, + Characteristics: u32, + ModuleIndex: u16, + Padding2: [2]u8, + DataCrc: u32, + RelocCrc: u32, +}; + +pub const ModInfo = packed struct { + Unused1: u32, + SectionContr: SectionContribEntry, + Flags: u16, + ModuleSymStream: u16, + SymByteSize: u32, + C11ByteSize: u32, + C13ByteSize: u32, + SourceFileCount: u16, + Padding: [2]u8, + Unused2: u32, + SourceFileNameIndex: u32, + PdbFilePathNameIndex: u32, + // These fields are variable length + //ModuleName: char[], + //ObjFileName: char[], +}; + +pub const SectionMapHeader = packed struct { + Count: u16, /// Number of segment descriptors + LogCount: u16, /// Number of logical segment descriptors +}; + +pub const SectionMapEntry = packed struct { + Flags: u16 , /// See the SectionMapEntryFlags enum below. + Ovl: u16 , /// Logical overlay number + Group: u16 , /// Group index into descriptor array. + Frame: u16 , + SectionName: u16 , /// Byte index of segment / group name in string table, or 0xFFFF. + ClassName: u16 , /// Byte index of class in string table, or 0xFFFF. + Offset: u32 , /// Byte offset of the logical segment within physical segment. If group is set in flags, this is the offset of the group. + SectionLength: u32 , /// Byte count of the segment or group. +}; + +pub const StreamType = enum(u16) { + Pdb = 1, + Tpi = 2, + Dbi = 3, + Ipi = 4, +}; + +/// Duplicate copy of SymbolRecordKind, but using the official CV names. Useful +/// for reference purposes and when dealing with unknown record types. +pub const SymbolKind = packed enum(u16) { + S_COMPILE = 1, + S_REGISTER_16t = 2, + S_CONSTANT_16t = 3, + S_UDT_16t = 4, + S_SSEARCH = 5, + S_SKIP = 7, + S_CVRESERVE = 8, + S_OBJNAME_ST = 9, + S_ENDARG = 10, + S_COBOLUDT_16t = 11, + S_MANYREG_16t = 12, + S_RETURN = 13, + S_ENTRYTHIS = 14, + S_BPREL16 = 256, + S_LDATA16 = 257, + S_GDATA16 = 258, + S_PUB16 = 259, + S_LPROC16 = 260, + S_GPROC16 = 261, + S_THUNK16 = 262, + S_BLOCK16 = 263, + S_WITH16 = 264, + S_LABEL16 = 265, + S_CEXMODEL16 = 266, + S_VFTABLE16 = 267, + S_REGREL16 = 268, + S_BPREL32_16t = 512, + S_LDATA32_16t = 513, + S_GDATA32_16t = 514, + S_PUB32_16t = 515, + S_LPROC32_16t = 516, + S_GPROC32_16t = 517, + S_THUNK32_ST = 518, + S_BLOCK32_ST = 519, + S_WITH32_ST = 520, + S_LABEL32_ST = 521, + S_CEXMODEL32 = 522, + S_VFTABLE32_16t = 523, + S_REGREL32_16t = 524, + S_LTHREAD32_16t = 525, + S_GTHREAD32_16t = 526, + S_SLINK32 = 527, + S_LPROCMIPS_16t = 768, + S_GPROCMIPS_16t = 769, + S_PROCREF_ST = 1024, + S_DATAREF_ST = 1025, + S_ALIGN = 1026, + S_LPROCREF_ST = 1027, + S_OEM = 1028, + S_TI16_MAX = 4096, + S_REGISTER_ST = 4097, + S_CONSTANT_ST = 4098, + S_UDT_ST = 4099, + S_COBOLUDT_ST = 4100, + S_MANYREG_ST = 4101, + S_BPREL32_ST = 4102, + S_LDATA32_ST = 4103, + S_GDATA32_ST = 4104, + S_PUB32_ST = 4105, + S_LPROC32_ST = 4106, + S_GPROC32_ST = 4107, + S_VFTABLE32 = 4108, + S_REGREL32_ST = 4109, + S_LTHREAD32_ST = 4110, + S_GTHREAD32_ST = 4111, + S_LPROCMIPS_ST = 4112, + S_GPROCMIPS_ST = 4113, + S_COMPILE2_ST = 4115, + S_MANYREG2_ST = 4116, + S_LPROCIA64_ST = 4117, + S_GPROCIA64_ST = 4118, + S_LOCALSLOT_ST = 4119, + S_PARAMSLOT_ST = 4120, + S_ANNOTATION = 4121, + S_GMANPROC_ST = 4122, + S_LMANPROC_ST = 4123, + S_RESERVED1 = 4124, + S_RESERVED2 = 4125, + S_RESERVED3 = 4126, + S_RESERVED4 = 4127, + S_LMANDATA_ST = 4128, + S_GMANDATA_ST = 4129, + S_MANFRAMEREL_ST = 4130, + S_MANREGISTER_ST = 4131, + S_MANSLOT_ST = 4132, + S_MANMANYREG_ST = 4133, + S_MANREGREL_ST = 4134, + S_MANMANYREG2_ST = 4135, + S_MANTYPREF = 4136, + S_UNAMESPACE_ST = 4137, + S_ST_MAX = 4352, + S_WITH32 = 4356, + S_MANYREG = 4362, + S_LPROCMIPS = 4372, + S_GPROCMIPS = 4373, + S_MANYREG2 = 4375, + S_LPROCIA64 = 4376, + S_GPROCIA64 = 4377, + S_LOCALSLOT = 4378, + S_PARAMSLOT = 4379, + S_MANFRAMEREL = 4382, + S_MANREGISTER = 4383, + S_MANSLOT = 4384, + S_MANMANYREG = 4385, + S_MANREGREL = 4386, + S_MANMANYREG2 = 4387, + S_UNAMESPACE = 4388, + S_DATAREF = 4390, + S_ANNOTATIONREF = 4392, + S_TOKENREF = 4393, + S_GMANPROC = 4394, + S_LMANPROC = 4395, + S_ATTR_FRAMEREL = 4398, + S_ATTR_REGISTER = 4399, + S_ATTR_REGREL = 4400, + S_ATTR_MANYREG = 4401, + S_SEPCODE = 4402, + S_LOCAL_2005 = 4403, + S_DEFRANGE_2005 = 4404, + S_DEFRANGE2_2005 = 4405, + S_DISCARDED = 4411, + S_LPROCMIPS_ID = 4424, + S_GPROCMIPS_ID = 4425, + S_LPROCIA64_ID = 4426, + S_GPROCIA64_ID = 4427, + S_DEFRANGE_HLSL = 4432, + S_GDATA_HLSL = 4433, + S_LDATA_HLSL = 4434, + S_LOCAL_DPC_GROUPSHARED = 4436, + S_DEFRANGE_DPC_PTR_TAG = 4439, + S_DPC_SYM_TAG_MAP = 4440, + S_ARMSWITCHTABLE = 4441, + S_POGODATA = 4444, + S_INLINESITE2 = 4445, + S_MOD_TYPEREF = 4447, + S_REF_MINIPDB = 4448, + S_PDBMAP = 4449, + S_GDATA_HLSL32 = 4450, + S_LDATA_HLSL32 = 4451, + S_GDATA_HLSL32_EX = 4452, + S_LDATA_HLSL32_EX = 4453, + S_FASTLINK = 4455, + S_INLINEES = 4456, + S_END = 6, + S_INLINESITE_END = 4430, + S_PROC_ID_END = 4431, + S_THUNK32 = 4354, + S_TRAMPOLINE = 4396, + S_SECTION = 4406, + S_COFFGROUP = 4407, + S_EXPORT = 4408, + S_LPROC32 = 4367, + S_GPROC32 = 4368, + S_LPROC32_ID = 4422, + S_GPROC32_ID = 4423, + S_LPROC32_DPC = 4437, + S_LPROC32_DPC_ID = 4438, + S_REGISTER = 4358, + S_PUB32 = 4366, + S_PROCREF = 4389, + S_LPROCREF = 4391, + S_ENVBLOCK = 4413, + S_INLINESITE = 4429, + S_LOCAL = 4414, + S_DEFRANGE = 4415, + S_DEFRANGE_SUBFIELD = 4416, + S_DEFRANGE_REGISTER = 4417, + S_DEFRANGE_FRAMEPOINTER_REL = 4418, + S_DEFRANGE_SUBFIELD_REGISTER = 4419, + S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE = 4420, + S_DEFRANGE_REGISTER_REL = 4421, + S_BLOCK32 = 4355, + S_LABEL32 = 4357, + S_OBJNAME = 4353, + S_COMPILE2 = 4374, + S_COMPILE3 = 4412, + S_FRAMEPROC = 4114, + S_CALLSITEINFO = 4409, + S_FILESTATIC = 4435, + S_HEAPALLOCSITE = 4446, + S_FRAMECOOKIE = 4410, + S_CALLEES = 4442, + S_CALLERS = 4443, + S_UDT = 4360, + S_COBOLUDT = 4361, + S_BUILDINFO = 4428, + S_BPREL32 = 4363, + S_REGREL32 = 4369, + S_CONSTANT = 4359, + S_MANCONSTANT = 4397, + S_LDATA32 = 4364, + S_GDATA32 = 4365, + S_LMANDATA = 4380, + S_GMANDATA = 4381, + S_LTHREAD32 = 4370, + S_GTHREAD32 = 4371, +}; + +pub const TypeIndex = u32; + +pub const ProcSym = packed struct { + Parent: u32 , + End: u32 , + Next: u32 , + CodeSize: u32 , + DbgStart: u32 , + DbgEnd: u32 , + FunctionType: TypeIndex , + CodeOffset: u32, + Segment: u16, + Flags: ProcSymFlags, + // following is a null terminated string + // Name: [*]u8, +}; + +pub const ProcSymFlags = packed struct { + HasFP: bool, + HasIRET: bool, + HasFRET: bool, + IsNoReturn: bool, + IsUnreachable: bool, + HasCustomCallingConv: bool, + IsNoInline: bool, + HasOptimizedDebugInfo: bool, +}; + +pub const SectionContrSubstreamVersion = enum(u32) { + Ver60 = 0xeffe0000 + 19970605, + V2 = 0xeffe0000 + 20140516 +}; + +pub const RecordPrefix = packed struct { + RecordLen: u16, /// Record length, starting from &RecordKind. + RecordKind: SymbolKind, /// Record kind enum (SymRecordKind or TypeRecordKind) +}; + +pub const LineFragmentHeader = packed struct { + RelocOffset: u32, /// Code offset of line contribution. + RelocSegment: u16, /// Code segment of line contribution. + Flags: LineFlags, + CodeSize: u32, /// Code size of this line contribution. +}; + +pub const LineFlags = packed struct { + LF_HaveColumns: bool, /// CV_LINES_HAVE_COLUMNS + unused: u15, +}; + +/// The following two variable length arrays appear immediately after the +/// header. The structure definitions follow. +/// LineNumberEntry Lines[NumLines]; +/// ColumnNumberEntry Columns[NumLines]; +pub const LineBlockFragmentHeader = packed struct { + /// Offset of FileChecksum entry in File + /// checksums buffer. The checksum entry then + /// contains another offset into the string + /// table of the actual name. + NameIndex: u32, + NumLines: u32, + BlockSize: u32, /// code size of block, in bytes +}; + + +pub const LineNumberEntry = packed struct { + Offset: u32, /// Offset to start of code bytes for line number + Flags: u32, + + /// TODO runtime crash when I make the actual type of Flags this + const Flags = packed struct { + Start: u24, + End: u7, + IsStatement: bool, + }; +}; + +pub const ColumnNumberEntry = packed struct { + StartColumn: u16, + EndColumn: u16, +}; + +/// Checksum bytes follow. +pub const FileChecksumEntryHeader = packed struct { + FileNameOffset: u32, /// Byte offset of filename in global string table. + ChecksumSize: u8, /// Number of bytes of checksum. + ChecksumKind: u8, /// FileChecksumKind +}; + +pub const DebugSubsectionKind = packed enum(u32) { + None = 0, + Symbols = 0xf1, + Lines = 0xf2, + StringTable = 0xf3, + FileChecksums = 0xf4, + FrameData = 0xf5, + InlineeLines = 0xf6, + CrossScopeImports = 0xf7, + CrossScopeExports = 0xf8, + + // These appear to relate to .Net assembly info. + ILLines = 0xf9, + FuncMDTokenMap = 0xfa, + TypeMDTokenMap = 0xfb, + MergedAssemblyInput = 0xfc, + + CoffSymbolRVA = 0xfd, +}; + + +pub const DebugSubsectionHeader = packed struct { + Kind: DebugSubsectionKind, /// codeview::DebugSubsectionKind enum + Length: u32, /// number of bytes occupied by this record. +}; + + +pub const PDBStringTableHeader = packed struct { + Signature: u32, /// PDBStringTableSignature + HashVersion: u32, /// 1 or 2 + ByteSize: u32, /// Number of bytes of names buffer. +}; + +pub const Pdb = struct { + in_file: os.File, + allocator: *mem.Allocator, + coff: *coff.Coff, + string_table: *MsfStream, + dbi: *MsfStream, + + msf: Msf, + + pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []u8) !void { + self.in_file = try os.File.openRead(file_name); + self.allocator = coff_ptr.allocator; + self.coff = coff_ptr; + + try self.msf.openFile(self.allocator, self.in_file); + } + + pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream { + if (id >= self.msf.streams.len) + return null; + return &self.msf.streams[id]; + } + + pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream { + const id = @enumToInt(stream); + return self.getStreamById(id); + } +}; + +// see https://llvm.org/docs/PDB/MsfFile.html +const Msf = struct { + directory: MsfStream, + streams: []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 superblock: SuperBlock = undefined; + try in.readStruct(SuperBlock, &superblock); + + if (!mem.eql(u8, superblock.FileMagic, SuperBlock.file_magic)) + return error.InvalidDebugInfo; + + switch (superblock.BlockSize) { + // llvm only supports 4096 but we can handle any of these values + 512, 1024, 2048, 4096 => {}, + else => return error.InvalidDebugInfo + } + + if (superblock.NumBlocks * superblock.BlockSize != try file.getEndPos()) + return error.InvalidDebugInfo; + + self.directory = try MsfStream.init( + superblock.BlockSize, + blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize), + superblock.BlockSize * superblock.BlockMapAddr, + file, + allocator, + ); + + const stream_count = try self.directory.stream.readIntLe(u32); + + const stream_sizes = try allocator.alloc(u32, stream_count); + for (stream_sizes) |*s| { + const size = try self.directory.stream.readIntLe(u32); + s.* = blockCountFromSize(size, superblock.BlockSize); + } + + self.streams = try allocator.alloc(MsfStream, stream_count); + for (self.streams) |*stream, i| { + stream.* = try MsfStream.init( + superblock.BlockSize, + stream_sizes[i], + // MsfStream.init expects the file to be at the part where it reads [N]u32 + try file.getPos(), + file, + allocator, + ); + } + } +}; + +fn blockCountFromSize(size: u32, block_size: u32) u32 { + return (size + block_size - 1) / block_size; +} + +// https://llvm.org/docs/PDB/MsfFile.html#the-superblock +const SuperBlock = packed struct { + /// The LLVM docs list a space between C / C++ but empirically this is not the case. + const file_magic = "Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00"; + + FileMagic: [file_magic.len]u8, + + /// The block size of the internal file system. Valid values are 512, 1024, + /// 2048, and 4096 bytes. Certain aspects of the MSF file layout vary depending + /// on the block sizes. For the purposes of LLVM, we handle only block sizes of + /// 4KiB, and all further discussion assumes a block size of 4KiB. + BlockSize: u32, + + /// The index of a block within the file, at which begins a bitfield representing + /// the set of all blocks within the file which are “free” (i.e. the data within + /// that block is not used). See The Free Block Map for more information. Important: + /// FreeBlockMapBlock can only be 1 or 2! + FreeBlockMapBlock: u32, + + /// The total number of blocks in the file. NumBlocks * BlockSize should equal the + /// size of the file on disk. + NumBlocks: u32, + + /// The size of the stream directory, in bytes. The stream directory contains + /// information about each stream’s size and the set of blocks that it occupies. + /// It will be described in more detail later. + NumDirectoryBytes: u32, + + Unknown: u32, + + /// The index of a block within the MSF file. At this block is an array of + /// ulittle32_t’s listing the blocks that the stream directory resides on. + /// For large MSF files, the stream directory (which describes the block + /// layout of each stream) may not fit entirely on a single block. As a + /// result, this extra layer of indirection is introduced, whereby this + /// block contains the list of blocks that the stream directory occupies, + /// and the stream directory itself can be stitched together accordingly. + /// The number of ulittle32_t’s in this array is given by + /// ceil(NumDirectoryBytes / BlockSize). + BlockMapAddr: u32, + +}; + +const MsfStream = struct { + in_file: os.File, + pos: usize, + blocks: []u32, + block_size: u32, + + /// Implementation of InStream trait for Pdb.MsfStream + stream: Stream, + + pub const Error = @typeOf(read).ReturnType.ErrorSet; + pub const Stream = io.InStream(Error); + + 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 = try allocator.alloc(u32, block_count), + .block_size = block_size, + .stream = Stream { + .readFn = readFn, + }, + }; + + var file_stream = io.FileInStream.init(file); + const in = &file_stream.stream; + try file.seekTo(pos); + + var i: u32 = 0; + while (i < block_count) : (i += 1) { + stream.blocks[i] = try in.readIntLe(u32); + } + + return stream; + } + + fn readNullTermString(self: *MsfStream, allocator: *mem.Allocator) ![]u8 { + var list = ArrayList(u8).init(allocator); + defer list.deinit(); + while (true) { + const byte = try self.stream.readByte(); + if (byte == 0) { + return list.toSlice(); + } + try list.append(byte); + } + } + + fn read(self: *MsfStream, buffer: []u8) !usize { + var block_id = self.pos / self.block_size; + var block = self.blocks[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[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: MsfStream) usize { + const block_id = self.pos / self.block_size; + const block = self.blocks[block_id]; + const offset = self.pos % self.block_size; + + return block * self.block_size + offset; + } + + fn readFn(in_stream: *Stream, buffer: []u8) Error!usize { + const self = @fieldParentPtr(MsfStream, "stream", in_stream); + return self.read(buffer); + } +}; diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index 982c60aed81f..8cf237f634db 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -49,14 +49,14 @@ pub fn main() !void { var stderr_file = io.getStdErr(); var stderr_file_stream: io.FileOutStream = undefined; - var stderr_stream = if (stderr_file) |*f| x: { + var stderr_stream = if (stderr_file) |f| x: { stderr_file_stream = io.FileOutStream.init(f); break :x &stderr_file_stream.stream; } else |err| err; var stdout_file = io.getStdOut(); var stdout_file_stream: io.FileOutStream = undefined; - var stdout_stream = if (stdout_file) |*f| x: { + var stdout_stream = if (stdout_file) |f| x: { stdout_file_stream = io.FileOutStream.init(f); break :x &stdout_file_stream.stream; } else |err| err; diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 6ea25e54f182..7f3ce7bd8a12 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1865,7 +1865,7 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *bool) ![]u8 { var stderr_file = try io.getStdErr(); - var stderr = &io.FileOutStream.init(&stderr_file).stream; + var stderr = &io.FileOutStream.init(stderr_file).stream; var tree = try std.zig.parse(allocator, source); defer tree.deinit(); diff --git a/test/compare_output.zig b/test/compare_output.zig index a18a78b419c6..bcd9d15b9c74 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -19,7 +19,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn main() void { \\ privateFunction(); - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK 2\n") catch unreachable; \\} \\ @@ -34,7 +34,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\// purposefully conflicting function with main.zig \\// but it's private so it should be OK \\fn privateFunction() void { - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK 1\n") catch unreachable; \\} \\ @@ -60,7 +60,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { tc.addSourceFile("foo.zig", \\use @import("std").io; \\pub fn foo_function() void { - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK\n") catch unreachable; \\} ); @@ -71,7 +71,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn bar_function() void { \\ if (foo_function()) { - \\ const stdout = &(FileOutStream.init(&(getStdOut() catch unreachable)).stream); + \\ const stdout = &FileOutStream.init(getStdOut() catch unreachable).stream; \\ stdout.print("OK\n") catch unreachable; \\ } \\} @@ -103,7 +103,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub const a_text = "OK\n"; \\ \\pub fn ok() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print(b_text) catch unreachable; \\} ); @@ -121,7 +121,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\ \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable; \\} , "Hello, world!\n0012 012 a\n"); @@ -274,7 +274,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ var x_local : i32 = print_ok(x); \\} \\fn print_ok(val: @typeOf(x)) @typeOf(foo) { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("OK\n") catch unreachable; \\ return 0; \\} @@ -356,7 +356,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() void { \\ const bar = Bar {.field2 = 13,}; \\ const foo = Foo {.field1 = bar,}; - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ if (!foo.method()) { \\ stdout.print("BAD\n") catch unreachable; \\ } @@ -370,7 +370,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.add("defer with only fallthrough", \\const io = @import("std").io; \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ defer stdout.print("defer2\n") catch unreachable; @@ -383,7 +383,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\const os = @import("std").os; \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ defer stdout.print("defer2\n") catch unreachable; @@ -400,7 +400,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ do_test() catch return; \\} \\fn do_test() !void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ errdefer stdout.print("deferErr\n") catch unreachable; @@ -419,7 +419,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ do_test() catch return; \\} \\fn do_test() !void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print("before\n") catch unreachable; \\ defer stdout.print("defer1\n") catch unreachable; \\ errdefer stdout.print("deferErr\n") catch unreachable; @@ -436,7 +436,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\ \\pub fn main() void { - \\ const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); + \\ const stdout = &io.FileOutStream.init(io.getStdOut() catch unreachable).stream; \\ stdout.print(foo_txt) catch unreachable; \\} , "1234\nabcd\n"); @@ -456,7 +456,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() !void { \\ var args_it = os.args(); \\ var stdout_file = try io.getStdOut(); - \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ var stdout_adapter = io.FileOutStream.init(stdout_file); \\ const stdout = &stdout_adapter.stream; \\ var index: usize = 0; \\ _ = args_it.skip(); @@ -497,7 +497,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() !void { \\ var args_it = os.args(); \\ var stdout_file = try io.getStdOut(); - \\ var stdout_adapter = io.FileOutStream.init(&stdout_file); + \\ var stdout_adapter = io.FileOutStream.init(stdout_file); \\ const stdout = &stdout_adapter.stream; \\ var index: usize = 0; \\ _ = args_it.skip(); diff --git a/test/tests.zig b/test/tests.zig index aa5eed17ee43..a0e17920797e 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -263,8 +263,8 @@ pub const CompareOutputContext = struct { var stdout = Buffer.initNull(b.allocator); var stderr = Buffer.initNull(b.allocator); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable; stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable; @@ -578,8 +578,8 @@ pub const CompileErrorContext = struct { var stdout_buf = Buffer.initNull(b.allocator); var stderr_buf = Buffer.initNull(b.allocator); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable; stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable; @@ -842,8 +842,8 @@ pub const TranslateCContext = struct { var stdout_buf = Buffer.initNull(b.allocator); var stderr_buf = Buffer.initNull(b.allocator); - var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?); - var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?); + var stdout_file_in_stream = io.FileInStream.init(child.stdout.?); + var stderr_file_in_stream = io.FileInStream.init(child.stderr.?); stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable; stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable;