Commit a40d160a5c

Andrew Kelley <andrew@ziglang.org>
2018-12-03 00:35:41
introduce std.io.SeekableStream
Relevant #764 dwarf debug info is modified to use this instead of std.os.File directly to make it easier for bare metal projects to take advantage of debug info parsing
1 parent 6f5e7ee
std/debug/index.zig
@@ -284,7 +284,7 @@ fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_addres
     const mod_index = for (di.sect_contribs) |sect_contrib| {
         if (sect_contrib.Section > di.coff.sections.len) continue;
         // Remember that SectionContribEntry.Section is 1-based.
-        coff_section = &di.coff.sections.toSlice()[sect_contrib.Section-1];
+        coff_section = &di.coff.sections.toSlice()[sect_contrib.Section - 1];
 
         const vaddr_start = coff_section.header.virtual_address + sect_contrib.Offset;
         const vaddr_end = vaddr_start + sect_contrib.Size;
@@ -872,9 +872,14 @@ fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize {
     return list.toOwnedSlice();
 }
 
-fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo {
+pub fn openDwarfDebugInfo(
+    allocator: *mem.Allocator,
+    dwarf_seekable_stream: *DwarfSeekableStream,
+    dwarf_in_stream: *DwarfInStream,
+) !DebugInfo {
     var di = DebugInfo{
-        .self_exe_file = undefined,
+        .dwarf_seekable_stream = dwarf_seekable_stream,
+        .dwarf_in_stream = dwarf_in_stream,
         .elf = undefined,
         .debug_info = undefined,
         .debug_abbrev = undefined,
@@ -884,10 +889,7 @@ fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo {
         .abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator),
         .compile_unit_list = ArrayList(CompileUnit).init(allocator),
     };
-    di.self_exe_file = try os.openSelfExe();
-    errdefer di.self_exe_file.close();
-
-    try di.elf.openFile(allocator, di.self_exe_file);
+    try di.elf.openStream(allocator, dwarf_seekable_stream, dwarf_in_stream);
     errdefer di.elf.close();
 
     di.debug_info = (try di.elf.findSection(".debug_info")) orelse return error.MissingDebugInfo;
@@ -899,6 +901,27 @@ fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo {
     return di;
 }
 
+fn openSelfDebugInfoLinux(allocator: *mem.Allocator) !DebugInfo {
+    const S = struct {
+        var self_exe_file: os.File = undefined;
+        var self_exe_seekable_stream: os.File.SeekableStream = undefined;
+        var self_exe_in_stream: os.File.InStream = undefined;
+    };
+    S.self_exe_file = try os.openSelfExe();
+    errdefer S.self_exe_file.close();
+
+    S.self_exe_seekable_stream = S.self_exe_file.seekableStream();
+    S.self_exe_in_stream = S.self_exe_file.inStream();
+
+    return openDwarfDebugInfo(
+        allocator,
+        // TODO https://github.com/ziglang/zig/issues/764
+        @ptrCast(*DwarfSeekableStream, &S.self_exe_seekable_stream.stream),
+        // TODO https://github.com/ziglang/zig/issues/764
+        @ptrCast(*DwarfInStream, &S.self_exe_in_stream.stream),
+    );
+}
+
 pub fn findElfSection(elf: *Elf, name: []const u8) ?*elf.Shdr {
     var file_stream = elf.in_file.inStream();
     const in = &file_stream.stream;
@@ -1053,6 +1076,9 @@ const MachOFile = struct {
     sect_debug_line: ?*const macho.section_64,
 };
 
+pub const DwarfSeekableStream = io.SeekableStream(anyerror, anyerror);
+pub const DwarfInStream = io.InStream(anyerror);
+
 pub const DebugInfo = switch (builtin.os) {
     builtin.Os.macosx => struct {
         symbols: []const MachoSymbol,
@@ -1077,7 +1103,8 @@ pub const DebugInfo = switch (builtin.os) {
         modules: []Module,
     },
     builtin.Os.linux => struct {
-        self_exe_file: os.File,
+        dwarf_seekable_stream: *DwarfSeekableStream,
+        dwarf_in_stream: *DwarfInStream,
         elf: elf.Elf,
         debug_info: *elf.SectionHeader,
         debug_abbrev: *elf.SectionHeader,
@@ -1092,13 +1119,10 @@ pub const DebugInfo = switch (builtin.os) {
         }
 
         pub fn readString(self: *DebugInfo) ![]u8 {
-            var in_file_stream = self.self_exe_file.inStream();
-            const in_stream = &in_file_stream.stream;
-            return readStringRaw(self.allocator(), in_stream);
+            return readStringRaw(self.allocator(), self.dwarf_in_stream);
         }
 
         pub fn close(self: *DebugInfo) void {
-            self.self_exe_file.close();
             self.elf.close();
         }
     },
@@ -1206,11 +1230,11 @@ const Die = struct {
         };
     }
 
-    fn getAttrString(self: *const Die, st: *DebugInfo, id: u64) ![]u8 {
+    fn getAttrString(self: *const Die, di: *DebugInfo, id: u64) ![]u8 {
         const form_value = self.getAttr(id) orelse return error.MissingDebugInfo;
         return switch (form_value.*) {
             FormValue.String => |value| value,
-            FormValue.StrPtr => |offset| getString(st, offset),
+            FormValue.StrPtr => |offset| getString(di, offset),
             else => error.InvalidDebugInfo,
         };
     }
@@ -1321,10 +1345,10 @@ fn readStringRaw(allocator: *mem.Allocator, in_stream: var) ![]u8 {
     return buf.toSlice();
 }
 
-fn getString(st: *DebugInfo, offset: u64) ![]u8 {
-    const pos = st.debug_str.offset + offset;
-    try st.self_exe_file.seekTo(pos);
-    return st.readString();
+fn getString(di: *DebugInfo, offset: u64) ![]u8 {
+    const pos = di.debug_str.offset + offset;
+    try di.dwarf_seekable_stream.seekTo(pos);
+    return di.readString();
 }
 
 fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 {
@@ -1371,14 +1395,7 @@ fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, comptime T: type
     return parseFormValueRefLen(allocator, in_stream, block_len);
 }
 
-const ParseFormValueError = error{
-    EndOfStream,
-    InvalidDebugInfo,
-    EndOfFile,
-    OutOfMemory,
-} || std.os.File.ReadError;
-
-fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) ParseFormValueError!FormValue {
+fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) anyerror!FormValue {
     return switch (form_id) {
         DW.FORM_addr => FormValue{ .Address = try parseFormValueTargetAddrSize(in_stream) },
         DW.FORM_block1 => parseFormValueBlock(allocator, in_stream, 1),
@@ -1428,25 +1445,22 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64
     };
 }
 
-fn parseAbbrevTable(st: *DebugInfo) !AbbrevTable {
-    const in_file = st.self_exe_file;
-    var in_file_stream = in_file.inStream();
-    const in_stream = &in_file_stream.stream;
-    var result = AbbrevTable.init(st.allocator());
+fn parseAbbrevTable(di: *DebugInfo) !AbbrevTable {
+    var result = AbbrevTable.init(di.allocator());
     while (true) {
-        const abbrev_code = try readULeb128(in_stream);
+        const abbrev_code = try readULeb128(di.dwarf_in_stream);
         if (abbrev_code == 0) return result;
         try result.append(AbbrevTableEntry{
             .abbrev_code = abbrev_code,
-            .tag_id = try readULeb128(in_stream),
-            .has_children = (try in_stream.readByte()) == DW.CHILDREN_yes,
-            .attrs = ArrayList(AbbrevAttr).init(st.allocator()),
+            .tag_id = try readULeb128(di.dwarf_in_stream),
+            .has_children = (try di.dwarf_in_stream.readByte()) == DW.CHILDREN_yes,
+            .attrs = ArrayList(AbbrevAttr).init(di.allocator()),
         });
         const attrs = &result.items[result.len - 1].attrs;
 
         while (true) {
-            const attr_id = try readULeb128(in_stream);
-            const form_id = try readULeb128(in_stream);
+            const attr_id = try readULeb128(di.dwarf_in_stream);
+            const form_id = try readULeb128(di.dwarf_in_stream);
             if (attr_id == 0 and form_id == 0) break;
             try attrs.append(AbbrevAttr{
                 .attr_id = attr_id,
@@ -1458,18 +1472,18 @@ fn parseAbbrevTable(st: *DebugInfo) !AbbrevTable {
 
 /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
 /// seeks in the stream and parses it.
-fn getAbbrevTable(st: *DebugInfo, abbrev_offset: u64) !*const AbbrevTable {
-    for (st.abbrev_table_list.toSlice()) |*header| {
+fn getAbbrevTable(di: *DebugInfo, abbrev_offset: u64) !*const AbbrevTable {
+    for (di.abbrev_table_list.toSlice()) |*header| {
         if (header.offset == abbrev_offset) {
             return &header.table;
         }
     }
-    try st.self_exe_file.seekTo(st.debug_abbrev.offset + abbrev_offset);
-    try st.abbrev_table_list.append(AbbrevTableHeader{
+    try di.dwarf_seekable_stream.seekTo(di.debug_abbrev.offset + abbrev_offset);
+    try di.abbrev_table_list.append(AbbrevTableHeader{
         .offset = abbrev_offset,
-        .table = try parseAbbrevTable(st),
+        .table = try parseAbbrevTable(di),
     });
-    return &st.abbrev_table_list.items[st.abbrev_table_list.len - 1].table;
+    return &di.abbrev_table_list.items[di.abbrev_table_list.len - 1].table;
 }
 
 fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*const AbbrevTableEntry {
@@ -1479,23 +1493,20 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con
     return null;
 }
 
-fn parseDie(st: *DebugInfo, abbrev_table: *const AbbrevTable, is_64: bool) !Die {
-    const in_file = st.self_exe_file;
-    var in_file_stream = in_file.inStream();
-    const in_stream = &in_file_stream.stream;
-    const abbrev_code = try readULeb128(in_stream);
+fn parseDie(di: *DebugInfo, abbrev_table: *const AbbrevTable, is_64: bool) !Die {
+    const abbrev_code = try readULeb128(di.dwarf_in_stream);
     const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo;
 
     var result = Die{
         .tag_id = table_entry.tag_id,
         .has_children = table_entry.has_children,
-        .attrs = ArrayList(Die.Attr).init(st.allocator()),
+        .attrs = ArrayList(Die.Attr).init(di.allocator()),
     };
     try result.attrs.resize(table_entry.attrs.len);
     for (table_entry.attrs.toSliceConst()) |attr, i| {
         result.attrs.items[i] = Die.Attr{
             .id = attr.attr_id,
-            .value = try parseFormValue(st.allocator(), in_stream, attr.form_id, is_64),
+            .value = try parseFormValue(di.allocator(), di.dwarf_in_stream, attr.form_id, is_64),
         };
     }
     return result;
@@ -1702,19 +1713,15 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u
 fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, target_address: usize) !LineInfo {
     const compile_unit_cwd = try compile_unit.die.getAttrString(di, DW.AT_comp_dir);
 
-    const in_file = di.self_exe_file;
     const debug_line_end = di.debug_line.offset + di.debug_line.size;
     var this_offset = di.debug_line.offset;
     var this_index: usize = 0;
 
-    var in_file_stream = in_file.inStream();
-    const in_stream = &in_file_stream.stream;
-
     while (this_offset < debug_line_end) : (this_index += 1) {
-        try in_file.seekTo(this_offset);
+        try di.dwarf_seekable_stream.seekTo(this_offset);
 
         var is_64: bool = undefined;
-        const unit_length = try readInitialLength(@typeOf(in_stream.readFn).ReturnType.ErrorSet, in_stream, &is_64);
+        const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64);
         if (unit_length == 0) return error.MissingDebugInfo;
         const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
 
@@ -1723,35 +1730,35 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
             continue;
         }
 
-        const version = try in_stream.readInt(di.elf.endian, u16);
+        const version = try di.dwarf_in_stream.readInt(di.elf.endian, u16);
         // TODO support 3 and 5
         if (version != 2 and version != 4) return error.InvalidDebugInfo;
 
-        const prologue_length = if (is_64) try in_stream.readInt(di.elf.endian, u64) else try in_stream.readInt(di.elf.endian, u32);
-        const prog_start_offset = (try in_file.getPos()) + prologue_length;
+        const prologue_length = if (is_64) try di.dwarf_in_stream.readInt(di.elf.endian, u64) else try di.dwarf_in_stream.readInt(di.elf.endian, u32);
+        const prog_start_offset = (try di.dwarf_seekable_stream.getPos()) + prologue_length;
 
-        const minimum_instruction_length = try in_stream.readByte();
+        const minimum_instruction_length = try di.dwarf_in_stream.readByte();
         if (minimum_instruction_length == 0) return error.InvalidDebugInfo;
 
         if (version >= 4) {
             // maximum_operations_per_instruction
-            _ = try in_stream.readByte();
+            _ = try di.dwarf_in_stream.readByte();
         }
 
-        const default_is_stmt = (try in_stream.readByte()) != 0;
-        const line_base = try in_stream.readByteSigned();
+        const default_is_stmt = (try di.dwarf_in_stream.readByte()) != 0;
+        const line_base = try di.dwarf_in_stream.readByteSigned();
 
-        const line_range = try in_stream.readByte();
+        const line_range = try di.dwarf_in_stream.readByte();
         if (line_range == 0) return error.InvalidDebugInfo;
 
-        const opcode_base = try in_stream.readByte();
+        const opcode_base = try di.dwarf_in_stream.readByte();
 
         const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1);
 
         {
             var i: usize = 0;
             while (i < opcode_base - 1) : (i += 1) {
-                standard_opcode_lengths[i] = try in_stream.readByte();
+                standard_opcode_lengths[i] = try di.dwarf_in_stream.readByte();
             }
         }
 
@@ -1769,9 +1776,9 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
         while (true) {
             const file_name = try di.readString();
             if (file_name.len == 0) break;
-            const dir_index = try readULeb128(in_stream);
-            const mtime = try readULeb128(in_stream);
-            const len_bytes = try readULeb128(in_stream);
+            const dir_index = try readULeb128(di.dwarf_in_stream);
+            const mtime = try readULeb128(di.dwarf_in_stream);
+            const len_bytes = try readULeb128(di.dwarf_in_stream);
             try file_entries.append(FileEntry{
                 .file_name = file_name,
                 .dir_index = dir_index,
@@ -1780,15 +1787,15 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
             });
         }
 
-        try in_file.seekTo(prog_start_offset);
+        try di.dwarf_seekable_stream.seekTo(prog_start_offset);
 
         while (true) {
-            const opcode = try in_stream.readByte();
+            const opcode = try di.dwarf_in_stream.readByte();
 
             if (opcode == DW.LNS_extended_op) {
-                const op_size = try readULeb128(in_stream);
+                const op_size = try readULeb128(di.dwarf_in_stream);
                 if (op_size < 1) return error.InvalidDebugInfo;
-                var sub_op = try in_stream.readByte();
+                var sub_op = try di.dwarf_in_stream.readByte();
                 switch (sub_op) {
                     DW.LNE_end_sequence => {
                         prog.end_sequence = true;
@@ -1796,14 +1803,14 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
                         return error.MissingDebugInfo;
                     },
                     DW.LNE_set_address => {
-                        const addr = try in_stream.readInt(di.elf.endian, usize);
+                        const addr = try di.dwarf_in_stream.readInt(di.elf.endian, usize);
                         prog.address = addr;
                     },
                     DW.LNE_define_file => {
                         const file_name = try di.readString();
-                        const dir_index = try readULeb128(in_stream);
-                        const mtime = try readULeb128(in_stream);
-                        const len_bytes = try readULeb128(in_stream);
+                        const dir_index = try readULeb128(di.dwarf_in_stream);
+                        const mtime = try readULeb128(di.dwarf_in_stream);
+                        const len_bytes = try readULeb128(di.dwarf_in_stream);
                         try file_entries.append(FileEntry{
                             .file_name = file_name,
                             .dir_index = dir_index,
@@ -1813,7 +1820,7 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
                     },
                     else => {
                         const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo;
-                        try in_file.seekForward(fwd_amt);
+                        try di.dwarf_seekable_stream.seekForward(fwd_amt);
                     },
                 }
             } else if (opcode >= opcode_base) {
@@ -1832,19 +1839,19 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
                         prog.basic_block = false;
                     },
                     DW.LNS_advance_pc => {
-                        const arg = try readULeb128(in_stream);
+                        const arg = try readULeb128(di.dwarf_in_stream);
                         prog.address += arg * minimum_instruction_length;
                     },
                     DW.LNS_advance_line => {
-                        const arg = try readILeb128(in_stream);
+                        const arg = try readILeb128(di.dwarf_in_stream);
                         prog.line += arg;
                     },
                     DW.LNS_set_file => {
-                        const arg = try readULeb128(in_stream);
+                        const arg = try readULeb128(di.dwarf_in_stream);
                         prog.file = arg;
                     },
                     DW.LNS_set_column => {
-                        const arg = try readULeb128(in_stream);
+                        const arg = try readULeb128(di.dwarf_in_stream);
                         prog.column = arg;
                     },
                     DW.LNS_negate_stmt => {
@@ -1858,14 +1865,14 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
                         prog.address += inc_addr;
                     },
                     DW.LNS_fixed_advance_pc => {
-                        const arg = try in_stream.readInt(di.elf.endian, u16);
+                        const arg = try di.dwarf_in_stream.readInt(di.elf.endian, u16);
                         prog.address += arg;
                     },
                     DW.LNS_set_prologue_end => {},
                     else => {
                         if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo;
                         const len_bytes = standard_opcode_lengths[opcode - 1];
-                        try in_file.seekForward(len_bytes);
+                        try di.dwarf_seekable_stream.seekForward(len_bytes);
                     },
                 }
             }
@@ -1877,36 +1884,33 @@ fn getLineNumberInfoLinux(di: *DebugInfo, compile_unit: *const CompileUnit, targ
     return error.MissingDebugInfo;
 }
 
-fn scanAllCompileUnits(st: *DebugInfo) !void {
-    const debug_info_end = st.debug_info.offset + st.debug_info.size;
-    var this_unit_offset = st.debug_info.offset;
+fn scanAllCompileUnits(di: *DebugInfo) !void {
+    const debug_info_end = di.debug_info.offset + di.debug_info.size;
+    var this_unit_offset = di.debug_info.offset;
     var cu_index: usize = 0;
 
-    var in_file_stream = st.self_exe_file.inStream();
-    const in_stream = &in_file_stream.stream;
-
     while (this_unit_offset < debug_info_end) {
-        try st.self_exe_file.seekTo(this_unit_offset);
+        try di.dwarf_seekable_stream.seekTo(this_unit_offset);
 
         var is_64: bool = undefined;
-        const unit_length = try readInitialLength(@typeOf(in_stream.readFn).ReturnType.ErrorSet, in_stream, &is_64);
+        const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64);
         if (unit_length == 0) return;
         const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
 
-        const version = try in_stream.readInt(st.elf.endian, u16);
+        const version = try di.dwarf_in_stream.readInt(di.elf.endian, u16);
         if (version < 2 or version > 5) return error.InvalidDebugInfo;
 
-        const debug_abbrev_offset = if (is_64) try in_stream.readInt(st.elf.endian, u64) else try in_stream.readInt(st.elf.endian, u32);
+        const debug_abbrev_offset = if (is_64) try di.dwarf_in_stream.readInt(di.elf.endian, u64) else try di.dwarf_in_stream.readInt(di.elf.endian, u32);
 
-        const address_size = try in_stream.readByte();
+        const address_size = try di.dwarf_in_stream.readByte();
         if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo;
 
-        const compile_unit_pos = try st.self_exe_file.getPos();
-        const abbrev_table = try getAbbrevTable(st, debug_abbrev_offset);
+        const compile_unit_pos = try di.dwarf_seekable_stream.getPos();
+        const abbrev_table = try getAbbrevTable(di, debug_abbrev_offset);
 
-        try st.self_exe_file.seekTo(compile_unit_pos);
+        try di.dwarf_seekable_stream.seekTo(compile_unit_pos);
 
-        const compile_unit_die = try st.allocator().create(try parseDie(st, abbrev_table, is_64));
+        const compile_unit_die = try di.allocator().create(try parseDie(di, abbrev_table, is_64));
 
         if (compile_unit_die.tag_id != DW.TAG_compile_unit) return error.InvalidDebugInfo;
 
@@ -1934,7 +1938,7 @@ fn scanAllCompileUnits(st: *DebugInfo) !void {
             }
         };
 
-        try st.compile_unit_list.append(CompileUnit{
+        try di.compile_unit_list.append(CompileUnit{
             .version = version,
             .is_64 = is_64,
             .pc_range = pc_range,
@@ -1947,20 +1951,18 @@ fn scanAllCompileUnits(st: *DebugInfo) !void {
     }
 }
 
-fn findCompileUnit(st: *DebugInfo, target_address: u64) !*const CompileUnit {
-    var in_file_stream = st.self_exe_file.inStream();
-    const in_stream = &in_file_stream.stream;
-    for (st.compile_unit_list.toSlice()) |*compile_unit| {
+fn findCompileUnit(di: *DebugInfo, target_address: u64) !*const CompileUnit {
+    for (di.compile_unit_list.toSlice()) |*compile_unit| {
         if (compile_unit.pc_range) |range| {
             if (target_address >= range.start and target_address < range.end) return compile_unit;
         }
         if (compile_unit.die.getAttrSecOffset(DW.AT_ranges)) |ranges_offset| {
             var base_address: usize = 0;
-            if (st.debug_ranges) |debug_ranges| {
-                try st.self_exe_file.seekTo(debug_ranges.offset + ranges_offset);
+            if (di.debug_ranges) |debug_ranges| {
+                try di.dwarf_seekable_stream.seekTo(debug_ranges.offset + ranges_offset);
                 while (true) {
-                    const begin_addr = try in_stream.readIntLe(usize);
-                    const end_addr = try in_stream.readIntLe(usize);
+                    const begin_addr = try di.dwarf_in_stream.readIntLe(usize);
+                    const end_addr = try di.dwarf_in_stream.readIntLe(usize);
                     if (begin_addr == 0 and end_addr == 0) {
                         break;
                     }
std/io/seekable_stream.zig
@@ -0,0 +1,32 @@
+const std = @import("../index.zig");
+const InStream = std.io.InStream;
+
+pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType: type) type {
+    return struct {
+        const Self = @This();
+        pub const SeekError = SeekErrorType;
+        pub const GetSeekPosError = GetSeekPosErrorType;
+
+        seekToFn: fn (self: *Self, pos: usize) SeekError!void,
+        seekForwardFn: fn (self: *Self, pos: isize) SeekError!void,
+
+        getPosFn: fn (self: *Self) GetSeekPosError!usize,
+        getEndPosFn: fn (self: *Self) GetSeekPosError!usize,
+
+        pub fn seekTo(self: *Self, pos: usize) SeekError!void {
+            return self.seekToFn(self, pos);
+        }
+
+        pub fn seekForward(self: *Self, amt: isize) SeekError!void {
+            return self.seekForwardFn(self, amt);
+        }
+
+        pub fn getEndPos(self: *Self) GetSeekPosError!usize {
+            return self.getEndPosFn(self);
+        }
+
+        pub fn getPos(self: *Self) GetSeekPosError!usize {
+            return self.getPosFn(self);
+        }
+    };
+}
std/os/file.zig
@@ -228,7 +228,14 @@ pub const File = struct {
         return os.isTty(self.handle);
     }
 
-    pub fn seekForward(self: File, amount: isize) !void {
+    pub const SeekError = error{
+        /// TODO make this error impossible to get
+        Overflow,
+        Unseekable,
+        Unexpected,
+    };
+
+    pub fn seekForward(self: File, amount: isize) SeekError!void {
         switch (builtin.os) {
             Os.linux, Os.macosx, Os.ios, Os.freebsd => {
                 const result = posix.lseek(self.handle, amount, posix.SEEK_CUR);
@@ -259,7 +266,7 @@ pub const File = struct {
         }
     }
 
-    pub fn seekTo(self: File, pos: usize) !void {
+    pub fn seekTo(self: File, pos: usize) SeekError!void {
         switch (builtin.os) {
             Os.linux, Os.macosx, Os.ios, Os.freebsd => {
                 const ipos = try math.cast(isize, pos);
@@ -293,7 +300,14 @@ pub const File = struct {
         }
     }
 
-    pub fn getPos(self: File) !usize {
+    pub const GetSeekPosError = error{
+        Overflow,
+        SystemResources,
+        Unseekable,
+        Unexpected,
+    };
+
+    pub fn getPos(self: File) GetSeekPosError!usize {
         switch (builtin.os) {
             Os.linux, Os.macosx, Os.ios, Os.freebsd => {
                 const result = posix.lseek(self.handle, 0, posix.SEEK_CUR);
@@ -323,13 +337,13 @@ pub const File = struct {
                 }
 
                 assert(pos >= 0);
-                return math.cast(usize, pos) catch error.FilePosLargerThanPointerRange;
+                return math.cast(usize, pos);
             },
             else => @compileError("unsupported OS"),
         }
     }
 
-    pub fn getEndPos(self: File) !usize {
+    pub fn getEndPos(self: File) GetSeekPosError!usize {
         if (is_posix) {
             const stat = try os.posixFStat(self.handle);
             return @intCast(usize, stat.size);
@@ -431,6 +445,18 @@ pub const File = struct {
         };
     }
 
+    pub fn seekableStream(file: File) SeekableStream {
+        return SeekableStream{
+            .file = file,
+            .stream = SeekableStream.Stream{
+                .seekToFn = SeekableStream.seekToFn,
+                .seekForwardFn = SeekableStream.seekForwardFn,
+                .getPosFn = SeekableStream.getPosFn,
+                .getEndPosFn = SeekableStream.getEndPosFn,
+            },
+        };
+    }
+
     /// Implementation of io.InStream trait for File
     pub const InStream = struct {
         file: File,
@@ -458,4 +484,32 @@ pub const File = struct {
             return self.file.write(bytes);
         }
     };
+
+    /// Implementation of io.SeekableStream trait for File
+    pub const SeekableStream = struct {
+        file: File,
+        stream: Stream,
+
+        pub const Stream = io.SeekableStream(SeekError, GetSeekPosError);
+
+        pub fn seekToFn(seekable_stream: *Stream, pos: usize) SeekError!void {
+            const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
+            return self.file.seekTo(pos);
+        }
+
+        pub fn seekForwardFn(seekable_stream: *Stream, amt: isize) SeekError!void {
+            const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
+            return self.file.seekForward(amt);
+        }
+
+        pub fn getEndPosFn(seekable_stream: *Stream) GetSeekPosError!usize {
+            const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
+            return self.file.getEndPos();
+        }
+
+        pub fn getPosFn(seekable_stream: *Stream) GetSeekPosError!usize {
+            const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
+            return self.file.getPos();
+        }
+    };
 };
std/elf.zig
@@ -353,7 +353,8 @@ pub const SectionHeader = struct {
 };
 
 pub const Elf = struct {
-    in_file: os.File,
+    seekable_stream: *io.SeekableStream(anyerror, anyerror),
+    in_stream: *io.InStream(anyerror),
     auto_close_stream: bool,
     is_64: bool,
     endian: builtin.Endian,
@@ -370,19 +371,24 @@ pub const Elf = struct {
 
     /// Call close when done.
     pub fn openPath(elf: *Elf, allocator: *mem.Allocator, path: []const u8) !void {
-        try elf.prealloc_file.open(path);
-        try elf.openFile(allocator, *elf.prealloc_file);
-        elf.auto_close_stream = true;
+        @compileError("TODO implement");
     }
 
     /// Call close when done.
     pub fn openFile(elf: *Elf, allocator: *mem.Allocator, file: os.File) !void {
-        elf.allocator = allocator;
-        elf.in_file = file;
-        elf.auto_close_stream = false;
+        @compileError("TODO implement");
+    }
 
-        var file_stream = elf.in_file.inStream();
-        const in = &file_stream.stream;
+    pub fn openStream(
+        elf: *Elf,
+        allocator: *mem.Allocator,
+        seekable_stream: *io.SeekableStream(anyerror, anyerror),
+        in: *io.InStream(anyerror),
+    ) !void {
+        elf.auto_close_stream = false;
+        elf.allocator = allocator;
+        elf.seekable_stream = seekable_stream;
+        elf.in_stream = in;
 
         var magic: [4]u8 = undefined;
         try in.readNoEof(magic[0..]);
@@ -404,7 +410,7 @@ pub const Elf = struct {
         if (version_byte != 1) return error.InvalidFormat;
 
         // skip over padding
-        try elf.in_file.seekForward(9);
+        try seekable_stream.seekForward(9);
 
         elf.file_type = switch (try in.readInt(elf.endian, u16)) {
             1 => FileType.Relocatable,
@@ -441,7 +447,7 @@ pub const Elf = struct {
         }
 
         // skip over flags
-        try elf.in_file.seekForward(4);
+        try seekable_stream.seekForward(4);
 
         const header_size = try in.readInt(elf.endian, u16);
         if ((elf.is_64 and header_size != 64) or (!elf.is_64 and header_size != 52)) {
@@ -461,12 +467,12 @@ pub const Elf = struct {
         const ph_byte_count = u64(ph_entry_size) * u64(ph_entry_count);
         const end_ph = try math.add(u64, elf.program_header_offset, ph_byte_count);
 
-        const stream_end = try elf.in_file.getEndPos();
+        const stream_end = try seekable_stream.getEndPos();
         if (stream_end < end_sh or stream_end < end_ph) {
             return error.InvalidFormat;
         }
 
-        try elf.in_file.seekTo(elf.section_header_offset);
+        try seekable_stream.seekTo(elf.section_header_offset);
 
         elf.section_headers = try elf.allocator.alloc(SectionHeader, sh_entry_count);
         errdefer elf.allocator.free(elf.section_headers);
@@ -521,26 +527,23 @@ pub const Elf = struct {
     pub fn close(elf: *Elf) void {
         elf.allocator.free(elf.section_headers);
 
-        if (elf.auto_close_stream) elf.in_file.close();
+        if (elf.auto_close_stream) elf.prealloc_file.close();
     }
 
     pub fn findSection(elf: *Elf, name: []const u8) !?*SectionHeader {
-        var file_stream = elf.in_file.inStream();
-        const in = &file_stream.stream;
-
         section_loop: for (elf.section_headers) |*elf_section| {
             if (elf_section.sh_type == SHT_NULL) continue;
 
             const name_offset = elf.string_section.offset + elf_section.name;
-            try elf.in_file.seekTo(name_offset);
+            try elf.seekable_stream.seekTo(name_offset);
 
             for (name) |expected_c| {
-                const target_c = try in.readByte();
+                const target_c = try elf.in_stream.readByte();
                 if (target_c == 0 or expected_c != target_c) continue :section_loop;
             }
 
             {
-                const null_byte = try in.readByte();
+                const null_byte = try elf.in_stream.readByte();
                 if (null_byte == 0) return elf_section;
             }
         }
@@ -549,7 +552,7 @@ pub const Elf = struct {
     }
 
     pub fn seekToSection(elf: *Elf, elf_section: *SectionHeader) !void {
-        try elf.in_file.seekTo(elf_section.offset);
+        try elf.seekable_stream.seekTo(elf_section.offset);
     }
 };
 
std/io.zig
@@ -32,6 +32,8 @@ pub fn getStdIn() GetStdIoErrs!File {
     return File.openHandle(handle);
 }
 
+pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
+
 pub fn InStream(comptime ReadError: type) type {
     return struct {
         const Self = @This();
CMakeLists.txt
@@ -491,6 +491,7 @@ set(ZIG_STD_FILES
     "heap.zig"
     "index.zig"
     "io.zig"
+    "io/seekable_stream.zig"
     "json.zig"
     "lazy_init.zig"
     "linked_list.zig"