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