Commit 12686d9b7d

Andrew Kelley <andrew@ziglang.org>
2025-08-23 05:36:30
delete std.debug.FixedBufferReader
now that std.Io.Reader has sufficient debug performance
1 parent afea419
lib/std/debug/Dwarf.zig
@@ -25,8 +25,9 @@ const assert = std.debug.assert;
 const cast = std.math.cast;
 const maxInt = std.math.maxInt;
 const Path = std.Build.Cache.Path;
-const FixedBufferReader = std.debug.FixedBufferReader;
 const ArrayList = std.ArrayList;
+const Endian = std.builtin.Endian;
+const Reader = std.Io.Reader;
 
 const Dwarf = @This();
 
@@ -37,7 +38,7 @@ pub const call_frame = @import("Dwarf/call_frame.zig");
 /// Useful to temporarily enable while working on this file.
 const debug_debug_mode = false;
 
-endian: std.builtin.Endian,
+endian: Endian,
 sections: SectionArray = null_section_array,
 is_macho: bool,
 
@@ -355,23 +356,23 @@ pub const ExceptionFrameHeader = struct {
         pc: usize,
         cie: *CommonInformationEntry,
         fde: *FrameDescriptionEntry,
+        endian: Endian,
     ) !void {
         const entry_size = try entrySize(self.table_enc);
 
         var left: usize = 0;
         var len: usize = self.fde_count;
-
-        var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian };
+        var fbr: Reader = .fixed(self.entries);
 
         while (len > 1) {
             const mid = left + len / 2;
 
-            fbr.pos = mid * entry_size;
+            fbr.seek = mid * entry_size;
             const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
-                .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]),
+                .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
                 .follow_indirect = true,
                 .data_rel_base = eh_frame_hdr_ptr,
-            }) orelse return bad();
+            }, endian) orelse return bad();
 
             if (pc < pc_begin) {
                 len /= 2;
@@ -383,39 +384,36 @@ pub const ExceptionFrameHeader = struct {
         }
 
         if (len == 0) return missing();
-        fbr.pos = left * entry_size;
+        fbr.seek = left * entry_size;
 
         // Read past the pc_begin field of the entry
         _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
-            .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]),
+            .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
             .follow_indirect = true,
             .data_rel_base = eh_frame_hdr_ptr,
-        }) orelse return bad();
+        }, endian) orelse return bad();
 
         const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
-            .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]),
+            .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
             .follow_indirect = true,
             .data_rel_base = eh_frame_hdr_ptr,
-        }) orelse return bad()) orelse return bad();
+        }, endian) orelse return bad()) orelse return bad();
 
         if (fde_ptr < self.eh_frame_ptr) return bad();
 
         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 = .{
-            .buf = eh_frame,
-            .pos = fde_offset,
-            .endian = native_endian,
-        };
+        var eh_frame_fbr: Reader = .fixed(eh_frame);
+        eh_frame_fbr.seek = fde_offset;
 
-        const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame);
+        const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
         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, .eh_frame);
+        eh_frame_fbr.seek = @intCast(cie_offset);
+        const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
         if (cie_entry_header.type != .cie) return bad();
 
         cie.* = try CommonInformationEntry.parse(
@@ -426,7 +424,7 @@ pub const ExceptionFrameHeader = struct {
             .eh_frame,
             cie_entry_header.length_offset,
             @sizeOf(usize),
-            native_endian,
+            endian,
         );
 
         fde.* = try FrameDescriptionEntry.parse(
@@ -435,7 +433,7 @@ pub const ExceptionFrameHeader = struct {
             true,
             cie.*,
             @sizeOf(usize),
-            native_endian,
+            endian,
         );
 
         if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return missing();
@@ -460,13 +458,18 @@ pub const EntryHeader = struct {
         return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4);
     }
 
-    /// 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, dwarf_section: Section.Id) !EntryHeader {
+    /// Reads a header for either an FDE or a CIE, then advances the fbr to the
+    /// position after the trailing structure.
+    ///
+    /// `fbr` must be backed by either the .eh_frame or .debug_frame sections.
+    ///
+    /// TODO that's a bad API, don't do that. this function should neither require
+    /// a fixed reader nor depend on seeking.
+    pub fn read(fbr: *Reader, dwarf_section: Section.Id, endian: Endian) !EntryHeader {
         assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
 
-        const length_offset = fbr.pos;
-        const unit_header = try readUnitHeader(fbr);
+        const length_offset = fbr.seek;
+        const unit_header = try readUnitHeader(fbr, endian);
         const unit_length = cast(usize, unit_header.unit_length) orelse return bad();
         if (unit_length == 0) return .{
             .length_offset = length_offset,
@@ -474,12 +477,12 @@ pub const EntryHeader = struct {
             .type = .terminator,
             .entry_bytes = &.{},
         };
-        const start_offset = fbr.pos;
+        const start_offset = fbr.seek;
         const end_offset = start_offset + unit_length;
-        defer fbr.pos = end_offset;
+        defer fbr.seek = end_offset;
 
-        const id = try fbr.readAddress(unit_header.format);
-        const entry_bytes = fbr.buf[fbr.pos..end_offset];
+        const id = try readAddress(fbr, unit_header.format, endian);
+        const entry_bytes = fbr.buffer[fbr.seek..end_offset];
         const cie_id: u64 = switch (dwarf_section) {
             .eh_frame => CommonInformationEntry.eh_id,
             .debug_frame => switch (unit_header.format) {
@@ -565,13 +568,13 @@ pub const CommonInformationEntry = struct {
         dwarf_section: Section.Id,
         length_offset: u64,
         addr_size_bytes: u8,
-        endian: std.builtin.Endian,
+        endian: Endian,
     ) !CommonInformationEntry {
         if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
 
-        var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian };
+        var fbr: Reader = .fixed(cie_bytes);
 
-        const version = try fbr.readByte();
+        const version = try fbr.takeByte();
         switch (dwarf_section) {
             .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,
             .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,
@@ -582,9 +585,9 @@ pub const CommonInformationEntry = struct {
         var has_aug_data = false;
 
         var aug_str_len: usize = 0;
-        const aug_str_start = fbr.pos;
-        var aug_byte = try fbr.readByte();
-        while (aug_byte != 0) : (aug_byte = try fbr.readByte()) {
+        const aug_str_start = fbr.seek;
+        var aug_byte = try fbr.takeByte();
+        while (aug_byte != 0) : (aug_byte = try fbr.takeByte()) {
             switch (aug_byte) {
                 'z' => {
                     if (aug_str_len != 0) return bad();
@@ -592,7 +595,7 @@ pub const CommonInformationEntry = struct {
                 },
                 'e' => {
                     if (has_aug_data or aug_str_len != 0) return bad();
-                    if (try fbr.readByte() != 'h') return bad();
+                    if (try fbr.takeByte() != 'h') return bad();
                     has_eh_data = true;
                 },
                 else => if (has_eh_data) return bad(),
@@ -603,15 +606,15 @@ pub const CommonInformationEntry = struct {
 
         if (has_eh_data) {
             // legacy data created by older versions of gcc - unsupported here
-            for (0..addr_size_bytes) |_| _ = try fbr.readByte();
+            for (0..addr_size_bytes) |_| _ = try fbr.takeByte();
         }
 
-        const address_size = if (version == 4) try fbr.readByte() else addr_size_bytes;
-        const segment_selector_size = if (version == 4) try fbr.readByte() else null;
+        const address_size = if (version == 4) try fbr.takeByte() else addr_size_bytes;
+        const segment_selector_size = if (version == 4) try fbr.takeByte() else null;
 
-        const code_alignment_factor = try fbr.readUleb128(u32);
-        const data_alignment_factor = try fbr.readIleb128(i32);
-        const return_address_register = if (version == 1) try fbr.readByte() else try fbr.readUleb128(u8);
+        const code_alignment_factor = try fbr.takeLeb128(u32);
+        const data_alignment_factor = try fbr.takeLeb128(i32);
+        const return_address_register = if (version == 1) try fbr.takeByte() else try fbr.takeLeb128(u8);
 
         var lsda_pointer_enc: u8 = EH.PE.omit;
         var personality_enc: ?u8 = null;
@@ -620,25 +623,25 @@ pub const CommonInformationEntry = struct {
 
         var aug_data: []const u8 = &[_]u8{};
         const aug_str = if (has_aug_data) blk: {
-            const aug_data_len = try fbr.readUleb128(usize);
-            const aug_data_start = fbr.pos;
+            const aug_data_len = try fbr.takeLeb128(usize);
+            const aug_data_start = fbr.seek;
             aug_data = cie_bytes[aug_data_start..][0..aug_data_len];
 
             const aug_str = cie_bytes[aug_str_start..][0..aug_str_len];
             for (aug_str[1..]) |byte| {
                 switch (byte) {
                     'L' => {
-                        lsda_pointer_enc = try fbr.readByte();
+                        lsda_pointer_enc = try fbr.takeByte();
                     },
                     'P' => {
-                        personality_enc = try fbr.readByte();
+                        personality_enc = try fbr.takeByte();
                         personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{
-                            .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.pos]), pc_rel_offset),
+                            .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.seek]), pc_rel_offset),
                             .follow_indirect = is_runtime,
-                        });
+                        }, endian);
                     },
                     'R' => {
-                        fde_pointer_enc = try fbr.readByte();
+                        fde_pointer_enc = try fbr.takeByte();
                     },
                     'S', 'B', 'G' => {},
                     else => return bad(),
@@ -646,11 +649,11 @@ pub const CommonInformationEntry = struct {
             }
 
             // aug_data_len can include padding so the CIE ends on an address boundary
-            fbr.pos = aug_data_start + aug_data_len;
+            fbr.seek = aug_data_start + aug_data_len;
             break :blk aug_str;
         } else &[_]u8{};
 
-        const initial_instructions = cie_bytes[fbr.pos..];
+        const initial_instructions = cie_bytes[fbr.seek..];
         return .{
             .length_offset = length_offset,
             .version = version,
@@ -699,41 +702,41 @@ pub const FrameDescriptionEntry = struct {
         is_runtime: bool,
         cie: CommonInformationEntry,
         addr_size_bytes: u8,
-        endian: std.builtin.Endian,
+        endian: Endian,
     ) !FrameDescriptionEntry {
         if (addr_size_bytes > 8) return error.InvalidAddrSize;
 
-        var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian };
+        var fbr: Reader = .fixed(fde_bytes);
 
         const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
-            .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset),
+            .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
             .follow_indirect = is_runtime,
-        }) orelse return bad();
+        }, endian) orelse return bad();
 
         const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
             .pc_rel_base = 0,
             .follow_indirect = false,
-        }) orelse return bad();
+        }, endian) orelse return bad();
 
         var aug_data: []const u8 = &[_]u8{};
         const lsda_pointer = if (cie.aug_str.len > 0) blk: {
-            const aug_data_len = try fbr.readUleb128(usize);
-            const aug_data_start = fbr.pos;
+            const aug_data_len = try fbr.takeLeb128(usize);
+            const aug_data_start = fbr.seek;
             aug_data = fde_bytes[aug_data_start..][0..aug_data_len];
 
             const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit)
                 try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{
-                    .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset),
+                    .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
                     .follow_indirect = is_runtime,
-                })
+                }, endian)
             else
                 null;
 
-            fbr.pos = aug_data_start + aug_data_len;
+            fbr.seek = aug_data_start + aug_data_len;
             break :blk lsda_pointer;
         } else null;
 
-        const instructions = fde_bytes[fbr.pos..];
+        const instructions = fde_bytes[fbr.seek..];
         return .{
             .cie_length_offset = cie.length_offset,
             .pc_begin = pc_begin,
@@ -816,32 +819,37 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 {
 pub const ScanError = error{
     InvalidDebugInfo,
     MissingDebugInfo,
-} || Allocator.Error || std.debug.FixedBufferReader.Error;
+    ReadFailed,
+    EndOfStream,
+    Overflow,
+    StreamTooLong,
+} || Allocator.Error;
 
 fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
-    var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
+    const endian = di.endian;
+    var fbr: Reader = .fixed(di.section(.debug_info).?);
     var this_unit_offset: u64 = 0;
 
-    while (this_unit_offset < fbr.buf.len) {
-        try fbr.seekTo(this_unit_offset);
+    while (this_unit_offset < fbr.buffer.len) {
+        fbr.seek = @intCast(this_unit_offset);
 
-        const unit_header = try readUnitHeader(&fbr);
+        const unit_header = try readUnitHeader(&fbr, endian);
         if (unit_header.unit_length == 0) return;
         const next_offset = unit_header.header_length + unit_header.unit_length;
 
-        const version = try fbr.readInt(u16);
+        const version = try fbr.takeInt(u16, endian);
         if (version < 2 or version > 5) return bad();
 
         var address_size: u8 = undefined;
         var debug_abbrev_offset: u64 = undefined;
         if (version >= 5) {
-            const unit_type = try fbr.readInt(u8);
+            const unit_type = try fbr.takeByte();
             if (unit_type != DW.UT.compile) return bad();
-            address_size = try fbr.readByte();
-            debug_abbrev_offset = try fbr.readAddress(unit_header.format);
+            address_size = try fbr.takeByte();
+            debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
         } else {
-            debug_abbrev_offset = try fbr.readAddress(unit_header.format);
-            address_size = try fbr.readByte();
+            debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
+            address_size = try fbr.takeByte();
         }
         if (address_size != @sizeOf(usize)) return bad();
 
@@ -882,15 +890,16 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
         };
 
         while (true) {
-            fbr.pos = std.mem.indexOfNonePos(u8, fbr.buf, fbr.pos, &.{
+            fbr.seek = std.mem.indexOfNonePos(u8, fbr.buffer, fbr.seek, &.{
                 zig_padding_abbrev_code, 0,
-            }) orelse fbr.buf.len;
-            if (fbr.pos >= next_unit_pos) break;
+            }) orelse fbr.buffer.len;
+            if (fbr.seek >= next_unit_pos) break;
             var die_obj = (try parseDie(
                 &fbr,
                 attrs_bufs[0],
                 abbrev_table,
                 unit_header.format,
+                endian,
             )) orelse continue;
 
             switch (die_obj.tag_id) {
@@ -913,30 +922,32 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
                             if (this_die_obj.getAttr(AT.name)) |_| {
                                 break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit);
                             } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
-                                const after_die_offset = fbr.pos;
-                                defer fbr.pos = after_die_offset;
+                                const after_die_offset = fbr.seek;
+                                defer fbr.seek = after_die_offset;
 
                                 // Follow the DIE it points to and repeat
                                 const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin, this_unit_offset, next_offset);
-                                try fbr.seekTo(ref_offset);
+                                fbr.seek = @intCast(ref_offset);
                                 this_die_obj = (try parseDie(
                                     &fbr,
                                     attrs_bufs[2],
                                     abbrev_table, // wrong abbrev table for different cu
                                     unit_header.format,
+                                    endian,
                                 )) orelse return bad();
                             } else if (this_die_obj.getAttr(AT.specification)) |_| {
-                                const after_die_offset = fbr.pos;
-                                defer fbr.pos = after_die_offset;
+                                const after_die_offset = fbr.seek;
+                                defer fbr.seek = after_die_offset;
 
                                 // Follow the DIE it points to and repeat
                                 const ref_offset = try this_die_obj.getAttrRef(AT.specification, this_unit_offset, next_offset);
-                                try fbr.seekTo(ref_offset);
+                                fbr.seek = @intCast(ref_offset);
                                 this_die_obj = (try parseDie(
                                     &fbr,
                                     attrs_bufs[2],
                                     abbrev_table, // wrong abbrev table for different cu
                                     unit_header.format,
+                                    endian,
                                 )) orelse return bad();
                             } else {
                                 break :x null;
@@ -1005,32 +1016,33 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
 }
 
 fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
-    var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian };
+    const endian = di.endian;
+    var fbr: Reader = .fixed(di.section(.debug_info).?);
     var this_unit_offset: u64 = 0;
 
     var attrs_buf = std.array_list.Managed(Die.Attr).init(allocator);
     defer attrs_buf.deinit();
 
-    while (this_unit_offset < fbr.buf.len) {
-        try fbr.seekTo(this_unit_offset);
+    while (this_unit_offset < fbr.buffer.len) {
+        fbr.seek = @intCast(this_unit_offset);
 
-        const unit_header = try readUnitHeader(&fbr);
+        const unit_header = try readUnitHeader(&fbr, endian);
         if (unit_header.unit_length == 0) return;
         const next_offset = unit_header.header_length + unit_header.unit_length;
 
-        const version = try fbr.readInt(u16);
+        const version = try fbr.takeInt(u16, endian);
         if (version < 2 or version > 5) return bad();
 
         var address_size: u8 = undefined;
         var debug_abbrev_offset: u64 = undefined;
         if (version >= 5) {
-            const unit_type = try fbr.readInt(u8);
+            const unit_type = try fbr.takeByte();
             if (unit_type != UT.compile) return bad();
-            address_size = try fbr.readByte();
-            debug_abbrev_offset = try fbr.readAddress(unit_header.format);
+            address_size = try fbr.takeByte();
+            debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
         } else {
-            debug_abbrev_offset = try fbr.readAddress(unit_header.format);
-            address_size = try fbr.readByte();
+            debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian);
+            address_size = try fbr.takeByte();
         }
         if (address_size != @sizeOf(usize)) return bad();
 
@@ -1047,6 +1059,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
             attrs_buf.items,
             abbrev_table,
             unit_header.format,
+            endian,
         )) orelse return bad();
 
         if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad();
@@ -1132,7 +1145,7 @@ const DebugRangeIterator = struct {
     section_type: Section.Id,
     di: *const Dwarf,
     compile_unit: *const CompileUnit,
-    fbr: FixedBufferReader,
+    fbr: Reader,
 
     pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() {
         const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges;
@@ -1168,36 +1181,36 @@ const DebugRangeIterator = struct {
             else => return err,
         };
 
+        var fbr: Reader = .fixed(debug_ranges);
+        fbr.seek = cast(usize, ranges_offset) orelse return bad();
+
         return .{
             .base_address = base_address,
             .section_type = section_type,
             .di = di,
             .compile_unit = compile_unit,
-            .fbr = .{
-                .buf = debug_ranges,
-                .pos = cast(usize, ranges_offset) orelse return bad(),
-                .endian = di.endian,
-            },
+            .fbr = fbr,
         };
     }
 
     // Returns the next range in the list, or null if the end was reached.
     pub fn next(self: *@This()) !?PcRange {
+        const endian = self.di.endian;
         switch (self.section_type) {
             .debug_rnglists => {
-                const kind = try self.fbr.readByte();
+                const kind = try self.fbr.takeByte();
                 switch (kind) {
                     RLE.end_of_list => return null,
                     RLE.base_addressx => {
-                        const index = try self.fbr.readUleb128(usize);
+                        const index = try self.fbr.takeLeb128(usize);
                         self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index);
                         return try self.next();
                     },
                     RLE.startx_endx => {
-                        const start_index = try self.fbr.readUleb128(usize);
+                        const start_index = try self.fbr.takeLeb128(usize);
                         const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
 
-                        const end_index = try self.fbr.readUleb128(usize);
+                        const end_index = try self.fbr.takeLeb128(usize);
                         const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index);
 
                         return .{
@@ -1206,10 +1219,10 @@ const DebugRangeIterator = struct {
                         };
                     },
                     RLE.startx_length => {
-                        const start_index = try self.fbr.readUleb128(usize);
+                        const start_index = try self.fbr.takeLeb128(usize);
                         const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
 
-                        const len = try self.fbr.readUleb128(usize);
+                        const len = try self.fbr.takeLeb128(usize);
                         const end_addr = start_addr + len;
 
                         return .{
@@ -1218,8 +1231,8 @@ const DebugRangeIterator = struct {
                         };
                     },
                     RLE.offset_pair => {
-                        const start_addr = try self.fbr.readUleb128(usize);
-                        const end_addr = try self.fbr.readUleb128(usize);
+                        const start_addr = try self.fbr.takeLeb128(usize);
+                        const end_addr = try self.fbr.takeLeb128(usize);
 
                         // This is the only kind that uses the base address
                         return .{
@@ -1228,12 +1241,12 @@ const DebugRangeIterator = struct {
                         };
                     },
                     RLE.base_address => {
-                        self.base_address = try self.fbr.readInt(usize);
+                        self.base_address = try self.fbr.takeInt(usize, endian);
                         return try self.next();
                     },
                     RLE.start_end => {
-                        const start_addr = try self.fbr.readInt(usize);
-                        const end_addr = try self.fbr.readInt(usize);
+                        const start_addr = try self.fbr.takeInt(usize, endian);
+                        const end_addr = try self.fbr.takeInt(usize, endian);
 
                         return .{
                             .start = start_addr,
@@ -1241,8 +1254,8 @@ const DebugRangeIterator = struct {
                         };
                     },
                     RLE.start_length => {
-                        const start_addr = try self.fbr.readInt(usize);
-                        const len = try self.fbr.readUleb128(usize);
+                        const start_addr = try self.fbr.takeInt(usize, endian);
+                        const len = try self.fbr.takeLeb128(usize);
                         const end_addr = start_addr + len;
 
                         return .{
@@ -1254,8 +1267,8 @@ const DebugRangeIterator = struct {
                 }
             },
             .debug_ranges => {
-                const start_addr = try self.fbr.readInt(usize);
-                const end_addr = try self.fbr.readInt(usize);
+                const start_addr = try self.fbr.takeInt(usize, endian);
+                const end_addr = try self.fbr.takeInt(usize, endian);
                 if (start_addr == 0 and end_addr == 0) return null;
 
                 // This entry selects a new value for the base address
@@ -1307,11 +1320,8 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const
 }
 
 fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table {
-    var fbr: FixedBufferReader = .{
-        .buf = di.section(.debug_abbrev).?,
-        .pos = cast(usize, offset) orelse return bad(),
-        .endian = di.endian,
-    };
+    var fbr: Reader = .fixed(di.section(.debug_abbrev).?);
+    fbr.seek = cast(usize, offset) orelse return bad();
 
     var abbrevs = std.array_list.Managed(Abbrev).init(allocator);
     defer {
@@ -1325,20 +1335,20 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table
     defer attrs.deinit();
 
     while (true) {
-        const code = try fbr.readUleb128(u64);
+        const code = try fbr.takeLeb128(u64);
         if (code == 0) break;
-        const tag_id = try fbr.readUleb128(u64);
-        const has_children = (try fbr.readByte()) == DW.CHILDREN.yes;
+        const tag_id = try fbr.takeLeb128(u64);
+        const has_children = (try fbr.takeByte()) == DW.CHILDREN.yes;
 
         while (true) {
-            const attr_id = try fbr.readUleb128(u64);
-            const form_id = try fbr.readUleb128(u64);
+            const attr_id = try fbr.takeLeb128(u64);
+            const form_id = try fbr.takeLeb128(u64);
             if (attr_id == 0 and form_id == 0) break;
             try attrs.append(.{
                 .id = attr_id,
                 .form_id = form_id,
                 .payload = switch (form_id) {
-                    FORM.implicit_const => try fbr.readIleb128(i64),
+                    FORM.implicit_const => try fbr.takeLeb128(i64),
                     else => undefined,
                 },
             });
@@ -1359,24 +1369,20 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table
 }
 
 fn parseDie(
-    fbr: *FixedBufferReader,
+    fbr: *Reader,
     attrs_buf: []Die.Attr,
     abbrev_table: *const Abbrev.Table,
     format: Format,
+    endian: Endian,
 ) ScanError!?Die {
-    const abbrev_code = try fbr.readUleb128(u64);
+    const abbrev_code = try fbr.takeLeb128(u64);
     if (abbrev_code == 0) return null;
     const table_entry = abbrev_table.get(abbrev_code) orelse return bad();
 
     const attrs = attrs_buf[0..table_entry.attrs.len];
-    for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{
+    for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{
         .id = attr.id,
-        .value = try parseFormValue(
-            fbr,
-            attr.form_id,
-            format,
-            attr.payload,
-        ),
+        .value = try parseFormValue(fbr, attr.form_id, format, endian, attr.payload),
     };
     return .{
         .tag_id = table_entry.tag_id,
@@ -1387,26 +1393,24 @@ fn parseDie(
 
 /// Ensures that addresses in the returned LineTable are monotonically increasing.
 fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache {
+    const endian = d.endian;
     const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*);
     const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
 
-    var fbr: FixedBufferReader = .{
-        .buf = d.section(.debug_line).?,
-        .endian = d.endian,
-    };
-    try fbr.seekTo(line_info_offset);
+    var fbr: Reader = .fixed(d.section(.debug_line).?);
+    fbr.seek = @intCast(line_info_offset);
 
-    const unit_header = try readUnitHeader(&fbr);
+    const unit_header = try readUnitHeader(&fbr, endian);
     if (unit_header.unit_length == 0) return missing();
 
     const next_offset = unit_header.header_length + unit_header.unit_length;
 
-    const version = try fbr.readInt(u16);
+    const version = try fbr.takeInt(u16, endian);
     if (version < 2) return bad();
 
     const addr_size: u8, const seg_size: u8 = if (version >= 5) .{
-        try fbr.readByte(),
-        try fbr.readByte(),
+        try fbr.takeByte(),
+        try fbr.takeByte(),
     } else .{
         switch (unit_header.format) {
             .@"32" => 4,
@@ -1417,26 +1421,26 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
     _ = addr_size;
     _ = seg_size;
 
-    const prologue_length = try fbr.readAddress(unit_header.format);
-    const prog_start_offset = fbr.pos + prologue_length;
+    const prologue_length = try readAddress(&fbr, unit_header.format, endian);
+    const prog_start_offset = fbr.seek + prologue_length;
 
-    const minimum_instruction_length = try fbr.readByte();
+    const minimum_instruction_length = try fbr.takeByte();
     if (minimum_instruction_length == 0) return bad();
 
     if (version >= 4) {
-        const maximum_operations_per_instruction = try fbr.readByte();
+        const maximum_operations_per_instruction = try fbr.takeByte();
         _ = maximum_operations_per_instruction;
     }
 
-    const default_is_stmt = (try fbr.readByte()) != 0;
-    const line_base = try fbr.readByteSigned();
+    const default_is_stmt = (try fbr.takeByte()) != 0;
+    const line_base = try fbr.takeByteSigned();
 
-    const line_range = try fbr.readByte();
+    const line_range = try fbr.takeByte();
     if (line_range == 0) return bad();
 
-    const opcode_base = try fbr.readByte();
+    const opcode_base = try fbr.takeByte();
 
-    const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1);
+    const standard_opcode_lengths = try fbr.take(opcode_base - 1);
 
     var directories: ArrayList(FileEntry) = .empty;
     defer directories.deinit(gpa);
@@ -1447,17 +1451,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
         try directories.append(gpa, .{ .path = compile_unit_cwd });
 
         while (true) {
-            const dir = try fbr.readBytesTo(0);
+            const dir = try fbr.takeSentinel(0);
             if (dir.len == 0) break;
             try directories.append(gpa, .{ .path = dir });
         }
 
         while (true) {
-            const file_name = try fbr.readBytesTo(0);
+            const file_name = try fbr.takeSentinel(0);
             if (file_name.len == 0) break;
-            const dir_index = try fbr.readUleb128(u32);
-            const mtime = try fbr.readUleb128(u64);
-            const size = try fbr.readUleb128(u64);
+            const dir_index = try fbr.takeLeb128(u32);
+            const mtime = try fbr.takeLeb128(u64);
+            const size = try fbr.takeLeb128(u64);
             try file_entries.append(gpa, .{
                 .path = file_name,
                 .dir_index = dir_index,
@@ -1472,26 +1476,21 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
         };
         {
             var dir_ent_fmt_buf: [10]FileEntFmt = undefined;
-            const directory_entry_format_count = try fbr.readByte();
+            const directory_entry_format_count = try fbr.takeByte();
             if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();
             for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {
                 ent_fmt.* = .{
-                    .content_type_code = try fbr.readUleb128(u8),
-                    .form_code = try fbr.readUleb128(u16),
+                    .content_type_code = try fbr.takeLeb128(u8),
+                    .form_code = try fbr.takeLeb128(u16),
                 };
             }
 
-            const directories_count = try fbr.readUleb128(usize);
+            const directories_count = try fbr.takeLeb128(usize);
 
             for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {
                 e.* = .{ .path = &.{} };
                 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| {
-                    const form_value = try parseFormValue(
-                        &fbr,
-                        ent_fmt.form_code,
-                        unit_header.format,
-                        null,
-                    );
+                    const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null);
                     switch (ent_fmt.content_type_code) {
                         DW.LNCT.path => e.path = try form_value.getString(d.*),
                         DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
@@ -1508,27 +1507,22 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
         }
 
         var file_ent_fmt_buf: [10]FileEntFmt = undefined;
-        const file_name_entry_format_count = try fbr.readByte();
+        const file_name_entry_format_count = try fbr.takeByte();
         if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
         for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
             ent_fmt.* = .{
-                .content_type_code = try fbr.readUleb128(u16),
-                .form_code = try fbr.readUleb128(u16),
+                .content_type_code = try fbr.takeLeb128(u16),
+                .form_code = try fbr.takeLeb128(u16),
             };
         }
 
-        const file_names_count = try fbr.readUleb128(usize);
+        const file_names_count = try fbr.takeLeb128(usize);
         try file_entries.ensureUnusedCapacity(gpa, file_names_count);
 
         for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {
             e.* = .{ .path = &.{} };
             for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| {
-                const form_value = try parseFormValue(
-                    &fbr,
-                    ent_fmt.form_code,
-                    unit_header.format,
-                    null,
-                );
+                const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null);
                 switch (ent_fmt.content_type_code) {
                     DW.LNCT.path => e.path = try form_value.getString(d.*),
                     DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
@@ -1548,17 +1542,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
     var line_table: CompileUnit.SrcLocCache.LineTable = .{};
     errdefer line_table.deinit(gpa);
 
-    try fbr.seekTo(prog_start_offset);
+    fbr.seek = @intCast(prog_start_offset);
 
     const next_unit_pos = line_info_offset + next_offset;
 
-    while (fbr.pos < next_unit_pos) {
-        const opcode = try fbr.readByte();
+    while (fbr.seek < next_unit_pos) {
+        const opcode = try fbr.takeByte();
 
         if (opcode == DW.LNS.extended_op) {
-            const op_size = try fbr.readUleb128(u64);
+            const op_size = try fbr.takeLeb128(u64);
             if (op_size < 1) return bad();
-            const sub_op = try fbr.readByte();
+            const sub_op = try fbr.takeByte();
             switch (sub_op) {
                 DW.LNE.end_sequence => {
                     // The row being added here is an "end" address, meaning
@@ -1577,14 +1571,14 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
                     prog.reset();
                 },
                 DW.LNE.set_address => {
-                    const addr = try fbr.readInt(usize);
+                    const addr = try fbr.takeInt(usize, endian);
                     prog.address = addr;
                 },
                 DW.LNE.define_file => {
-                    const path = try fbr.readBytesTo(0);
-                    const dir_index = try fbr.readUleb128(u32);
-                    const mtime = try fbr.readUleb128(u64);
-                    const size = try fbr.readUleb128(u64);
+                    const path = try fbr.takeSentinel(0);
+                    const dir_index = try fbr.takeLeb128(u32);
+                    const mtime = try fbr.takeLeb128(u64);
+                    const size = try fbr.takeLeb128(u64);
                     try file_entries.append(gpa, .{
                         .path = path,
                         .dir_index = dir_index,
@@ -1592,7 +1586,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
                         .size = size,
                     });
                 },
-                else => try fbr.seekForward(op_size - 1),
+                else => try fbr.discardAll64(op_size - 1),
             }
         } else if (opcode >= opcode_base) {
             // special opcodes
@@ -1610,19 +1604,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
                     prog.basic_block = false;
                 },
                 DW.LNS.advance_pc => {
-                    const arg = try fbr.readUleb128(usize);
+                    const arg = try fbr.takeLeb128(usize);
                     prog.address += arg * minimum_instruction_length;
                 },
                 DW.LNS.advance_line => {
-                    const arg = try fbr.readIleb128(i64);
+                    const arg = try fbr.takeLeb128(i64);
                     prog.line += arg;
                 },
                 DW.LNS.set_file => {
-                    const arg = try fbr.readUleb128(usize);
+                    const arg = try fbr.takeLeb128(usize);
                     prog.file = arg;
                 },
                 DW.LNS.set_column => {
-                    const arg = try fbr.readUleb128(u64);
+                    const arg = try fbr.takeLeb128(u64);
                     prog.column = arg;
                 },
                 DW.LNS.negate_stmt => {
@@ -1636,13 +1630,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !
                     prog.address += inc_addr;
                 },
                 DW.LNS.fixed_advance_pc => {
-                    const arg = try fbr.readInt(u16);
+                    const arg = try fbr.takeInt(u16, endian);
                     prog.address += arg;
                 },
                 DW.LNS.set_prologue_end => {},
                 else => {
                     if (opcode - 1 >= standard_opcode_lengths.len) return bad();
-                    try fbr.seekForward(standard_opcode_lengths[opcode - 1]);
+                    try fbr.discardAll(standard_opcode_lengths[opcode - 1]);
                 },
             }
         }
@@ -1735,38 +1729,40 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 {
 ///
 /// See also `scanCieFdeInfo`.
 pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
+    const endian = di.endian;
+
     if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
-        var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian };
+        var fbr: Reader = .fixed(eh_frame_hdr);
 
-        const version = try fbr.readByte();
+        const version = try fbr.takeByte();
         if (version != 1) break :blk;
 
-        const eh_frame_ptr_enc = try fbr.readByte();
+        const eh_frame_ptr_enc = try fbr.takeByte();
         if (eh_frame_ptr_enc == EH.PE.omit) break :blk;
-        const fde_count_enc = try fbr.readByte();
+        const fde_count_enc = try fbr.takeByte();
         if (fde_count_enc == EH.PE.omit) break :blk;
-        const table_enc = try fbr.readByte();
+        const table_enc = try fbr.takeByte();
         if (table_enc == EH.PE.omit) break :blk;
 
         const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{
-            .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]),
+            .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
             .follow_indirect = true,
-        }) orelse return bad()) orelse return bad();
+        }, endian) orelse return bad()) orelse return bad();
 
         const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{
-            .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]),
+            .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
             .follow_indirect = true,
-        }) orelse return bad()) orelse return bad();
+        }, endian) orelse return bad()) orelse return bad();
 
         const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
         const entries_len = fde_count * entry_size;
-        if (entries_len > eh_frame_hdr.len - fbr.pos) return bad();
+        if (entries_len > eh_frame_hdr.len - fbr.seek) return bad();
 
         di.eh_frame_hdr = .{
             .eh_frame_ptr = eh_frame_ptr,
             .table_enc = table_enc,
             .fde_count = fde_count,
-            .entries = eh_frame_hdr[fbr.pos..][0..entries_len],
+            .entries = eh_frame_hdr[fbr.seek..][0..entries_len],
         };
 
         // No need to scan .eh_frame, we have a binary search table already
@@ -1779,12 +1775,13 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize)
 /// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during
 /// unwinding.
 pub fn scanCieFdeInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
+    const endian = di.endian;
     const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };
     for (frame_sections) |frame_section| {
         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, frame_section);
+            var fbr: Reader = .fixed(section_data);
+            while (fbr.seek < fbr.buffer.len) {
+                const entry_header = try EntryHeader.read(&fbr, frame_section, endian);
                 switch (entry_header.type) {
                     .cie => {
                         const cie = try CommonInformationEntry.parse(
@@ -1826,68 +1823,60 @@ pub fn scanCieFdeInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !vo
 }
 
 fn parseFormValue(
-    fbr: *FixedBufferReader,
+    r: *Reader,
     form_id: u64,
     format: Format,
+    endian: Endian,
     implicit_const: ?i64,
 ) ScanError!FormValue {
     return switch (form_id) {
-        FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) {
-            32 => .@"32",
-            64 => .@"64",
-            else => @compileError("unsupported @sizeOf(usize)"),
-        }) },
-        FORM.addrx1 => .{ .addrx = try fbr.readInt(u8) },
-        FORM.addrx2 => .{ .addrx = try fbr.readInt(u16) },
-        FORM.addrx3 => .{ .addrx = try fbr.readInt(u24) },
-        FORM.addrx4 => .{ .addrx = try fbr.readInt(u32) },
-        FORM.addrx => .{ .addrx = try fbr.readUleb128(usize) },
-
-        FORM.block1,
-        FORM.block2,
-        FORM.block4,
-        FORM.block,
-        => .{ .block = try fbr.readBytes(switch (form_id) {
-            FORM.block1 => try fbr.readInt(u8),
-            FORM.block2 => try fbr.readInt(u16),
-            FORM.block4 => try fbr.readInt(u32),
-            FORM.block => try fbr.readUleb128(usize),
-            else => unreachable,
-        }) },
-
-        FORM.data1 => .{ .udata = try fbr.readInt(u8) },
-        FORM.data2 => .{ .udata = try fbr.readInt(u16) },
-        FORM.data4 => .{ .udata = try fbr.readInt(u32) },
-        FORM.data8 => .{ .udata = try fbr.readInt(u64) },
-        FORM.data16 => .{ .data16 = (try fbr.readBytes(16))[0..16] },
-        FORM.udata => .{ .udata = try fbr.readUleb128(u64) },
-        FORM.sdata => .{ .sdata = try fbr.readIleb128(i64) },
-        FORM.exprloc => .{ .exprloc = try fbr.readBytes(try fbr.readUleb128(usize)) },
-        FORM.flag => .{ .flag = (try fbr.readByte()) != 0 },
+        // DWARF5.pdf page 213: the size of this value is encoded in the
+        // compilation unit header as address size.
+        FORM.addr => .{ .addr = try readAddress(r, nativeFormat(), endian) },
+        FORM.addrx1 => .{ .addrx = try r.takeByte() },
+        FORM.addrx2 => .{ .addrx = try r.takeInt(u16, endian) },
+        FORM.addrx3 => .{ .addrx = try r.takeInt(u24, endian) },
+        FORM.addrx4 => .{ .addrx = try r.takeInt(u32, endian) },
+        FORM.addrx => .{ .addrx = try r.takeLeb128(usize) },
+
+        FORM.block1 => .{ .block = try r.take(try r.takeByte()) },
+        FORM.block2 => .{ .block = try r.take(try r.takeInt(u16, endian)) },
+        FORM.block4 => .{ .block = try r.take(try r.takeInt(u32, endian)) },
+        FORM.block => .{ .block = try r.take(try r.takeLeb128(usize)) },
+
+        FORM.data1 => .{ .udata = try r.takeByte() },
+        FORM.data2 => .{ .udata = try r.takeInt(u16, endian) },
+        FORM.data4 => .{ .udata = try r.takeInt(u32, endian) },
+        FORM.data8 => .{ .udata = try r.takeInt(u64, endian) },
+        FORM.data16 => .{ .data16 = try r.takeArray(16) },
+        FORM.udata => .{ .udata = try r.takeLeb128(u64) },
+        FORM.sdata => .{ .sdata = try r.takeLeb128(i64) },
+        FORM.exprloc => .{ .exprloc = try r.take(try r.takeLeb128(usize)) },
+        FORM.flag => .{ .flag = (try r.takeByte()) != 0 },
         FORM.flag_present => .{ .flag = true },
-        FORM.sec_offset => .{ .sec_offset = try fbr.readAddress(format) },
-
-        FORM.ref1 => .{ .ref = try fbr.readInt(u8) },
-        FORM.ref2 => .{ .ref = try fbr.readInt(u16) },
-        FORM.ref4 => .{ .ref = try fbr.readInt(u32) },
-        FORM.ref8 => .{ .ref = try fbr.readInt(u64) },
-        FORM.ref_udata => .{ .ref = try fbr.readUleb128(u64) },
-
-        FORM.ref_addr => .{ .ref_addr = try fbr.readAddress(format) },
-        FORM.ref_sig8 => .{ .ref = try fbr.readInt(u64) },
-
-        FORM.string => .{ .string = try fbr.readBytesTo(0) },
-        FORM.strp => .{ .strp = try fbr.readAddress(format) },
-        FORM.strx1 => .{ .strx = try fbr.readInt(u8) },
-        FORM.strx2 => .{ .strx = try fbr.readInt(u16) },
-        FORM.strx3 => .{ .strx = try fbr.readInt(u24) },
-        FORM.strx4 => .{ .strx = try fbr.readInt(u32) },
-        FORM.strx => .{ .strx = try fbr.readUleb128(usize) },
-        FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) },
-        FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const),
+        FORM.sec_offset => .{ .sec_offset = try readAddress(r, format, endian) },
+
+        FORM.ref1 => .{ .ref = try r.takeByte() },
+        FORM.ref2 => .{ .ref = try r.takeInt(u16, endian) },
+        FORM.ref4 => .{ .ref = try r.takeInt(u32, endian) },
+        FORM.ref8 => .{ .ref = try r.takeInt(u64, endian) },
+        FORM.ref_udata => .{ .ref = try r.takeLeb128(u64) },
+
+        FORM.ref_addr => .{ .ref_addr = try readAddress(r, format, endian) },
+        FORM.ref_sig8 => .{ .ref = try r.takeInt(u64, endian) },
+
+        FORM.string => .{ .string = try r.takeSentinel(0) },
+        FORM.strp => .{ .strp = try readAddress(r, format, endian) },
+        FORM.strx1 => .{ .strx = try r.takeByte() },
+        FORM.strx2 => .{ .strx = try r.takeInt(u16, endian) },
+        FORM.strx3 => .{ .strx = try r.takeInt(u24, endian) },
+        FORM.strx4 => .{ .strx = try r.takeInt(u32, endian) },
+        FORM.strx => .{ .strx = try r.takeLeb128(usize) },
+        FORM.line_strp => .{ .line_strp = try readAddress(r, format, endian) },
+        FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, implicit_const),
         FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() },
-        FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) },
-        FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) },
+        FORM.loclistx => .{ .loclistx = try r.takeLeb128(u64) },
+        FORM.rnglistx => .{ .rnglistx = try r.takeLeb128(u64) },
         else => {
             //debug.print("unrecognized form id: {x}\n", .{form_id});
             return bad();
@@ -1957,8 +1946,8 @@ const UnitHeader = struct {
     unit_length: u64,
 };
 
-fn readUnitHeader(fbr: *FixedBufferReader) ScanError!UnitHeader {
-    return switch (try fbr.readInt(u32)) {
+fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
+    return switch (try r.takeInt(u32, endian)) {
         0...0xfffffff0 - 1 => |unit_length| .{
             .format = .@"32",
             .header_length = 4,
@@ -1968,7 +1957,7 @@ fn readUnitHeader(fbr: *FixedBufferReader) ScanError!UnitHeader {
         0xffffffff => .{
             .format = .@"64",
             .header_length = 12,
-            .unit_length = try fbr.readInt(u64),
+            .unit_length = try r.takeInt(u64, endian),
         },
     };
 }
@@ -2026,7 +2015,8 @@ const EhPointerContext = struct {
     text_rel_base: ?u64 = null,
     function_rel_base: ?u64 = null,
 };
-fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 {
+
+fn readEhPointer(fbr: *Reader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !?u64 {
     if (enc == EH.PE.omit) return null;
 
     const value: union(enum) {
@@ -2035,20 +2025,20 @@ fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhP
     } = switch (enc & EH.PE.type_mask) {
         EH.PE.absptr => .{
             .unsigned = switch (addr_size_bytes) {
-                2 => try fbr.readInt(u16),
-                4 => try fbr.readInt(u32),
-                8 => try fbr.readInt(u64),
+                2 => try fbr.takeInt(u16, endian),
+                4 => try fbr.takeInt(u32, endian),
+                8 => try fbr.takeInt(u64, endian),
                 else => return error.InvalidAddrSize,
             },
         },
-        EH.PE.uleb128 => .{ .unsigned = try fbr.readUleb128(u64) },
-        EH.PE.udata2 => .{ .unsigned = try fbr.readInt(u16) },
-        EH.PE.udata4 => .{ .unsigned = try fbr.readInt(u32) },
-        EH.PE.udata8 => .{ .unsigned = try fbr.readInt(u64) },
-        EH.PE.sleb128 => .{ .signed = try fbr.readIleb128(i64) },
-        EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) },
-        EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) },
-        EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) },
+        EH.PE.uleb128 => .{ .unsigned = try fbr.takeLeb128(u64) },
+        EH.PE.udata2 => .{ .unsigned = try fbr.takeInt(u16, endian) },
+        EH.PE.udata4 => .{ .unsigned = try fbr.takeInt(u32, endian) },
+        EH.PE.udata8 => .{ .unsigned = try fbr.takeInt(u64, endian) },
+        EH.PE.sleb128 => .{ .signed = try fbr.takeLeb128(i64) },
+        EH.PE.sdata2 => .{ .signed = try fbr.takeInt(i16, endian) },
+        EH.PE.sdata4 => .{ .signed = try fbr.takeInt(i32, endian) },
+        EH.PE.sdata8 => .{ .signed = try fbr.takeInt(i64, endian) },
         else => return bad(),
     };
 
@@ -2156,7 +2146,7 @@ pub const ElfModule = struct {
         if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
         if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
 
-        const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
+        const endian: Endian = switch (hdr.e_ident[elf.EI_DATA]) {
             elf.ELFDATA2LSB => .little,
             elf.ELFDATA2MSB => .big,
             else => return error.InvalidElfEndian,
@@ -2195,7 +2185,7 @@ pub const ElfModule = struct {
                 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
                 const crc_offset = mem.alignForward(usize, debug_filename.len + 1, 4);
                 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
-                separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
+                separate_debug_crc = mem.readInt(u32, crc_bytes, endian);
                 separate_debug_filename = debug_filename;
                 continue;
             }
@@ -2209,7 +2199,7 @@ pub const ElfModule = struct {
 
             const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
             sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
-                var section_reader: std.Io.Reader = .fixed(section_bytes);
+                var section_reader: Reader = .fixed(section_bytes);
                 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
                 if (chdr.ch_type != .ZLIB) continue;
 
@@ -2452,3 +2442,18 @@ pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]cons
     const end = start + (cast(usize, size) orelse return error.Overflow);
     return ptr[start..end];
 }
+
+fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
+    return switch (format) {
+        .@"32" => try r.takeInt(u32, endian),
+        .@"64" => try r.takeInt(u64, endian),
+    };
+}
+
+fn nativeFormat() std.dwarf.Format {
+    return switch (@sizeOf(usize)) {
+        4 => .@"32",
+        8 => .@"64",
+        else => @compileError("unsupported @sizeOf(usize)"),
+    };
+}
lib/std/debug/FixedBufferReader.zig
@@ -1,70 +0,0 @@
-//! Optimized for performance in debug builds.
-
-const std = @import("../std.zig");
-
-const FixedBufferReader = @This();
-
-buf: []const u8,
-pos: usize = 0,
-endian: std.builtin.Endian,
-
-pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer };
-
-pub fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void {
-    if (pos > fbr.buf.len) return error.EndOfBuffer;
-    fbr.pos = @intCast(pos);
-}
-
-pub fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void {
-    if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer;
-    fbr.pos += @intCast(amount);
-}
-
-pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 {
-    if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer;
-    defer fbr.pos += 1;
-    return fbr.buf[fbr.pos];
-}
-
-pub fn readByteSigned(fbr: *FixedBufferReader) Error!i8 {
-    return @bitCast(try fbr.readByte());
-}
-
-pub fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T {
-    const size = @divExact(@typeInfo(T).int.bits, 8);
-    if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer;
-    defer fbr.pos += size;
-    return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian);
-}
-
-pub fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
-    return std.leb.readUleb128(T, fbr);
-}
-
-pub fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
-    return std.leb.readIleb128(T, fbr);
-}
-
-pub fn readAddress(fbr: *FixedBufferReader, format: std.dwarf.Format) Error!u64 {
-    return switch (format) {
-        .@"32" => try fbr.readInt(u32),
-        .@"64" => try fbr.readInt(u64),
-    };
-}
-
-pub fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 {
-    if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer;
-    defer fbr.pos += len;
-    return fbr.buf[fbr.pos..][0..len];
-}
-
-pub fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 {
-    const end = @call(.always_inline, std.mem.indexOfScalarPos, .{
-        u8,
-        fbr.buf,
-        fbr.pos,
-        sentinel,
-    }) orelse return error.EndOfBuffer;
-    defer fbr.pos = end + 1;
-    return fbr.buf[fbr.pos..end :sentinel];
-}
lib/std/debug/SelfInfo.zig
@@ -1555,26 +1555,24 @@ pub fn unwindFrameDwarf(
     if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
     if (context.pc == 0) return 0;
 
+    const endian = di.endian;
+
     // Find the FDE and CIE
     const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: {
         const dwarf_section: Dwarf.Section.Id = .eh_frame;
         const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
         if (fde_offset >= frame_section.len) return error.MissingFDE;
 
-        var fbr: std.debug.FixedBufferReader = .{
-            .buf = frame_section,
-            .pos = fde_offset,
-            .endian = di.endian,
-        };
+        var fbr: std.Io.Reader = .fixed(frame_section);
+        fbr.seek = fde_offset;
 
-        const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section);
+        const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian);
         if (fde_entry_header.type != .fde) return error.MissingFDE;
 
         const cie_offset = fde_entry_header.type.fde;
-        try fbr.seekTo(cie_offset);
+        fbr.seek = @intCast(cie_offset);
 
-        fbr.endian = native_endian;
-        const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section);
+        const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian);
         if (cie_entry_header.type != .cie) return Dwarf.bad();
 
         const cie = try Dwarf.CommonInformationEntry.parse(
@@ -1617,6 +1615,7 @@ pub fn unwindFrameDwarf(
                 context.pc,
                 &cie,
                 &fde,
+                endian,
             ) catch |err| switch (err) {
                 error.MissingDebugInfo => {
                     // `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map`
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 FixedBufferReader = @import("debug/FixedBufferReader.zig");
 pub const Dwarf = @import("debug/Dwarf.zig");
 pub const Pdb = @import("debug/Pdb.zig");
 pub const SelfInfo = @import("debug/SelfInfo.zig");
@@ -1773,7 +1772,6 @@ pub inline fn inValgrind() bool {
 
 test {
     _ = &Dwarf;
-    _ = &FixedBufferReader;
     _ = &Pdb;
     _ = &SelfInfo;
     _ = &dumpHex;