Commit d00cc10086

Andrew Kelley <andrew@ziglang.org>
2025-08-23 03:30:21
std.debug: delete MemoryAccessor
This API is based around the unsound idea that a process can perform checked virtual memory loads to prevent crashing. This depends on OS-specific APIs that may be unavailable, disabled, or impossible due to virtualization. It also makes collecting stack traces ridiculously slow, which is a problem for users of DebugAllocator - in other words, everybody, all the time. It also makes strace go from being superbly clean to being awful.
1 parent 77c09d1
lib/std/debug/Dwarf/expression.zig
@@ -15,8 +15,6 @@ const assert = std.debug.assert;
 pub const Context = struct {
     /// The dwarf format of the section this expression is in
     format: std.dwarf.Format = .@"32",
-    /// If specified, any addresses will pass through before being accessed
-    memory_accessor: ?*std.debug.MemoryAccessor = null,
     /// The compilation unit this expression relates to, if any
     compile_unit: ?*const std.debug.Dwarf.CompileUnit = null,
     /// When evaluating a user-presented expression, this is the address of the object being evaluated
@@ -465,16 +463,6 @@ pub fn StackMachine(comptime options: Options) type {
                         else => unreachable,
                     };
 
-                    if (context.memory_accessor) |memory_accessor| {
-                        if (!switch (size) {
-                            1 => memory_accessor.load(u8, addr) != null,
-                            2 => memory_accessor.load(u16, addr) != null,
-                            4 => memory_accessor.load(u32, addr) != null,
-                            8 => memory_accessor.load(u64, addr) != null,
-                            else => return error.InvalidExpression,
-                        }) return error.InvalidExpression;
-                    }
-
                     const value: addr_type = std.math.cast(addr_type, @as(u64, switch (size) {
                         1 => @as(*const u8, @ptrFromInt(addr)).*,
                         2 => @as(*const u16, @ptrFromInt(addr)).*,
lib/std/debug/Dwarf.zig
@@ -24,7 +24,6 @@ const UT = DW.UT;
 const assert = std.debug.assert;
 const cast = std.math.cast;
 const maxInt = std.math.maxInt;
-const MemoryAccessor = std.debug.MemoryAccessor;
 const Path = std.Build.Cache.Path;
 const FixedBufferReader = std.debug.FixedBufferReader;
 const ArrayList = std.ArrayList;
@@ -349,29 +348,9 @@ pub const ExceptionFrameHeader = struct {
         };
     }
 
-    fn isValidPtr(
-        self: ExceptionFrameHeader,
-        comptime T: type,
-        ptr: usize,
-        ma: *MemoryAccessor,
-        eh_frame_len: ?usize,
-    ) bool {
-        if (eh_frame_len) |len| {
-            return ptr >= self.eh_frame_ptr and ptr <= self.eh_frame_ptr + len - @sizeOf(T);
-        } else {
-            return ma.load(T, ptr) != null;
-        }
-    }
-
-    /// Find an entry by binary searching the eh_frame_hdr section.
-    ///
-    /// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller,
-    /// MemoryAccessor will be used to verify readability of the header entries.
-    /// If `eh_frame_len` is provided, then these checks can be skipped.
     pub fn findEntry(
         self: ExceptionFrameHeader,
-        ma: *MemoryAccessor,
-        eh_frame_len: ?usize,
+        eh_frame_len: usize,
         eh_frame_hdr_ptr: usize,
         pc: usize,
         cie: *CommonInformationEntry,
@@ -421,8 +400,7 @@ pub const ExceptionFrameHeader = struct {
 
         if (fde_ptr < self.eh_frame_ptr) return bad();
 
-        // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor
-        const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)];
+        const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0..eh_frame_len];
 
         const fde_offset = fde_ptr - self.eh_frame_ptr;
         var eh_frame_fbr: FixedBufferReader = .{
@@ -431,15 +409,13 @@ pub const ExceptionFrameHeader = struct {
             .endian = native_endian,
         };
 
-        const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame);
-        if (fde_entry_header.entry_bytes.len > 0 and !self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad();
+        const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame);
         if (fde_entry_header.type != .fde) return bad();
 
         // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
         const cie_offset = fde_entry_header.type.fde;
         try eh_frame_fbr.seekTo(cie_offset);
-        const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame);
-        if (cie_entry_header.entry_bytes.len > 0 and !self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad();
+        const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame);
         if (cie_entry_header.type != .cie) return bad();
 
         cie.* = try CommonInformationEntry.parse(
@@ -486,15 +462,11 @@ pub const EntryHeader = struct {
 
     /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure.
     /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections.
-    pub fn read(
-        fbr: *FixedBufferReader,
-        opt_ma: ?*MemoryAccessor,
-        dwarf_section: Section.Id,
-    ) !EntryHeader {
+    pub fn read(fbr: *FixedBufferReader, dwarf_section: Section.Id) !EntryHeader {
         assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
 
         const length_offset = fbr.pos;
-        const unit_header = try readUnitHeader(fbr, opt_ma);
+        const unit_header = try readUnitHeader(fbr);
         const unit_length = cast(usize, unit_header.unit_length) orelse return bad();
         if (unit_length == 0) return .{
             .length_offset = length_offset,
@@ -506,10 +478,7 @@ pub const EntryHeader = struct {
         const end_offset = start_offset + unit_length;
         defer fbr.pos = end_offset;
 
-        const id = try if (opt_ma) |ma|
-            fbr.readAddressChecked(unit_header.format, ma)
-        else
-            fbr.readAddress(unit_header.format);
+        const id = try fbr.readAddress(unit_header.format);
         const entry_bytes = fbr.buf[fbr.pos..end_offset];
         const cie_id: u64 = switch (dwarf_section) {
             .eh_frame => CommonInformationEntry.eh_id,
@@ -856,7 +825,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
     while (this_unit_offset < fbr.buf.len) {
         try fbr.seekTo(this_unit_offset);
 
-        const unit_header = try readUnitHeader(&fbr, null);
+        const unit_header = try readUnitHeader(&fbr);
         if (unit_header.unit_length == 0) return;
         const next_offset = unit_header.header_length + unit_header.unit_length;
 
@@ -1045,7 +1014,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
     while (this_unit_offset < fbr.buf.len) {
         try fbr.seekTo(this_unit_offset);
 
-        const unit_header = try readUnitHeader(&fbr, null);
+        const unit_header = try readUnitHeader(&fbr);
         if (unit_header.unit_length == 0) return;
         const next_offset = unit_header.header_length + unit_header.unit_length;
 
@@ -1427,7 +1396,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
     };
     try fbr.seekTo(line_info_offset);
 
-    const unit_header = try readUnitHeader(&fbr, null);
+    const unit_header = try readUnitHeader(&fbr);
     if (unit_header.unit_length == 0) return missing();
 
     const next_offset = unit_header.header_length + unit_header.unit_length;
@@ -1815,7 +1784,7 @@ pub fn scanCieFdeInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !vo
         if (di.section(frame_section)) |section_data| {
             var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian };
             while (fbr.pos < fbr.buf.len) {
-                const entry_header = try EntryHeader.read(&fbr, null, frame_section);
+                const entry_header = try EntryHeader.read(&fbr, frame_section);
                 switch (entry_header.type) {
                     .cie => {
                         const cie = try CommonInformationEntry.parse(
@@ -1988,8 +1957,8 @@ const UnitHeader = struct {
     unit_length: u64,
 };
 
-fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!UnitHeader {
-    return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) {
+fn readUnitHeader(fbr: *FixedBufferReader) ScanError!UnitHeader {
+    return switch (try fbr.readInt(u32)) {
         0...0xfffffff0 - 1 => |unit_length| .{
             .format = .@"32",
             .header_length = 4,
@@ -1999,7 +1968,7 @@ fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*MemoryAccessor) ScanError!U
         0xffffffff => .{
             .format = .@"64",
             .header_length = 12,
-            .unit_length = try if (opt_ma) |ma| fbr.readIntChecked(u64, ma) else fbr.readInt(u64),
+            .unit_length = try fbr.readInt(u64),
         },
     };
 }
lib/std/debug/FixedBufferReader.zig
@@ -1,7 +1,6 @@
 //! Optimized for performance in debug builds.
 
 const std = @import("../std.zig");
-const MemoryAccessor = std.debug.MemoryAccessor;
 
 const FixedBufferReader = @This();
 
@@ -38,17 +37,6 @@ pub fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T {
     return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian);
 }
 
-pub fn readIntChecked(
-    fbr: *FixedBufferReader,
-    comptime T: type,
-    ma: *MemoryAccessor,
-) Error!T {
-    if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null)
-        return error.InvalidBuffer;
-
-    return fbr.readInt(T);
-}
-
 pub fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
     return std.leb.readUleb128(T, fbr);
 }
@@ -64,17 +52,6 @@ pub fn readAddress(fbr: *FixedBufferReader, format: std.dwarf.Format) Error!u64
     };
 }
 
-pub fn readAddressChecked(
-    fbr: *FixedBufferReader,
-    format: std.dwarf.Format,
-    ma: *MemoryAccessor,
-) Error!u64 {
-    return switch (format) {
-        .@"32" => try fbr.readIntChecked(u32, ma),
-        .@"64" => try fbr.readIntChecked(u64, ma),
-    };
-}
-
 pub fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 {
     if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer;
     defer fbr.pos += len;
lib/std/debug/MemoryAccessor.zig
@@ -1,141 +0,0 @@
-//! 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_min = std.heap.page_size_min;
-
-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 => {},
-    },
-};
-
-pub fn deinit(ma: *MemoryAccessor) void {
-    switch (native_os) {
-        .linux => switch (ma.mem.handle) {
-            -2, -1 => {},
-            else => ma.mem.close(),
-        },
-        else => {},
-    }
-    ma.* = undefined;
-}
-
-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, .SRCH => unreachable, // own pid is always valid
-                    .PERM => {}, // Known to happen in containers.
-                    .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 == .other or native_os == .uefi) return true;
-
-    const page_size = std.heap.pageSize();
-    const aligned_address = address & ~(page_size - 1);
-    if (aligned_address == 0) return false;
-    const aligned_memory = @as([*]align(page_size_min) 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(@ptrCast(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,
-};
lib/std/debug/SelfInfo.zig
@@ -1159,7 +1159,6 @@ pub fn unwindFrameMachO(
     allocator: Allocator,
     base_address: usize,
     context: *UnwindContext,
-    ma: *std.debug.MemoryAccessor,
     unwind_info: []const u8,
     eh_frame: ?[]const u8,
 ) !usize {
@@ -1323,9 +1322,6 @@ pub fn unwindFrameMachO(
                 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
                 const new_sp = fp + 2 * @sizeOf(usize);
 
-                // Verify the stack range we're about to read register values from
-                if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo;
-
                 const ip_ptr = fp + @sizeOf(usize);
                 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
                 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
@@ -1355,7 +1351,6 @@ pub fn unwindFrameMachO(
                         base_address +
                         entry.function_offset +
                         encoding.value.x86_64.frameless.stack.indirect.sub_offset;
-                    if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo;
 
                     // `sub_offset_addr` points to the offset of the literal within the instruction
                     const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
@@ -1397,7 +1392,6 @@ pub fn unwindFrameMachO(
                     }
 
                     var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
-                    if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo;
                     for (0..reg_count) |i| {
                         const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
                         (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
@@ -1409,7 +1403,6 @@ pub fn unwindFrameMachO(
 
                 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
                 const new_sp = ip_ptr + @sizeOf(usize);
-                if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo;
 
                 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
                 (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
@@ -1417,7 +1410,7 @@ pub fn unwindFrameMachO(
                 break :blk new_ip;
             },
             .DWARF => {
-                return unwindFrameMachODwarf(allocator, base_address, context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
+                return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
             },
         },
         .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
@@ -1426,25 +1419,16 @@ pub fn unwindFrameMachO(
                 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
                 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
                 const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
-                if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo;
                 (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
                 break :blk new_ip;
             },
             .DWARF => {
-                return unwindFrameMachODwarf(allocator, base_address, context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
+                return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
             },
             .FRAME => blk: {
                 const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
-                const new_sp = fp + 16;
                 const ip_ptr = fp + @sizeOf(usize);
 
-                const num_restored_pairs: usize =
-                    @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) +
-                    @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs)));
-                const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize);
-
-                if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo;
-
                 var reg_addr = fp - @sizeOf(usize);
                 inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
                     if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) {
@@ -1566,7 +1550,6 @@ pub fn unwindFrameDwarf(
     di: *Dwarf,
     base_address: usize,
     context: *UnwindContext,
-    ma: *std.debug.MemoryAccessor,
     explicit_fde_offset: ?usize,
 ) !usize {
     if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
@@ -1584,14 +1567,14 @@ pub fn unwindFrameDwarf(
             .endian = di.endian,
         };
 
-        const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section);
+        const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section);
         if (fde_entry_header.type != .fde) return error.MissingFDE;
 
         const cie_offset = fde_entry_header.type.fde;
         try fbr.seekTo(cie_offset);
 
         fbr.endian = native_endian;
-        const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section);
+        const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section);
         if (cie_entry_header.type != .cie) return Dwarf.bad();
 
         const cie = try Dwarf.CommonInformationEntry.parse(
@@ -1619,13 +1602,16 @@ pub fn unwindFrameDwarf(
         // back to loading `.eh_frame`/`.debug_frame` and using those from that point on.
 
         if (di.eh_frame_hdr) |header| hdr: {
-            const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
+            const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else {
+                try di.scanCieFdeInfo(allocator, base_address);
+                di.eh_frame_hdr = null;
+                break :hdr;
+            };
 
             var cie: Dwarf.CommonInformationEntry = undefined;
             var fde: Dwarf.FrameDescriptionEntry = undefined;
 
             header.findEntry(
-                ma,
                 eh_frame_len,
                 @intFromPtr(di.section(.eh_frame_hdr).?.ptr),
                 context.pc,
@@ -1669,7 +1655,6 @@ pub fn unwindFrameDwarf(
 
     var expression_context: Dwarf.expression.Context = .{
         .format = cie.format,
-        .memory_accessor = ma,
         .compile_unit = di.findCompileUnit(fde.pc_begin) catch null,
         .thread_context = context.thread_context,
         .reg_context = context.reg_context,
@@ -1704,7 +1689,6 @@ pub fn unwindFrameDwarf(
         else => return error.InvalidCFARule,
     };
 
-    if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA;
     expression_context.cfa = context.cfa;
 
     // Buffering the modifications is done because copying the thread context is not portable,
@@ -1740,12 +1724,7 @@ pub fn unwindFrameDwarf(
                 .prev = prev,
             };
 
-            try column.resolveValue(
-                context,
-                expression_context,
-                ma,
-                src,
-            );
+            try column.resolveValue(context, expression_context, src);
         }
     }
 
@@ -1833,7 +1812,6 @@ fn unwindFrameMachODwarf(
     allocator: Allocator,
     base_address: usize,
     context: *UnwindContext,
-    ma: *std.debug.MemoryAccessor,
     eh_frame: []const u8,
     fde_offset: usize,
 ) !usize {
@@ -1848,7 +1826,7 @@ fn unwindFrameMachODwarf(
         .owned = false,
     };
 
-    return unwindFrameDwarf(allocator, &di, base_address, context, ma, fde_offset);
+    return unwindFrameDwarf(allocator, &di, base_address, context, fde_offset);
 }
 
 /// This is a virtual machine that runs DWARF call frame instructions.
@@ -1898,7 +1876,6 @@ pub const VirtualMachine = struct {
             self: Column,
             context: *SelfInfo.UnwindContext,
             expression_context: std.debug.Dwarf.expression.Context,
-            ma: *std.debug.MemoryAccessor,
             out: []u8,
         ) !void {
             switch (self.rule) {
@@ -1919,7 +1896,6 @@ pub const VirtualMachine = struct {
                 .offset => |offset| {
                     if (context.cfa) |cfa| {
                         const addr = try applyOffset(cfa, offset);
-                        if (ma.load(usize, addr) == null) return error.InvalidAddress;
                         const ptr: *const usize = @ptrFromInt(addr);
                         mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
                     } else return error.InvalidCFA;
@@ -1942,7 +1918,6 @@ pub const VirtualMachine = struct {
                         break :blk v.generic;
                     } else return error.NoExpressionValue;
 
-                    if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress;
                     const ptr: *usize = @ptrFromInt(addr);
                     mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
                 },
lib/std/debug.zig
@@ -14,7 +14,6 @@ const native_os = builtin.os.tag;
 const native_endian = native_arch.endian();
 const Writer = std.io.Writer;
 
-pub const MemoryAccessor = @import("debug/MemoryAccessor.zig");
 pub const FixedBufferReader = @import("debug/FixedBufferReader.zig");
 pub const Dwarf = @import("debug/Dwarf.zig");
 pub const Pdb = @import("debug/Pdb.zig");
@@ -773,7 +772,6 @@ pub const StackIterator = struct {
     first_address: ?usize,
     // Last known value of the frame pointer register.
     fp: usize,
-    ma: MemoryAccessor = MemoryAccessor.init,
 
     // When SelfInfo and a register context is available, this iterator can unwind
     // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),
@@ -795,7 +793,7 @@ pub const StackIterator = struct {
                 ::: .{ .memory = true });
         }
 
-        return StackIterator{
+        return .{
             .first_address = first_address,
             // TODO: this is a workaround for #16876
             //.fp = fp orelse @frameAddress(),
@@ -825,7 +823,6 @@ pub const StackIterator = struct {
     }
 
     pub fn deinit(it: *StackIterator) void {
-        it.ma.deinit();
         if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit();
     }
 
@@ -896,7 +893,6 @@ pub const StackIterator = struct {
                         unwind_state.debug_info.allocator,
                         module.base_address,
                         &unwind_state.dwarf_context,
-                        &it.ma,
                         unwind_info,
                         module.eh_frame,
                     )) |return_address| {
@@ -915,7 +911,6 @@ pub const StackIterator = struct {
                 di,
                 module.base_address,
                 &unwind_state.dwarf_context,
-                &it.ma,
                 null,
             );
         } else return error.MissingDebugInfo;
@@ -951,7 +946,7 @@ pub const StackIterator = struct {
 
         // Sanity check.
         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
+        const new_fp = math.add(usize, @as(*usize, @ptrFromInt(fp)).*, fp_bias) catch
             return null;
 
         // Sanity check: the stack grows down thus all the parent frames must be
@@ -959,8 +954,7 @@ pub const StackIterator = struct {
         // 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 < 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(*usize, @ptrFromInt(math.add(usize, fp, pc_offset) catch return null)).*;
 
         it.fp = new_fp;
 
@@ -1774,7 +1768,6 @@ pub inline fn inValgrind() bool {
 
 test {
     _ = &Dwarf;
-    _ = &MemoryAccessor;
     _ = &FixedBufferReader;
     _ = &Pdb;
     _ = &SelfInfo;