From b4c50cbfa8705977100833156a46911e950fc868 Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Fri, 8 Apr 2022 16:09:36 +0200
Subject: [PATCH 01/53] Move memory write to be a shared module
---
src/lib.rs | 5 +
src/linux/dso_debug.rs | 8 +-
src/linux/errors.rs | 11 +-
src/linux/minidump_writer.rs | 3 +-
src/linux/sections.rs | 263 +--------------------------------
src/mem_writer.rs | 271 +++++++++++++++++++++++++++++++++++
6 files changed, 283 insertions(+), 278 deletions(-)
create mode 100644 src/mem_writer.rs
diff --git a/src/lib.rs b/src/lib.rs
index ab803757..da5ef8ea 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,3 +12,8 @@ cfg_if::cfg_if! {
pub mod minidump_cpu;
pub mod minidump_format;
+
+/// Non-windows platforms need additional code since they are essentially
+/// replicating functionality we get for free on Windows
+#[cfg(not(target_os = "windows"))]
+pub(crate) mod mem_writer;
diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs
index eb3cc396..7aa60adb 100644
--- a/src/linux/dso_debug.rs
+++ b/src/linux/dso_debug.rs
@@ -1,10 +1,6 @@
use crate::{
- linux::{
- auxv_reader::AuxvType,
- errors::SectionDsoDebugError,
- ptrace_dumper::PtraceDumper,
- sections::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter},
- },
+ linux::{auxv_reader::AuxvType, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper},
+ mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter},
minidump_format::*,
};
use std::collections::HashMap;
diff --git a/src/linux/errors.rs b/src/linux/errors.rs
index ca9d83f4..7caba1ce 100644
--- a/src/linux/errors.rs
+++ b/src/linux/errors.rs
@@ -1,4 +1,5 @@
use crate::maps_reader::MappingInfo;
+use crate::mem_writer::MemoryWriterError;
use crate::thread_info::Pid;
use goblin;
use thiserror::Error;
@@ -120,16 +121,6 @@ pub enum DumperError {
MapsReaderError(#[from] MapsReaderError),
}
-#[derive(Debug, Error)]
-pub enum MemoryWriterError {
- #[error("IO error when writing to DumpBuf")]
- IOError(#[from] std::io::Error),
- #[error("Failed integer conversion")]
- TryFromIntError(#[from] std::num::TryFromIntError),
- #[error("Failed to write to buffer")]
- Scroll(#[from] scroll::Error),
-}
-
#[derive(Debug, Error)]
pub enum SectionAppMemoryError {
#[error("Failed to copy memory from process")]
diff --git a/src/linux/minidump_writer.rs b/src/linux/minidump_writer.rs
index 069308a5..1510f198 100644
--- a/src/linux/minidump_writer.rs
+++ b/src/linux/minidump_writer.rs
@@ -3,12 +3,13 @@ use crate::{
app_memory::AppMemoryList,
crash_context::CrashContext,
dso_debug,
- errors::{FileWriterError, InitError, MemoryWriterError, WriterError},
+ errors::{FileWriterError, InitError, WriterError},
maps_reader::{MappingInfo, MappingList},
ptrace_dumper::PtraceDumper,
sections::*,
thread_info::Pid,
},
+ mem_writer::{Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError},
minidump_format::*,
};
use std::io::{Seek, SeekFrom, Write};
diff --git a/src/linux/sections.rs b/src/linux/sections.rs
index b7850ac0..d898ac5e 100644
--- a/src/linux/sections.rs
+++ b/src/linux/sections.rs
@@ -7,270 +7,11 @@ pub mod thread_list_stream;
pub mod thread_names_stream;
use crate::{
- errors::{self, MemoryWriterError},
+ errors::{self},
linux::{
minidump_writer::{self, DumpBuf, MinidumpWriter},
ptrace_dumper::PtraceDumper,
},
+ mem_writer::*,
minidump_format::*,
};
-use scroll::ctx::{SizeWith, TryIntoCtx};
-
-type WriteResult = std::result::Result;
-
-macro_rules! size {
- ($t:ty) => {
- <$t>::size_with(&scroll::Endian::Little)
- };
-}
-
-pub struct Buffer {
- inner: Vec,
-}
-
-impl Buffer {
- pub fn with_capacity(cap: usize) -> Self {
- Self {
- inner: Vec::with_capacity(cap),
- }
- }
-
- #[inline]
- pub fn position(&self) -> u64 {
- self.inner.len() as u64
- }
-
- #[inline]
- #[must_use]
- fn reserve(&mut self, len: usize) -> usize {
- let mark = self.inner.len();
- self.inner.resize(self.inner.len() + len, 0);
- mark
- }
-
- #[inline]
- fn write(&mut self, val: N) -> Result
- where
- N: TryIntoCtx + SizeWith,
- E: From,
- {
- self.write_at(self.inner.len(), val)
- }
-
- fn write_at(&mut self, offset: usize, val: N) -> Result
- where
- N: TryIntoCtx + SizeWith,
- E: From,
- {
- let to_write = size!(N);
- let remainder = self.inner.len() - offset;
- if remainder < to_write {
- self.inner
- .resize(self.inner.len() + to_write - remainder, 0);
- }
-
- let dst = &mut self.inner[offset..offset + to_write];
- val.try_into_ctx(dst, scroll::Endian::Little)
- }
-
- #[inline]
- pub fn write_all(&mut self, buffer: &[u8]) {
- self.inner.extend_from_slice(buffer);
- }
-}
-
-impl From for Vec {
- fn from(b: Buffer) -> Self {
- b.inner
- }
-}
-
-impl std::ops::Deref for Buffer {
- type Target = [u8];
-
- fn deref(&self) -> &Self::Target {
- &self.inner
- }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct MemoryWriter {
- pub position: MDRVA,
- pub size: usize,
- phantom: std::marker::PhantomData,
-}
-
-impl MemoryWriter
-where
- T: TryIntoCtx + SizeWith,
-{
- /// Create a slot for a type T in the buffer, we can fill right now with real values.
- pub fn alloc_with_val(buffer: &mut Buffer, val: T) -> WriteResult {
- // Mark the position as we may overwrite later
- let position = buffer.position();
- let size = buffer.write(val)?;
-
- Ok(Self {
- position: position as u32,
- size,
- phantom: std::marker::PhantomData,
- })
- }
-
- /// Create a slot for a type T in the buffer, we can fill later with real values.
- pub fn alloc(buffer: &mut Buffer) -> WriteResult {
- let size = size!(T);
- let position = buffer.reserve(size) as u32;
-
- Ok(Self {
- position: position as u32,
- size,
- phantom: std::marker::PhantomData,
- })
- }
-
- /// Write actual values in the buffer-slot we got during `alloc()`
- #[inline]
- pub fn set_value(&mut self, buffer: &mut Buffer, val: T) -> WriteResult<()> {
- Ok(buffer.write_at(self.position as usize, val).map(|_sz| ())?)
- }
-
- #[inline]
- pub fn location(&self) -> MDLocationDescriptor {
- MDLocationDescriptor {
- data_size: size!(T) as u32,
- rva: self.position,
- }
- }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct MemoryArrayWriter {
- pub position: MDRVA,
- array_size: usize,
- phantom: std::marker::PhantomData,
-}
-
-impl MemoryArrayWriter {
- #[inline]
- pub fn write_bytes(buffer: &mut Buffer, slice: &[u8]) -> Self {
- let position = buffer.position();
- buffer.write_all(slice);
-
- Self {
- position: position as u32,
- array_size: slice.len(),
- phantom: std::marker::PhantomData,
- }
- }
-}
-
-impl MemoryArrayWriter
-where
- T: TryIntoCtx + SizeWith + Copy,
-{
- pub fn alloc_from_array(buffer: &mut Buffer, array: &[T]) -> WriteResult {
- let array_size = array.len();
- let position = buffer.reserve(array_size * size!(T));
-
- for (idx, val) in array.iter().enumerate() {
- buffer.write_at(position + idx * size!(T), *val)?;
- }
-
- Ok(Self {
- position: position as u32,
- array_size,
- phantom: std::marker::PhantomData,
- })
- }
-}
-
-impl MemoryArrayWriter
-where
- T: TryIntoCtx + SizeWith,
-{
- /// Create a slot for a type T in the buffer, we can fill in the values in one go.
- pub fn alloc_from_iter(
- buffer: &mut Buffer,
- iter: impl IntoIterator- ,
- ) -> WriteResult
- where
- I: std::iter::ExactSizeIterator
- ,
- {
- let iter = iter.into_iter();
- let array_size = iter.len();
- let size = size!(T);
- let position = buffer.reserve(array_size * size);
-
- for (idx, val) in iter.enumerate() {
- buffer.write_at(position + idx * size, val)?;
- }
-
- Ok(Self {
- position: position as u32,
- array_size,
- phantom: std::marker::PhantomData,
- })
- }
-
- /// Create a slot for a type T in the buffer, we can fill later with real values.
- /// This function fills it with `Default::default()`, which is less performant than
- /// using uninitialized memory, but safe.
- pub fn alloc_array(buffer: &mut Buffer, array_size: usize) -> WriteResult {
- let position = buffer.reserve(array_size * size!(T));
-
- Ok(Self {
- position: position as u32,
- array_size,
- phantom: std::marker::PhantomData,
- })
- }
-
- /// Write actual values in the buffer-slot we got during `alloc()`
- #[inline]
- pub fn set_value_at(&mut self, buffer: &mut Buffer, val: T, index: usize) -> WriteResult<()> {
- Ok(buffer
- .write_at(self.position as usize + size!(T) * index, val)
- .map(|_sz| ())?)
- }
-
- #[inline]
- pub fn location(&self) -> MDLocationDescriptor {
- MDLocationDescriptor {
- data_size: (self.array_size * size!(T)) as u32,
- rva: self.position,
- }
- }
-
- #[inline]
- pub fn location_of_index(&self, idx: usize) -> MDLocationDescriptor {
- MDLocationDescriptor {
- data_size: size!(T) as u32,
- rva: self.position + (size!(T) * idx) as u32,
- }
- }
-}
-
-pub fn write_string_to_location(
- buffer: &mut Buffer,
- text: &str,
-) -> WriteResult {
- let letters: Vec = text.encode_utf16().collect();
-
- // First write size of the string (x letters in u16, times the size of u16)
- let text_header = MemoryWriter::::alloc_with_val(
- buffer,
- (letters.len() * std::mem::size_of::()).try_into()?,
- )?;
-
- // Then write utf-16 letters after that
- let mut text_section = MemoryArrayWriter::::alloc_array(buffer, letters.len())?;
- for (index, letter) in letters.iter().enumerate() {
- text_section.set_value_at(buffer, *letter, index)?;
- }
-
- let mut location = text_header.location();
- location.data_size += text_section.location().data_size;
-
- Ok(location)
-}
diff --git a/src/mem_writer.rs b/src/mem_writer.rs
new file mode 100644
index 00000000..5b5b6298
--- /dev/null
+++ b/src/mem_writer.rs
@@ -0,0 +1,271 @@
+use crate::minidump_format::{MDLocationDescriptor, MDRVA};
+use scroll::ctx::{SizeWith, TryIntoCtx};
+
+#[derive(Debug, thiserror::Error)]
+pub enum MemoryWriterError {
+ #[error("IO error when writing to DumpBuf")]
+ IOError(#[from] std::io::Error),
+ #[error("Failed integer conversion")]
+ TryFromIntError(#[from] std::num::TryFromIntError),
+ #[error("Failed to write to buffer")]
+ Scroll(#[from] scroll::Error),
+}
+
+type WriteResult = std::result::Result;
+
+macro_rules! size {
+ ($t:ty) => {
+ <$t>::size_with(&scroll::Endian::Little)
+ };
+}
+
+pub struct Buffer {
+ inner: Vec,
+}
+
+impl Buffer {
+ pub fn with_capacity(cap: usize) -> Self {
+ Self {
+ inner: Vec::with_capacity(cap),
+ }
+ }
+
+ #[inline]
+ pub fn position(&self) -> u64 {
+ self.inner.len() as u64
+ }
+
+ #[inline]
+ #[must_use]
+ fn reserve(&mut self, len: usize) -> usize {
+ let mark = self.inner.len();
+ self.inner.resize(self.inner.len() + len, 0);
+ mark
+ }
+
+ #[inline]
+ fn write(&mut self, val: N) -> Result
+ where
+ N: TryIntoCtx + SizeWith,
+ E: From,
+ {
+ self.write_at(self.inner.len(), val)
+ }
+
+ fn write_at(&mut self, offset: usize, val: N) -> Result
+ where
+ N: TryIntoCtx + SizeWith,
+ E: From,
+ {
+ let to_write = size!(N);
+ let remainder = self.inner.len() - offset;
+ if remainder < to_write {
+ self.inner
+ .resize(self.inner.len() + to_write - remainder, 0);
+ }
+
+ let dst = &mut self.inner[offset..offset + to_write];
+ val.try_into_ctx(dst, scroll::Endian::Little)
+ }
+
+ #[inline]
+ pub fn write_all(&mut self, buffer: &[u8]) {
+ self.inner.extend_from_slice(buffer);
+ }
+}
+
+impl From for Vec {
+ fn from(b: Buffer) -> Self {
+ b.inner
+ }
+}
+
+impl std::ops::Deref for Buffer {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct MemoryWriter {
+ pub position: MDRVA,
+ pub size: usize,
+ phantom: std::marker::PhantomData,
+}
+
+impl MemoryWriter
+where
+ T: TryIntoCtx + SizeWith,
+{
+ /// Create a slot for a type T in the buffer, we can fill right now with real values.
+ pub fn alloc_with_val(buffer: &mut Buffer, val: T) -> WriteResult {
+ // Mark the position as we may overwrite later
+ let position = buffer.position();
+ let size = buffer.write(val)?;
+
+ Ok(Self {
+ position: position as u32,
+ size,
+ phantom: std::marker::PhantomData,
+ })
+ }
+
+ /// Create a slot for a type T in the buffer, we can fill later with real values.
+ pub fn alloc(buffer: &mut Buffer) -> WriteResult {
+ let size = size!(T);
+ let position = buffer.reserve(size) as u32;
+
+ Ok(Self {
+ position: position as u32,
+ size,
+ phantom: std::marker::PhantomData,
+ })
+ }
+
+ /// Write actual values in the buffer-slot we got during `alloc()`
+ #[inline]
+ pub fn set_value(&mut self, buffer: &mut Buffer, val: T) -> WriteResult<()> {
+ Ok(buffer.write_at(self.position as usize, val).map(|_sz| ())?)
+ }
+
+ #[inline]
+ pub fn location(&self) -> MDLocationDescriptor {
+ MDLocationDescriptor {
+ data_size: size!(T) as u32,
+ rva: self.position,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct MemoryArrayWriter {
+ pub position: MDRVA,
+ array_size: usize,
+ phantom: std::marker::PhantomData,
+}
+
+impl MemoryArrayWriter {
+ #[inline]
+ pub fn write_bytes(buffer: &mut Buffer, slice: &[u8]) -> Self {
+ let position = buffer.position();
+ buffer.write_all(slice);
+
+ Self {
+ position: position as u32,
+ array_size: slice.len(),
+ phantom: std::marker::PhantomData,
+ }
+ }
+}
+
+impl MemoryArrayWriter
+where
+ T: TryIntoCtx + SizeWith + Copy,
+{
+ pub fn alloc_from_array(buffer: &mut Buffer, array: &[T]) -> WriteResult {
+ let array_size = array.len();
+ let position = buffer.reserve(array_size * size!(T));
+
+ for (idx, val) in array.iter().enumerate() {
+ buffer.write_at(position + idx * size!(T), *val)?;
+ }
+
+ Ok(Self {
+ position: position as u32,
+ array_size,
+ phantom: std::marker::PhantomData,
+ })
+ }
+}
+
+impl MemoryArrayWriter
+where
+ T: TryIntoCtx + SizeWith,
+{
+ /// Create a slot for a type T in the buffer, we can fill in the values in one go.
+ pub fn alloc_from_iter(
+ buffer: &mut Buffer,
+ iter: impl IntoIterator
- ,
+ ) -> WriteResult
+ where
+ I: std::iter::ExactSizeIterator
- ,
+ {
+ let iter = iter.into_iter();
+ let array_size = iter.len();
+ let size = size!(T);
+ let position = buffer.reserve(array_size * size);
+
+ for (idx, val) in iter.enumerate() {
+ buffer.write_at(position + idx * size, val)?;
+ }
+
+ Ok(Self {
+ position: position as u32,
+ array_size,
+ phantom: std::marker::PhantomData,
+ })
+ }
+
+ /// Create a slot for a type T in the buffer, we can fill later with real values.
+ /// This function fills it with `Default::default()`, which is less performant than
+ /// using uninitialized memory, but safe.
+ pub fn alloc_array(buffer: &mut Buffer, array_size: usize) -> WriteResult {
+ let position = buffer.reserve(array_size * size!(T));
+
+ Ok(Self {
+ position: position as u32,
+ array_size,
+ phantom: std::marker::PhantomData,
+ })
+ }
+
+ /// Write actual values in the buffer-slot we got during `alloc()`
+ #[inline]
+ pub fn set_value_at(&mut self, buffer: &mut Buffer, val: T, index: usize) -> WriteResult<()> {
+ Ok(buffer
+ .write_at(self.position as usize + size!(T) * index, val)
+ .map(|_sz| ())?)
+ }
+
+ #[inline]
+ pub fn location(&self) -> MDLocationDescriptor {
+ MDLocationDescriptor {
+ data_size: (self.array_size * size!(T)) as u32,
+ rva: self.position,
+ }
+ }
+
+ #[inline]
+ pub fn location_of_index(&self, idx: usize) -> MDLocationDescriptor {
+ MDLocationDescriptor {
+ data_size: size!(T) as u32,
+ rva: self.position + (size!(T) * idx) as u32,
+ }
+ }
+}
+
+pub fn write_string_to_location(
+ buffer: &mut Buffer,
+ text: &str,
+) -> WriteResult {
+ let letters: Vec = text.encode_utf16().collect();
+
+ // First write size of the string (x letters in u16, times the size of u16)
+ let text_header = MemoryWriter::::alloc_with_val(
+ buffer,
+ (letters.len() * std::mem::size_of::()).try_into()?,
+ )?;
+
+ // Then write utf-16 letters after that
+ let mut text_section = MemoryArrayWriter::::alloc_array(buffer, letters.len())?;
+ for (index, letter) in letters.iter().enumerate() {
+ text_section.set_value_at(buffer, *letter, index)?;
+ }
+
+ let mut location = text_header.location();
+ location.data_size += text_section.location().data_size;
+
+ Ok(location)
+}
From d777ecc06a3017d767bbdff43f135305a9331493 Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Tue, 12 Apr 2022 11:50:58 +0200
Subject: [PATCH 02/53] Begin fleshing out MacOS implementation
---
Cargo.toml | 4 +
src/lib.rs | 4 +
src/mac.rs | 3 +
src/mac/errors.rs | 20 ++
src/mac/minidump_writer.rs | 161 +++++++++++++++
src/mac/streams.rs | 7 +
src/mac/streams/memory_list.rs | 76 +++++++
src/mac/streams/module_list.rs | 262 ++++++++++++++++++++++++
src/mac/streams/system_info.rs | 258 ++++++++++++++++++++++++
src/mac/streams/thread_list.rs | 351 +++++++++++++++++++++++++++++++++
10 files changed, 1146 insertions(+)
create mode 100644 src/mac.rs
create mode 100644 src/mac/errors.rs
create mode 100644 src/mac/minidump_writer.rs
create mode 100644 src/mac/streams.rs
create mode 100644 src/mac/streams/memory_list.rs
create mode 100644 src/mac/streams/module_list.rs
create mode 100644 src/mac/streams/system_info.rs
create mode 100644 src/mac/streams/thread_list.rs
diff --git a/Cargo.toml b/Cargo.toml
index 1b8bd443..272835c2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -41,5 +41,9 @@ features = [
"Win32_System_Threading",
]
+[target.'cfg(target_os = "macos")'.dependencies]
+# Binds some additional mac specifics not in libc
+mach2 = "0.4"
+
[dev-dependencies]
minidump = "0.10"
diff --git a/src/lib.rs b/src/lib.rs
index da5ef8ea..13c5f82e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,6 +7,10 @@ cfg_if::cfg_if! {
mod windows;
pub use windows::*;
+ } else if #[cfg(target_os = "macos")] {
+ mod mac;
+
+ pub use mac::*;
}
}
diff --git a/src/mac.rs b/src/mac.rs
new file mode 100644
index 00000000..aaa7f530
--- /dev/null
+++ b/src/mac.rs
@@ -0,0 +1,3 @@
+pub mod errors;
+pub mod minidump_writer;
+mod streams;
diff --git a/src/mac/errors.rs b/src/mac/errors.rs
new file mode 100644
index 00000000..47536942
--- /dev/null
+++ b/src/mac/errors.rs
@@ -0,0 +1,20 @@
+use thiserror::Error;
+
+use mach2::kern_return::kern_return_t;
+
+#[derive(Debug, Error)]
+pub enum WriterError {
+ #[error("kernel error ({})", _0)]
+ Kernel(kern_return_t),
+}
+
+#[inline]
+pub(crate) fn kern_ret(func: impl FnOnce() -> kern_return_t) -> Result<(), WriterError> {
+ let res = func();
+
+ if res == KERN_SUCCESS {
+ Ok(())
+ } else {
+ Err(WriterError::Kerne(res))
+ }
+}
diff --git a/src/mac/minidump_writer.rs b/src/mac/minidump_writer.rs
new file mode 100644
index 00000000..ea8484cc
--- /dev/null
+++ b/src/mac/minidump_writer.rs
@@ -0,0 +1,161 @@
+use crate::mac::errors::WriterError;
+use crash_context::CrashContext;
+use std::io::{Seek, Write};
+
+pub type DumpBuf = Buffer;
+type Result = std::result::Result;
+
+#[derive(Debug)]
+pub struct DirSection<'a, W>
+where
+ W: Write + Seek,
+{
+ curr_idx: usize,
+ section: MemoryArrayWriter,
+ /// If we have to append to some file, we have to know where we currently are
+ destination_start_offset: u64,
+ destination: &'a mut W,
+ last_position_written_to_file: u64,
+}
+
+impl<'a, W> DirSection<'a, W>
+where
+ W: Write + Seek,
+{
+ fn new(
+ buffer: &mut DumpBuf,
+ index_length: u32,
+ destination: &'a mut W,
+ ) -> std::result::Result {
+ let dir_section =
+ MemoryArrayWriter::::alloc_array(buffer, index_length as usize)?;
+ Ok(DirSection {
+ curr_idx: 0,
+ section: dir_section,
+ destination_start_offset: destination.seek(SeekFrom::Current(0))?,
+ destination,
+ last_position_written_to_file: 0,
+ })
+ }
+
+ fn position(&self) -> u32 {
+ self.section.position
+ }
+
+ fn dump_dir_entry(
+ &mut self,
+ buffer: &mut DumpBuf,
+ dirent: MDRawDirectory,
+ ) -> std::result::Result<(), FileWriterError> {
+ self.section.set_value_at(buffer, dirent, self.curr_idx)?;
+
+ // Now write it to file
+
+ // First get all the positions
+ let curr_file_pos = self.destination.seek(SeekFrom::Current(0))?;
+ let idx_pos = self.section.location_of_index(self.curr_idx);
+ self.curr_idx += 1;
+
+ self.destination.seek(std::io::SeekFrom::Start(
+ self.destination_start_offset + idx_pos.rva as u64,
+ ))?;
+ let start = idx_pos.rva as usize;
+ let end = (idx_pos.rva + idx_pos.data_size) as usize;
+ self.destination.write_all(&buffer[start..end])?;
+
+ // Reset file-position
+ self.destination
+ .seek(std::io::SeekFrom::Start(curr_file_pos))?;
+
+ Ok(())
+ }
+
+ /// Writes 2 things to file:
+ /// 1. The given dirent into the dir section in the header (if any is given)
+ /// 2. Everything in the in-memory buffer that was added since the last call to this function
+ fn write_to_file(
+ &mut self,
+ buffer: &mut DumpBuf,
+ dirent: Option,
+ ) -> std::result::Result<(), FileWriterError> {
+ if let Some(dirent) = dirent {
+ self.dump_dir_entry(buffer, dirent)?;
+ }
+
+ let start_pos = self.last_position_written_to_file as usize;
+ self.destination.write_all(&buffer[start_pos..])?;
+ self.last_position_written_to_file = buffer.position();
+ Ok(())
+ }
+}
+
+pub struct MinidumpWriter {
+ /// The crash context as captured by an exception handler
+ crash_context: crash_context::CrashContext,
+ /// List of raw blocks of memory we've written into the stream. These are
+ /// referenced by other streams (eg thread list)
+ memory_blocks: Vec,
+}
+
+impl MinidumpWriter {
+ /// Creates a minidump writer
+ pub fn new(crash_context: crash_context::CrashContext) -> Self {
+ Self {
+ crash_context,
+ memory_blocks: Vec::new(),
+ }
+ }
+
+ pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result> {
+ let writers = {
+ let mut writers = vec![
+ Self::write_thread_list,
+ Self::write_memory_list,
+ Self::write_system_info,
+ Self::write_module_list,
+ Self::write_misc_info,
+ Self::write_breakpad_info,
+ ];
+
+ // Exception stream needs to be the last entry in this array as it may
+ // be omitted in the case where the minidump is written without an
+ // exception.
+ if self.crash_context.exception.is_some() {
+ writers.push_back(Self::write_exception);
+ }
+
+ writers
+ };
+
+ let num_writers = writers.len() as u32;
+ let mut buffer = Buffer::with_capacity(0);
+
+ let mut header_section = MemoryWriter::::alloc(buffer)?;
+ let mut dir_section = DirSection::new(buffer, num_writers, destination)?;
+
+ let header = MDRawHeader {
+ signature: MD_HEADER_SIGNATURE,
+ version: MD_HEADER_VERSION,
+ stream_count: num_writers,
+ stream_directory_rva: dir_section.position(),
+ checksum: 0, /* Can be 0. In fact, that's all that's
+ * been found in minidump files. */
+ time_date_stamp: std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)?
+ .as_secs() as u32, // TODO: This is not Y2038 safe, but thats how its currently defined as
+ flags: 0,
+ };
+ header_section.set_value(buffer, header)?;
+
+ // Ensure the header gets flushed. If we crash somewhere below,
+ // we should have a mostly-intact dump
+ dir_section.write_to_file(buffer, None)?;
+
+ for writer in writers {
+ let dirent = writer(self, buffer, dumper)?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+ }
+
+ Ok(buffer)
+ }
+}
diff --git a/src/mac/streams.rs b/src/mac/streams.rs
new file mode 100644
index 00000000..4d149fd6
--- /dev/null
+++ b/src/mac/streams.rs
@@ -0,0 +1,7 @@
+mod memory_list;
+mod module_list;
+mod system_info;
+mod thread_list;
+
+use super::minidump_writer::{DumpBuf, MinidumpWriter};
+use crate::mac::errors::ker_ret;
diff --git a/src/mac/streams/memory_list.rs b/src/mac/streams/memory_list.rs
new file mode 100644
index 00000000..f782cd93
--- /dev/null
+++ b/src/mac/streams/memory_list.rs
@@ -0,0 +1,76 @@
+use super::*;
+
+impl MiniDumpWriter {
+ fn write_memory_list(&mut self, buffer: &mut DumpBuf) -> Result {
+ // Include some memory around the instruction pointer if the crash was
+ // due to an exception
+ const IP_MEM_SIZE: usize = 256;
+
+ if self.crash_context.exc_info.is_some() {
+ let mut thread_state = thread_list_stream::ThreadState::default();
+ // SAFETY: syscall
+ if unsafe {
+ mach2::thread_act::thread_get_state(
+ tid,
+ THREAD_STATE_FLAVOR,
+ thread_state.state.as_mut_ptr(),
+ &mut thread_state.state_size,
+ )
+ } == mach2::kern_return::KERN_SUCCESS
+ {
+ } else {
+ None
+ }
+
+ let get_ip_block = |task, tid| -> Option {
+ let thread_state = Self::get_thread_state(tid).ok()?;
+
+ let ip = thread_state.pc();
+
+ // Bound it to the upper and lower bounds of the region
+ // it's contained within. If it's not in a known memory region,
+ // don't bother trying to write it.
+ let region = self.get_vm_region(ip).ok()?;
+
+ if ip < region.start || ip > region.end {
+ return None;
+ }
+
+ // Try to get IP_MEM_SIZE / 2 bytes before and after the IP, but
+ // settle for whatever's available.
+ let start = std::cmp::max(region.start, ip - IP_MEM_SIZE / 2);
+ let end = std::cmp::min(ip + IP_MEM_SIZE / 2, region.end);
+
+ Some(start..end)
+ };
+
+ if let Some(ip_range) = get_ip_block() {
+ let size = ip_range.end - ip_range.start;
+ let stack_buffer = self.read_task_memory(ip_range.start as _, size)?;
+ let ip_location = MDLocationDescriptor {
+ data_size: size as u32,
+ rva: buffer.position() as u32,
+ };
+ buffer.write_all(&stack_buffer)?;
+
+ self.memory_blocks.push(MDMemoryDescriptor {
+ start_of_memory_range: ip_range.start,
+ memory: ip_location,
+ });
+ }
+ }
+
+ let list_header =
+ MemoryWriter::::alloc_with_val(buffer, self.memory_blocks.len() as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::MemoryListStream as u32,
+ location: list_header.location(),
+ };
+
+ let block_list =
+ MemoryArrayWriter::::alloc_from_array(buffer, &self.memory_blocks)?;
+
+ dirent.location.data_size += block_list.location().data_size;
+ }
+}
diff --git a/src/mac/streams/module_list.rs b/src/mac/streams/module_list.rs
new file mode 100644
index 00000000..e603721a
--- /dev/null
+++ b/src/mac/streams/module_list.rs
@@ -0,0 +1,262 @@
+use super::*;
+
+#[cfg(target_pointer_width = "32")]
+compile_error!("this module assumes a 64-bit pointer width");
+
+fn all_image_addr(task: mach2::mach_types::task_name_t) -> Option {
+ let mut task_dyld_info = std::mem::MaybeUninit::::uninit();
+ let mut count = std::mem::size_of::()
+ / std::mem::size_of::();
+
+ // SAFETY: syscall
+ kern_ret(|| unsafe {
+ mach2::task::task_info(
+ task,
+ mach2::task_info::TASK_DYLD_INFO,
+ task_dyld_info.as_mut_ptr().cast(),
+ &mut count,
+ )
+ })
+ .ok()?;
+
+ Some(task_dyld_info.all_image_info_addr)
+}
+
+impl MiniDumpWriter {
+ fn write_module_list(&mut self, buffer: &mut DumpBuf) -> Result {
+ let modules = if let Some(all_images) = all_image_addr(self.crash_context.task) {
+
+ } else {
+ vec![]
+ };
+
+ let list_header = MemoryWriter::::alloc_with_val(buffer, modules.len() as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ModuleListStream as u32,
+ location: list_header.location(),
+ };
+
+ if !modules.is_empty() {
+ let mapping_list = MemoryArrayWriter::::alloc_from_iter(buffer, modules)?;
+ dirent.location.data_size += mapping_list.location().data_size;
+ }
+
+ Ok(dirent)
+ }
+
+ fn read_loaded_images(&self, all_images_addr: u64) -> Result, WriterError> {
+ // Read the structure inside of dyld that contains information about
+ // loaded images. We're reading from the desired task's address space.
+
+ // dyld_all_image_infos defined in usr/include/mach-o/dyld_images.h, we
+ // only need a couple of fields at the beginning
+ #[repr(C)]
+ struct AllImagesInfo {
+ version: u32, // == 1 in Mac OS X 10.4
+ info_array_count: u32,
+ info_array_addr: u64,
+ }
+
+ // dyld_image_info
+ #[repr(C)]
+ struct ImageInfo {
+ load_address: u64,
+ file_path: u64,
+ file_mod_date: u64,
+ }
+
+ // usr/include/mach-o/loader.h
+ #[repr(C)]
+ struct MachHeader {
+ magic: u32, // mach magic number identifier
+ cpu_type: i32, // cpu_type_t cpu specifier
+ cpu_sub_type: i32, // cpu_subtype_t machine specifier
+ file_type: u32, // type of file
+ num_commands: u32, // number of load commands
+ size_commands: u32, // size of all the load commands
+ flags: u32,
+ __reserved: u32,
+ }
+
+ // Here we make the assumption that dyld loaded at the same address in
+ // the crashed process vs. this one. This is an assumption made in
+ // "dyld_debug.c" and is said to be nearly always valid.
+ let dyld_all_info_buf = self.read_task_memory(all_images_addr, std::mem::size_of::())?;
+ let dyld_info: &AllImagesInfo = &*(dyld_all_info_buf.cast());
+
+ let dyld_info_buf = self.read_task_memory(dyld_info.info_array_addr, dyld_info.info_array_count * std::mem::size_of::())?;
+
+ let all_images = unsafe {
+ std::slice::from_raw_parts(dyld_info.buf.as_ptr().cast::(), dyld_info.info_array_count as usize)
+ };
+
+ let mut images = Vec::with_capacity(all_images.len();
+
+ for image in all_images {
+ let mach_header_buf = if let Ok(buf) = self.read_task_memory(image.load_address, std::mem::size_of::()) {
+ buf
+ } else {
+ continue;
+ };
+
+ let header: &MachHeader = &*(mach_header_buf.cast());
+ //let header_size = std::mem::size_of::() + header.size_commands;
+
+ let file_path = if image.file_path != 0 {
+ }
+ }
+
+ for (int i = 0; i < count; ++i) {
+ dyld_image_info& info = infoArray[i];
+
+ // First read just the mach_header from the image in the task.
+ vector mach_header_bytes;
+ if (ReadTaskMemory(images.task_,
+ info.load_address_,
+ sizeof(mach_header_type),
+ mach_header_bytes) != KERN_SUCCESS)
+ continue; // bail on this dynamic image
+
+ mach_header_type* header =
+ reinterpret_cast(&mach_header_bytes[0]);
+
+ // Now determine the total amount necessary to read the header
+ // plus all of the load commands.
+ size_t header_size =
+ sizeof(mach_header_type) + header->sizeofcmds;
+
+ if (ReadTaskMemory(images.task_,
+ info.load_address_,
+ header_size,
+ mach_header_bytes) != KERN_SUCCESS)
+ continue;
+
+ // Read the file name from the task's memory space.
+ string file_path;
+ if (info.file_path_) {
+ // Although we're reading kMaxStringLength bytes, it's copied in the
+ // the DynamicImage constructor below with the correct string length,
+ // so it's not really wasting memory.
+ file_path = ReadTaskString(images.task_, info.file_path_);
+ }
+
+ // Create an object representing this image and add it to our list.
+ DynamicImage* new_image;
+ new_image = new DynamicImage(&mach_header_bytes[0],
+ header_size,
+ info.load_address_,
+ file_path,
+ static_cast(info.file_mod_date_),
+ images.task_,
+ images.cpu_type_);
+
+ if (new_image->IsValid()) {
+ images.image_list_.push_back(DynamicImageRef(new_image));
+ } else {
+ delete new_image;
+ }
+ }
+
+ // sorts based on loading address
+ sort(images.image_list_.begin(), images.image_list_.end());
+ // remove duplicates - this happens in certain strange cases
+ // You can see it in DashboardClient when Google Gadgets plugin
+ // is installed. Apple's crash reporter log and gdb "info shared"
+ // both show the same library multiple times at the same address
+
+ vector::iterator it = unique(images.image_list_.begin(),
+ images.image_list_.end());
+ images.image_list_.erase(it, images.image_list_.end());
+ }
+
+ fn read_string(&self, addr: u64) -> Result {
+ // The problem is we don't know how much to read until we know how long
+ // the string is. And we don't know how long the string is, until we've read
+ // the memory! So, we'll try to read kMaxStringLength bytes
+ // (or as many bytes as we can until we reach the end of the vm region).
+ let size_to_end = {
+ let mut region_base = addr;
+ let mut region_size = 0;
+ let mut nesting_level = 0;
+ vm_region_submap_info_64 submap_info;
+ mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
+
+ // Get information about the vm region containing |address|
+ vm_region_recurse_info_t region_info;
+ region_info = reinterpret_cast(&submap_info);
+
+ kern_return_t result =
+ mach_vm_region_recurse(target_task,
+ ®ion_base,
+ ®ion_size,
+ &nesting_level,
+ region_info,
+ &info_count);
+
+ if (result == KERN_SUCCESS) {
+ // Get distance from |address| to the end of this region
+ *size_to_end = region_base + region_size -(mach_vm_address_t)address;
+
+ // If we want to handle strings as long as 4096 characters we may need
+ // to check if there's a vm region immediately following the first one.
+ // If so, we need to extend |*size_to_end| to go all the way to the end
+ // of the second region.
+ if (*size_to_end < 4096) {
+ // Second region starts where the first one ends
+ mach_vm_address_t region_base2 =
+ (mach_vm_address_t)(region_base + region_size);
+ mach_vm_size_t region_size2;
+
+ // Get information about the following vm region
+ result =
+ mach_vm_region_recurse(target_task,
+ ®ion_base2,
+ ®ion_size2,
+ &nesting_level,
+ region_info,
+ &info_count);
+
+ // Extend region_size to go all the way to the end of the 2nd region
+ if (result == KERN_SUCCESS
+ && region_base2 == region_base + region_size) {
+ region_size += region_size2;
+ }
+ }
+
+ *size_to_end = region_base + region_size -(mach_vm_address_t)address;
+ } else {
+ region_size = 0;
+ *size_to_end = 0;
+ }
+
+ return region_size;
+ };
+ mach_vm_size_t size_to_end;
+ GetMemoryRegionSize(target_task, address, &size_to_end);
+
+ if (size_to_end > 0) {
+ mach_vm_size_t size_to_read =
+ size_to_end > kMaxStringLength ? kMaxStringLength : size_to_end;
+
+ vector bytes;
+ if (ReadTaskMemory(target_task, address, (size_t)size_to_read, bytes) !=
+ KERN_SUCCESS)
+ return string();
+
+ //==============================================================================
+ // Returns the size of the memory region containing |address| and the
+ // number of bytes from |address| to the end of the region.
+ // We potentially, will extend the size of the original
+ // region by the size of the following region if it's contiguous with the
+ // first in order to handle cases when we're reading strings and they
+ // straddle two vm regions.
+ //
+ static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task,
+ const uint64_t address,
+ mach_vm_size_t* size_to_end) {
+
+ }
+ }
+ }
+}
diff --git a/src/mac/streams/system_info.rs b/src/mac/streams/system_info.rs
new file mode 100644
index 00000000..b20ba27b
--- /dev/null
+++ b/src/mac/streams/system_info.rs
@@ -0,0 +1,258 @@
+use super::*;
+use crate::minidump_format::*;
+
+fn sysctl_by_name(name: &[u8]) -> T {
+ let mut out = T::default();
+ let mut len = std::mem::size_of_val(&out);
+
+ // SAFETY: syscall
+ unsafe {
+ if libc::sysctlbyname(
+ name.as_ptr().cast(),
+ (&mut out).cast(),
+ &mut len,
+ std::ptr::null_mut(),
+ 0,
+ ) != 0
+ {
+ // log?
+ T::default()
+ } else {
+ out
+ }
+ }
+}
+
+fn int_sysctl_by_name + Default>(name: &[u8]) -> T {
+ let val = sysctl_by_name::(name);
+ T::try_from(val).unwrap_or_default()
+}
+
+fn sysctl_string(name: &[u8]) -> String {
+ let mut buf_len = 0;
+
+ // SAFETY: syscalls
+ let string_buf = unsafe {
+ // Retrieve the size of the string (including null terminator)
+ if libc::sysctlbyname(
+ name.as_ptr().cast(),
+ std::ptr::null_mut(),
+ &mut buf_len,
+ std::ptr::null_mut(),
+ 0,
+ ) != 0
+ || buf_len <= 1
+ {
+ return String::new();
+ }
+
+ let mut buff = Vec::new();
+ buff.resize(buf_len, 0);
+
+ if libc::sysctlbyname(
+ name.as_ptr().cast(),
+ buff.as_mut_ptr().cast(),
+ &mut buf_len,
+ std::ptr::null_mut(),
+ 0,
+ ) != 0
+ {
+ return String::new();
+ }
+
+ buff.pop(); // remove null terminator
+ buff
+ };
+
+ String::from_utf8(string_buf).unwrap_or_default()
+}
+
+/// Retrieve the OS version information.
+///
+/// Note that this only works on 10.13.4+, but that release is over 4 years old
+/// and 1 version behind the latest unsupported release at the time of this writing
+///
+/// Note that Breakpad/Crashpad use a private API in CoreFoundation to do this
+/// via _CFCopySystemVersionDictionary->_kCFSystemVersionProductVersionKey
+fn os_version() -> (u32, u32, u32) {
+ let vers = sysctl_string(b"kern.osproductversion\0");
+
+ let inner = || {
+ let mut it = vers.split('.');
+
+ let major: u32 = it.next()?.parse().ok()?;
+ let minor: u32 = it.next()?.parse().ok()?;
+ let patch: u32 = it.next().and_then(|p| p.parse().ok()).unwrap_or_default();
+
+ Some((major, minor, patch))
+ };
+
+ inner().unwrap_or_default()
+}
+
+/// Retrieves the OS build version.
+///
+/// Note that Breakpad/Crashpad use a private API in CoreFoundation to do this
+/// via _CFCopySystemVersionDictionary->_kCFSystemVersionBuildVersionKey. I have
+/// no idea how long this has been the case, but the same information can be
+/// retrieved via `sysctlbyname` via the `kern.osversion` key as seen by comparing
+/// its value versus the output of the `sw_vers -buildVersion` command
+#[inline]
+fn build_version() -> String {
+ sysctl_string(b"kern.osversion\0")
+}
+
+/// Retrieves more detailed information on the cpu.
+///
+/// Note that this function is only implemented on `x86_64` as Apple doesn't
+/// expose similar info on `aarch64` (or at least, not via the same mechanisms)
+fn read_cpu_info(cpu: &mut format::CPU_INFORMATION) {
+ if !cfg!(target_arch = "x86_64") {
+ return;
+ }
+
+ let mut md_feats = 1 << 2 /*PF_COMPARE_EXCHANGE_DOUBLE*/;
+ let features: u64 = sysctl_by_name(b"machdep.cpu.feature_bits\0");
+
+ // Map the cpuid feature to its equivalent minidump cpu feature.
+ // See https://en.wikipedia.org/wiki/CPUID for where the values for the
+ // various cpuid bits come from, and
+ // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
+ // for where the bits for the the minidump come from
+ macro_rules! map_feature {
+ ($set:expr, $cpuid_bit:expr, $md_bit:expr) => {
+ if $set & (1 << $cpuid_bit) != 0 {
+ md_feats |= 1 << $md_bit;
+ }
+ };
+ }
+
+ map_feature!(
+ features, 4, /*TSC*/
+ 8 /* PF_RDTSC_INSTRUCTION_AVAILABLE */
+ );
+ map_feature!(features, 6 /*PAE*/, 9 /* PF_PAE_ENABLED */);
+ map_feature!(
+ features, 23, /*MMX*/
+ 3 /* PF_MMX_INSTRUCTIONS_AVAILABLE */
+ );
+ map_feature!(
+ features, 25, /*SSE*/
+ 6 /* PF_XMMI_INSTRUCTIONS_AVAILABLE */
+ );
+ map_feature!(
+ features, 26, /*SSE2*/
+ 10 /* PF_XMMI64_INSTRUCTIONS_AVAILABLE */
+ );
+ map_feature!(
+ features, 32, /*SSE3*/
+ 13 /* PF_SSE3_INSTRUCTIONS_AVAILABLE */
+ );
+ map_feature!(
+ features, 45, /*CX16*/
+ 14 /* PF_COMPARE_EXCHANGE128 */
+ );
+ map_feature!(features, 58 /*XSAVE*/, 17 /* PF_XSAVE_ENABLED */);
+ map_feature!(
+ features, 62, /*RDRAND*/
+ 28 /* PF_RDRAND_INSTRUCTION_AVAILABLE */
+ );
+
+ let ext_features: u64 = sysctl_by_name(b"machdep.cpu.extfeature_bits\0");
+
+ map_feature!(
+ ext_features,
+ 27, /* RDTSCP */
+ 32 /* PF_RDTSCP_INSTRUCTION_AVAILABLE */
+ );
+ map_feature!(
+ ext_features,
+ 31, /* 3DNOW */
+ 7 /* PF_3DNOW_INSTRUCTIONS_AVAILABLE */
+ );
+
+ let leaf_features: u32 = sysctl_by_name(b"machdep.cpu.leaf7_feature_bits\0");
+ map_feature!(
+ leaf_features,
+ 0, /* F7_FSGSBASE */
+ 22 /* PF_RDWRFSGSBASE_AVAILABLE */
+ );
+
+ // In newer production kernels, NX is always enabled.
+ // See 10.15.0 xnu-6153.11.26/osfmk/x86_64/pmap.c nx_enabled.
+ md_feats |= 1 << 12 /* PF_NX_ENABLED */;
+
+ // All CPUs that Apple is known to have shipped should support DAZ.
+ md_feats |= 1 << 11 /* PF_SSE_DAZ_MODE_AVAILABLE */;
+
+ // minidump_common::format::OtherCpuInfo is just 2 adjacent u64's, we only
+ // set the first, so just do a direct write to the bytes
+ cpu[..std::mem::size_of::()].copy_from_slice(md_feats.to_ne_bytes());
+}
+
+impl MiniDumpWriter {
+ fn write_system_info(&mut self, buffer: &mut DumpBuf) -> Result {
+ let mut info_section = MemoryWriter::::alloc(buffer)?;
+ let dirent = MDRawDirectory {
+ stream_type: MDStreamType::SystemInfoStream as u32,
+ location: info_section.location(),
+ };
+
+ let number_of_processors: u8 = int_sysctl_by_name(b"hw.ncpu\0");
+ // SAFETY: POD buffer
+ let mut cpu: format::CPU_INFORMATION = unsafe { std::mem::zeroed() };
+ read_cpu_info(&mut cpu);
+
+ cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ let processor_architecture = MDCPUArchitecture::PROCESSOR_ARCHITECTURE_AMD64;
+
+ // machdep.cpu.family and machdep.cpu.model already take the extended family
+ // and model IDs into account. See 10.9.2 xnu-2422.90.20/osfmk/i386/cpuid.c
+ // cpuid_set_generic_info().
+ let processor_level: u16 = int_sysctl_by_name(b"machdep.cpu.family\0");
+ let model: u8 = int_sysctl_by_name(b"machdep.cpu.model\0");
+ let stepping: u8 = int_sysctl_by_name(b"machdep.cpu.stepping\0");
+
+ let processor_revision: u16 = (model << 8) | stepping;
+ } else if #[cfg(target_arch = "aarch64")] {
+ let processor_architecture = MDCPUArchitecture::PROCESSOR_ARCHITECTURE_ARM64;
+
+ let family: u32 = sysctl_by_name(b"hw.cpufamily\0");
+
+ let processor_level = (family & 0xffff0000 >> 16) as u16;
+ let processor_revision = (family & 0x0000ffff) as u16;
+ } else {
+ compile_error!("unsupported target architecture");
+ }
+ }
+
+ let (major_version, minor_version, build_number) = os_version();
+ let os_version_loc = write_string_to_location(buffer, &build_version())?;
+
+ let info = MDRawSystemInfo {
+ // CPU
+ processor_architecture: processor_architecture as u16,
+ processor_level,
+ processor_revision,
+ number_of_processors,
+ product_type,
+ cpu,
+
+ // OS
+ platform_id: PlatformId::MacOs,
+ product_type: 1, // VER_NT_WORKSTATION, could also be VER_NT_SERVER but...seriously?
+ major_version,
+ minor_version,
+ build_number,
+ csd_version_rva: os_version_loc.rva,
+
+ suite_mask: 0,
+ reserved2: 0,
+ };
+
+ info_section.set_value(buffer, info)?;
+
+ Ok(dirent)
+ }
+}
diff --git a/src/mac/streams/thread_list.rs b/src/mac/streams/thread_list.rs
new file mode 100644
index 00000000..6e54f834
--- /dev/null
+++ b/src/mac/streams/thread_list.rs
@@ -0,0 +1,351 @@
+use super::*;
+
+// From /usr/include/mach/machine/thread_state.h
+const THREAD_STATE_MAX: usize = 1296;
+
+cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ /// x86_THREAD_STATE64 in /usr/include/mach/i386/thread_status.h
+ const THREAD_STATE_FLAVOR: u32 = 4;
+ } else if #[cfg(target_arch = "aarch64")] {
+ /// ARM_THREAD_STATE64 in /usr/include/mach/arm/thread_status.h
+ const THREAD_STATE_FLAVOR: u32 = 6;
+
+ // Missing from mach2 atm
+ // _STRUCT_ARM_THREAD_STATE64 from /usr/include/mach/arm/_structs.h
+ #[repr(C)]
+ struct Arm64ThreadState {
+ x: [u64; 29],
+ fp: u64,
+ lr: u64,
+ sp: u64,
+ pc: u64,
+ cpsr: u32,
+ __pad: u32,
+ }
+ }
+}
+
+struct ThreadState {
+ state: [u32; THREAD_STATE_MAX],
+ state_size: u32,
+}
+
+impl Default for ThreadState {
+ fn default() -> Self {
+ Self {
+ state: [0u32; THREAD_STATE_MAX],
+ state_size: THREAD_STATE_MAX * std::mem::size_of::() as u32,
+ }
+ }
+}
+
+impl ThreadState {
+ pub fn pc(&self) -> u64 {
+ cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ let x86_64_state: &mach2::structs::x86_thread_state64_t = &*(thread_state.state.as_ptr().cast());
+ x86_64_state.__pc
+ } else if #[cfg(target_arch = "aarch64")] {
+ let aarch64_state: &Arm64ThreadState = &*(thread_state.state.as_ptr().cast());
+ aarch64_state.pc
+ }
+ }
+ }
+}
+
+pub(crate) struct VMRegionInfo {
+ pub(crate) info: mach2::vm_region::vm_region_submap_info_64,
+ pub(crate) range: std::ops::Range,
+}
+
+impl MinidumpWriter {
+ fn write_thread_list(&mut self, buffer: &mut DumpBuf) -> Result {
+ // Retrieve the list of threads from the task that crashed.
+ // SAFETY: syscall
+ let mut threads = std::ptr::null_mut();
+ let mut thread_count = 0;
+
+ kern_ret(|| unsafe {
+ mach2::task::task_threads(self.crash_context.task, &mut threads, &mut thread_count)
+ })?;
+
+ // Ignore the thread that handled the exception
+ if self.crash_context.handler_thread != mach2::port::MACH_PORT_NULL {
+ thread_count -= 1;
+ }
+
+ let list_header = MemoryWriter::::alloc_with_val(buffer, thread_count as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ThreadListStream as u32,
+ location: list_header.location(),
+ };
+
+ let mut thread_list = MemoryArrayWriter::::alloc_array(buffer, num_threads)?;
+ dirent.location.data_size += thread_list.location().data_size;
+
+ let threads = unsafe { std::slice::from_raw_parts(threads, thread_count as usize) };
+
+ for (i, tid) in threads.iter().enumerate() {
+ let thread = self.write_thread(buffer, tid)?;
+ thread_list.set_value_at(buffer, thread, i)?;
+ }
+
+ Ok(dirent)
+ }
+
+ fn write_thread(&mut self, buffer: &mut DumpBuf, tid: u32) -> Result {
+ let mut thread = MDRawThread {
+ thread_id: tid,
+ suspend_count: 0,
+ priority_class: 0,
+ priority: 0,
+ teb: 0,
+ stack: MDMemoryDescriptor::default(),
+ thread_context: MDLocationDescriptor::default(),
+ };
+
+ let thread_state = Self::get_thread_state(tid)?;
+
+ cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ let x86_64_state: &mach2::structs::x86_thread_state64_t = &*(thread_state.state.as_ptr().cast());
+
+ self.write_stack_from_start_address(x86_64_state.__rsp, buffer, &mut thread)?;
+ } else if #[cfg(target_arch = "aarch64")] {
+ let aarch64_state: &Arm64ThreadState = &*(thread_state.state.as_ptr().cast());
+ self.write_stack_from_start_address(aarch64_state.sp, buffer, &mut thread)?;
+ } else {
+ compile_error!("unsupported target arch");
+ }
+ }
+
+ let mut cpu: RawContextCPU = Default::default();
+ Self::fill_cpu_context(thread_state, &mut cpu);
+ let cpu_section = MemoryWriter::alloc_with_val(buffer, cpu)?;
+ thread.thread_context = cpu_section.location();
+ Ok(thread)
+ }
+
+ fn get_thread_state(tid: u32) -> Result {
+ let mut thread_state = ThreadState::default();
+
+ // SAFETY: syscall
+ kern_ret(|| unsafe {
+ mach2::thread_act::thread_get_state(
+ tid,
+ THREAD_STATE_FLAVOR,
+ thread_state.state.as_mut_ptr(),
+ &mut thread_state.state_size,
+ )
+ })?;
+
+ Ok(thread_state)
+ }
+
+ fn write_stack_from_start_address(
+ &mut self,
+ start: u64,
+ buffer: &mut DumpBuf,
+ thread: &mut MDRawThread,
+ ) -> Result<(), WriterError> {
+ thread.stack.start_of_memory_range = start.try_into()?;
+ thread.stack.memory.data_size = 0;
+ thread.stack.memory.rva = buffer.position() as u32;
+
+ let stack_size = self.calculate_stack_size(start);
+
+ let stack_location = if stack_size == 0 {
+ // In some situations the stack address for the thread can come back 0.
+ // In these cases we skip over the threads in question and stuff the
+ // stack with a clearly borked value.
+ thread.stack.start_of_memory_range = 0xdeadbeef;
+
+ let stack_location = MDLocationDescriptor {
+ data_size: 16,
+ rva: buffer.position() as u32,
+ };
+ buffer.write_all(0xdeadbeefu64.as_ne_bytes())?;
+ buffer.write_all(0xdeadbeefu64.as_ne_bytes())?;
+ stack_location
+ } else {
+ let stack_buffer = self.read_task_memory(start, stack_size)?;
+ let stack_location = MDLocationDescriptor {
+ data_size: stack_buffer.len() as u32,
+ rva: buffer.position() as u32,
+ };
+ buffer.write_all(&stack_buffer)?;
+ stack_location
+ };
+
+ thread.stack.memory = stack_location;
+ self.memory_blocks.push(thread.stack);
+ Ok(())
+ }
+
+ fn calculate_stack_size(&self, start_address: u64) -> usize {
+ if start_address == 0 {
+ return 0;
+ }
+
+ let mut region = if let Ok(region) = self.get_vm_region(start_address) {
+ region
+ } else {
+ return 0;
+ };
+
+ // Failure or stack corruption, since mach_vm_region had to go
+ // higher in the process address space to find a valid region.
+ if start_address < region.range.start {
+ return 0;
+ }
+
+ // If the user tag is VM_MEMORY_STACK, look for more readable regions with
+ // the same tag placed immediately above the computed stack region. Under
+ // some circumstances, the stack for thread 0 winds up broken up into
+ // multiple distinct abutting regions. This can happen for several reasons,
+ // including user code that calls setrlimit(RLIMIT_STACK, ...) or changes
+ // the access on stack pages by calling mprotect.
+ if region.info.user_tag == mach2::vm_statistics::VM_MEMORY_STACK {
+ loop {
+ let proposed_next_region_base = region.range.end;
+
+ region = if let Ok(reg) = self.get_vm_region(region.range.end) {
+ reg
+ } else {
+ break;
+ };
+
+ if region.range.start != proposed_next_region_base
+ || region.info.user_tag != mach2::vm_statistics::VM_MEMORY_STACK
+ || (region.info.protection & mach2::vm_prot::VM_PROT_READ) == 0
+ {
+ break;
+ }
+
+ stack_region_size += region.range.end - region.range.start;
+ }
+ }
+
+ stack_region_base + stack_region_size - start_addr
+ }
+
+ fn read_task_memory(&self, address: u64, length: usize) -> Result, WriterError> {
+ let sys_page_size = libc::getpagesize();
+
+ // use the negative of the page size for the mask to find the page address
+ let page_address = address & (-sys_page_size);
+ let last_page_address = (address + length + (sys_page_size - 1)) & (-sys_page_size);
+
+ let page_size = last_page_address - page_address;
+ let mut local_start = std::ptr::null_mut();
+ let mut local_length = 0;
+
+ kern_ret(|| unsafe {
+ mach2::vm::mach_vm_read(
+ self.crash_context.task,
+ page_address,
+ page_size,
+ &mut local_start,
+ &mut local_length,
+ )
+ })?;
+
+ let mut buffer = Vec::with_capacity(length);
+
+ let task_buffer =
+ std::slice::from_raw_parts(local_start.offset(address - page_address), length);
+ buffer.extend_from_slice(task_buffer);
+
+ // Don't worry about the return here, if something goes wrong there's probably
+ // not much we can do about, and we have what we want anyways
+ mach2::vm::mach_vm_deallocate(mach2::traps::mach_task_self(), local_start, local_length);
+
+ Ok(buffer)
+ }
+
+ fn fill_cpu_context(thread_state: &ThreadState, out: &mut RawContextCPU) {
+ cfg_if::cfg_if! {
+ if #[cfg(target_arch = "x86_64")] {
+ out.context_flags = format::ContextFlagsCpu::CONTEXT_AMD64.bits();
+
+ let ts: &Arm64ThreadState = &*(thread_state.state.as_ptr().cast());
+
+ out.rax = ts.__rax;
+ out.rbx = ts.__rbx;
+ out.rcx = ts.__rcx;
+ out.rdx = ts.__rdx;
+ out.rdi = ts.__rdi;
+ out.rsi = ts.__rsi;
+ out.rbp = ts.__rbp;
+ out.rsp = ts.__rsp;
+ out.r8 = ts.__r8;
+ out.r9 = ts.__r9;
+ out.r10 = ts.__r10;
+ out.r11 = ts.__r11;
+ out.r12 = ts.__r12;
+ out.r13 = ts.__r13;
+ out.r14 = ts.__r14;
+ out.r15 = ts.__r15;
+ out.rip = ts.__rip;
+ // according to AMD's software developer guide, bits above 18 are
+ // not used in the flags register. Since the minidump format
+ // specifies 32 bits for the flags register, we can truncate safely
+ // with no loss.
+ out.eflags = ts.__rflags as _;
+ out.cs = ts.__cs;
+ out.fs = ts.__fs;
+ out.gs = ts.__gs;
+ } else if #[cfg(target_arch = "aarch64")] {
+ // This is kind of a lie as we don't actually include the full float state..?
+ out.context_flags = format::ContextFlagsArm64Old::CONTEXT_ARM64_OLD_FULL.bits() as u64;
+
+ let ts: &Arm64ThreadState = &*(thread_state.state.as_ptr().cast());
+
+ out.cpsr = ts.cpsr;
+ out.iregs[..28].copy_from_slice(&ts.x[..28]);
+ out.iregs[29] = ts.fp;
+ out.iregs[30] = ts.lr;
+ out.sp = ts.sp;
+ out.pc = ts.pc;
+ } else {
+ compile_error!("unsupported target arch");
+ }
+ }
+ }
+
+ fn get_vm_region(&self, addr: u64) -> Result {
+ let mut region_base = addr;
+ let mut region_size = 0;
+ let mut nesting_level = 0;
+ let mut region_info = 0;
+ let mut submap_info = std::mem::MaybeUninit::::uninit();
+
+ // mach/vm_region.h
+ const VM_REGION_SUBMAP_INFO_COUNT_64: u32 =
+ (std::mem::size_of::()
+ / std::mem::size_of::()) as u32;
+
+ let mut info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
+
+ kern_ret(||
+ // SAFETY: syscall
+ unsafe {
+ mach2::vm::mach_vm_region_recurse(
+ self.crash_context.task,
+ &mut region_base,
+ &mut region_size,
+ &mut nesting_level,
+ submap_info.as_mut_ptr().cast(),
+ &mut info_count,
+ )
+ })?;
+
+ Ok(VMRegionInfo {
+ // SAFETY: this will be valid if the syscall succeeded
+ info: unsafe { submap_info.assume_init() },
+ range: region_base..region_base + region_base,
+ })
+ }
+}
From 977ebb221c7d6cef9a40abb52cb089dda973e82f Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Tue, 12 Apr 2022 17:52:53 +0200
Subject: [PATCH 03/53] Further fleshing out
---
src/mac/errors.rs | 2 +
src/mac/streams/module_list.rs | 446 ++++++++++++++++++++-------------
2 files changed, 274 insertions(+), 174 deletions(-)
diff --git a/src/mac/errors.rs b/src/mac/errors.rs
index 47536942..00f99008 100644
--- a/src/mac/errors.rs
+++ b/src/mac/errors.rs
@@ -6,6 +6,8 @@ use mach2::kern_return::kern_return_t;
pub enum WriterError {
#[error("kernel error ({})", _0)]
Kernel(kern_return_t),
+ #[error("detected an invalid mach image header")]
+ InvalidMachHeader,
}
#[inline]
diff --git a/src/mac/streams/module_list.rs b/src/mac/streams/module_list.rs
index e603721a..ce61915d 100644
--- a/src/mac/streams/module_list.rs
+++ b/src/mac/streams/module_list.rs
@@ -22,10 +22,29 @@ fn all_image_addr(task: mach2::mach_types::task_name_t) -> Option {
Some(task_dyld_info.all_image_info_addr)
}
+// dyld_image_info
+#[repr(C)]
+struct ImageInfo {
+ load_address: u64,
+ file_path: u64,
+ file_mod_date: u64,
+}
+
+// usr/include/mach-o/loader.h, the file type for the main executable image
+const MH_EXECUTE: u32 = 0x2;
+// usr/include/mach-o/loader.h, magic number for MachHeader
+const MH_MAGIC_64: u32 = 0xfeedfacf;
+// usr/include/mach-o/loader.h, command to map a segment
+const LC_SEGMENT_64: u32 = 0x19;
+// usr/include/mach-o/loader.h, dynamically linked shared lib ident
+const LC_ID_DYLIB: u32 = 0xd;
+// usr/include/mach-o/loader.h, the uuid
+const LC_UUID: u32 = 0x1b;
+
impl MiniDumpWriter {
fn write_module_list(&mut self, buffer: &mut DumpBuf) -> Result {
let modules = if let Some(all_images) = all_image_addr(self.crash_context.task) {
-
+ self.read_loaded_modules(all_images)?
} else {
vec![]
};
@@ -45,7 +64,7 @@ impl MiniDumpWriter {
Ok(dirent)
}
- fn read_loaded_images(&self, all_images_addr: u64) -> Result, WriterError> {
+ fn read_loaded_modules(&self, all_images_addr: u64) -> Result {
// Read the structure inside of dyld that contains information about
// loaded images. We're reading from the desired task's address space.
@@ -58,205 +77,284 @@ impl MiniDumpWriter {
info_array_addr: u64,
}
- // dyld_image_info
- #[repr(C)]
- struct ImageInfo {
- load_address: u64,
- file_path: u64,
- file_mod_date: u64,
- }
-
- // usr/include/mach-o/loader.h
- #[repr(C)]
- struct MachHeader {
- magic: u32, // mach magic number identifier
- cpu_type: i32, // cpu_type_t cpu specifier
- cpu_sub_type: i32, // cpu_subtype_t machine specifier
- file_type: u32, // type of file
- num_commands: u32, // number of load commands
- size_commands: u32, // size of all the load commands
- flags: u32,
- __reserved: u32,
- }
-
// Here we make the assumption that dyld loaded at the same address in
// the crashed process vs. this one. This is an assumption made in
// "dyld_debug.c" and is said to be nearly always valid.
- let dyld_all_info_buf = self.read_task_memory(all_images_addr, std::mem::size_of::())?;
+ let dyld_all_info_buf =
+ self.read_task_memory(all_images_addr, std::mem::size_of::())?;
let dyld_info: &AllImagesInfo = &*(dyld_all_info_buf.cast());
- let dyld_info_buf = self.read_task_memory(dyld_info.info_array_addr, dyld_info.info_array_count * std::mem::size_of::())?;
+ let dyld_info_buf = self.read_task_memory(
+ dyld_info.info_array_addr,
+ dyld_info.info_array_count * std::mem::size_of::(),
+ )?;
let all_images = unsafe {
- std::slice::from_raw_parts(dyld_info.buf.as_ptr().cast::(), dyld_info.info_array_count as usize)
+ std::slice::from_raw_parts(
+ dyld_info.buf.as_ptr().cast::(),
+ dyld_info.info_array_count as usize,
+ )
};
-
- let mut images = Vec::with_capacity(all_images.len();
+
+ let mut images = Vec::with_capacity(all_images.len());
for image in all_images {
- let mach_header_buf = if let Ok(buf) = self.read_task_memory(image.load_address, std::mem::size_of::()) {
- buf
+ // Apparently MacOS will happily list the same image multiple times
+ // for some reason, so only add images once
+ let insert_index = if let Err(i) =
+ images.binary_search_by(|img| image.load_address.cmp(&img.load_address))
+ {
+ i
} else {
continue;
};
- let header: &MachHeader = &*(mach_header_buf.cast());
- //let header_size = std::mem::size_of::() + header.size_commands;
-
- let file_path = if image.file_path != 0 {
+ if let Ok(module) = self.read_module(image) {
+ images.insert(insert_index, module);
}
}
- for (int i = 0; i < count; ++i) {
- dyld_image_info& info = infoArray[i];
-
- // First read just the mach_header from the image in the task.
- vector mach_header_bytes;
- if (ReadTaskMemory(images.task_,
- info.load_address_,
- sizeof(mach_header_type),
- mach_header_bytes) != KERN_SUCCESS)
- continue; // bail on this dynamic image
-
- mach_header_type* header =
- reinterpret_cast(&mach_header_bytes[0]);
-
- // Now determine the total amount necessary to read the header
- // plus all of the load commands.
- size_t header_size =
- sizeof(mach_header_type) + header->sizeofcmds;
-
- if (ReadTaskMemory(images.task_,
- info.load_address_,
- header_size,
- mach_header_bytes) != KERN_SUCCESS)
- continue;
-
- // Read the file name from the task's memory space.
- string file_path;
- if (info.file_path_) {
- // Although we're reading kMaxStringLength bytes, it's copied in the
- // the DynamicImage constructor below with the correct string length,
- // so it's not really wasting memory.
- file_path = ReadTaskString(images.task_, info.file_path_);
- }
-
- // Create an object representing this image and add it to our list.
- DynamicImage* new_image;
- new_image = new DynamicImage(&mach_header_bytes[0],
- header_size,
- info.load_address_,
- file_path,
- static_cast(info.file_mod_date_),
- images.task_,
- images.cpu_type_);
-
- if (new_image->IsValid()) {
- images.image_list_.push_back(DynamicImageRef(new_image));
- } else {
- delete new_image;
- }
+ // The modules are sorted by load address, but we always want the
+ // main executable to be first in the minidump
+
+ Ok(images)
}
- // sorts based on loading address
- sort(images.image_list_.begin(), images.image_list_.end());
- // remove duplicates - this happens in certain strange cases
- // You can see it in DashboardClient when Google Gadgets plugin
- // is installed. Apple's crash reporter log and gdb "info shared"
- // both show the same library multiple times at the same address
+ fn read_module(&self, image: ImageInfo, buf: &mut DumpBuf) -> Result {
+ // usr/include/mach-o/loader.h
+ #[repr(C)]
+ struct MachHeader {
+ magic: u32, // mach magic number identifier
+ cpu_type: i32, // cpu_type_t cpu specifier
+ cpu_sub_type: i32, // cpu_subtype_t machine specifier
+ file_type: u32, // type of file
+ num_commands: u32, // number of load commands
+ size_commands: u32, // size of all the load commands
+ flags: u32,
+ __reserved: u32,
+ }
+
+ // usr/include/mach-o/loader.h
+ #[repr(C)]
+ struct LoadCommand {
+ cmd: u32, // type of load command
+ cmd_size: u32, // total size of the command in bytes
+ }
+
+ /*
+ * The 64-bit segment load command indicates that a part of this file is to be
+ * mapped into a 64-bit task's address space. If the 64-bit segment has
+ * sections then section_64 structures directly follow the 64-bit segment
+ * command and their size is reflected in cmdsize.
+ */
+ #[repr(C)]
+ struct SegmentCommand64 {
+ cmd: u32, // type of load command
+ cmd_size: u32, // total size of the command in bytes
+ segment_name: [u8; 16],
+ vm_addr: u64, // memory address the segment is mapped to
+ vm_size: u64, // total size of the segment
+ file_off: u64, // file offset of the segment
+ file_size: u64, // amount mapped from the file
+ max_prot: i32, // maximum VM protection
+ init_prot: i32, // initial VM protection
+ num_sections: u32, // number of sections in the segment
+ flags: u32,
+ }
- vector::iterator it = unique(images.image_list_.begin(),
- images.image_list_.end());
- images.image_list_.erase(it, images.image_list_.end());
+ /*
+ * Dynamicly linked shared libraries are identified by two things. The
+ * pathname (the name of the library as found for execution), and the
+ * compatibility version number. The pathname must match and the compatibility
+ * number in the user of the library must be greater than or equal to the
+ * library being used. The time stamp is used to record the time a library was
+ * built and copied into user so it can be use to determined if the library used
+ * at runtime is exactly the same as used to built the program.
+ */
+ #[repr(C)]
+ struct Dylib {
+ name: u32, // offset from the load command start to the pathname
+ timestamp: u32, // library's build time stamp
+ current_version: u32, // library's current version number
+ compatibility_version: u32, // library's compatibility vers number
+ }
+
+ /*
+ * A dynamically linked shared library (filetype == MH_DYLIB in the mach header)
+ * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library.
+ * An object that uses a dynamically linked shared library also contains a
+ * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or
+ * LC_REEXPORT_DYLIB) for each library it uses.
+ */
+ #[repr(C)]
+ struct DylibCommand {
+ cmd: u32, // type of load command
+ cmd_size: u32, // total size of the command in bytes, including pathname string
+ dylib: Dylib, // library identification
+ }
+
+ /*
+ * The uuid load command contains a single 128-bit unique random number that
+ * identifies an object produced by the static link editor.
+ */
+ #[repr(C)]
+ struct UuidCommand {
+ cmd: u32, // type of load command
+ cmd_size: u32, // total size of the command in bytes
+ uuid: [u8; 16],
+ }
+
+ let mach_header_buf =
+ self.read_task_memory(image.load_address, std::mem::size_of::())?;
+
+ let header: &MachHeader = &*(mach_header_buf.cast());
+
+ //let header_size = std::mem::size_of::() + header.size_commands;
+
+ if header.magic != MH_MAGIC_64 {
+ return Err(WriterError::InvalidMachHeader);
+ }
+
+ // Read the load commands which immediately follow the image header from
+ // the task memory
+ let load_commands_buf = self.read_task_memory(
+ image.load_address + std::mem::size_of::() as u64,
+ header.size_commands,
+ )?;
+
+ // Loads commands vary in size depending on the actual type, so we have
+ // to manually update the pointer offset rather than just stuffing the
+ // buffer into a slice
+ let mut next_header = load_commands.buf.as_ptr();
+
+ struct ImageSizes {
+ vm_addr: u64,
+ vm_size: u64,
+ slide: isize,
+ }
+
+ let mut image_sizes = None;
+ let mut image_version = None;
+ let mut image_uuid = None;
+
+ // TODO: pullout the load command parsing to its own function for testing
+ for i in 0..header.num_commands {
+ let header = &*(next_header.cast::());
+
+ if image_sizes.is_none() && header.cmd == LC_SEGMENT_64 {
+ let seg: &SegmentCommand64 = &*(next_header.cast());
+
+ if seg.segment_name[..7] == b"__TEXT\0" {
+ let slide = if seg.file_off == 0 && seg.file_size != 0 {
+ image.load_address - seg.vm_addr
+ } else {
+ 0
+ };
+
+ image_sizes = Some(ImageSizes {
+ vm_addr: seg.vm_addr,
+ vm_size: seg.vm_size,
+ slide,
+ });
+ }
+ }
+
+ if image_version.is_none() && header.cmd == LC_ID_DYLIB {
+ let seg: &DylibComand = &*(next_header.cast());
+
+ image_version = Some(seg.current_version);
+ }
+
+ if image_uuid.is_none() && header.cmd == LC_UUID {
+ let seg: &UuidComand = &*(next_header.cast());
+ image_uuid = Some(seg.uuid);
+ }
+
+ if image_sizes.is_some() && image_version.is_some() {
+ break;
+ }
+
+ next_header = next_header.offset(header.cmd_size as isize);
+ }
+
+ let image_sizes = image_sizes.ok_or_else(|| WriterError::InvalidMachHeader)?;
+
+ let file_path = if image.file_path != 0 {
+ self.read_string(image.file_path)?.unwrap_or_default()
+ } else {
+ String::new()
+ };
+
+ let module_name = write_string_to_location(buf, &file_path)?;
+
+ let mut raw_module = MDRawModule {
+ base_of_image: image_sizes.vm_addr + image_sizes.slide,
+ size_of_image: image_sizes.vm_size as u32,
+ module_name_rva: module_name.rva,
+ ..Default::default()
+ };
+
+ // Version info is not available for the main executable image since
+ // it doesn't have a LC_ID_DYLIB load command
+ if let Some(version) = image_version {
+ raw_module.version_info.signature = format::VS_FFI_SIGNATURE;
+ raw_module.version_info.struct_version = format::VS_FFI_STRUCVERSION;
+
+ // Convert MAC dylib version format, which is a 32 bit number, to the
+ // format used by minidump. The mac format is <16 bits>.<8 bits>.<8 bits>
+ // so it fits nicely into the windows version with some massaging
+ // The mapping is:
+ // 1) upper 16 bits of MAC version go to lower 16 bits of product HI
+ // 2) Next most significant 8 bits go to upper 16 bits of product LO
+ // 3) Least significant 8 bits go to lower 16 bits of product LO
+ raw_module.version_info.file_version_hi = version >> 16;
+ raw_module.version_info.file_version_lo = ((version & 0xff00) << 8) | (version & 0xff);
+ }
+
+ // TODO: write CV record
}
- fn read_string(&self, addr: u64) -> Result {
+ /// Reads a null terminated string starting at the specified address from
+ /// the crashing tasks' memory.
+ ///
+ /// This string is capped at 8k which should never be close to being hit as
+ /// it is only used for file paths for loaded modules, but then again, this
+ /// is MacOS, so who knows what insanity goes on.
+ fn read_string(&self, addr: u64) -> Result