From df629044fad2648e13d686bb1245bd9d5d4a4e8b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 8 Sep 2024 10:58:38 -0700 Subject: [PATCH] crash/minidump: working on rich stream type decoding, ThreadList --- src/crash/minidump/external.zig | 24 ++++++++++++++ src/crash/minidump/reader.zig | 16 ++++++++-- src/crash/minidump/stream.zig | 56 +++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/crash/minidump/external.zig b/src/crash/minidump/external.zig index 9356a6cb3..a6f89d3e9 100644 --- a/src/crash/minidump/external.zig +++ b/src/crash/minidump/external.zig @@ -34,3 +34,27 @@ pub const LocationDescriptor = extern struct { data_size: u32, rva: u32, }; + +/// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory_descriptor +pub const MemoryDescriptor = extern struct { + start_of_memory_range: u64, + memory: LocationDescriptor, +}; + +/// 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. +}; + +/// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread +pub const Thread = extern struct { + thread_id: u32, + suspend_count: u32, + priority_class: u32, + priority: u32, + teb: u64, + stack: MemoryDescriptor, + thread_context: LocationDescriptor, +}; diff --git a/src/crash/minidump/reader.zig b/src/crash/minidump/reader.zig index a88e6ed08..582044879 100644 --- a/src/crash/minidump/reader.zig +++ b/src/crash/minidump/reader.zig @@ -25,7 +25,7 @@ pub const ReadError = error{ /// to be aware since custom stream types are allowed), its possible any stream /// type can define their own pointers and offsets. So, the source must always /// be available so callers can decode the streams as needed. -pub fn Reader(comptime Source: type) type { +pub fn Reader(comptime S: type) type { return struct { const Self = @This(); @@ -53,11 +53,15 @@ pub fn Reader(comptime Source: type) type { const SourceReader = @typeInfo(@TypeOf(SourceCallable.reader)).Fn.return_type.?; const SourceSeeker = @typeInfo(@TypeOf(SourceCallable.seekableStream)).Fn.return_type.?; + /// The source type for the reader. + pub const Source = S; + /// 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. pub const StreamReader = struct { source: Source, + endian: std.builtin.Endian, directory: external.Directory, /// Should not be accessed directly. This is setup whenever @@ -82,6 +86,11 @@ pub fn Reader(comptime Source: type) type { }; return self.limit_reader.reader(); } + + /// Seeks the source to the location of the directory. + pub fn seekToPayload(self: *StreamReader) !void { + try self.source.seekableStream().seekTo(self.directory.location.rva); + } }; /// Iterator type to read over the streams in the minidump file. @@ -129,6 +138,7 @@ pub fn Reader(comptime Source: type) type { ) SourceSeeker.SeekError!StreamReader { return .{ .source = self.source, + .endian = self.endian, .directory = dir, }; } @@ -182,7 +192,7 @@ fn readHeader(comptime T: type, source: T) !struct { } // Uncomment to dump some debug information for a minidump file. -test "Minidump debug" { +test "minidump debug" { var fbs = std.io.fixedBufferStream(@embedFile("../testdata/macos.dmp")); const r = try Reader(*@TypeOf(fbs)).init(&fbs); var it = r.streamIterator(); @@ -191,7 +201,7 @@ test "Minidump debug" { } } -test "Minidump read" { +test "minidump read" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/crash/minidump/stream.zig b/src/crash/minidump/stream.zig index d607ed82b..bb383cce0 100644 --- a/src/crash/minidump/stream.zig +++ b/src/crash/minidump/stream.zig @@ -1,6 +1,9 @@ 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); /// 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 @@ -19,3 +22,56 @@ pub const EncodedStream = struct { type: u32, 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 }); +}