1use std::collections::HashMap;
2
3use crate::crash_rustler::{CrashRustler, mac_roman_to_char};
4use crate::types::*;
5
6impl MappedMemory {
7 pub fn read_bytes(&self, address: u64, len: usize) -> Option<&[u8]> {
10 let offset = address.checked_sub(self.base_address)? as usize;
11 let end = offset.checked_add(len)?;
12 if end > self.data.len() {
13 return None;
14 }
15 Some(&self.data[offset..end])
16 }
17
18 pub fn contains_address(&self, address: u64) -> bool {
20 address >= self.base_address && address < self.base_address + self.data.len() as u64
21 }
22}
23
24impl CrashRustler {
25 pub fn read_address_from_memory(&self, memory: &MappedMemory, address: u64) -> u64 {
29 memory.read_pointer(address, self.is_64_bit).unwrap_or(0)
30 }
31
32 pub fn read_address_from_memory_at_symbol(
36 &self,
37 memory: &MappedMemory,
38 symbol_address: u64,
39 ) -> u64 {
40 self.read_address_from_memory(memory, symbol_address)
41 }
42
43 pub fn read_string_from_memory(
49 &self,
50 address: u64,
51 mapped_regions: &[MappedMemory],
52 ) -> Option<String> {
53 if address == 0 {
54 return None;
55 }
56
57 let region = mapped_regions
59 .iter()
60 .find(|r| r.contains_address(address))?;
61
62 let offset = (address - region.base_address) as usize;
63 let available = ®ion.data[offset..];
64
65 let max_len = available.len().min(0x4000);
67 let bytes = &available[..max_len];
68 let len = bytes.iter().position(|&b| b == 0).unwrap_or(max_len);
69 let bytes = &bytes[..len];
70
71 if bytes.is_empty() {
72 return None;
73 }
74
75 if let Ok(s) = std::str::from_utf8(bytes) {
77 return Some(s.to_string());
78 }
79
80 Some(bytes.iter().map(|&b| mac_roman_to_char(b)).collect())
82 }
83
84 pub fn build_crash_reporter_info(
93 cri_dict: &HashMap<i32, String>,
94 cri_errors: &[String],
95 ) -> String {
96 let mut result = String::new();
97 for value in cri_dict.values() {
98 result.push_str(value);
99 }
100 if !cri_errors.is_empty() {
101 let joined = cri_errors.join("\n");
102 result.push_str(&format!("ReportCrash Internal Errors: {joined}"));
103 }
104 result
105 }
106
107 pub fn append_crash_reporter_info_internal_error(&mut self, error: &str) {
111 let path = self.executable_path.as_deref().unwrap_or("notfound");
112 let formatted = format!("[{path}] {error}");
113 self.record_internal_error(&formatted);
114 }
115
116 pub fn crash_reporter_info_string(&self) -> String {
123 let name = self.process_name.as_deref().unwrap_or("");
124 let path = self.executable_path.as_deref().unwrap_or("notfound");
125 if self.ppid == 0 {
126 format!(
127 "Analyzing process {name} ({}, path={path}), \
128 couldn't determine parent process pid",
129 self.pid
130 )
131 } else {
132 let parent_name = self.parent_process_name.as_deref().unwrap_or("");
133 let parent_path = self.parent_executable_path.as_deref().unwrap_or("notfound");
134 format!(
135 "Analyzing process {name} ({}, path={path}), \
136 parent process {parent_name} ({}, path={parent_path})",
137 self.pid, self.ppid
138 )
139 }
140 }
141
142 pub fn append_application_specific_info(&mut self, info: &str, is_source_little_endian: bool) {
148 if info.is_empty() {
149 return;
150 }
151 let formatted = format!("{info}\n");
152 if !self.is_native && is_source_little_endian {
153 match &mut self.rosetta_info {
154 Some(existing) => existing.push_str(&formatted),
155 None => self.rosetta_info = Some(formatted),
156 }
157 } else {
158 match &mut self.application_specific_info {
159 Some(existing) => existing.push_str(&formatted),
160 None => self.application_specific_info = Some(formatted),
161 }
162 }
163 }
164
165 pub fn extract_crash_reporter_info(
170 &mut self,
171 memory: &MappedMemory,
172 symbol_address: u64,
173 is_source_little_endian: bool,
174 mapped_regions: &[MappedMemory],
175 ) {
176 let ptr = self.read_address_from_memory(memory, symbol_address);
177 if ptr != 0
178 && let Some(info_string) = self.read_string_from_memory(ptr, mapped_regions)
179 {
180 self.append_application_specific_info(&info_string, is_source_little_endian);
181 }
182 }
183
184 pub fn extract_crash_reporter_annotations(
189 &mut self,
190 crash_info_data: &[u8],
191 is_source_little_endian: bool,
192 mapped_regions: &[MappedMemory],
193 ) {
194 if crash_info_data.len() < 16 {
195 return;
196 }
197
198 let read_u64 = |offset: usize| -> u64 {
199 if offset + 8 > crash_info_data.len() {
200 return 0;
201 }
202 let bytes: [u8; 8] = crash_info_data[offset..offset + 8].try_into().unwrap();
203 u64::from_le_bytes(bytes)
204 };
205
206 let version = read_u64(0);
207 if version == 0 {
208 return;
209 }
210
211 let message_ptr = read_u64(8);
213 if message_ptr != 0
214 && let Some(msg) = self.read_string_from_memory(message_ptr, mapped_regions)
215 {
216 self.append_application_specific_info(&msg, is_source_little_endian);
217 }
218
219 let signature_ptr = read_u64(0x10);
221 if signature_ptr != 0
222 && let Some(sig) = self.read_string_from_memory(signature_ptr, mapped_regions)
223 && !sig.is_empty()
224 {
225 self.application_specific_signature_strings.push(sig);
226 }
227
228 let backtrace_ptr = read_u64(0x18);
230 if backtrace_ptr != 0
231 && let Some(bt) = self.read_string_from_memory(backtrace_ptr, mapped_regions)
232 && !bt.is_empty()
233 {
234 self.application_specific_backtraces.push(bt);
235 }
236
237 if version >= 2 {
239 let message2_ptr = read_u64(0x20);
240 if message2_ptr != 0
241 && let Some(msg2) = self.read_string_from_memory(message2_ptr, mapped_regions)
242 {
243 self.append_application_specific_info(&msg2, is_source_little_endian);
244 }
245 }
246
247 if version >= 3 {
249 let thread_id = read_u64(0x28);
250 if thread_id != 0 {
251 self.thread_id = Some(thread_id);
252 }
253 }
254
255 if version >= 4 {
257 let dialog_mode_ptr = read_u64(0x30);
258 if dialog_mode_ptr != 0
259 && let Some(mode) = self.read_string_from_memory(dialog_mode_ptr, mapped_regions)
260 {
261 self.application_specific_dialog_mode = Some(mode);
262 }
263 }
264 }
265
266 pub fn extract_crash_reporter_binary_image_hints(
271 &mut self,
272 memory: &MappedMemory,
273 symbol_address: u64,
274 mapped_regions: &[MappedMemory],
275 ) {
276 let ptr = self.read_address_from_memory(memory, symbol_address);
277 if ptr == 0 {
278 return;
279 }
280 if let Some(plist_string) = self.read_string_from_memory(ptr, mapped_regions)
281 && !plist_string.is_empty()
282 {
283 self.binary_image_hints.push(plist_string);
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use crate::crash_rustler::CrashRustler;
291 use crate::test_helpers::*;
292 use std::collections::HashMap;
293
294 mod record_error {
298 use super::*;
299
300 #[test]
301 fn record_internal_error_first_creates() {
302 let mut cr = make_test_cr();
303 cr.record_internal_error("first error");
304 assert_eq!(cr.internal_error, Some("first error".into()));
305 }
306
307 #[test]
308 fn record_internal_error_appends_with_newline() {
309 let mut cr = make_test_cr();
310 cr.record_internal_error("first");
311 cr.record_internal_error("second");
312 assert_eq!(cr.internal_error, Some("first\nsecond".into()));
313 }
314
315 #[test]
316 fn append_crash_reporter_info_internal_error_with_path() {
317 let mut cr = make_test_cr();
318 cr.append_crash_reporter_info_internal_error("some error");
319 let err = cr.internal_error.as_ref().unwrap();
320 assert!(err.starts_with("[/Applications/TestApp.app/Contents/MacOS/TestApp]"));
321 assert!(err.contains("some error"));
322 }
323
324 #[test]
325 fn append_crash_reporter_info_internal_error_no_path() {
326 let mut cr = make_test_cr();
327 cr.executable_path = None;
328 cr.append_crash_reporter_info_internal_error("some error");
329 let err = cr.internal_error.as_ref().unwrap();
330 assert!(err.starts_with("[notfound]"));
331 }
332
333 #[test]
334 fn crash_reporter_info_string_ppid_zero() {
335 let mut cr = make_test_cr();
336 cr.ppid = 0;
337 let info = cr.crash_reporter_info_string();
338 assert!(info.contains("couldn't determine parent process pid"));
339 assert!(info.contains("TestApp"));
340 assert!(info.contains("1234"));
341 }
342 }
343
344 mod crash_reporter_info {
348 use super::*;
349
350 #[test]
351 fn build_crash_reporter_info_empty() {
352 let dict = HashMap::new();
353 let errors: Vec<String> = vec![];
354 assert_eq!(CrashRustler::build_crash_reporter_info(&dict, &errors), "");
355 }
356
357 #[test]
358 fn build_crash_reporter_info_with_entries() {
359 let mut dict = HashMap::new();
360 dict.insert(42, "status for 42".to_string());
361 let errors: Vec<String> = vec![];
362 let result = CrashRustler::build_crash_reporter_info(&dict, &errors);
363 assert_eq!(result, "status for 42");
364 }
365
366 #[test]
367 fn build_crash_reporter_info_with_errors() {
368 let dict = HashMap::new();
369 let errors = vec!["err1".to_string(), "err2".to_string()];
370 let result = CrashRustler::build_crash_reporter_info(&dict, &errors);
371 assert!(result.contains("ReportCrash Internal Errors:"));
372 assert!(result.contains("err1"));
373 assert!(result.contains("err2"));
374 }
375
376 #[test]
377 fn crash_reporter_info_string_with_parent() {
378 let cr = make_test_cr();
379 let info = cr.crash_reporter_info_string();
380 assert!(info.contains("parent process launchd"));
381 assert!(info.contains("1234"));
382 assert!(info.contains(&cr.ppid.to_string()));
383 }
384
385 #[test]
386 fn append_application_specific_info_empty_noop() {
387 let mut cr = make_test_cr();
388 cr.append_application_specific_info("", true);
389 assert!(cr.application_specific_info.is_none());
390 assert!(cr.rosetta_info.is_none());
391 }
392
393 #[test]
394 fn append_application_specific_info_native_little_endian_to_rosetta() {
395 let mut cr = make_test_cr();
396 cr.is_native = false; cr.append_application_specific_info("rosetta data", true);
398 assert!(cr.rosetta_info.is_some());
399 assert!(cr.rosetta_info.as_ref().unwrap().contains("rosetta data"));
400 assert!(cr.application_specific_info.is_none());
401 }
402
403 #[test]
404 fn append_application_specific_info_native_to_app_info() {
405 let mut cr = make_test_cr();
406 cr.is_native = true;
407 cr.append_application_specific_info("app data", true);
408 assert!(cr.application_specific_info.is_some());
409 assert!(
410 cr.application_specific_info
411 .as_ref()
412 .unwrap()
413 .contains("app data")
414 );
415 }
416
417 #[test]
418 fn append_application_specific_info_appends_to_existing() {
419 let mut cr = make_test_cr();
420 cr.application_specific_info = Some("existing\n".into());
421 cr.append_application_specific_info("more data", true);
422 let info = cr.application_specific_info.as_ref().unwrap();
423 assert!(info.contains("existing"));
424 assert!(info.contains("more data"));
425 }
426 }
427}