From bc31c1280e012377c3a2b00356a5876919a72775 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Jul 2019 12:15:16 -0400 Subject: disable segfault handler when panicking this prevents a segfault in stack trace printing to activate the segfault handler. --- std/debug.zig | 62 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 18 deletions(-) (limited to 'std/debug.zig') diff --git a/std/debug.zig b/std/debug.zig index d81e62901a..7d1fd6ce47 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -12,6 +12,7 @@ const coff = std.coff; const pdb = std.pdb; const ArrayList = std.ArrayList; const builtin = @import("builtin"); +const root = @import("root"); const maxInt = std.math.maxInt; const File = std.fs.File; const windows = std.os.windows; @@ -217,6 +218,12 @@ var panicking: u8 = 0; // TODO make this a bool pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: ...) noreturn { @setCold(true); + if (enable_segfault_handler) { + // If a segfault happens while panicking, we want it to actually segfault, not trigger + // the handler. + resetSegfaultHandler(); + } + if (@atomicRmw(u8, &panicking, builtin.AtomicRmwOp.Xchg, 1, builtin.AtomicOrder.SeqCst) == 1) { // Panicked during a panic. @@ -2312,39 +2319,58 @@ fn getDebugInfoAllocator() *mem.Allocator { /// Whether or not the current target can print useful debug information when a segfault occurs. pub const have_segfault_handling_support = (builtin.arch == builtin.Arch.x86_64 and builtin.os == .linux) or builtin.os == .windows; +pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) + root.enable_segfault_handler +else + runtime_safety and have_segfault_handling_support; + +pub fn maybeEnableSegfaultHandler() void { + if (enable_segfault_handler) { + std.debug.attachSegfaultHandler(); + } +} + +var windows_segfault_handle: ?windows.HANDLE = null; /// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); pub fn attachSegfaultHandler() void { if (!have_segfault_handling_support) { @compileError("segfault handler not supported for this target"); } - switch (builtin.os) { - .linux => { - var act = os.Sigaction{ - .sigaction = handleSegfaultLinux, - .mask = os.empty_sigset, - .flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND), - }; - - os.sigaction(os.SIGSEGV, &act, null); - }, - .windows => { - _ = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); - }, - else => unreachable, + if (windows.is_the_target) { + windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); + return; } + var act = os.Sigaction{ + .sigaction = handleSegfaultLinux, + .mask = os.empty_sigset, + .flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND), + }; + + os.sigaction(os.SIGSEGV, &act, null); } -extern fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) noreturn { - // Reset to the default handler so that if a segfault happens in this handler it will crash - // the process. Also when this handler returns, the original instruction will be repeated - // and the resulting segfault will crash the process rather than continually dump stack traces. +fn resetSegfaultHandler() void { + if (windows.is_the_target) { + if (windows_segfault_handle) |handle| { + assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0); + windows_segfault_handle = null; + } + return; + } var act = os.Sigaction{ .sigaction = os.SIG_DFL, .mask = os.empty_sigset, .flags = 0, }; os.sigaction(os.SIGSEGV, &act, null); +} + +extern fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) noreturn { + // Reset to the default handler so that if a segfault happens in this handler it will crash + // the process. Also when this handler returns, the original instruction will be repeated + // and the resulting segfault will crash the process rather than continually dump stack traces. + resetSegfaultHandler(); const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]); -- cgit v1.2.3 From 05032c869378e7c7e3da3a2770161266058aa320 Mon Sep 17 00:00:00 2001 From: Sahnvour Date: Sun, 28 Jul 2019 19:03:36 +0200 Subject: coff & pdb: improved correctness of our implementation, it is now able to handle stage1's pdb and print its stack traces --- std/coff.zig | 42 +++++++++++++++++++++--- std/debug.zig | 13 ++++++-- std/pdb.zig | 103 +++++++++++++++++++++++++++++++++++++--------------------- 3 files changed, 113 insertions(+), 45 deletions(-) (limited to 'std/debug.zig') diff --git a/std/coff.zig b/std/coff.zig index 7c53f48f66..3890151d09 100644 --- a/std/coff.zig +++ b/std/coff.zig @@ -19,6 +19,7 @@ const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; +const IMAGE_DEBUG_TYPE_CODEVIEW = 2; const DEBUG_DIRECTORY = 6; pub const CoffError = error{ @@ -28,6 +29,7 @@ pub const CoffError = error{ MissingCoffSection, }; +// Official documentation of the format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format pub const Coff = struct { in_file: File, allocator: *mem.Allocator, @@ -120,6 +122,7 @@ pub const Coff = struct { pub fn getPdbPath(self: *Coff, buffer: []u8) !usize { try self.loadSections(); + const header = blk: { if (self.getSection(".buildid")) |section| { break :blk section.header; @@ -130,14 +133,32 @@ pub const Coff = struct { } }; - // 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 = self.in_file.inStream(); const in = &file_stream.stream; + try self.in_file.seekTo(file_offset); + + // Find the correct DebugDirectoryEntry, and where its data is stored. + // It can be in any section. + const debug_dir_entry_count = debug_dir.size / @sizeOf(DebugDirectoryEntry); + var i: u32 = 0; + blk: while (i < debug_dir_entry_count) : (i += 1) { + const debug_dir_entry = try in.readStruct(DebugDirectoryEntry); + if (debug_dir_entry.type == IMAGE_DEBUG_TYPE_CODEVIEW) { + for (self.sections.toSlice()) |*section| { + const section_start = section.header.virtual_address; + const section_size = section.header.misc.virtual_size; + const rva = debug_dir_entry.address_of_raw_data; + const offset = rva - section_start; + if (section_start <= rva and offset < section_size and debug_dir_entry.size_of_data <= section_size - offset) { + try self.in_file.seekTo(section.header.pointer_to_raw_data + offset); + break :blk; + } + } + } + } var cv_signature: [4]u8 = undefined; // CodeView signature try in.readNoEof(cv_signature[0..]); @@ -149,7 +170,7 @@ pub const Coff = struct { // Finally read the null-terminated string. var byte = try in.readByte(); - var i: usize = 0; + i = 0; while (byte != 0 and i < buffer.len) : (i += 1) { buffer[i] = byte; byte = try in.readByte(); @@ -178,7 +199,7 @@ pub const Coff = struct { try self.sections.append(Section{ .header = SectionHeader{ .name = name, - .misc = SectionHeader.Misc{ .physical_address = try in.readIntLittle(u32) }, + .misc = SectionHeader.Misc{ .virtual_size = try in.readIntLittle(u32) }, .virtual_address = try in.readIntLittle(u32), .size_of_raw_data = try in.readIntLittle(u32), .pointer_to_raw_data = try in.readIntLittle(u32), @@ -222,6 +243,17 @@ const OptionalHeader = struct { data_directory: [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]DataDirectory, }; +const DebugDirectoryEntry = packed struct { + characteristiccs: u32, + time_date_stamp: u32, + major_version: u16, + minor_version: u16, + @"type": u32, + size_of_data: u32, + address_of_raw_data: u32, + pointer_to_raw_data: u32, +}; + pub const Section = struct { header: SectionHeader, }; diff --git a/std/debug.zig b/std/debug.zig index 7d1fd6ce47..32f96d3e15 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -375,7 +375,7 @@ fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_addres const obj_basename = fs.path.basename(mod.obj_file_name); var symbol_i: usize = 0; - const symbol_name = while (symbol_i != mod.symbols.len) { + const symbol_name = if (!mod.populated) "???" else while (symbol_i != mod.symbols.len) { const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]); if (prefix.RecordLen < 2) return error.InvalidDebugInfo; @@ -858,8 +858,10 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { const age = try pdb_stream.stream.readIntLittle(u32); var guid: [16]u8 = undefined; try pdb_stream.stream.readNoEof(guid[0..]); + if (version != 20000404) // VC70, only value observed by LLVM team + return error.UnknownPDBVersion; if (!mem.eql(u8, di.coff.guid, guid) or di.coff.age != age) - return error.InvalidDebugInfo; + return error.PDBMismatch; // We validated the executable and pdb match. const string_table_index = str_tab_index: { @@ -903,13 +905,18 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { return error.MissingDebugInfo; }; - di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.InvalidDebugInfo; + di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.MissingDebugInfo; di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo; const dbi = di.pdb.dbi; // Dbi Header const dbi_stream_header = try dbi.stream.readStruct(pdb.DbiStreamHeader); + if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team + return error.UnknownPDBVersion; + if (dbi_stream_header.Age != age) + return error.UnmatchingPDB; + const mod_info_size = dbi_stream_header.ModInfoSize; const section_contrib_size = dbi_stream_header.SectionContributionSize; diff --git a/std/pdb.zig b/std/pdb.zig index ffe2120296..39f304afb5 100644 --- a/std/pdb.zig +++ b/std/pdb.zig @@ -499,45 +499,78 @@ const Msf = struct { const superblock = try in.readStruct(SuperBlock); + // Sanity checks if (!mem.eql(u8, superblock.FileMagic, SuperBlock.file_magic)) return error.InvalidDebugInfo; - + if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2) + return error.InvalidDebugInfo; + if (superblock.NumBlocks * superblock.BlockSize != try file.getEndPos()) + 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; + const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize); + if (dir_block_count > superblock.BlockSize / @sizeOf(u32)) + return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment. - self.directory = try MsfStream.init( + try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr); + var dir_blocks = try allocator.alloc(u32, dir_block_count); + for (dir_blocks) |*b| { + b.* = try in.readIntLittle(u32); + } + self.directory = MsfStream.init( superblock.BlockSize, - blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize), - superblock.BlockSize * superblock.BlockMapAddr, file, - allocator, + dir_blocks, ); + const begin = self.directory.pos; const stream_count = try self.directory.stream.readIntLittle(u32); - const stream_sizes = try allocator.alloc(u32, stream_count); - for (stream_sizes) |*s| { + defer allocator.free(stream_sizes); + + // Microsoft's implementation uses u32(-1) for inexistant streams. + // These streams are not used, but still participate in the file + // and must be taken into account when resolving stream indices. + const Nil = 0xFFFFFFFF; + for (stream_sizes) |*s, i| { const size = try self.directory.stream.readIntLittle(u32); - s.* = blockCountFromSize(size, superblock.BlockSize); + s.* = if (size == Nil) 0 else 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, - ); + const size = stream_sizes[i]; + if (size == 0) { + stream.* = MsfStream{ + .blocks = [_]u32{}, + }; + } else { + var blocks = try allocator.alloc(u32, size); + var j: u32 = 0; + while (j < size) : (j += 1) { + const block_id = try self.directory.stream.readIntLittle(u32); + const n = (block_id % superblock.BlockSize); + // 0 is for SuperBlock, 1 and 2 for FPMs. + if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > try file.getEndPos()) + return error.InvalidBlockIndex; + blocks[j] = block_id; + } + + stream.* = MsfStream.init( + superblock.BlockSize, + file, + blocks, + ); + } } + + const end = self.directory.pos; + if (end - begin != superblock.NumDirectoryBytes) + return error.InvalidStreamDirectory; } }; @@ -574,7 +607,6 @@ const SuperBlock = packed struct { 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 @@ -584,45 +616,41 @@ const SuperBlock = packed struct { /// 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). + // Note: microsoft-pdb code actually suggests this is a variable-length + // array. If the indices of blocks occupied by the Stream Directory didn't + // fit in one page, there would be other u32 following it. + // This would mean the Stream Directory is bigger than BlockSize / sizeof(u32) + // blocks. We're not even close to this with a 1GB pdb file, and LLVM didn't + // implement it so we're kind of safe making this assumption for now. BlockMapAddr: u32, }; const MsfStream = struct { - in_file: File, - pos: u64, - blocks: []u32, - block_size: u32, + in_file: File = undefined, + pos: u64 = undefined, + blocks: []u32 = undefined, + block_size: u32 = undefined, /// Implementation of InStream trait for Pdb.MsfStream - stream: Stream, + stream: Stream = undefined, pub const Error = @typeOf(read).ReturnType.ErrorSet; pub const Stream = io.InStream(Error); - fn init(block_size: u32, block_count: u32, pos: u64, file: File, allocator: *mem.Allocator) !MsfStream { - var stream = MsfStream{ + fn init(block_size: u32, file: File, blocks: []u32) MsfStream { + const stream = MsfStream{ .in_file = file, .pos = 0, - .blocks = try allocator.alloc(u32, block_count), + .blocks = blocks, .block_size = block_size, .stream = Stream{ .readFn = readFn }, }; - var file_stream = file.inStream(); - const in = &file_stream.stream; - try file.seekTo(pos); - - var i: u32 = 0; - while (i < block_count) : (i += 1) { - stream.blocks[i] = try in.readIntLittle(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) { @@ -633,6 +661,7 @@ const MsfStream = struct { } fn read(self: *MsfStream, buffer: []u8) !usize { + var block_id = @intCast(usize, self.pos / self.block_size); var block = self.blocks[block_id]; var offset = self.pos % self.block_size; -- cgit v1.2.3