diff --git a/src/crash/minidump.zig b/src/crash/minidump.zig index 1e103283f..0abd67eae 100644 --- a/src/crash/minidump.zig +++ b/src/crash/minidump.zig @@ -1,5 +1,4 @@ -const reader = @import("minidump/reader.zig"); - +pub const reader = @import("minidump/reader.zig"); pub const stream = @import("minidump/stream.zig"); pub const Reader = reader.Reader; diff --git a/src/crash/minidump/external.zig b/src/crash/minidump/external.zig index a6f89d3e9..451810883 100644 --- a/src/crash/minidump/external.zig +++ b/src/crash/minidump/external.zig @@ -44,8 +44,7 @@ pub const MemoryDescriptor = extern struct { /// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread_list pub const ThreadList = extern struct { number_of_threads: u32, - - // This struct has a trailing array of `Thread` structs. + threads: [1]Thread, }; /// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread diff --git a/src/crash/minidump/reader.zig b/src/crash/minidump/reader.zig index 582044879..f316e63b0 100644 --- a/src/crash/minidump/reader.zig +++ b/src/crash/minidump/reader.zig @@ -13,6 +13,7 @@ const log = std.log.scoped(.minidump_reader); pub const ReadError = error{ InvalidHeader, InvalidVersion, + StreamSizeMismatch, }; /// Reader creates a new minidump reader for the given source type. The @@ -56,6 +57,9 @@ pub fn Reader(comptime S: type) type { /// The source type for the reader. pub const Source = S; + /// The stream types for reading + pub const ThreadList = stream.thread_list.ThreadListReader(Self); + /// The reader type for stream reading. This has some other methods so /// you must still call reader() on the result to get the actual /// reader to read the data. diff --git a/src/crash/minidump/stream.zig b/src/crash/minidump/stream.zig index bb383cce0..00ec6b042 100644 --- a/src/crash/minidump/stream.zig +++ b/src/crash/minidump/stream.zig @@ -1,10 +1,12 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const Reader = @import("reader.zig").Reader; const log = std.log.scoped(.minidump_stream); +/// The known stream types. +pub const thread_list = @import("stream_threadlist.zig"); + /// A stream within the minidump file. A stream can be either in an encoded /// form or decoded form. The encoded form are raw bytes and aren't validated /// until they're decoded. The decoded form is a structured form of the stream. @@ -23,55 +25,6 @@ pub const EncodedStream = struct { data: []const u8, }; -/// This is the list of threads from the process. -/// -/// ThreadList is stream type 0x3. -/// StreamReader is the Reader(T).StreamReader type. -pub fn ThreadList(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, - - /// The source data and endianness so we can continue reading. - 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(); - const count = try reader.readInt(u32, r.endian); - const rva = r.directory.location.rva + @as(u32, @intCast(@sizeOf(u32))); - - return .{ - .count = count, - .rva = rva, - .source = r.source, - .endian = r.endian, - }; - } - }; -} - -test "minidump: threadlist" { - const testing = std.testing; - - 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 ThreadList(R).init(&sr); - log.warn("threadlist count={} rva={}", .{ v.count, v.rva }); +test { + @import("std").testing.refAllDecls(@This()); } diff --git a/src/crash/minidump/stream_threadlist.zig b/src/crash/minidump/stream_threadlist.zig new file mode 100644 index 000000000..e74d11e3e --- /dev/null +++ b/src/crash/minidump/stream_threadlist.zig @@ -0,0 +1,111 @@ +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; + + 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 }); + } +} diff --git a/src/crash/testdata/macos.dmp b/src/crash/testdata/macos.dmp index 5931c13a0..212cc7e62 100644 Binary files a/src/crash/testdata/macos.dmp and b/src/crash/testdata/macos.dmp differ