aboutsummaryrefslogtreecommitdiff
path: root/lib/std/debug/MemoryAccessor.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2024-08-02 17:10:41 -0700
committerGitHub <noreply@github.com>2024-08-02 17:10:41 -0700
commita931bfada5e358ace980b2f8fbc50ce424ced526 (patch)
tree5aabd9fb3833765926ee5409c1ce14e04d2d9fd0 /lib/std/debug/MemoryAccessor.zig
parent9e2668cd2ecc587390335e1c9f6e1592a7bd6eb6 (diff)
parent6d606cc38b4df2b20af9d77367f8ab22bbbea092 (diff)
downloadzig-a931bfada5e358ace980b2f8fbc50ce424ced526.tar.gz
zig-a931bfada5e358ace980b2f8fbc50ce424ced526.zip
Merge pull request #20908 from ziglang/reorg-std.debug-again
std.debug: reorg and clarify API goals
Diffstat (limited to 'lib/std/debug/MemoryAccessor.zig')
-rw-r--r--lib/std/debug/MemoryAccessor.zig128
1 files changed, 128 insertions, 0 deletions
diff --git a/lib/std/debug/MemoryAccessor.zig b/lib/std/debug/MemoryAccessor.zig
new file mode 100644
index 0000000000..bfdda609f6
--- /dev/null
+++ b/lib/std/debug/MemoryAccessor.zig
@@ -0,0 +1,128 @@
+//! Reads memory from any address of the current location using OS-specific
+//! syscalls, bypassing memory page protection. Useful for stack unwinding.
+
+const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+
+const std = @import("../std.zig");
+const posix = std.posix;
+const File = std.fs.File;
+const page_size = std.mem.page_size;
+
+const MemoryAccessor = @This();
+
+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 => {}, // QEMU is known not to implement this syscall.
+ else => unreachable, // unexpected
+ }
+ var path_buf: [
+ std.fmt.count("/proc/{d}/mem", .{std.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;
+}
+
+pub fn isValidMemory(address: usize) bool {
+ // We are unable to determine validity of memory for freestanding targets
+ if (native_os == .freestanding or native_os == .uefi) return true;
+
+ const aligned_address = address & ~@as(usize, @intCast((page_size - 1)));
+ if (aligned_address == 0) return false;
+ const aligned_memory = @as([*]align(page_size) u8, @ptrFromInt(aligned_address))[0..page_size];
+
+ if (native_os == .windows) {
+ const windows = std.os.windows;
+
+ var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined;
+
+ // The only error this function can throw is ERROR_INVALID_PARAMETER.
+ // supply an address that invalid i'll be thrown.
+ const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch {
+ return false;
+ };
+
+ // Result code has to be bigger than zero (number of bytes written)
+ if (rc == 0) {
+ return false;
+ }
+
+ // Free pages cannot be read, they are unmapped
+ if (memory_info.State == windows.MEM_FREE) {
+ return false;
+ }
+
+ return true;
+ } else if (have_msync) {
+ posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| {
+ switch (err) {
+ error.UnmappedMemory => return false,
+ else => unreachable,
+ }
+ };
+
+ return true;
+ } else {
+ // We are unable to determine validity of memory on this target.
+ return true;
+ }
+}
+
+const have_msync = switch (native_os) {
+ .wasi, .emscripten, .windows => false,
+ else => true,
+};