mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
crash/minidump: working on rich stream type decoding, ThreadList
This commit is contained in:
@ -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,
|
||||||
|
};
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 });
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user