crash/minidump: working on rich stream type decoding, ThreadList

This commit is contained in:
Mitchell Hashimoto
2024-09-08 10:58:38 -07:00
parent c0719fceef
commit df629044fa
3 changed files with 93 additions and 3 deletions

View File

@ -34,3 +34,27 @@ pub const LocationDescriptor = extern struct {
data_size: u32, data_size: u32,
rva: 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,
};

View File

@ -25,7 +25,7 @@ pub const ReadError = error{
/// to be aware since custom stream types are allowed), its possible any stream /// 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 /// type can define their own pointers and offsets. So, the source must always
/// be available so callers can decode the streams as needed. /// 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 { return struct {
const Self = @This(); const Self = @This();
@ -53,11 +53,15 @@ pub fn Reader(comptime Source: type) type {
const SourceReader = @typeInfo(@TypeOf(SourceCallable.reader)).Fn.return_type.?; const SourceReader = @typeInfo(@TypeOf(SourceCallable.reader)).Fn.return_type.?;
const SourceSeeker = @typeInfo(@TypeOf(SourceCallable.seekableStream)).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 /// The reader type for stream reading. This has some other methods so
/// you must still call reader() on the result to get the actual /// you must still call reader() on the result to get the actual
/// reader to read the data. /// reader to read the data.
pub const StreamReader = struct { pub const StreamReader = struct {
source: Source, source: Source,
endian: std.builtin.Endian,
directory: external.Directory, directory: external.Directory,
/// Should not be accessed directly. This is setup whenever /// 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(); 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. /// 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 { ) SourceSeeker.SeekError!StreamReader {
return .{ return .{
.source = self.source, .source = self.source,
.endian = self.endian,
.directory = dir, .directory = dir,
}; };
} }
@ -182,7 +192,7 @@ fn readHeader(comptime T: type, source: T) !struct {
} }
// Uncomment to dump some debug information for a minidump file. // 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")); var fbs = std.io.fixedBufferStream(@embedFile("../testdata/macos.dmp"));
const r = try Reader(*@TypeOf(fbs)).init(&fbs); const r = try Reader(*@TypeOf(fbs)).init(&fbs);
var it = r.streamIterator(); var it = r.streamIterator();
@ -191,7 +201,7 @@ test "Minidump debug" {
} }
} }
test "Minidump read" { test "minidump read" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;

View File

@ -1,6 +1,9 @@
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; 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 /// 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 /// form or decoded form. The encoded form are raw bytes and aren't validated
@ -19,3 +22,56 @@ pub const EncodedStream = struct {
type: u32, type: u32,
data: []const u8, 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 });
}