1 use crate::app_memory::AppMemoryList;
2 use crate::crash_context::CrashContext;
3 use crate::dso_debug;
4 use crate::errors::{FileWriterError, InitError, MemoryWriterError, WriterError};
5 use crate::linux_ptrace_dumper::LinuxPtraceDumper;
6 use crate::maps_reader::{MappingInfo, MappingList};
7 use crate::minidump_format::*;
8 use crate::sections::*;
9 use crate::thread_info::Pid;
10 use std::io::{Cursor, Read, Seek, SeekFrom, Write};
11 
12 pub type DumpBuf = Cursor<Vec<u8>>;
13 
14 #[derive(Debug)]
15 pub struct DirSection<'a, W>
16 where
17     W: Write + Seek,
18 {
19     curr_idx: usize,
20     section: MemoryArrayWriter<MDRawDirectory>,
21     /// If we have to append to some file, we have to know where we currently are
22     destination_start_offset: u64,
23     destination: &'a mut W,
24     last_position_written_to_file: u64,
25 }
26 
27 impl<'a, W> DirSection<'a, W>
28 where
29     W: Write + Seek,
30 {
new( buffer: &mut DumpBuf, index_length: u32, destination: &'a mut W, ) -> std::result::Result<Self, FileWriterError>31     fn new(
32         buffer: &mut DumpBuf,
33         index_length: u32,
34         destination: &'a mut W,
35     ) -> std::result::Result<Self, FileWriterError> {
36         let dir_section =
37             MemoryArrayWriter::<MDRawDirectory>::alloc_array(buffer, index_length as usize)?;
38         Ok(DirSection {
39             curr_idx: 0,
40             section: dir_section,
41             destination_start_offset: destination.seek(SeekFrom::Current(0))?,
42             destination,
43             last_position_written_to_file: 0,
44         })
45     }
46 
position(&self) -> u3247     fn position(&self) -> u32 {
48         self.section.position
49     }
50 
dump_dir_entry( &mut self, buffer: &mut DumpBuf, dirent: MDRawDirectory, ) -> std::result::Result<(), FileWriterError>51     fn dump_dir_entry(
52         &mut self,
53         buffer: &mut DumpBuf,
54         dirent: MDRawDirectory,
55     ) -> std::result::Result<(), FileWriterError> {
56         self.section.set_value_at(buffer, dirent, self.curr_idx)?;
57 
58         // Now write it to file
59 
60         // First get all the positions
61         let curr_file_pos = self.destination.seek(SeekFrom::Current(0))?;
62         let idx_pos = self.section.location_of_index(self.curr_idx);
63         self.curr_idx += 1;
64 
65         self.destination.seek(std::io::SeekFrom::Start(
66             self.destination_start_offset + idx_pos.rva as u64,
67         ))?;
68         let start = idx_pos.rva as usize;
69         let end = (idx_pos.rva + idx_pos.data_size) as usize;
70         self.destination.write_all(&buffer.get_ref()[start..end])?;
71 
72         // Reset file-position
73         self.destination
74             .seek(std::io::SeekFrom::Start(curr_file_pos))?;
75 
76         Ok(())
77     }
78 
79     /// Writes 2 things to file:
80     /// 1. The given dirent into the dir section in the header (if any is given)
81     /// 2. Everything in the in-memory buffer that was added since the last call to this function
write_to_file( &mut self, buffer: &mut DumpBuf, dirent: Option<MDRawDirectory>, ) -> std::result::Result<(), FileWriterError>82     fn write_to_file(
83         &mut self,
84         buffer: &mut DumpBuf,
85         dirent: Option<MDRawDirectory>,
86     ) -> std::result::Result<(), FileWriterError> {
87         if let Some(dirent) = dirent {
88             self.dump_dir_entry(buffer, dirent)?;
89         }
90 
91         let start_pos = self.last_position_written_to_file as usize;
92         self.destination.write_all(&buffer.get_ref()[start_pos..])?;
93         self.last_position_written_to_file = buffer.position();
94         Ok(())
95     }
96 }
97 
98 pub enum CrashingThreadContext {
99     None,
100     CrashContext(MDLocationDescriptor),
101     CrashContextPlusAddress((MDLocationDescriptor, usize)),
102 }
103 
104 pub struct MinidumpWriter {
105     pub process_id: Pid,
106     pub blamed_thread: Pid,
107     pub minidump_size_limit: Option<u64>,
108     pub skip_stacks_if_mapping_unreferenced: bool,
109     pub principal_mapping_address: Option<usize>,
110     pub user_mapping_list: MappingList,
111     pub app_memory: AppMemoryList,
112     pub memory_blocks: Vec<MDMemoryDescriptor>,
113     pub principal_mapping: Option<MappingInfo>,
114     pub sanitize_stack: bool,
115     pub crash_context: Option<CrashContext>,
116     pub crashing_thread_context: CrashingThreadContext,
117 }
118 
119 // This doesn't work yet:
120 // https://github.com/rust-lang/rust/issues/43408
121 // fn write<T: Sized, P: AsRef<Path>>(path: P, value: T) -> Result<()> {
122 //     let mut file = std::fs::File::open(path)?;
123 //     let bytes: [u8; size_of::<T>()] = unsafe { transmute(value) };
124 //     file.write_all(&bytes)?;
125 //     Ok(())
126 // }
127 
128 type Result<T> = std::result::Result<T, WriterError>;
129 
130 impl MinidumpWriter {
new(process: Pid, blamed_thread: Pid) -> Self131     pub fn new(process: Pid, blamed_thread: Pid) -> Self {
132         MinidumpWriter {
133             process_id: process,
134             blamed_thread,
135             minidump_size_limit: None,
136             skip_stacks_if_mapping_unreferenced: false,
137             principal_mapping_address: None,
138             user_mapping_list: MappingList::new(),
139             app_memory: AppMemoryList::new(),
140             memory_blocks: Vec::new(),
141             principal_mapping: None,
142             sanitize_stack: false,
143             crash_context: None,
144             crashing_thread_context: CrashingThreadContext::None,
145         }
146     }
147 
set_minidump_size_limit(&mut self, limit: u64) -> &mut Self148     pub fn set_minidump_size_limit(&mut self, limit: u64) -> &mut Self {
149         self.minidump_size_limit = Some(limit);
150         self
151     }
152 
set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self153     pub fn set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self {
154         self.user_mapping_list = user_mapping_list;
155         self
156     }
157 
set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self158     pub fn set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self {
159         self.principal_mapping_address = Some(principal_mapping_address);
160         self
161     }
162 
set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self163     pub fn set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self {
164         self.app_memory = app_memory;
165         self
166     }
167 
168     // Has to be deactivated for ARM for now, as libc doesn't include ucontext_t for ARM yet
169     #[cfg(not(target_arch = "arm"))]
set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self170     pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self {
171         self.crash_context = Some(crash_context);
172         self
173     }
174 
skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self175     pub fn skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self {
176         self.skip_stacks_if_mapping_unreferenced = true; // Off by default
177         self
178     }
179 
sanitize_stack(&mut self) -> &mut Self180     pub fn sanitize_stack(&mut self) -> &mut Self {
181         self.sanitize_stack = true; // Off by default
182         self
183     }
184 
185     /// Generates a minidump and writes to the destination provided. Returns the in-memory
186     /// version of the minidump as well.
dump(&mut self, destination: &mut (impl Write + Seek)) -> Result<Vec<u8>>187     pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result<Vec<u8>> {
188         let mut dumper = LinuxPtraceDumper::new(self.process_id)?;
189         dumper.suspend_threads()?;
190         dumper.late_init()?;
191 
192         if self.skip_stacks_if_mapping_unreferenced {
193             if let Some(address) = self.principal_mapping_address {
194                 self.principal_mapping = dumper.find_mapping_no_bias(address).cloned();
195             }
196 
197             if !self.crash_thread_references_principal_mapping(&dumper) {
198                 return Err(InitError::PrincipalMappingNotReferenced.into());
199             }
200         }
201 
202         let mut buffer = Cursor::new(Vec::new());
203         self.generate_dump(&mut buffer, &mut dumper, destination)?;
204 
205         // dumper would resume threads in drop() automatically,
206         // but in case there is an error, we want to catch it
207         dumper.resume_threads()?;
208 
209         Ok(buffer.into_inner())
210     }
211 
crash_thread_references_principal_mapping(&self, dumper: &LinuxPtraceDumper) -> bool212     fn crash_thread_references_principal_mapping(&self, dumper: &LinuxPtraceDumper) -> bool {
213         if self.crash_context.is_none() || self.principal_mapping.is_none() {
214             return false;
215         }
216 
217         let low_addr = self
218             .principal_mapping
219             .as_ref()
220             .unwrap()
221             .system_mapping_info
222             .start_address;
223         let high_addr = self
224             .principal_mapping
225             .as_ref()
226             .unwrap()
227             .system_mapping_info
228             .end_address;
229 
230         let pc = self
231             .crash_context
232             .as_ref()
233             .unwrap()
234             .get_instruction_pointer();
235         let stack_pointer = self.crash_context.as_ref().unwrap().get_stack_pointer();
236 
237         if pc >= low_addr && pc < high_addr {
238             return true;
239         }
240 
241         let (stack_ptr, stack_len) = match dumper.get_stack_info(stack_pointer as usize) {
242             Ok(x) => x,
243             Err(_) => {
244                 return false;
245             }
246         };
247         let stack_copy = match LinuxPtraceDumper::copy_from_process(
248             self.blamed_thread,
249             stack_ptr as *mut libc::c_void,
250             stack_len,
251         ) {
252             Ok(x) => x,
253             Err(_) => {
254                 return false;
255             }
256         };
257 
258         let sp_offset = stack_pointer as usize - stack_ptr;
259         self.principal_mapping
260             .as_ref()
261             .unwrap()
262             .stack_has_pointer_to_mapping(&stack_copy, sp_offset)
263     }
264 
generate_dump( &mut self, buffer: &mut DumpBuf, dumper: &mut LinuxPtraceDumper, destination: &mut (impl Write + Seek), ) -> Result<()>265     fn generate_dump(
266         &mut self,
267         buffer: &mut DumpBuf,
268         dumper: &mut LinuxPtraceDumper,
269         destination: &mut (impl Write + Seek),
270     ) -> Result<()> {
271         // A minidump file contains a number of tagged streams. This is the number
272         // of stream which we write.
273         let num_writers = 14u32;
274 
275         let mut header_section = MemoryWriter::<MDRawHeader>::alloc(buffer)?;
276 
277         let mut dir_section = DirSection::new(buffer, num_writers, destination)?;
278 
279         let header = MDRawHeader {
280             signature: MD_HEADER_SIGNATURE,
281             version: MD_HEADER_VERSION,
282             stream_count: num_writers,
283             //   header.get()->stream_directory_rva = dir.position();
284             stream_directory_rva: dir_section.position(),
285             checksum: 0, /* Can be 0.  In fact, that's all that's
286                           * been found in minidump files. */
287             time_date_stamp: std::time::SystemTime::now()
288                 .duration_since(std::time::UNIX_EPOCH)?
289                 .as_secs() as u32, // TODO: This is not Y2038 safe, but thats how its currently defined as
290             flags: 0,
291         };
292         header_section.set_value(buffer, header)?;
293 
294         // Ensure the header gets flushed. If we crash somewhere below,
295         // we should have a mostly-intact dump
296         dir_section.write_to_file(buffer, None)?;
297 
298         let dirent = thread_list_stream::write(self, buffer, &dumper)?;
299         // Write section to file
300         dir_section.write_to_file(buffer, Some(dirent))?;
301 
302         let dirent = mappings::write(self, buffer, dumper)?;
303         // Write section to file
304         dir_section.write_to_file(buffer, Some(dirent))?;
305 
306         let _ = app_memory::write(self, buffer)?;
307         // Write section to file
308         dir_section.write_to_file(buffer, None)?;
309 
310         let dirent = memory_list_stream::write(self, buffer)?;
311         // Write section to file
312         dir_section.write_to_file(buffer, Some(dirent))?;
313 
314         let dirent = exception_stream::write(self, buffer)?;
315         // Write section to file
316         dir_section.write_to_file(buffer, Some(dirent))?;
317 
318         let dirent = systeminfo_stream::write(buffer)?;
319         // Write section to file
320         dir_section.write_to_file(buffer, Some(dirent))?;
321 
322         let dirent = match self.write_file(buffer, "/proc/cpuinfo") {
323             Ok(location) => MDRawDirectory {
324                 stream_type: MDStreamType::LinuxCpuInfo as u32,
325                 location,
326             },
327             Err(_) => Default::default(),
328         };
329         // Write section to file
330         dir_section.write_to_file(buffer, Some(dirent))?;
331 
332         let dirent = match self.write_file(buffer, &format!("/proc/{}/status", self.blamed_thread))
333         {
334             Ok(location) => MDRawDirectory {
335                 stream_type: MDStreamType::LinuxProcStatus as u32,
336                 location,
337             },
338             Err(_) => Default::default(),
339         };
340         // Write section to file
341         dir_section.write_to_file(buffer, Some(dirent))?;
342 
343         let dirent = match self
344             .write_file(buffer, "/etc/lsb-release")
345             .or_else(|_| self.write_file(buffer, "/etc/os-release"))
346         {
347             Ok(location) => MDRawDirectory {
348                 stream_type: MDStreamType::LinuxLsbRelease as u32,
349                 location,
350             },
351             Err(_) => Default::default(),
352         };
353         // Write section to file
354         dir_section.write_to_file(buffer, Some(dirent))?;
355 
356         let dirent = match self.write_file(buffer, &format!("/proc/{}/cmdline", self.blamed_thread))
357         {
358             Ok(location) => MDRawDirectory {
359                 stream_type: MDStreamType::LinuxCmdLine as u32,
360                 location,
361             },
362             Err(_) => Default::default(),
363         };
364         // Write section to file
365         dir_section.write_to_file(buffer, Some(dirent))?;
366 
367         let dirent = match self.write_file(buffer, &format!("/proc/{}/environ", self.blamed_thread))
368         {
369             Ok(location) => MDRawDirectory {
370                 stream_type: MDStreamType::LinuxEnviron as u32,
371                 location,
372             },
373             Err(_) => Default::default(),
374         };
375         // Write section to file
376         dir_section.write_to_file(buffer, Some(dirent))?;
377 
378         let dirent = match self.write_file(buffer, &format!("/proc/{}/auxv", self.blamed_thread)) {
379             Ok(location) => MDRawDirectory {
380                 stream_type: MDStreamType::LinuxAuxv as u32,
381                 location,
382             },
383             Err(_) => Default::default(),
384         };
385         // Write section to file
386         dir_section.write_to_file(buffer, Some(dirent))?;
387 
388         let dirent = match self.write_file(buffer, &format!("/proc/{}/maps", self.blamed_thread)) {
389             Ok(location) => MDRawDirectory {
390                 stream_type: MDStreamType::LinuxMaps as u32,
391                 location,
392             },
393             Err(_) => Default::default(),
394         };
395         // Write section to file
396         dir_section.write_to_file(buffer, Some(dirent))?;
397 
398         let dirent = dso_debug::write_dso_debug_stream(buffer, self.blamed_thread, &dumper.auxv)
399             .unwrap_or_default();
400         // Write section to file
401         dir_section.write_to_file(buffer, Some(dirent))?;
402 
403         let dirent = thread_names_stream::write(buffer, dumper)?;
404         // Write section to file
405         dir_section.write_to_file(buffer, Some(dirent))?;
406 
407         // If you add more directory entries, don't forget to update kNumWriters,
408         // above.
409         Ok(())
410     }
411 
write_file( &self, buffer: &mut DumpBuf, filename: &str, ) -> std::result::Result<MDLocationDescriptor, MemoryWriterError>412     fn write_file(
413         &self,
414         buffer: &mut DumpBuf,
415         filename: &str,
416     ) -> std::result::Result<MDLocationDescriptor, MemoryWriterError> {
417         let mut file = std::fs::File::open(std::path::PathBuf::from(filename))?;
418         let mut content = Vec::new();
419         file.read_to_end(&mut content)?;
420 
421         let section = MemoryArrayWriter::<u8>::alloc_from_array(buffer, &content)?;
422         Ok(section.location())
423     }
424 }
425