const std = @import("std"); const assert = std.debug.assert; const external = @import("external.zig"); const readerpkg = @import("reader.zig"); const Reader = readerpkg.Reader; const ReadError = readerpkg.ReadError; const log = std.log.scoped(.minidump_stream); /// This is the list of threads from the process. /// /// This is the Reader implementation. You usually do not use this directly. /// Instead, use Reader(T).ThreadList which will get you the same thing. /// /// ThreadList is stream type 0x3. /// StreamReader is the Reader(T).StreamReader type. pub fn ThreadListReader(comptime R: type) type { return struct { const Self = @This(); /// The number of threads in the list. count: u32, /// The rva to the first thread in the list. rva: u32, /// Source data and endianness so we can read. source: R.Source, endian: std.builtin.Endian, pub fn init(r: *R.StreamReader) !Self { assert(r.directory.stream_type == 0x3); try r.seekToPayload(); const reader = r.source.reader(); // Our count is always a u32 in the header. const count = try reader.readInt(u32, r.endian); // Determine if we have padding in our header. It is possible // for there to be padding if the list header was written by // a 32-bit process but is being read on a 64-bit process. const padding = padding: { const maybe_size = @sizeOf(u32) + (@sizeOf(external.Thread) * count); switch (std.math.order(maybe_size, r.directory.location.data_size)) { // It should never be larger than what the directory says. .gt => return ReadError.StreamSizeMismatch, // If the sizes match exactly we're good. .eq => break :padding 0, .lt => { const padding = r.directory.location.data_size - maybe_size; if (padding != 4) return ReadError.StreamSizeMismatch; break :padding padding; }, } }; // Rva is the location of the first thread in the list. const rva = r.directory.location.rva + @as(u32, @sizeOf(u32)) + padding; return .{ .count = count, .rva = rva, .source = r.source, .endian = r.endian, }; } /// Get the thread entry for the given index. /// /// Index is asserted to be less than count. pub fn thread(self: *const Self, i: usize) !external.Thread { assert(i < self.count); // Seek to the thread const offset: u32 = @intCast(@sizeOf(external.Thread) * i); const rva: u32 = self.rva + offset; try self.source.seekableStream().seekTo(rva); // Read the thread return try self.source.reader().readStructEndian( external.Thread, self.endian, ); } }; } test "minidump: threadlist" { const testing = std.testing; const alloc = testing.allocator; var fbs = std.io.fixedBufferStream(@embedFile("../testdata/macos.dmp")); const R = Reader(*@TypeOf(fbs)); const r = try R.init(&fbs); // Get our thread list stream const dir = try r.directory(0); try testing.expectEqual(3, dir.stream_type); var sr = try r.streamReader(dir); // Get our rich structure const v = try R.ThreadList.init(&sr); log.warn("threadlist count={} rva={}", .{ v.count, v.rva }); try testing.expectEqual(12, v.count); for (0..v.count) |i| { const t = try v.thread(i); log.warn("thread i={} thread={}", .{ i, t }); // Read our stack memory var stack_reader = try r.locationReader(t.stack.memory); const bytes = try stack_reader.reader().readAllAlloc(alloc, t.stack.memory.data_size); defer alloc.free(bytes); } }