#![allow(missing_debug_implementations)]
use encoding::all::UTF_16LE;
use encoding::{EncoderTrap, Encoding};
use minidump_common::format as md;
use scroll::ctx::SizeWith;
use scroll::LE;
use std::marker::PhantomData;
use std::mem;
use test_assembler::*;
pub struct SynthMinidump {
section: Section,
flags: Label,
stream_count: u32,
stream_count_label: Label,
stream_directory_rva: Label,
stream_directory: Section,
system_info: Option<SystemInfo>,
exception: Option<Exception>,
module_list: Option<ListStream<Module>>,
unloaded_module_list: Option<ExListStream<UnloadedModule>>,
thread_list: Option<ListStream<Thread>>,
thread_names_list: Option<ListStream<ThreadName>>,
memory_list: Option<ListStream<Section>>,
memory64_list: Option<Memory64ListStream>,
memory_info_list: Option<ExListStream<MemoryInfo>>,
crashpad_info: Option<CrashpadInfo>,
linux_maps: Option<SimpleStream>,
linux_lsb_release: Option<SimpleStream>,
linux_cpu_info: Option<SimpleStream>,
linux_environ: Option<SimpleStream>,
linux_proc_status: Option<SimpleStream>,
memory64_section: Option<Section>,
}
pub trait DumpSection {
fn file_offset(&self) -> Label;
fn file_size(&self) -> Label;
}
pub trait ListItem: DumpSection {
fn into_sections(self) -> (Section, Option<Section>);
}
impl<T> ListItem for T
where
T: Into<Section> + DumpSection,
{
fn into_sections(self) -> (Section, Option<Section>) {
(self.into(), None)
}
}
pub trait CiteLocation {
fn cite_location_in(&self, section: Section) -> Section;
}
impl<T: DumpSection> CiteLocation for T {
fn cite_location_in(&self, section: Section) -> Section {
section.D32(&self.file_size()).D32(&self.file_offset())
}
}
impl CiteLocation for (Label, Label) {
fn cite_location_in(&self, section: Section) -> Section {
section.D32(&self.0).D32(&self.1)
}
}
impl<T: CiteLocation> CiteLocation for Option<T> {
fn cite_location_in(&self, section: Section) -> Section {
match *self {
Some(ref inner) => inner.cite_location_in(section),
None => section.D32(0).D32(0),
}
}
}
pub trait SectionExtra {
fn cite_location<T: CiteLocation>(self, thing: &T) -> Self;
fn cite_memory(self, memory: &Memory) -> Self;
}
impl SectionExtra for Section {
fn cite_location<T: CiteLocation>(self, thing: &T) -> Self {
thing.cite_location_in(self)
}
fn cite_memory(self, memory: &Memory) -> Self {
memory.cite_memory_in(self)
}
}
pub trait Stream: DumpSection + Into<Section> {
fn stream_type(&self) -> u32;
fn cite_stream_in(&self, section: Section) -> Section {
section.D32(self.stream_type()).cite_location(self)
}
}
impl SynthMinidump {
pub fn new() -> SynthMinidump {
SynthMinidump::with_endian(DEFAULT_ENDIAN)
}
pub fn with_endian(endian: Endian) -> SynthMinidump {
let flags = Label::new();
let stream_count_label = Label::new();
let stream_directory_rva = Label::new();
let section = Section::with_endian(endian)
.D32(md::MINIDUMP_SIGNATURE)
.D32(md::MINIDUMP_VERSION)
.D32(&stream_count_label)
.D32(&stream_directory_rva)
.D32(0)
.D32(1262805309) .D64(&flags);
section.start().set_const(0);
assert_eq!(section.size(), mem::size_of::<md::MINIDUMP_HEADER>() as u64);
let memory64_section = Section::with_endian(endian);
SynthMinidump {
section,
flags,
stream_count: 0,
stream_count_label,
stream_directory_rva,
stream_directory: Section::with_endian(endian),
system_info: None,
exception: None,
module_list: Some(ListStream::new(
md::MINIDUMP_STREAM_TYPE::ModuleListStream,
endian,
)),
unloaded_module_list: Some(ExListStream::new(
md::MINIDUMP_STREAM_TYPE::UnloadedModuleListStream,
mem::size_of::<md::MINIDUMP_UNLOADED_MODULE>(),
endian,
)),
thread_list: Some(ListStream::new(
md::MINIDUMP_STREAM_TYPE::ThreadListStream,
endian,
)),
thread_names_list: Some(ListStream::new(
md::MINIDUMP_STREAM_TYPE::ThreadNamesStream,
endian,
)),
memory_list: Some(ListStream::new(
md::MINIDUMP_STREAM_TYPE::MemoryListStream,
endian,
)),
memory64_list: Some(Memory64ListStream::new(
endian,
&memory64_section.file_offset(),
)),
memory_info_list: Some(ExListStream::new(
md::MINIDUMP_STREAM_TYPE::MemoryInfoListStream,
mem::size_of::<md::MINIDUMP_MEMORY_INFO>(),
endian,
)),
linux_maps: None,
linux_lsb_release: None,
linux_environ: None,
linux_cpu_info: None,
linux_proc_status: None,
crashpad_info: None,
memory64_section: Some(memory64_section),
}
}
pub fn flags(self, flags: u64) -> SynthMinidump {
self.flags.set_const(flags);
self
}
#[allow(clippy::should_implement_trait)]
pub fn add<T>(mut self, section: T) -> SynthMinidump
where
T: DumpSection + Into<Section>,
{
let offset = section.file_offset();
self.section = self.section.mark(&offset).append_section(section);
self
}
pub fn add_module(mut self, module: Module) -> SynthMinidump {
self.module_list = self
.module_list
.take()
.map(|module_list| module_list.add(module));
self
}
pub fn add_unloaded_module(mut self, module: UnloadedModule) -> SynthMinidump {
self.unloaded_module_list = self
.unloaded_module_list
.take()
.map(|module_list| module_list.add(module));
self
}
pub fn add_memory(mut self, memory: Memory) -> SynthMinidump {
let descriptor = memory.cite_memory_in(Section::with_endian(self.section.endian));
self.memory_list = self
.memory_list
.take()
.map(|memory_list| memory_list.add(descriptor));
self.add(memory)
}
pub fn add_memory64(mut self, memory: Memory) -> SynthMinidump {
self.memory64_list = self
.memory64_list
.take()
.map(|memory64_list| memory64_list.add_memory(&memory));
self.memory64_section = self
.memory64_section
.take()
.map(|memory64_section| memory64_section.append_section(memory.section));
self
}
pub fn add_memory_info(mut self, info: MemoryInfo) -> SynthMinidump {
self.memory_info_list = self
.memory_info_list
.take()
.map(|info_list| info_list.add(info));
self
}
pub fn add_thread(mut self, thread: Thread) -> SynthMinidump {
self.thread_list = self
.thread_list
.take()
.map(|thread_list| thread_list.add(thread));
self
}
pub fn add_thread_name(mut self, thread_name: ThreadName) -> SynthMinidump {
self.thread_names_list = self
.thread_names_list
.take()
.map(|thread_names_list| thread_names_list.add(thread_name));
self
}
pub fn add_crashpad_info(mut self, crashpad_info: CrashpadInfo) -> Self {
self.crashpad_info = Some(crashpad_info);
self
}
pub fn add_system_info(mut self, system_info: SystemInfo) -> Self {
self.system_info = Some(system_info);
self
}
pub fn add_exception(mut self, exception: Exception) -> Self {
self.exception = Some(exception);
self
}
pub fn set_linux_maps(mut self, maps: &[u8]) -> SynthMinidump {
self.linux_maps = Some(SimpleStream {
stream_type: md::MINIDUMP_STREAM_TYPE::LinuxMaps as u32,
section: Section::new().append_bytes(maps),
});
self
}
pub fn set_linux_lsb_release(mut self, lsb: &[u8]) -> SynthMinidump {
self.linux_lsb_release = Some(SimpleStream {
stream_type: md::MINIDUMP_STREAM_TYPE::LinuxLsbRelease as u32,
section: Section::new().append_bytes(lsb),
});
self
}
pub fn set_linux_proc_status(mut self, proc_status: &[u8]) -> SynthMinidump {
self.linux_proc_status = Some(SimpleStream {
stream_type: md::MINIDUMP_STREAM_TYPE::LinuxProcStatus as u32,
section: Section::new().append_bytes(proc_status),
});
self
}
pub fn set_linux_cpu_info(mut self, cpu_info: &[u8]) -> SynthMinidump {
self.linux_cpu_info = Some(SimpleStream {
stream_type: md::MINIDUMP_STREAM_TYPE::LinuxCpuInfo as u32,
section: Section::new().append_bytes(cpu_info),
});
self
}
pub fn set_linux_environ(mut self, environ: &[u8]) -> SynthMinidump {
self.linux_environ = Some(SimpleStream {
stream_type: md::MINIDUMP_STREAM_TYPE::LinuxEnviron as u32,
section: Section::new().append_bytes(environ),
});
self
}
pub fn add_stream<T: Stream>(mut self, stream: T) -> SynthMinidump {
self.stream_directory = stream.cite_stream_in(self.stream_directory);
self.stream_count += 1;
self.add(stream)
}
fn finish_list<T: ListItem>(self, list: Option<ListStream<T>>) -> SynthMinidump {
match list {
Some(l) => {
if !l.is_empty() {
self.add_stream(l)
} else {
self
}
}
None => self,
}
}
fn finish_ex_list<T: ListItem>(self, list: Option<ExListStream<T>>) -> SynthMinidump {
match list {
Some(l) => {
if !l.is_empty() {
self.add_stream(l)
} else {
self
}
}
None => self,
}
}
pub fn finish(mut self) -> Option<Vec<u8>> {
let modules = self.module_list.take();
self = self.finish_list(modules);
let unloaded_modules = self.unloaded_module_list.take();
self = self.finish_ex_list(unloaded_modules);
let memories = self.memory_list.take();
self = self.finish_list(memories);
if let Some(memories64) = self.memory64_list.take() {
if !memories64.is_empty() {
self = self.add_stream(memories64);
}
}
let memory_infos = self.memory_info_list.take();
self = self.finish_ex_list(memory_infos);
let threads = self.thread_list.take();
self = self.finish_list(threads);
let thread_names = self.thread_names_list.take();
self = self.finish_list(thread_names);
if let Some(crashpad_info) = self.crashpad_info.take() {
self = self.add_stream(crashpad_info);
}
if let Some(stream) = self.system_info.take() {
self = self.add_stream(stream);
}
if let Some(stream) = self.exception.take() {
self = self.add_stream(stream);
}
if let Some(stream) = self.linux_maps.take() {
self = self.add_stream(stream);
}
if let Some(stream) = self.linux_lsb_release.take() {
self = self.add_stream(stream);
}
if let Some(stream) = self.linux_cpu_info.take() {
self = self.add_stream(stream);
}
if let Some(stream) = self.linux_proc_status.take() {
self = self.add_stream(stream);
}
if let Some(stream) = self.linux_environ.take() {
self = self.add_stream(stream);
}
if let Some(memory64_section) = self.memory64_section.take() {
self = self.add(memory64_section);
}
let SynthMinidump {
section,
flags,
stream_count,
stream_count_label,
stream_directory_rva,
stream_directory,
..
} = self;
if flags.value().is_none() {
flags.set_const(0);
}
stream_count_label.set_const(stream_count as u64);
section
.mark(&stream_directory_rva)
.append_section(stream_directory)
.get_contents()
}
}
impl Default for SynthMinidump {
fn default() -> Self {
Self::new()
}
}
impl DumpSection for Section {
fn file_offset(&self) -> Label {
self.start()
}
fn file_size(&self) -> Label {
self.final_size()
}
}
macro_rules! impl_dumpsection {
( $x:ty ) => {
impl DumpSection for $x {
fn file_offset(&self) -> Label {
self.section.file_offset()
}
fn file_size(&self) -> Label {
self.section.file_size()
}
}
};
}
pub struct SimpleStream {
pub stream_type: u32,
pub section: Section,
}
impl From<SimpleStream> for Section {
fn from(stream: SimpleStream) -> Self {
stream.section
}
}
impl_dumpsection!(SimpleStream);
impl Stream for SimpleStream {
fn stream_type(&self) -> u32 {
self.stream_type
}
}
pub struct List<T: ListItem> {
section: Section,
count: u32,
count_label: Label,
out_of_band: Section,
_type: PhantomData<T>,
}
impl<T: ListItem> List<T> {
pub fn new(endian: Endian) -> Self {
let count_label = Label::new();
List {
section: Section::with_endian(endian).D32(&count_label),
count_label,
count: 0,
out_of_band: Section::with_endian(endian),
_type: PhantomData,
}
}
#[allow(clippy::should_implement_trait)]
pub fn add(mut self, entry: T) -> Self {
self.count += 1;
let (section, out_of_band_opt) = entry.into_sections();
self.section = self
.section
.mark(§ion.file_offset())
.append_section(section);
if let Some(out_of_band) = out_of_band_opt {
self.out_of_band = self
.out_of_band
.mark(&out_of_band.file_offset())
.append_section(out_of_band);
}
self
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
}
impl<T: ListItem> From<List<T>> for Section {
fn from(list: List<T>) -> Self {
list.count_label.set_const(list.count as u64);
list.section
.mark(&list.out_of_band.file_offset())
.append_section(list.out_of_band)
}
}
impl<T: ListItem> DumpSection for List<T> {
fn file_offset(&self) -> Label {
self.section.file_offset()
}
fn file_size(&self) -> Label {
self.section.file_size()
}
}
pub struct ListStream<T: ListItem> {
stream_type: u32,
list: List<T>,
}
impl<T: ListItem> ListStream<T> {
pub fn new<S: Into<u32>>(stream_type: S, endian: Endian) -> Self {
Self {
stream_type: stream_type.into(),
list: List::new(endian),
}
}
#[allow(clippy::should_implement_trait)]
pub fn add(mut self, entry: T) -> Self {
self.list = self.list.add(entry);
self
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
}
impl<T: ListItem> From<ListStream<T>> for Section {
fn from(stream: ListStream<T>) -> Self {
stream.list.into()
}
}
impl<T: ListItem> DumpSection for ListStream<T> {
fn file_offset(&self) -> Label {
self.list.file_offset()
}
fn file_size(&self) -> Label {
self.list.file_size()
}
}
impl<T: ListItem> Stream for ListStream<T> {
fn stream_type(&self) -> u32 {
self.stream_type
}
}
pub struct Memory64ListStream {
section: Section,
count: u64,
count_label: Label,
}
impl Memory64ListStream {
pub fn new(endian: Endian, memory64_rva: &Label) -> Self {
let count_label = Label::new();
let section = Section::with_endian(endian)
.D64(&count_label)
.D64(memory64_rva);
Self {
section,
count: 0,
count_label,
}
}
pub fn add_memory(mut self, memory: &Memory) -> Self {
self.count += 1;
self.section = self.section.D64(memory.address).D64(memory.section.size());
self
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
}
impl From<Memory64ListStream> for Section {
fn from(list: Memory64ListStream) -> Self {
list.count_label.set_const(list.count as u64);
list.section
}
}
impl DumpSection for Memory64ListStream {
fn file_offset(&self) -> Label {
self.section.file_offset()
}
fn file_size(&self) -> Label {
self.section.file_size()
}
}
impl Stream for Memory64ListStream {
fn stream_type(&self) -> u32 {
md::MINIDUMP_STREAM_TYPE::Memory64ListStream as u32
}
}
pub struct ExList<T: ListItem> {
section: Section,
count: u32,
count_label: Label,
out_of_band: Section,
_type: PhantomData<T>,
}
impl<T: ListItem> ExList<T> {
pub fn new(size_of_entry: usize, endian: Endian) -> Self {
let count_label = Label::new();
let section = Section::with_endian(endian)
.D32(12)
.D32(size_of_entry as u32)
.D32(&count_label);
ExList {
section,
count_label,
count: 0,
out_of_band: Section::with_endian(endian),
_type: PhantomData,
}
}
#[allow(clippy::should_implement_trait)]
pub fn add(mut self, entry: T) -> Self {
self.count += 1;
let (section, out_of_band_opt) = entry.into_sections();
self.section = self
.section
.mark(§ion.file_offset())
.append_section(section);
if let Some(out_of_band) = out_of_band_opt {
self.out_of_band = self
.out_of_band
.mark(&out_of_band.file_offset())
.append_section(out_of_band);
}
self
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
}
impl<T: ListItem> From<ExList<T>> for Section {
fn from(list: ExList<T>) -> Self {
list.count_label.set_const(list.count as u64);
list.section
.mark(&list.out_of_band.file_offset())
.append_section(list.out_of_band)
}
}
impl<T: ListItem> DumpSection for ExList<T> {
fn file_offset(&self) -> Label {
self.section.file_offset()
}
fn file_size(&self) -> Label {
self.section.file_size()
}
}
pub struct ExListStream<T: ListItem> {
stream_type: u32,
list: ExList<T>,
}
impl<T: ListItem> ExListStream<T> {
pub fn new<S: Into<u32>>(stream_type: S, size_of_entry: usize, endian: Endian) -> Self {
Self {
stream_type: stream_type.into(),
list: ExList::new(size_of_entry, endian),
}
}
#[allow(clippy::should_implement_trait)]
pub fn add(mut self, entry: T) -> Self {
self.list = self.list.add(entry);
self
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
}
impl<T: ListItem> From<ExListStream<T>> for Section {
fn from(stream: ExListStream<T>) -> Self {
stream.list.into()
}
}
impl<T: ListItem> DumpSection for ExListStream<T> {
fn file_offset(&self) -> Label {
self.list.file_offset()
}
fn file_size(&self) -> Label {
self.list.file_size()
}
}
impl<T: ListItem> Stream for ExListStream<T> {
fn stream_type(&self) -> u32 {
self.stream_type
}
}
pub struct DumpString {
section: Section,
}
impl DumpString {
pub fn new(s: &str, endian: Endian) -> DumpString {
let u16_s = UTF_16LE.encode(s, EncoderTrap::Strict).unwrap();
let section = Section::with_endian(endian)
.D32(u16_s.len() as u32)
.append_bytes(&u16_s);
DumpString { section }
}
}
impl From<DumpString> for Section {
fn from(string: DumpString) -> Self {
string.section
}
}
impl_dumpsection!(DumpString);
pub struct DumpUtf8String {
section: Section,
}
impl DumpUtf8String {
pub fn new(s: &str, endian: Endian) -> Self {
let section = Section::with_endian(endian)
.D32(s.len() as u32)
.append_bytes(s.as_bytes())
.D8(0);
Self { section }
}
}
impl From<DumpUtf8String> for Section {
fn from(string: DumpUtf8String) -> Self {
string.section
}
}
impl_dumpsection!(DumpUtf8String);
pub const STOCK_VERSION_INFO: md::VS_FIXEDFILEINFO = md::VS_FIXEDFILEINFO {
signature: md::VS_FFI_SIGNATURE as u32,
struct_version: md::VS_FFI_STRUCVERSION as u32,
file_version_hi: 0x11111111,
file_version_lo: 0x22222222,
product_version_hi: 0x33333333,
product_version_lo: 0x44444444,
file_flags_mask: 1,
file_flags: 1,
file_os: 0x40004,
file_type: 1,
file_subtype: 0,
file_date_hi: 0,
file_date_lo: 0,
};
pub struct Module {
section: Section,
cv_record: Option<(Label, Label)>,
misc_record: Option<(Label, Label)>,
}
impl Module {
pub fn new<'a, T: Into<Option<&'a md::VS_FIXEDFILEINFO>>>(
endian: Endian,
base_of_image: u64,
size_of_image: u32,
name: &DumpString,
time_date_stamp: u32,
checksum: u32,
version_info: T,
) -> Module {
let stock_version = &STOCK_VERSION_INFO;
let version_info = version_info.into().unwrap_or(stock_version);
let section = Section::with_endian(endian)
.D64(base_of_image)
.D32(size_of_image)
.D32(checksum)
.D32(time_date_stamp)
.D32(name.file_offset())
.D32(version_info.signature)
.D32(version_info.struct_version)
.D32(version_info.file_version_hi)
.D32(version_info.file_version_lo)
.D32(version_info.product_version_hi)
.D32(version_info.product_version_lo)
.D32(version_info.file_flags_mask)
.D32(version_info.file_flags)
.D32(version_info.file_os)
.D32(version_info.file_type)
.D32(version_info.file_subtype)
.D32(version_info.file_date_hi)
.D32(version_info.file_date_lo);
Module {
section,
cv_record: None,
misc_record: None,
}
}
pub fn cv_record<T: DumpSection>(mut self, cv_record: &T) -> Module {
self.cv_record = Some((cv_record.file_size(), cv_record.file_offset()));
self
}
pub fn misc_record<T: DumpSection>(mut self, misc_record: &T) -> Module {
self.misc_record = Some((misc_record.file_size(), misc_record.file_offset()));
self
}
}
impl_dumpsection!(Module);
impl From<Module> for Section {
fn from(module: Module) -> Self {
let Module {
section,
cv_record,
misc_record,
} = module;
section
.cite_location(&cv_record)
.cite_location(&misc_record)
.D64(0)
.D64(0)
}
}
pub struct UnloadedModule {
section: Section,
}
impl UnloadedModule {
pub fn new(
endian: Endian,
base_of_image: u64,
size_of_image: u32,
name: &DumpString,
time_date_stamp: u32,
checksum: u32,
) -> UnloadedModule {
let section = Section::with_endian(endian)
.D64(base_of_image)
.D32(size_of_image)
.D32(checksum)
.D32(time_date_stamp)
.D32(name.file_offset());
UnloadedModule { section }
}
}
impl_dumpsection!(UnloadedModule);
impl From<UnloadedModule> for Section {
fn from(module: UnloadedModule) -> Self {
let UnloadedModule { section } = module;
section
}
}
pub struct Thread {
section: Section,
}
impl Thread {
pub fn new<T>(endian: Endian, id: u32, stack: &Memory, context: &T) -> Thread
where
T: DumpSection,
{
let section = Section::with_endian(endian)
.D32(id)
.D32(0) .D32(0) .D32(0) .D64(0) .cite_memory(stack)
.cite_location(context);
Thread { section }
}
}
impl_dumpsection!(Thread);
impl From<Thread> for Section {
fn from(thread: Thread) -> Self {
thread.section
}
}
pub struct ThreadName {
section: Section,
}
impl ThreadName {
pub fn new(endian: Endian, id: u32, name: Option<&DumpString>) -> Self {
let section = Section::with_endian(endian).D32(id);
let section = if let Some(name) = name {
section.D64(name.file_offset())
} else {
section.D64(0xFFFF_FFFF_FFFF_FFFF)
};
ThreadName { section }
}
}
impl_dumpsection!(ThreadName);
impl From<ThreadName> for Section {
fn from(thread: ThreadName) -> Self {
thread.section
}
}
pub struct Memory {
section: Section,
pub address: u64,
}
impl Memory {
pub fn with_section(section: Section, address: u64) -> Memory {
Memory { section, address }
}
pub fn cite_memory_in(&self, section: Section) -> Section {
section.D64(self.address).cite_location(self)
}
}
impl_dumpsection!(Memory);
impl From<Memory> for Section {
fn from(memory: Memory) -> Self {
memory.section
}
}
pub struct MemoryInfo {
section: Section,
}
impl MemoryInfo {
#[allow(clippy::too_many_arguments)]
pub fn new(
endian: Endian,
base_address: u64,
allocation_base: u64,
allocation_protection: u32,
region_size: u64,
state: u32,
protection: u32,
ty: u32,
) -> MemoryInfo {
let section = Section::with_endian(endian)
.D64(base_address)
.D64(allocation_base)
.D32(allocation_protection)
.D32(0) .D64(region_size)
.D32(state)
.D32(protection)
.D32(ty)
.D32(0); MemoryInfo { section }
}
}
impl_dumpsection!(MemoryInfo);
impl From<MemoryInfo> for Section {
fn from(info: MemoryInfo) -> Self {
info.section
}
}
pub struct MiscStream {
section: Section,
pub process_id: Option<u32>,
pub process_times: Option<MiscFieldsProcessTimes>,
pub power_info: Option<MiscFieldsPowerInfo>,
pub process_integrity_level: Option<u32>,
pub process_execute_flags: Option<u32>,
pub protected_process: Option<u32>,
pub time_zone: Option<MiscFieldsTimeZone>,
pub build_strings: Option<MiscFieldsBuildString>,
pub misc_5: Option<MiscInfo5Fields>,
pub pad_to_size: Option<usize>,
}
#[derive(Default)]
pub struct MiscFieldsProcessTimes {
pub process_create_time: u32,
pub process_user_time: u32,
pub process_kernel_time: u32,
}
#[derive(Default)]
pub struct MiscFieldsPowerInfo {
pub processor_max_mhz: u32,
pub processor_current_mhz: u32,
pub processor_mhz_limit: u32,
pub processor_max_idle_state: u32,
pub processor_current_idle_state: u32,
}
#[derive(Default)]
pub struct MiscFieldsTimeZone {
pub time_zone_id: u32,
pub time_zone: md::TIME_ZONE_INFORMATION,
}
pub struct MiscFieldsBuildString {
pub build_string: [u16; 260],
pub dbg_bld_str: [u16; 40],
}
impl Default for MiscFieldsBuildString {
fn default() -> Self {
Self {
build_string: [0; 260],
dbg_bld_str: [0; 40],
}
}
}
#[derive(Default)]
pub struct MiscInfo5Fields {
pub xstate_data: md::XSTATE_CONFIG_FEATURE_MSC_INFO,
pub process_cookie: Option<u32>,
}
impl MiscStream {
pub fn new(endian: Endian) -> MiscStream {
let section = Section::with_endian(endian);
let size = section.final_size();
MiscStream {
section: section.D32(size),
process_id: None,
process_times: None,
power_info: None,
process_integrity_level: None,
process_execute_flags: None,
protected_process: None,
time_zone: None,
build_strings: None,
misc_5: None,
pad_to_size: None,
}
}
}
impl From<MiscStream> for Section {
fn from(stream: MiscStream) -> Self {
let MiscStream {
section,
process_id,
process_times,
power_info,
process_integrity_level,
process_execute_flags,
protected_process,
time_zone,
build_strings,
misc_5,
pad_to_size,
} = stream;
let mut misc_info_version = 1;
let mut flags = md::MiscInfoFlags::empty();
if process_id.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC1_PROCESS_ID;
}
if process_times.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC1_PROCESS_TIMES;
}
if power_info.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC1_PROCESSOR_POWER_INFO;
misc_info_version = 2;
}
if process_integrity_level.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC3_PROCESS_INTEGRITY;
misc_info_version = 3;
}
if process_execute_flags.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC3_PROCESS_EXECUTE_FLAGS;
misc_info_version = 3;
}
if protected_process.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC3_PROTECTED_PROCESS;
misc_info_version = 3;
}
if time_zone.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC3_TIMEZONE;
misc_info_version = 3;
}
if build_strings.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC4_BUILDSTRING;
misc_info_version = 4;
}
if let Some(ref misc_5) = misc_5 {
if misc_5.process_cookie.is_some() {
flags |= md::MiscInfoFlags::MINIDUMP_MISC5_PROCESS_COOKIE
}
misc_info_version = 5;
}
let mut section = section.D32(flags.bits() as u64 as u32);
let process_id = process_id.unwrap_or_default();
let process_times = process_times.unwrap_or_default();
section = section.D32(process_id);
section = section
.D32(process_times.process_create_time)
.D32(process_times.process_user_time)
.D32(process_times.process_kernel_time);
if misc_info_version >= 2 {
let power_info = power_info.unwrap_or_default();
section = section
.D32(power_info.processor_max_mhz)
.D32(power_info.processor_current_mhz)
.D32(power_info.processor_mhz_limit)
.D32(power_info.processor_max_idle_state)
.D32(power_info.processor_current_idle_state);
}
if misc_info_version >= 3 {
let process_integrity_level = process_integrity_level.unwrap_or_default();
let process_execute_flags = process_execute_flags.unwrap_or_default();
let protected_process = protected_process.unwrap_or_default();
let time_zone = time_zone.unwrap_or_default();
section = section.D32(process_integrity_level);
section = section.D32(process_execute_flags);
section = section.D32(protected_process);
fn write_system_time(section: Section, time: &md::SYSTEMTIME) -> Section {
section
.D16(time.year)
.D16(time.month)
.D16(time.day_of_week)
.D16(time.day)
.D16(time.hour)
.D16(time.minute)
.D16(time.second)
.D16(time.milliseconds)
}
section = section.D32(time_zone.time_zone_id);
let time_zone = time_zone.time_zone;
section = section.D32(time_zone.bias as u32);
for &val in &time_zone.standard_name {
section = section.D16(val);
}
section = write_system_time(section, &time_zone.standard_date);
section = section.D32(time_zone.standard_bias as u32);
for &val in &time_zone.daylight_name {
section = section.D16(val);
}
section = write_system_time(section, &time_zone.daylight_date);
section = section.D32(time_zone.daylight_bias as u32);
}
if misc_info_version >= 4 {
let build_strings = build_strings.unwrap_or_default();
for &val in &build_strings.build_string {
section = section.D16(val);
}
for &val in &build_strings.dbg_bld_str {
section = section.D16(val);
}
}
if misc_info_version >= 5 {
let misc_5 = misc_5.unwrap_or_default();
let process_cookie = misc_5.process_cookie.unwrap_or_default();
let xstate = misc_5.xstate_data;
section = section
.D32(xstate.size_of_info)
.D32(xstate.context_size)
.D64(xstate.enabled_features);
for feature in &xstate.features {
section = section.D32(feature.offset).D32(feature.size);
}
section = section.D32(process_cookie);
}
if let Some(size) = pad_to_size {
let size = (size as u64 - section.size()) as usize;
section.append_repeated(0, size)
} else {
section
}
}
}
impl_dumpsection!(MiscStream);
impl Stream for MiscStream {
fn stream_type(&self) -> u32 {
md::MINIDUMP_STREAM_TYPE::MiscInfoStream as u32
}
}
pub fn x86_context(endian: Endian, eip: u32, esp: u32) -> Section {
let section = Section::with_endian(endian)
.D32(0x1007f) .append_repeated(0, 4 * 6) .append_repeated(0, md::FLOATING_SAVE_AREA_X86::size_with(&LE)) .append_repeated(0, 4 * 11) .D32(eip)
.D32(0) .D32(0) .D32(esp)
.D32(0) .append_repeated(0, 512); assert_eq!(section.size(), md::CONTEXT_X86::size_with(&LE) as u64);
section
}
pub fn amd64_context(endian: Endian, rip: u64, rsp: u64) -> Section {
let section = Section::with_endian(endian)
.append_repeated(0, mem::size_of::<u64>() * 6) .D32(0x10001f) .D32(0) .append_repeated(0, mem::size_of::<u16>() * 6) .D32(0) .append_repeated(0, mem::size_of::<u64>() * 6) .append_repeated(0, mem::size_of::<u64>() * 4) .D64(rsp)
.append_repeated(0, mem::size_of::<u64>() * 11) .D64(rip)
.append_repeated(0, 512) .append_repeated(0, mem::size_of::<u128>() * 26) .append_repeated(0, mem::size_of::<u64>() * 6); assert_eq!(section.size(), md::CONTEXT_AMD64::size_with(&LE) as u64);
section
}
pub struct SectionRef {
section: Section,
data_section: Section,
}
impl SectionRef {
pub fn new(data_section: impl Into<Section>, endian: Endian) -> Self {
let data_section = data_section.into();
let section = Section::with_endian(endian).D32(data_section.file_offset());
Self {
section,
data_section,
}
}
}
impl_dumpsection!(SectionRef);
impl ListItem for SectionRef {
fn into_sections(self) -> (Section, Option<Section>) {
(self.section, Some(self.data_section))
}
}
pub struct SimpleStringDictionaryEntry {
endian: Endian,
section: Section,
key: DumpUtf8String,
value: DumpUtf8String,
}
impl SimpleStringDictionaryEntry {
pub fn new(key: &str, value: &str, endian: Endian) -> Self {
Self {
endian,
section: Section::with_endian(endian),
key: DumpUtf8String::new(key, endian),
value: DumpUtf8String::new(value, endian),
}
}
}
impl_dumpsection!(SimpleStringDictionaryEntry);
impl ListItem for SimpleStringDictionaryEntry {
fn into_sections(self) -> (Section, Option<Section>) {
let section = self
.section
.D32(self.key.file_offset())
.D32(self.value.file_offset());
let out_of_band = Section::with_endian(self.endian)
.mark(&self.key.file_offset())
.append_section(self.key)
.mark(&self.value.file_offset())
.append_section(self.value);
(section, Some(out_of_band))
}
}
pub type SimpleStringDictionary = List<SimpleStringDictionaryEntry>;
#[derive(Clone, Debug)]
pub enum AnnotationValue {
Invalid,
String(String),
Custom(u16, Vec<u8>),
}
pub struct AnnotationObject {
section: Section,
out_of_band: Section,
}
impl AnnotationObject {
pub fn new(name: &str, value: AnnotationValue, endian: Endian) -> Self {
let name = DumpUtf8String::new(name, endian);
let (ty, value) = match value {
AnnotationValue::Invalid => (md::MINIDUMP_ANNOTATION::TYPE_INVALID, None),
AnnotationValue::String(s) => (
md::MINIDUMP_ANNOTATION::TYPE_STRING,
Some(DumpUtf8String::new(&s, endian).into()),
),
AnnotationValue::Custom(ty, bytes) => (ty, Some(Section::new().append_bytes(&bytes))),
};
let mut section = Section::with_endian(endian)
.D32(name.file_offset())
.D16(ty)
.D16(0);
section = match value {
Some(ref value) => section.D32(value.file_offset()),
None => section.D32(0),
};
let mut out_of_band = Section::with_endian(endian)
.mark(&name.file_offset())
.append_section(name);
if let Some(value) = value {
out_of_band = out_of_band.mark(&value.file_offset()).append_section(value);
}
Self {
section,
out_of_band,
}
}
}
impl_dumpsection!(AnnotationObject);
impl ListItem for AnnotationObject {
fn into_sections(self) -> (Section, Option<Section>) {
(self.section, Some(self.out_of_band))
}
}
pub type AnnotationObjects = List<AnnotationObject>;
pub struct ModuleCrashpadInfo {
endian: Endian,
section: Section,
list_annotations: List<SectionRef>,
simple_annotations: SimpleStringDictionary,
annotation_objects: AnnotationObjects,
}
impl ModuleCrashpadInfo {
pub fn new(index: u32, endian: Endian) -> Self {
Self {
endian,
section: Section::with_endian(endian).D32(index),
list_annotations: List::new(endian),
simple_annotations: SimpleStringDictionary::new(endian),
annotation_objects: AnnotationObjects::new(endian),
}
}
pub fn add_list_annotation(mut self, value: &str) -> Self {
let section = SectionRef::new(DumpUtf8String::new(value, self.endian), self.endian);
self.list_annotations = self.list_annotations.add(section);
self
}
pub fn add_simple_annotation(mut self, key: &str, value: &str) -> Self {
let entry = SimpleStringDictionaryEntry::new(key, value, self.endian);
self.simple_annotations = self.simple_annotations.add(entry);
self
}
pub fn add_annotation_object(mut self, key: &str, value: AnnotationValue) -> Self {
let object = AnnotationObject::new(key, value, self.endian);
self.annotation_objects = self.annotation_objects.add(object);
self
}
}
impl_dumpsection!(ModuleCrashpadInfo);
impl ListItem for ModuleCrashpadInfo {
fn into_sections(self) -> (Section, Option<Section>) {
let info = Section::with_endian(self.endian)
.D32(md::MINIDUMP_MODULE_CRASHPAD_INFO::VERSION)
.cite_location(&self.list_annotations)
.cite_location(&self.simple_annotations)
.cite_location(&self.annotation_objects)
.mark(&self.list_annotations.file_offset())
.append_section(self.list_annotations)
.mark(&self.simple_annotations.file_offset())
.append_section(self.simple_annotations)
.mark(&self.annotation_objects.file_offset())
.append_section(self.annotation_objects);
let link = self.section.cite_location(&info);
(link, Some(info))
}
}
pub type ModuleCrashpadInfoList = List<ModuleCrashpadInfo>;
pub struct Guid {
section: Section,
}
impl Guid {
pub fn new(guid: md::GUID, endian: Endian) -> Self {
let section = Section::with_endian(endian)
.D32(guid.data1)
.D16(guid.data2)
.D16(guid.data3)
.append_bytes(&guid.data4);
Self { section }
}
pub fn empty(endian: Endian) -> Self {
let guid = md::GUID {
data1: 0,
data2: 0,
data3: 0,
data4: [0, 0, 0, 0, 0, 0, 0, 0],
};
Self::new(guid, endian)
}
}
impl From<Guid> for Section {
fn from(guid: Guid) -> Self {
guid.section
}
}
pub struct CrashpadInfo {
endian: Endian,
section: Section,
report_id: Guid,
client_id: Guid,
simple_annotations: SimpleStringDictionary,
module_list: ModuleCrashpadInfoList,
}
impl CrashpadInfo {
pub fn new(endian: Endian) -> Self {
Self {
endian,
section: Section::with_endian(endian),
report_id: Guid::empty(endian),
client_id: Guid::empty(endian),
simple_annotations: SimpleStringDictionary::new(endian),
module_list: ModuleCrashpadInfoList::new(endian),
}
}
pub fn report_id(mut self, report_id: md::GUID) -> Self {
self.report_id = Guid::new(report_id, self.endian);
self
}
pub fn client_id(mut self, client_id: md::GUID) -> Self {
self.client_id = Guid::new(client_id, self.endian);
self
}
pub fn add_simple_annotation(mut self, key: &str, value: &str) -> Self {
let entry = SimpleStringDictionaryEntry::new(key, value, self.endian);
self.simple_annotations = self.simple_annotations.add(entry);
self
}
pub fn add_module(mut self, info: ModuleCrashpadInfo) -> Self {
self.module_list = self.module_list.add(info);
self
}
}
impl_dumpsection!(CrashpadInfo);
impl From<CrashpadInfo> for Section {
fn from(info: CrashpadInfo) -> Self {
info.section
.D32(md::MINIDUMP_CRASHPAD_INFO::VERSION)
.append_section(info.report_id)
.append_section(info.client_id)
.cite_location(&info.simple_annotations)
.cite_location(&info.module_list)
.mark(&info.simple_annotations.file_offset())
.append_section(info.simple_annotations)
.mark(&info.module_list.file_offset())
.append_section(info.module_list)
}
}
impl Stream for CrashpadInfo {
fn stream_type(&self) -> u32 {
md::MINIDUMP_STREAM_TYPE::CrashpadInfoStream.into()
}
}
pub struct SystemInfo {
section: Section,
pub processor_architecture: u16,
pub processor_level: u16,
pub processor_revision: u16,
pub number_of_processors: u8,
pub product_type: u8,
pub major_version: u32,
pub minor_version: u32,
pub build_number: u32,
pub platform_id: u32,
pub csd_version_rva: u32,
pub suite_mask: u16,
pub reserved2: u16,
pub cpu: CpuInfo,
}
pub enum CpuInfo {
X86CpuInfo {
vendor_id: [u32; 3],
version_information: u32,
feature_information: u32,
amd_extended_cpu_features: u32,
},
}
impl SystemInfo {
pub fn new(endian: Endian) -> Self {
Self {
section: Section::with_endian(endian),
processor_architecture: 0,
processor_level: 6,
processor_revision: 0x0000,
number_of_processors: 1,
product_type: 0,
major_version: 0,
minor_version: 0,
build_number: 0,
platform_id: 0,
csd_version_rva: 0,
suite_mask: 0,
reserved2: 0,
cpu: CpuInfo::X86CpuInfo {
vendor_id: [0; 3],
version_information: 0,
feature_information: 0,
amd_extended_cpu_features: 0,
},
}
}
pub fn set_processor_architecture(mut self, arch: u16) -> Self {
self.processor_architecture = arch;
self
}
pub fn set_platform_id(mut self, platform_id: u32) -> Self {
self.platform_id = platform_id;
self
}
}
impl_dumpsection!(SystemInfo);
impl From<SystemInfo> for Section {
fn from(info: SystemInfo) -> Self {
let section = info
.section
.D16(info.processor_architecture)
.D16(info.processor_level)
.D16(info.processor_revision)
.D8(info.number_of_processors)
.D8(info.product_type)
.D32(info.major_version)
.D32(info.minor_version)
.D32(info.build_number)
.D32(info.platform_id)
.D32(info.csd_version_rva)
.D16(info.suite_mask)
.D16(info.reserved2);
match info.cpu {
CpuInfo::X86CpuInfo {
vendor_id,
version_information,
feature_information,
amd_extended_cpu_features,
} => section
.D32(vendor_id[0])
.D32(vendor_id[1])
.D32(vendor_id[2])
.D32(version_information)
.D32(feature_information)
.D32(amd_extended_cpu_features),
}
}
}
impl Stream for SystemInfo {
fn stream_type(&self) -> u32 {
md::MINIDUMP_STREAM_TYPE::SystemInfoStream.into()
}
}
pub struct Exception {
section: Section,
pub thread_id: u32,
pub exception_record: ExceptionRecord,
pub thread_context: (u32, u32),
}
pub struct ExceptionRecord {
pub exception_code: u32,
pub exception_flags: u32,
pub exception_record: u64,
pub exception_address: u64,
pub number_parameters: u32,
pub exception_information: [u64; 15],
}
impl Exception {
pub fn new(endian: Endian) -> Self {
Self {
section: Section::with_endian(endian),
thread_id: 0,
exception_record: ExceptionRecord {
exception_code: 0,
exception_flags: 0,
exception_record: 0,
exception_address: 0,
number_parameters: 0,
exception_information: [0; 15],
},
thread_context: (0, 0),
}
}
}
impl_dumpsection!(Exception);
impl From<Exception> for Section {
fn from(info: Exception) -> Self {
let mut section = info
.section
.D32(info.thread_id)
.D32(0) .D32(info.exception_record.exception_code)
.D32(info.exception_record.exception_flags)
.D64(info.exception_record.exception_record)
.D64(info.exception_record.exception_address)
.D32(info.exception_record.number_parameters)
.D32(0);
for &chunk in &info.exception_record.exception_information {
section = section.D64(chunk);
}
section = section
.D32(info.thread_context.0)
.D32(info.thread_context.1);
section
}
}
impl Stream for Exception {
fn stream_type(&self) -> u32 {
md::MINIDUMP_STREAM_TYPE::ExceptionStream.into()
}
}
#[test]
fn test_dump_header() {
let dump = SynthMinidump::with_endian(Endian::Little).flags(0x9f738b33685cc84c);
assert_eq!(
dump.finish().unwrap(),
vec![
0x4d, 0x44, 0x4d, 0x50, 0x93, 0xa7, 0x00, 0x00, 0, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0x3d, 0xe1, 0x44, 0x4b, 0x4c, 0xc8, 0x5c, 0x68, 0x33, 0x8b, 0x73, 0x9f,
]
);
}
#[test]
fn test_dump_header_bigendian() {
let dump = SynthMinidump::with_endian(Endian::Big).flags(0x9f738b33685cc84c);
assert_eq!(
dump.finish().unwrap(),
vec![
0x50, 0x4d, 0x44, 0x4d, 0x00, 0x00, 0xa7, 0x93, 0, 0, 0, 0, 0, 0, 0, 0x20, 0, 0, 0, 0, 0x4b, 0x44, 0xe1, 0x3d, 0x9f, 0x73, 0x8b, 0x33, 0x68, 0x5c, 0xc8, 0x4c,
]
);
}
#[test]
fn test_section_cite() {
let s1 = Section::with_endian(Endian::Little).append_repeated(0, 0x0a);
s1.start().set_const(0xff00ee11);
let s2 = Section::with_endian(Endian::Little);
let s2 = s1.cite_location_in(s2);
s1.get_contents().unwrap();
assert_eq!(
s2.get_contents().unwrap(),
vec![0x0a, 0, 0, 0, 0x11, 0xee, 0x00, 0xff]
);
}
#[test]
fn test_dump_string() {
let dump = SynthMinidump::with_endian(Endian::Little);
let s = DumpString::new("hello", Endian::Little);
let contents = dump.add(s).finish().unwrap();
assert_eq!(
&contents[mem::size_of::<md::MINIDUMP_HEADER>()..],
&[
0xa, 0x0, 0x0, 0x0, b'h', 0x0, b'e', 0x0, b'l', 0x0, b'l', 0x0, b'o', 0x0
]
);
}
#[test]
fn test_list_out_of_band() {
let list = List::<SectionRef>::new(Endian::Little);
assert_eq!(
Into::<Section>::into(list).get_contents().unwrap(),
vec![0, 0, 0, 0]
);
let a = SectionRef::new(DumpUtf8String::new("foo", Endian::Little), Endian::Little);
let b = SectionRef::new(DumpUtf8String::new("bar", Endian::Little), Endian::Little);
let section: Section = List::new(Endian::Little).add(a).add(b).into();
assert_eq!(
section.set_start_const(0).get_contents().unwrap(),
vec![
2, 0, 0, 0, 12, 0, 0, 0, 20, 0, 0, 0, 3, 0, 0, 0, 102, 111, 111, 0, 3, 0, 0, 0, 98, 97, 114, 0 ]
);
}
#[test]
fn test_list_stream() {
let list = ListStream::<DumpString>::new(0x11223344u32, Endian::Little);
assert_eq!(
Into::<Section>::into(list).get_contents().unwrap(),
vec![0, 0, 0, 0]
);
let list = ListStream::new(0x11223344u32, Endian::Little)
.add(DumpString::new("a", Endian::Little))
.add(DumpString::new("b", Endian::Little));
assert_eq!(
Into::<Section>::into(list).get_contents().unwrap(),
vec![
2, 0, 0, 0, 0x2, 0x0, 0x0, 0x0, b'a', 0x0, 0x2, 0x0, 0x0, 0x0, b'b', 0x0
]
);
}
#[test]
fn test_simple_stream() {
let section = Section::with_endian(Endian::Little).D32(0x55667788);
let stream_rva = mem::size_of::<md::MINIDUMP_HEADER>() as u8;
let directory_rva = stream_rva + section.size() as u8;
let dump = SynthMinidump::with_endian(Endian::Little)
.flags(0x9f738b33685cc84c)
.add_stream(SimpleStream {
stream_type: 0x11223344,
section,
});
assert_eq!(
dump.finish().unwrap(),
vec![
0x4d,
0x44,
0x4d,
0x50, 0x93,
0xa7,
0x00,
0x00, 1,
0,
0,
0, directory_rva,
0,
0,
0, 0,
0,
0,
0, 0x3d,
0xe1,
0x44,
0x4b, 0x4c,
0xc8,
0x5c,
0x68, 0x33,
0x8b,
0x73,
0x9f,
0x88,
0x77,
0x66,
0x55,
0x44,
0x33,
0x22,
0x11, 4,
0,
0,
0, stream_rva,
0,
0,
0, ]
);
}
#[test]
fn test_simple_stream_bigendian() {
let section = Section::with_endian(Endian::Big).D32(0x55667788);
let stream_rva = mem::size_of::<md::MINIDUMP_HEADER>() as u8;
let directory_rva = stream_rva + section.size() as u8;
let dump = SynthMinidump::with_endian(Endian::Big)
.flags(0x9f738b33685cc84c)
.add_stream(SimpleStream {
stream_type: 0x11223344,
section,
});
assert_eq!(
dump.finish().unwrap(),
vec![
0x50,
0x4d,
0x44,
0x4d, 0x00,
0x00,
0xa7,
0x93, 0,
0,
0,
1, 0,
0,
0,
directory_rva, 0,
0,
0,
0, 0x4b,
0x44,
0xe1,
0x3d, 0x9f,
0x73,
0x8b,
0x33, 0x68,
0x5c,
0xc8,
0x4c,
0x55,
0x66,
0x77,
0x88,
0x11,
0x22,
0x33,
0x44, 0,
0,
0,
4, 0,
0,
0,
stream_rva, ]
);
}