summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/linux/minidump_writer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/minidump-writer/src/linux/minidump_writer.rs')
-rw-r--r--third_party/rust/minidump-writer/src/linux/minidump_writer.rs341
1 files changed, 341 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/src/linux/minidump_writer.rs b/third_party/rust/minidump-writer/src/linux/minidump_writer.rs
new file mode 100644
index 0000000000..606a85907b
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/linux/minidump_writer.rs
@@ -0,0 +1,341 @@
+use crate::{
+ dir_section::{DirSection, DumpBuf},
+ linux::{
+ app_memory::AppMemoryList,
+ crash_context::CrashContext,
+ dso_debug,
+ errors::{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, Write};
+
+pub enum CrashingThreadContext {
+ None,
+ CrashContext(MDLocationDescriptor),
+ CrashContextPlusAddress((MDLocationDescriptor, usize)),
+}
+
+pub struct MinidumpWriter {
+ pub process_id: Pid,
+ pub blamed_thread: Pid,
+ pub minidump_size_limit: Option<u64>,
+ pub skip_stacks_if_mapping_unreferenced: bool,
+ pub principal_mapping_address: Option<usize>,
+ pub user_mapping_list: MappingList,
+ pub app_memory: AppMemoryList,
+ pub memory_blocks: Vec<MDMemoryDescriptor>,
+ pub principal_mapping: Option<MappingInfo>,
+ pub sanitize_stack: bool,
+ pub crash_context: Option<CrashContext>,
+ pub crashing_thread_context: CrashingThreadContext,
+}
+
+// This doesn't work yet:
+// https://github.com/rust-lang/rust/issues/43408
+// fn write<T: Sized, P: AsRef<Path>>(path: P, value: T) -> Result<()> {
+// let mut file = std::fs::File::open(path)?;
+// let bytes: [u8; size_of::<T>()] = unsafe { transmute(value) };
+// file.write_all(&bytes)?;
+// Ok(())
+// }
+
+type Result<T> = std::result::Result<T, WriterError>;
+
+impl MinidumpWriter {
+ pub fn new(process: Pid, blamed_thread: Pid) -> Self {
+ Self {
+ process_id: process,
+ blamed_thread,
+ minidump_size_limit: None,
+ skip_stacks_if_mapping_unreferenced: false,
+ principal_mapping_address: None,
+ user_mapping_list: MappingList::new(),
+ app_memory: AppMemoryList::new(),
+ memory_blocks: Vec::new(),
+ principal_mapping: None,
+ sanitize_stack: false,
+ crash_context: None,
+ crashing_thread_context: CrashingThreadContext::None,
+ }
+ }
+
+ pub fn set_minidump_size_limit(&mut self, limit: u64) -> &mut Self {
+ self.minidump_size_limit = Some(limit);
+ self
+ }
+
+ pub fn set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self {
+ self.user_mapping_list = user_mapping_list;
+ self
+ }
+
+ pub fn set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self {
+ self.principal_mapping_address = Some(principal_mapping_address);
+ self
+ }
+
+ pub fn set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self {
+ self.app_memory = app_memory;
+ self
+ }
+
+ pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self {
+ self.crash_context = Some(crash_context);
+ self
+ }
+
+ pub fn skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self {
+ self.skip_stacks_if_mapping_unreferenced = true; // Off by default
+ self
+ }
+
+ pub fn sanitize_stack(&mut self) -> &mut Self {
+ self.sanitize_stack = true; // Off by default
+ self
+ }
+
+ /// Generates a minidump and writes to the destination provided. Returns the in-memory
+ /// version of the minidump as well.
+ pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result<Vec<u8>> {
+ let mut dumper = PtraceDumper::new(self.process_id)?;
+ dumper.suspend_threads()?;
+ dumper.late_init()?;
+
+ if self.skip_stacks_if_mapping_unreferenced {
+ if let Some(address) = self.principal_mapping_address {
+ self.principal_mapping = dumper.find_mapping_no_bias(address).cloned();
+ }
+
+ if !self.crash_thread_references_principal_mapping(&dumper) {
+ return Err(InitError::PrincipalMappingNotReferenced.into());
+ }
+ }
+
+ let mut buffer = Buffer::with_capacity(0);
+ self.generate_dump(&mut buffer, &mut dumper, destination)?;
+
+ // dumper would resume threads in drop() automatically,
+ // but in case there is an error, we want to catch it
+ dumper.resume_threads()?;
+
+ Ok(buffer.into())
+ }
+
+ fn crash_thread_references_principal_mapping(&self, dumper: &PtraceDumper) -> bool {
+ if self.crash_context.is_none() || self.principal_mapping.is_none() {
+ return false;
+ }
+
+ let low_addr = self
+ .principal_mapping
+ .as_ref()
+ .unwrap()
+ .system_mapping_info
+ .start_address;
+ let high_addr = self
+ .principal_mapping
+ .as_ref()
+ .unwrap()
+ .system_mapping_info
+ .end_address;
+
+ let pc = self
+ .crash_context
+ .as_ref()
+ .unwrap()
+ .get_instruction_pointer();
+ let stack_pointer = self.crash_context.as_ref().unwrap().get_stack_pointer();
+
+ if pc >= low_addr && pc < high_addr {
+ return true;
+ }
+
+ let (stack_ptr, stack_len) = match dumper.get_stack_info(stack_pointer) {
+ Ok(x) => x,
+ Err(_) => {
+ return false;
+ }
+ };
+ let stack_copy = match PtraceDumper::copy_from_process(
+ self.blamed_thread,
+ stack_ptr as *mut libc::c_void,
+ stack_len,
+ ) {
+ Ok(x) => x,
+ Err(_) => {
+ return false;
+ }
+ };
+
+ let sp_offset = stack_pointer - stack_ptr;
+ self.principal_mapping
+ .as_ref()
+ .unwrap()
+ .stack_has_pointer_to_mapping(&stack_copy, sp_offset)
+ }
+
+ fn generate_dump(
+ &mut self,
+ buffer: &mut DumpBuf,
+ dumper: &mut PtraceDumper,
+ destination: &mut (impl Write + Seek),
+ ) -> Result<()> {
+ // A minidump file contains a number of tagged streams. This is the number
+ // of stream which we write.
+ let num_writers = 14u32;
+
+ let mut header_section = MemoryWriter::<MDRawHeader>::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,
+ // header.get()->stream_directory_rva = dir.position();
+ 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)?;
+
+ let dirent = thread_list_stream::write(self, buffer, dumper)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = mappings::write(self, buffer, dumper)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ app_memory::write(self, buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, None)?;
+
+ let dirent = memory_list_stream::write(self, buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = exception_stream::write(self, buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = systeminfo_stream::write(buffer)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, "/proc/cpuinfo") {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxCpuInfo as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/status", self.blamed_thread))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxProcStatus as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self
+ .write_file(buffer, "/etc/lsb-release")
+ .or_else(|_| self.write_file(buffer, "/etc/os-release"))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxLsbRelease as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/cmdline", self.blamed_thread))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxCmdLine as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/environ", self.blamed_thread))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxEnviron as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/auxv", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxAuxv as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_file(buffer, &format!("/proc/{}/maps", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxMaps as u32,
+ location,
+ },
+ Err(_) => Default::default(),
+ };
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = dso_debug::write_dso_debug_stream(buffer, self.process_id, &dumper.auxv)
+ .unwrap_or_default();
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = thread_names_stream::write(buffer, dumper)?;
+ // Write section to file
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ // If you add more directory entries, don't forget to update kNumWriters,
+ // above.
+ Ok(())
+ }
+
+ #[allow(clippy::unused_self)]
+ fn write_file(
+ &self,
+ buffer: &mut DumpBuf,
+ filename: &str,
+ ) -> std::result::Result<MDLocationDescriptor, MemoryWriterError> {
+ let content = std::fs::read(filename)?;
+
+ let section = MemoryArrayWriter::write_bytes(buffer, &content);
+ Ok(section.location())
+ }
+}