mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
crash/minidump: handle padding in the ThreadList stream
This commit is contained in:
@ -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 stream = @import("minidump/stream.zig");
|
||||||
pub const Reader = reader.Reader;
|
pub const Reader = reader.Reader;
|
||||||
|
|
||||||
|
@ -44,8 +44,7 @@ pub const MemoryDescriptor = extern struct {
|
|||||||
/// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread_list
|
/// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread_list
|
||||||
pub const ThreadList = extern struct {
|
pub const ThreadList = extern struct {
|
||||||
number_of_threads: u32,
|
number_of_threads: u32,
|
||||||
|
threads: [1]Thread,
|
||||||
// This struct has a trailing array of `Thread` structs.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread
|
/// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread
|
||||||
|
@ -13,6 +13,7 @@ const log = std.log.scoped(.minidump_reader);
|
|||||||
pub const ReadError = error{
|
pub const ReadError = error{
|
||||||
InvalidHeader,
|
InvalidHeader,
|
||||||
InvalidVersion,
|
InvalidVersion,
|
||||||
|
StreamSizeMismatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Reader creates a new minidump reader for the given source type. The
|
/// 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.
|
/// The source type for the reader.
|
||||||
pub const Source = S;
|
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
|
/// 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.
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
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);
|
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
|
/// 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
|
||||||
/// until they're decoded. The decoded form is a structured form of the stream.
|
/// 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,
|
data: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This is the list of threads from the process.
|
test {
|
||||||
///
|
@import("std").testing.refAllDecls(@This());
|
||||||
/// 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 });
|
|
||||||
}
|
}
|
||||||
|
111
src/crash/minidump/stream_threadlist.zig
Normal file
111
src/crash/minidump/stream_threadlist.zig
Normal file
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
BIN
src/crash/testdata/macos.dmp
vendored
BIN
src/crash/testdata/macos.dmp
vendored
Binary file not shown.
Reference in New Issue
Block a user