Skip to main content

crashrustler/
crash_rustler.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::types::*;
4
5/// CrashRustler: Rust equivalent of CrashWrangler's CrashReport Objective-C class.
6///
7/// This struct captures all the state needed to represent a macOS crash report,
8/// including process information, exception details, thread backtraces,
9/// binary image mappings, and crash analysis metadata.
10#[derive(Debug)]
11pub struct CrashRustler {
12    // -- Process identification --
13    /// Process ID of the crashed process.
14    pub pid: i32,
15    /// Parent process ID.
16    pub ppid: i32,
17    /// User ID of the crashed process.
18    pub uid: u32,
19    /// Mach task port for the crashed process.
20    pub task: u32,
21    /// Name of the crashed process (derived from executable path).
22    pub process_name: Option<String>,
23    /// Full path to the crashed process executable.
24    pub executable_path: Option<String>,
25    /// Name of the parent process.
26    pub parent_process_name: Option<String>,
27    /// Full path to the parent process executable.
28    pub parent_executable_path: Option<String>,
29    /// Name of the responsible process (may differ from parent).
30    pub responsible_process_name: Option<String>,
31    /// PID of the responsible process (`responsibility_get_pid_responsible_for_pid`).
32    pub r_process_pid: i32,
33    /// Crash date as a formatted string.
34    pub date: Option<String>,
35
36    // -- Exception information --
37    /// Mach exception type (e.g., 1 = `EXC_BAD_ACCESS`).
38    pub exception_type: i32,
39    /// Mach exception codes (architecture-specific sub-codes).
40    pub exception_code: Vec<i64>,
41    /// Number of valid exception codes.
42    pub exception_code_count: u32,
43    /// POSIX signal number (e.g., 11 = `SIGSEGV`).
44    pub signal: u32,
45    /// Virtual address that caused the crash (for `EXC_BAD_ACCESS`).
46    pub crashing_address: u64,
47
48    // -- Thread information --
49    /// Mach thread port of the crashing thread.
50    pub thread: u32,
51    /// Thread ID from `thread_identifier_info` or crash annotations.
52    pub thread_id: Option<u64>,
53    /// Index of the crashed thread in [`backtraces`](Self::backtraces), or -1 if unknown.
54    pub crashed_thread_number: i32,
55    /// Register state of the crashed thread.
56    pub thread_state: ThreadState,
57    /// Exception state from the faulting thread.
58    pub exception_state: ExceptionState,
59    /// Raw exception state registers (architecture-specific).
60    pub thread_exception_state: Vec<u32>,
61    /// Number of valid words in `thread_exception_state`.
62    pub thread_exception_state_count: u32,
63    /// Backtraces for all threads in the crashed process.
64    pub backtraces: Vec<ThreadBacktrace>,
65
66    // -- CPU and architecture --
67    /// CPU type of the crashed process.
68    pub cpu_type: CpuType,
69    /// Whether the process is 64-bit.
70    pub is_64_bit: bool,
71    /// Whether the process runs natively (not under Rosetta translation).
72    pub is_native: bool,
73    /// Raw architecture value from the Mach-O header.
74    pub architecture: u64,
75
76    // -- Binary image information --
77    /// All binary images loaded in the crashed process.
78    pub binary_images: Vec<BinaryImage>,
79    /// Binary image hints from `___crashreporter_binary_image_hints__`.
80    pub binary_image_hints: Vec<String>,
81    /// UUID of the main executable binary.
82    pub binary_uuid: Option<String>,
83    /// Name of the binary image currently being processed.
84    pub current_binary_image: Option<String>,
85    /// Set of binary image keys already processed (for deduplication).
86    pub attempted_binary_images: HashSet<String>,
87    /// Count of errors encountered during binary image processing.
88    pub binary_image_error_count: u32,
89    /// Whether binary image post-processing (sorting, enrichment) is done.
90    pub binary_image_post_processing_complete: bool,
91    /// Whether all binary images have been enumerated.
92    pub completed_all_binary_images: bool,
93    /// Longest binary identifier string length (for formatting alignment).
94    pub max_binary_identifier_length: u32,
95
96    // -- Application metadata --
97    /// Environment variables of the crashed process.
98    pub environment: HashMap<String, String>,
99    /// Crash report notes (e.g., translocated process, OS update).
100    pub notes: Option<String>,
101    /// Process version info (`CFBundleShortVersionString`, `CFBundleVersion`).
102    pub process_version_dictionary: HashMap<String, String>,
103    /// Build version info (`ProjectName`, `SourceVersion`, `BuildVersion`).
104    pub build_version_dictionary: HashMap<String, String>,
105    /// OS version info (`ProductVersion`, `BuildVersion`, `ProductName`).
106    pub os_version_dictionary: HashMap<String, String>,
107    /// App Store Adam ID.
108    pub adam_id: Option<String>,
109    /// App Store software version external identifier.
110    pub software_version_external_identifier: Option<String>,
111    /// Path used to reopen/relaunch the application.
112    pub reopen_path: Option<String>,
113    /// Launch Services application information dictionary.
114    /// Contains keys such as `CFBundleIdentifier` and display name.
115    pub ls_application_information: Option<HashMap<String, String>>,
116    /// Whether the application has an App Store receipt.
117    pub has_receipt: bool,
118    /// Whether the process was running from a translocated path.
119    pub is_translocated_process: bool,
120
121    // -- Application specific info --
122    /// Application-specific crash info from `___crashreporter_info__` or `__crash_info`.
123    pub application_specific_info: Option<String>,
124    /// Application-specific backtrace strings from crash annotations.
125    pub application_specific_backtraces: Vec<String>,
126    /// Application-specific signature strings for crash grouping.
127    pub application_specific_signature_strings: Vec<String>,
128    /// Dialog mode hint from crash annotations (version 4+).
129    pub application_specific_dialog_mode: Option<String>,
130
131    // -- Crash reporter info --
132    /// Accumulated internal error messages.
133    pub internal_error: Option<String>,
134    /// Rosetta translation thread info (for non-native processes).
135    pub rosetta_info: Option<String>,
136    /// Dyld error string (from `dyld_all_image_infos` or `__crash_info`).
137    pub dyld_error_string: Option<String>,
138    /// Additional dyld error info for presignature.
139    pub dyld_error_info: Option<String>,
140    /// Whether to attempt legacy dyld error string extraction.
141    pub extract_legacy_dyld_error_string: bool,
142    /// Whether a fatal dyld error occurred on launch.
143    pub fatal_dyld_error_on_launch: bool,
144    /// Exec failure error (set when `___NEW_PROCESS_COULD_NOT_BE_EXECD___` is detected).
145    pub exec_failure_error: Option<String>,
146
147    // -- Code signing --
148    /// Code signing status flags (bit 0x1000000 = `CS_KILLED`).
149    pub cs_status: u32,
150    /// Description of code signing invalidity messages.
151    pub code_sign_invalid_messages_description: Option<String>,
152
153    // -- External modifications --
154    /// External modification info (task_for_pid callers, injected libraries).
155    pub ext_mod_info: ExternalModInfo,
156
157    // -- Timing --
158    /// System uptime (awake time) at crash, in seconds.
159    pub awake_system_uptime: u64,
160    /// Sleep/wake UUID for correlating crashes with sleep events.
161    pub sleep_wake_uuid: Option<String>,
162
163    // -- Sandbox --
164    /// Sandbox container path for the crashed process.
165    pub sandbox_container: Option<String>,
166
167    // -- VM map --
168    /// Human-readable VM region map of the crashed process.
169    pub vm_map_string: Option<String>,
170    /// Summary of VM region statistics.
171    pub vm_summary_string: Option<String>,
172
173    // -- ObjC info --
174    /// ObjC selector name if the crash occurred in `objc_msgSend*`.
175    pub objc_selector_name: Option<String>,
176
177    // -- Misc --
178    /// Path to a third-party bundle involved in the crash.
179    pub third_party_bundle_path: Option<String>,
180    /// Anonymous UUID for crash report deduplication.
181    pub anon_uuid: Option<String>,
182    /// Whether this report is for a corpse (post-mortem) analysis.
183    pub performing_autopsy: bool,
184    /// Whether the executable path needs correction after resolution.
185    pub executable_path_needs_correction: bool,
186    /// Whether the process name needs correction after resolution.
187    pub process_name_needs_correction: bool,
188    /// Previous OS build version if crash occurred during an OS update.
189    pub in_update_previous_os_build: Option<String>,
190    /// Raw item info record data from Launch Services.
191    pub item_info_record: Option<Vec<u8>>,
192    /// Exit snapshot data from the process.
193    pub exit_snapshot: Option<Vec<u8>>,
194    /// Length of the exit snapshot data.
195    pub exit_snapshot_length: u32,
196    /// Exit payload data from the process.
197    pub exit_payload: Option<Vec<u8>>,
198    /// Length of the exit payload data.
199    pub exit_payload_length: u32,
200    /// Work queue thread limits hit at time of crash.
201    pub work_queue_limits: Option<WorkQueueLimits>,
202    /// PID of the process that terminated this process.
203    pub terminator_pid: i32,
204    /// Name of the process that terminated this process.
205    pub terminator_proc: Option<String>,
206    /// Reason string from the terminating process.
207    pub terminator_reason: Option<String>,
208}
209
210impl Default for CrashRustler {
211    fn default() -> Self {
212        Self {
213            pid: 0,
214            ppid: 0,
215            uid: 0,
216            task: 0,
217            process_name: None,
218            executable_path: None,
219            parent_process_name: None,
220            parent_executable_path: None,
221            responsible_process_name: None,
222            r_process_pid: 0,
223            date: None,
224            exception_type: 0,
225            exception_code: Vec::new(),
226            exception_code_count: 0,
227            signal: 0,
228            crashing_address: 0,
229            thread: 0,
230            thread_id: None,
231            crashed_thread_number: -1,
232            thread_state: ThreadState {
233                flavor: 0,
234                registers: Vec::new(),
235            },
236            exception_state: ExceptionState {
237                state: Vec::new(),
238                count: 0,
239            },
240            thread_exception_state: Vec::new(),
241            thread_exception_state_count: 0,
242            backtraces: Vec::new(),
243            cpu_type: CpuType(0),
244            is_64_bit: false,
245            is_native: false,
246            architecture: 0,
247            binary_images: Vec::new(),
248            binary_image_hints: Vec::new(),
249            binary_uuid: None,
250            current_binary_image: None,
251            attempted_binary_images: HashSet::new(),
252            binary_image_error_count: 0,
253            binary_image_post_processing_complete: false,
254            completed_all_binary_images: false,
255            max_binary_identifier_length: 0,
256            environment: HashMap::new(),
257            notes: None,
258            process_version_dictionary: HashMap::new(),
259            build_version_dictionary: HashMap::new(),
260            os_version_dictionary: HashMap::new(),
261            adam_id: None,
262            software_version_external_identifier: None,
263            reopen_path: None,
264            ls_application_information: None,
265            has_receipt: false,
266            is_translocated_process: false,
267            application_specific_info: None,
268            application_specific_backtraces: Vec::new(),
269            application_specific_signature_strings: Vec::new(),
270            application_specific_dialog_mode: None,
271            internal_error: None,
272            rosetta_info: None,
273            dyld_error_string: None,
274            dyld_error_info: None,
275            extract_legacy_dyld_error_string: false,
276            fatal_dyld_error_on_launch: false,
277            exec_failure_error: None,
278            cs_status: 0,
279            code_sign_invalid_messages_description: None,
280            ext_mod_info: ExternalModInfo {
281                description: None,
282                warnings: None,
283                dictionary: HashMap::new(),
284            },
285            awake_system_uptime: 0,
286            sleep_wake_uuid: None,
287            sandbox_container: None,
288            vm_map_string: None,
289            vm_summary_string: None,
290            objc_selector_name: None,
291            third_party_bundle_path: None,
292            anon_uuid: None,
293            performing_autopsy: false,
294            executable_path_needs_correction: false,
295            process_name_needs_correction: false,
296            in_update_previous_os_build: None,
297            item_info_record: None,
298            exit_snapshot: None,
299            exit_snapshot_length: 0,
300            exit_payload: None,
301            exit_payload_length: 0,
302            work_queue_limits: None,
303            terminator_pid: 0,
304            terminator_proc: None,
305            terminator_reason: None,
306        }
307    }
308}
309
310/// Converts a MacRoman byte to its Unicode character equivalent.
311/// MacRoman bytes 0x00-0x7F map to ASCII. Bytes 0x80-0xFF map to
312/// specific Unicode codepoints used by classic Mac OS.
313pub(crate) fn mac_roman_to_char(b: u8) -> char {
314    if b < 0x80 {
315        return b as char;
316    }
317    // MacRoman high-byte mapping to Unicode codepoints
318    const MAC_ROMAN_HIGH: [u16; 128] = [
319        0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, // 80-87
320        0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, // 88-8F
321        0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, // 90-97
322        0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, // 98-9F
323        0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, // A0-A7
324        0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, // A8-AF
325        0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, // B0-B7
326        0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, // B8-BF
327        0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, // C0-C7
328        0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, // C8-CF
329        0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, // D0-D7
330        0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, // D8-DF
331        0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, // E0-E7
332        0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, // E8-EF
333        0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, // F0-F7
334        0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, // F8-FF
335    ];
336    char::from_u32(MAC_ROMAN_HIGH[(b - 0x80) as usize] as u32).unwrap_or('?')
337}
338
339#[cfg(test)]
340mod tests {
341    use crate::*;
342
343    mod default_impl {
344        use super::*;
345
346        #[test]
347        fn all_fields_initialized() {
348            let cr = CrashRustler::default();
349            assert_eq!(cr.pid, 0);
350            assert_eq!(cr.ppid, 0);
351            assert_eq!(cr.uid, 0);
352            assert_eq!(cr.task, 0);
353            assert!(cr.process_name.is_none());
354            assert!(cr.executable_path.is_none());
355            assert!(cr.parent_process_name.is_none());
356            assert!(cr.parent_executable_path.is_none());
357            assert!(cr.responsible_process_name.is_none());
358            assert_eq!(cr.r_process_pid, 0);
359            assert!(cr.date.is_none());
360            assert_eq!(cr.exception_type, 0);
361            assert!(cr.exception_code.is_empty());
362            assert_eq!(cr.exception_code_count, 0);
363            assert_eq!(cr.signal, 0);
364            assert_eq!(cr.crashing_address, 0);
365            assert_eq!(cr.thread, 0);
366            assert!(cr.thread_id.is_none());
367            assert_eq!(cr.crashed_thread_number, -1);
368            assert_eq!(cr.thread_state.flavor, 0);
369            assert!(cr.thread_state.registers.is_empty());
370            assert!(cr.exception_state.state.is_empty());
371            assert_eq!(cr.exception_state.count, 0);
372            assert!(cr.backtraces.is_empty());
373            assert_eq!(cr.cpu_type, CpuType(0));
374            assert!(!cr.is_64_bit);
375            assert!(!cr.is_native);
376            assert_eq!(cr.architecture, 0);
377            assert!(cr.binary_images.is_empty());
378            assert!(cr.binary_image_hints.is_empty());
379            assert!(cr.binary_uuid.is_none());
380            assert!(cr.current_binary_image.is_none());
381            assert!(cr.attempted_binary_images.is_empty());
382            assert_eq!(cr.binary_image_error_count, 0);
383            assert!(!cr.binary_image_post_processing_complete);
384            assert!(!cr.completed_all_binary_images);
385            assert_eq!(cr.max_binary_identifier_length, 0);
386            assert!(cr.environment.is_empty());
387            assert!(cr.notes.is_none());
388            assert!(cr.process_version_dictionary.is_empty());
389            assert!(cr.build_version_dictionary.is_empty());
390            assert!(cr.os_version_dictionary.is_empty());
391            assert!(cr.adam_id.is_none());
392            assert!(cr.software_version_external_identifier.is_none());
393            assert!(cr.reopen_path.is_none());
394            assert!(cr.ls_application_information.is_none());
395            assert!(!cr.has_receipt);
396            assert!(!cr.is_translocated_process);
397            assert!(cr.application_specific_info.is_none());
398            assert!(cr.application_specific_backtraces.is_empty());
399            assert!(cr.application_specific_signature_strings.is_empty());
400            assert!(cr.application_specific_dialog_mode.is_none());
401            assert!(cr.internal_error.is_none());
402            assert!(cr.rosetta_info.is_none());
403            assert!(cr.dyld_error_string.is_none());
404            assert!(cr.dyld_error_info.is_none());
405            assert!(!cr.extract_legacy_dyld_error_string);
406            assert!(!cr.fatal_dyld_error_on_launch);
407            assert!(cr.exec_failure_error.is_none());
408            assert_eq!(cr.cs_status, 0);
409            assert!(cr.code_sign_invalid_messages_description.is_none());
410            assert!(cr.ext_mod_info.description.is_none());
411            assert!(cr.ext_mod_info.warnings.is_none());
412            assert!(cr.ext_mod_info.dictionary.is_empty());
413            assert_eq!(cr.awake_system_uptime, 0);
414            assert!(cr.sleep_wake_uuid.is_none());
415            assert!(cr.sandbox_container.is_none());
416            assert!(cr.vm_map_string.is_none());
417            assert!(cr.vm_summary_string.is_none());
418            assert!(cr.objc_selector_name.is_none());
419            assert!(cr.third_party_bundle_path.is_none());
420            assert!(cr.anon_uuid.is_none());
421            assert!(!cr.performing_autopsy);
422            assert!(!cr.executable_path_needs_correction);
423            assert!(!cr.process_name_needs_correction);
424            assert!(cr.in_update_previous_os_build.is_none());
425            assert!(cr.item_info_record.is_none());
426            assert!(cr.exit_snapshot.is_none());
427            assert_eq!(cr.exit_snapshot_length, 0);
428            assert!(cr.exit_payload.is_none());
429            assert_eq!(cr.exit_payload_length, 0);
430            assert!(cr.work_queue_limits.is_none());
431            assert_eq!(cr.terminator_pid, 0);
432            assert!(cr.terminator_proc.is_none());
433            assert!(cr.terminator_reason.is_none());
434        }
435    }
436}