diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2024-07-09 21:03:36 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-09 21:03:36 -0400 |
| commit | b3b923e51f53330403bd99a224c19bc3f01005c4 (patch) | |
| tree | 935255872a406744b2914f76d189dfc5025d9a90 /lib/std | |
| parent | c5283eb49b50c0d8b0d590b90f43523bed96e80a (diff) | |
| parent | 1b34ae19beffcaa988de636c05264b3e9357a66f (diff) | |
| download | zig-b3b923e51f53330403bd99a224c19bc3f01005c4.tar.gz zig-b3b923e51f53330403bd99a224c19bc3f01005c4.zip | |
Merge pull request #20561 from jacobly0/debug-segfaults
debug: prevent segfaults on linux
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/debug.zig | 116 | ||||
| -rw-r--r-- | lib/std/os/linux.zig | 8 |
2 files changed, 93 insertions, 31 deletions
diff --git a/lib/std/debug.zig b/lib/std/debug.zig index fb6609b8b1..5fc27a02f4 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -570,6 +570,7 @@ pub const StackIterator = struct { first_address: ?usize, // Last known value of the frame pointer register. fp: usize, + ma: MemoryAccessor = MemoryAccessor.init, // When DebugInfo and a register context is available, this iterator can unwind // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), @@ -616,16 +617,16 @@ pub const StackIterator = struct { } } - pub fn deinit(self: *StackIterator) void { - if (have_ucontext and self.unwind_state != null) self.unwind_state.?.dwarf_context.deinit(); + pub fn deinit(it: *StackIterator) void { + if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit(); } - pub fn getLastError(self: *StackIterator) ?struct { + pub fn getLastError(it: *StackIterator) ?struct { err: UnwindError, address: usize, } { if (!have_ucontext) return null; - if (self.unwind_state) |*unwind_state| { + if (it.unwind_state) |*unwind_state| { if (unwind_state.last_error) |err| { unwind_state.last_error = null; return .{ @@ -662,14 +663,14 @@ pub const StackIterator = struct { else @sizeOf(usize); - pub fn next(self: *StackIterator) ?usize { - var address = self.next_internal() orelse return null; + pub fn next(it: *StackIterator) ?usize { + var address = it.next_internal() orelse return null; - if (self.first_address) |first_address| { + if (it.first_address) |first_address| { while (address != first_address) { - address = self.next_internal() orelse return null; + address = it.next_internal() orelse return null; } - self.first_address = null; + it.first_address = null; } return address; @@ -718,8 +719,74 @@ pub const StackIterator = struct { } } - fn next_unwind(self: *StackIterator) !usize { - const unwind_state = &self.unwind_state.?; + pub const MemoryAccessor = struct { + var cached_pid: posix.pid_t = -1; + + mem: switch (native_os) { + .linux => File, + else => void, + }, + + pub const init: MemoryAccessor = .{ + .mem = switch (native_os) { + .linux => .{ .handle = -1 }, + else => {}, + }, + }; + + fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool { + switch (native_os) { + .linux => while (true) switch (ma.mem.handle) { + -2 => break, + -1 => { + const linux = std.os.linux; + const pid = switch (@atomicLoad(posix.pid_t, &cached_pid, .monotonic)) { + -1 => pid: { + const pid = linux.getpid(); + @atomicStore(posix.pid_t, &cached_pid, pid, .monotonic); + break :pid pid; + }, + else => |pid| pid, + }; + const bytes_read = linux.process_vm_readv( + pid, + &.{.{ .base = buf.ptr, .len = buf.len }}, + &.{.{ .base = @ptrFromInt(address), .len = buf.len }}, + 0, + ); + switch (linux.E.init(bytes_read)) { + .SUCCESS => return bytes_read == buf.len, + .FAULT => return false, + .INVAL, .PERM, .SRCH => unreachable, // own pid is always valid + .NOMEM, .NOSYS => {}, + else => unreachable, // unexpected + } + var path_buf: [ + std.fmt.count("/proc/{d}/mem", .{math.minInt(posix.pid_t)}) + ]u8 = undefined; + const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/mem", .{pid}) catch + unreachable; + ma.mem = std.fs.openFileAbsolute(path, .{}) catch { + ma.mem.handle = -2; + break; + }; + }, + else => return (ma.mem.pread(buf, address) catch return false) == buf.len, + }, + else => {}, + } + if (!isValidMemory(address)) return false; + @memcpy(buf, @as([*]const u8, @ptrFromInt(address))); + return true; + } + pub fn load(ma: *MemoryAccessor, comptime Type: type, address: usize) ?Type { + var result: Type = undefined; + return if (ma.read(address, std.mem.asBytes(&result))) result else null; + } + }; + + fn next_unwind(it: *StackIterator) !usize { + const unwind_state = &it.unwind_state.?; const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); switch (native_os) { .macos, .ios, .watchos, .tvos, .visionos => { @@ -741,13 +808,13 @@ pub const StackIterator = struct { } else return error.MissingDebugInfo; } - fn next_internal(self: *StackIterator) ?usize { + fn next_internal(it: *StackIterator) ?usize { if (have_ucontext) { - if (self.unwind_state) |*unwind_state| { + if (it.unwind_state) |*unwind_state| { if (!unwind_state.failed) { if (unwind_state.dwarf_context.pc == 0) return null; - defer self.fp = unwind_state.dwarf_context.getFp() catch 0; - if (self.next_unwind()) |return_address| { + defer it.fp = unwind_state.dwarf_context.getFp() catch 0; + if (it.next_unwind()) |return_address| { return return_address; } else |err| { unwind_state.last_error = err; @@ -763,29 +830,24 @@ pub const StackIterator = struct { const fp = if (comptime native_arch.isSPARC()) // On SPARC the offset is positive. (!) - math.add(usize, self.fp, fp_offset) catch return null + math.add(usize, it.fp, fp_offset) catch return null else - math.sub(usize, self.fp, fp_offset) catch return null; + math.sub(usize, it.fp, fp_offset) catch return null; // Sanity check. - if (fp == 0 or !mem.isAligned(fp, @alignOf(usize)) or !isValidMemory(fp)) + if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) return null; + const new_fp = math.add(usize, it.ma.load(usize, fp) orelse return null, fp_bias) catch return null; - const new_fp = math.add(usize, @as(*const usize, @ptrFromInt(fp)).*, fp_bias) catch return null; - // Sanity check: the stack grows down thus all the parent frames must be // be at addresses that are greater (or equal) than the previous one. // A zero frame pointer often signals this is the last frame, that case // is gracefully handled by the next call to next_internal. - if (new_fp != 0 and new_fp < self.fp) + if (new_fp != 0 and new_fp < it.fp) return null; + const new_pc = it.ma.load(usize, math.add(usize, fp, pc_offset) catch return null) orelse return null; - const new_pc = @as( - *const usize, - @ptrFromInt(math.add(usize, fp, pc_offset) catch return null), - ).*; - - self.fp = new_fp; + it.fp = new_fp; return new_pc; } diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index a69c419453..8ef74dec7b 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1519,15 +1519,15 @@ pub fn setgroups(size: usize, list: [*]const gid_t) usize { } pub fn setsid() pid_t { - return @as(pid_t, @bitCast(@as(u32, @truncate(syscall0(.setsid))))); + return @bitCast(@as(u32, @truncate(syscall0(.setsid)))); } pub fn getpid() pid_t { - return @as(pid_t, @bitCast(@as(u32, @truncate(syscall0(.getpid))))); + return @bitCast(@as(u32, @truncate(syscall0(.getpid)))); } pub fn gettid() pid_t { - return @as(pid_t, @bitCast(@as(u32, @truncate(syscall0(.gettid))))); + return @bitCast(@as(u32, @truncate(syscall0(.gettid)))); } pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*sigset_t) usize { @@ -2116,7 +2116,7 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u ); } -pub fn process_vm_readv(pid: pid_t, local: []iovec, remote: []const iovec_const, flags: usize) usize { +pub fn process_vm_readv(pid: pid_t, local: []const iovec, remote: []const iovec_const, flags: usize) usize { return syscall6( .process_vm_readv, @as(usize, @bitCast(@as(isize, pid))), |
