1use crate::crash_rustler::CrashRustler;
2use crate::types::*;
3
4impl CrashRustler {
5 pub fn add_binary_image(&mut self, image: BinaryImage) -> bool {
10 let key = format!("{} @ 0x{:x}", image.name, image.base_address);
11 self.current_binary_image = Some(key.clone());
12
13 if self.attempted_binary_images.contains(&key) {
14 return false;
15 }
16 self.attempted_binary_images.insert(key);
17 self.binary_images.push(image);
18 true
19 }
20
21 pub fn finalize_binary_images(&mut self) {
25 if self.binary_image_post_processing_complete {
26 return;
27 }
28
29 for image in &mut self.binary_images {
31 if image.identifier.is_none()
32 && let Some(last) = image.path.rsplit('/').next()
33 {
34 image.identifier = Some(last.to_string());
35 }
36 }
37
38 self.binary_images.sort_by_key(|img| img.base_address);
40
41 self.max_binary_identifier_length = self
43 .binary_images
44 .iter()
45 .map(|img| img.identifier.as_deref().unwrap_or("").len() as u32)
46 .max()
47 .unwrap_or(0);
48
49 self.binary_image_post_processing_complete = true;
50 }
51
52 pub fn binary_image_for_address(&self, address: u64) -> Option<&BinaryImage> {
56 self.binary_images
57 .iter()
58 .find(|img| address >= img.base_address && address < img.end_address)
59 }
60
61 pub fn binary_image_for_path(&self, path: &str) -> Option<&BinaryImage> {
64 self.binary_images.iter().find(|img| img.path == path)
65 }
66
67 pub fn format_binary_image_line(&self, image: &BinaryImage, force_64bit: bool) -> String {
72 let is_apple = Self::path_is_apple(&image.path)
73 || image
74 .identifier
75 .as_deref()
76 .is_some_and(Self::bundle_identifier_is_apple);
77 let apple_marker = if is_apple { "+" } else { " " };
78
79 let identifier = image.identifier.as_deref().unwrap_or("???");
80
81 let version_str = match (&image.version, image.identifier.as_deref()) {
82 (Some(ver), _) => {
83 let sanitized = Self::sanitize_version(Some(ver));
84 format!("({sanitized})")
85 }
86 _ => String::new(),
87 };
88
89 let uuid_str = image
90 .uuid
91 .as_deref()
92 .map(|u| format!("<{u}>"))
93 .unwrap_or_default();
94
95 let end_addr = if image.end_address > 0 {
96 image.end_address - 1
97 } else {
98 0
99 };
100
101 if self.is_64_bit || force_64bit {
102 format!(
103 " 0x{:018x} - 0x{:018x} {apple_marker}{identifier} {version_str} {uuid_str} {}",
104 image.base_address, end_addr, image.path
105 )
106 } else {
107 format!(
108 " 0x{:010x} - 0x{:010x} {apple_marker}{identifier} {version_str} {uuid_str} {}",
109 image.base_address, end_addr, image.path
110 )
111 }
112 }
113
114 pub fn binary_images_description(&self) -> String {
117 let mut result = String::new();
118
119 if self.binary_images.is_empty() {
120 result.push_str("Binary images description not available.\n");
121 return result;
122 }
123
124 result.push_str("Binary Images:\n");
125 for image in &self.binary_images {
126 let line = self.format_binary_image_line(image, false);
127 result.push_str(&line);
128 result.push('\n');
129 }
130 result
131 }
132
133 pub fn add_thread_backtrace(&mut self, backtrace: ThreadBacktrace) {
143 let thread_idx = self.backtraces.len() as i32;
144
145 if self.crashed_thread_number < 0 {
147 if backtrace.is_crashed {
148 self.crashed_thread_number = thread_idx;
149 } else if let Some(tid) = backtrace.thread_id
150 && self.thread_id == Some(tid)
151 {
152 self.crashed_thread_number = thread_idx;
153 }
154 }
155
156 if self.crashed_thread_number == thread_idx
158 && let Some(frame) = backtrace.frames.first()
159 && let Some(ref sym) = frame.symbol_name
160 {
161 if sym == "___NEW_PROCESS_COULD_NOT_BE_EXECD___" {
162 self.exec_failure_error = Some(String::new());
163 } else if sym.starts_with("objc_msgSend") {
164 self.objc_selector_name = Some(sym.clone());
165 } else if sym.starts_with("dyld_fatal_error") && self.dyld_error_string.is_none() {
166 self.extract_legacy_dyld_error_string = true;
167 }
168 if self.signal == 6 && (sym == "abort" || sym == "__abort") {
170 self.crashed_thread_number = thread_idx;
171 }
172 }
173
174 self.backtraces.push(backtrace);
175 }
176
177 pub fn backtrace_description(&self) -> String {
183 if self.backtraces.is_empty() {
184 return "Backtrace not available\n".to_string();
185 }
186
187 let mut result = String::new();
188
189 for (thread_idx, bt) in self.backtraces.iter().enumerate() {
190 let crashed_marker = if thread_idx as i32 == self.crashed_thread_number {
191 " Crashed"
192 } else {
193 ""
194 };
195
196 result.push_str(&format!("Thread {thread_idx}{crashed_marker}:"));
197 if let Some(ref name) = bt.thread_name {
198 let clean_name = name.replace('\n', " ");
199 result.push_str(&format!(" {clean_name}"));
200 }
201 result.push('\n');
202
203 for frame in &bt.frames {
204 let identifier = if let Some(img) = self.binary_image_for_address(frame.address) {
205 let id = img.identifier.as_deref().unwrap_or("???");
206 if id.len() < 30 {
207 format!("{id:<30}")
208 } else {
209 id.to_string()
210 }
211 } else {
212 format!("{:<30}", "???")
213 };
214
215 let addr_str = if self.is_64_bit {
216 format!("0x{:018x}", frame.address)
217 } else {
218 format!("0x{:010x}", frame.address)
219 };
220
221 result.push_str(&format!(
222 "{} {} {}",
223 frame.frame_number, identifier, addr_str
224 ));
225
226 if let Some(ref sym) = frame.symbol_name {
227 result.push_str(&format!(" {} + 0x{:x}", sym, frame.symbol_offset));
228 } else if let Some(img) = self.binary_image_for_address(frame.address) {
229 let offset = frame.address - img.base_address;
230 result.push_str(&format!(" 0x{:x} + {offset}", img.base_address));
231 }
232
233 if let Some(ref file) = frame.source_file
234 && let Some(line) = frame.source_line
235 {
236 result.push_str(&format!(" ({file}:{line})"));
237 }
238
239 result.push('\n');
240 }
241 result.push('\n');
242 }
243
244 result
245 }
246
247 pub fn thread_state_description(&self) -> String {
253 let thread_label = if self.crashed_thread_number >= 0 {
254 format!("Thread {}", self.crashed_thread_number)
255 } else {
256 "Unknown thread".to_string()
257 };
258
259 let regs = &self.thread_state.registers;
260 let flavor = self.thread_state.flavor;
261
262 if flavor == 6 && regs.len() >= 68 {
265 return self.format_arm64_regs(&thread_label, regs, 0);
266 }
267
268 if flavor == 7 && !regs.is_empty() {
270 let sub_flavor = regs[0];
271 if sub_flavor == 1 && regs.len() >= 18 {
272 return format!(
274 "{thread_label} crashed with X86 Thread State (32-bit):\n \
275 eax: 0x{:08x} ebx: 0x{:08x} ecx: 0x{:08x} edx: 0x{:08x}\n \
276 edi: 0x{:08x} esi: 0x{:08x} ebp: 0x{:08x} esp: 0x{:08x}\n \
277 ss: 0x{:08x} efl: 0x{:08x} eip: 0x{:08x} cs: 0x{:08x}\n \
278 ds: 0x{:08x} es: 0x{:08x} fs: 0x{:08x} gs: 0x{:08x}\n",
279 regs[2],
280 regs[3],
281 regs[4],
282 regs[5],
283 regs[6],
284 regs[7],
285 regs[8],
286 regs[9],
287 regs[10],
288 regs[11],
289 regs[12],
290 regs[13],
291 regs[14],
292 regs[15],
293 regs[16],
294 regs[17]
295 );
296 }
297 if regs.len() >= 44 {
299 let r = |idx: usize| -> u64 {
300 let base = 2 + idx * 2;
301 (regs[base] as u64) | ((regs[base + 1] as u64) << 32)
302 };
303 return format!(
304 "{thread_label} crashed with X86 Thread State (64-bit):\n \
305 rax: 0x{:016x} rbx: 0x{:016x} rcx: 0x{:016x} rdx: 0x{:016x}\n \
306 rdi: 0x{:016x} rsi: 0x{:016x} rbp: 0x{:016x} rsp: 0x{:016x}\n \
307 r8: 0x{:016x} r9: 0x{:016x} r10: 0x{:016x} r11: 0x{:016x}\n \
308 r12: 0x{:016x} r13: 0x{:016x} r14: 0x{:016x} r15: 0x{:016x}\n \
309 rip: 0x{:016x} rfl: 0x{:016x}\n",
310 r(0),
311 r(1),
312 r(2),
313 r(3),
314 r(4),
315 r(5),
316 r(6),
317 r(7),
318 r(8),
319 r(9),
320 r(10),
321 r(11),
322 r(12),
323 r(13),
324 r(14),
325 r(15),
326 r(16),
327 r(17)
328 );
329 }
330 }
331
332 if flavor == 1 && !regs.is_empty() {
334 if self.is_arm_cpu() {
335 let sub_flavor = regs[0];
336 if sub_flavor == 2 && regs.len() >= 70 {
337 return self.format_arm64_regs(&thread_label, regs, 2);
339 }
340 if sub_flavor == 1 && regs.len() >= 19 {
341 return format!(
344 "{thread_label} crashed with ARM Thread State (32-bit):\n \
345 r0: 0x{:08x} r1: 0x{:08x} r2: 0x{:08x} r3: 0x{:08x}\n \
346 r4: 0x{:08x} r5: 0x{:08x} r6: 0x{:08x} r7: 0x{:08x}\n \
347 r8: 0x{:08x} r9: 0x{:08x} r10: 0x{:08x} r11: 0x{:08x}\n \
348 r12: 0x{:08x} sp: 0x{:08x} lr: 0x{:08x} pc: 0x{:08x}\n \
349 cpsr: 0x{:08x}\n",
350 regs[2],
351 regs[3],
352 regs[4],
353 regs[5],
354 regs[6],
355 regs[7],
356 regs[8],
357 regs[9],
358 regs[10],
359 regs[11],
360 regs[12],
361 regs[13],
362 regs[14],
363 regs[15],
364 regs[16],
365 regs[17],
366 regs[18]
367 );
368 }
369 } else if regs.len() >= 16 {
370 return format!(
372 "{thread_label} crashed with X86 Thread State (32-bit):\n \
373 eax: 0x{:08x} ebx: 0x{:08x} ecx: 0x{:08x} edx: 0x{:08x}\n \
374 edi: 0x{:08x} esi: 0x{:08x} ebp: 0x{:08x} esp: 0x{:08x}\n \
375 ss: 0x{:08x} efl: 0x{:08x} eip: 0x{:08x} cs: 0x{:08x}\n \
376 ds: 0x{:08x} es: 0x{:08x} fs: 0x{:08x} gs: 0x{:08x}\n",
377 regs[0],
378 regs[1],
379 regs[2],
380 regs[3],
381 regs[4],
382 regs[5],
383 regs[6],
384 regs[7],
385 regs[8],
386 regs[9],
387 regs[10],
388 regs[11],
389 regs[12],
390 regs[13],
391 regs[14],
392 regs[15]
393 );
394 }
395 }
396
397 format!(
398 "{thread_label} crashed with unknown flavor {}, state count {}\n",
399 flavor,
400 regs.len()
401 )
402 }
403
404 fn format_arm64_regs(&self, thread_label: &str, regs: &[u32], offset: usize) -> String {
407 let r = |idx: usize| -> u64 {
408 let base = offset + idx * 2;
409 (regs[base] as u64) | ((regs[base + 1] as u64) << 32)
410 };
411 let cpsr = regs[offset + 66];
412 format!(
413 "{thread_label} crashed with ARM Thread State (64-bit):\n \
414 x0: 0x{:016x} x1: 0x{:016x} x2: 0x{:016x} x3: 0x{:016x}\n \
415 x4: 0x{:016x} x5: 0x{:016x} x6: 0x{:016x} x7: 0x{:016x}\n \
416 x8: 0x{:016x} x9: 0x{:016x} x10: 0x{:016x} x11: 0x{:016x}\n \
417 x12: 0x{:016x} x13: 0x{:016x} x14: 0x{:016x} x15: 0x{:016x}\n \
418 x16: 0x{:016x} x17: 0x{:016x} x18: 0x{:016x} x19: 0x{:016x}\n \
419 x20: 0x{:016x} x21: 0x{:016x} x22: 0x{:016x} x23: 0x{:016x}\n \
420 x24: 0x{:016x} x25: 0x{:016x} x26: 0x{:016x} x27: 0x{:016x}\n \
421 x28: 0x{:016x} fp: 0x{:016x} lr: 0x{:016x} sp: 0x{:016x}\n \
422 pc: 0x{:016x} cpsr: 0x{:08x}\n",
423 r(0),
424 r(1),
425 r(2),
426 r(3),
427 r(4),
428 r(5),
429 r(6),
430 r(7),
431 r(8),
432 r(9),
433 r(10),
434 r(11),
435 r(12),
436 r(13),
437 r(14),
438 r(15),
439 r(16),
440 r(17),
441 r(18),
442 r(19),
443 r(20),
444 r(21),
445 r(22),
446 r(23),
447 r(24),
448 r(25),
449 r(26),
450 r(27),
451 r(28),
452 r(29),
453 r(30),
454 r(31),
455 r(32),
456 cpsr
457 )
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use crate::test_helpers::*;
464 use crate::types::*;
465
466 mod binary_images {
470 use super::*;
471
472 fn make_image(name: &str, base: u64, end: u64) -> BinaryImage {
473 BinaryImage {
474 name: name.into(),
475 path: format!("/usr/lib/{name}"),
476 uuid: Some("AAAA-BBBB-CCCC".into()),
477 base_address: base,
478 end_address: end,
479 arch: Some("x86_64".into()),
480 identifier: None,
481 version: Some("1.0".into()),
482 }
483 }
484
485 #[test]
486 fn add_binary_image_normal() {
487 let mut cr = make_test_cr();
488 let img = make_image("libfoo.dylib", 0x1000, 0x2000);
489 assert!(cr.add_binary_image(img));
490 assert_eq!(cr.binary_images.len(), 1);
491 }
492
493 #[test]
494 fn add_binary_image_duplicate() {
495 let mut cr = make_test_cr();
496 let img1 = make_image("libfoo.dylib", 0x1000, 0x2000);
497 let img2 = make_image("libfoo.dylib", 0x1000, 0x2000);
498 assert!(cr.add_binary_image(img1));
499 assert!(!cr.add_binary_image(img2));
500 assert_eq!(cr.binary_images.len(), 1);
501 }
502
503 #[test]
504 fn finalize_binary_images_sorts_by_address() {
505 let mut cr = make_test_cr();
506 cr.add_binary_image(make_image("libB.dylib", 0x3000, 0x4000));
507 cr.add_binary_image(make_image("libA.dylib", 0x1000, 0x2000));
508 cr.finalize_binary_images();
509 assert_eq!(cr.binary_images[0].base_address, 0x1000);
510 assert_eq!(cr.binary_images[1].base_address, 0x3000);
511 }
512
513 #[test]
514 fn finalize_binary_images_fills_identifier_from_path() {
515 let mut cr = make_test_cr();
516 let mut img = make_image("libfoo.dylib", 0x1000, 0x2000);
517 img.identifier = None;
518 cr.add_binary_image(img);
519 cr.finalize_binary_images();
520 assert_eq!(
521 cr.binary_images[0].identifier.as_deref(),
522 Some("libfoo.dylib")
523 );
524 }
525
526 #[test]
527 fn finalize_binary_images_max_identifier_length() {
528 let mut cr = make_test_cr();
529 let mut img1 = make_image("short", 0x1000, 0x2000);
530 img1.identifier = Some("short".into());
531 let mut img2 = make_image("much_longer_name.dylib", 0x3000, 0x4000);
532 img2.identifier = Some("much_longer_name.dylib".into());
533 cr.add_binary_image(img1);
534 cr.add_binary_image(img2);
535 cr.finalize_binary_images();
536 assert_eq!(cr.max_binary_identifier_length, 22);
537 }
538
539 #[test]
540 fn finalize_binary_images_idempotent() {
541 let mut cr = make_test_cr();
542 cr.add_binary_image(make_image("libfoo.dylib", 0x1000, 0x2000));
543 cr.finalize_binary_images();
544 cr.finalize_binary_images(); assert_eq!(cr.binary_images.len(), 1);
546 }
547
548 #[test]
549 fn binary_image_for_address_found() {
550 let mut cr = make_test_cr();
551 cr.add_binary_image(make_image("libfoo.dylib", 0x1000, 0x2000));
552 assert!(cr.binary_image_for_address(0x1500).is_some());
553 }
554
555 #[test]
556 fn binary_image_for_address_not_found() {
557 let mut cr = make_test_cr();
558 cr.add_binary_image(make_image("libfoo.dylib", 0x1000, 0x2000));
559 assert!(cr.binary_image_for_address(0x3000).is_none());
560 }
561
562 #[test]
563 fn binary_image_for_address_boundaries() {
564 let mut cr = make_test_cr();
565 cr.add_binary_image(make_image("libfoo.dylib", 0x1000, 0x2000));
566 assert!(cr.binary_image_for_address(0x1000).is_some());
568 assert!(cr.binary_image_for_address(0x2000).is_none());
570 assert!(cr.binary_image_for_address(0x1FFF).is_some());
572 }
573
574 #[test]
575 fn binary_image_for_path_found() {
576 let mut cr = make_test_cr();
577 cr.add_binary_image(make_image("libfoo.dylib", 0x1000, 0x2000));
578 assert!(cr.binary_image_for_path("/usr/lib/libfoo.dylib").is_some());
579 }
580
581 #[test]
582 fn binary_image_for_path_not_found() {
583 let mut cr = make_test_cr();
584 cr.add_binary_image(make_image("libfoo.dylib", 0x1000, 0x2000));
585 assert!(cr.binary_image_for_path("/usr/lib/libbar.dylib").is_none());
586 }
587
588 #[test]
589 fn format_binary_image_line_apple() {
590 let cr = make_test_cr();
591 let mut img = make_image("libSystem.B.dylib", 0x1000, 0x2000);
592 img.identifier = Some("libSystem.B.dylib".into());
593 let line = cr.format_binary_image_line(&img, false);
594 assert!(line.contains("+libSystem.B.dylib"));
595 }
596
597 #[test]
598 fn format_binary_image_line_non_apple() {
599 let cr = make_test_cr();
600 let mut img = make_image("libfoo.dylib", 0x1000, 0x2000);
601 img.path = "/Applications/Foo.app/Contents/Frameworks/libfoo.dylib".into();
602 img.identifier = Some("libfoo.dylib".into());
603 let line = cr.format_binary_image_line(&img, false);
604 assert!(line.contains(" libfoo.dylib"));
605 assert!(!line.contains("+libfoo.dylib"));
606 }
607
608 #[test]
609 fn format_binary_image_line_32bit() {
610 let mut cr = make_test_cr();
611 cr.is_64_bit = false;
612 let img = make_image("libfoo.dylib", 0x1000, 0x2000);
613 let line = cr.format_binary_image_line(&img, false);
614 assert!(line.contains("0x0000001000"));
616 }
617
618 #[test]
619 fn format_binary_image_line_64bit() {
620 let cr = make_test_cr();
621 let img = make_image("libfoo.dylib", 0x1000, 0x2000);
622 let line = cr.format_binary_image_line(&img, false);
623 assert!(line.contains("0x000000000000001000"));
625 }
626
627 #[test]
628 fn format_binary_image_line_missing_uuid() {
629 let cr = make_test_cr();
630 let mut img = make_image("libfoo.dylib", 0x1000, 0x2000);
631 img.uuid = None;
632 let line = cr.format_binary_image_line(&img, false);
633 assert!(!line.contains('<'));
634 }
635
636 #[test]
637 fn format_binary_image_line_missing_version() {
638 let cr = make_test_cr();
639 let mut img = make_image("libfoo.dylib", 0x1000, 0x2000);
640 img.version = None;
641 let line = cr.format_binary_image_line(&img, false);
642 assert!(!line.contains('('));
643 }
644
645 #[test]
646 fn binary_images_description_empty() {
647 let cr = make_test_cr();
648 let desc = cr.binary_images_description();
649 assert!(desc.contains("not available"));
650 }
651
652 #[test]
653 fn binary_images_description_populated() {
654 let mut cr = make_test_cr();
655 cr.add_binary_image(make_image("libfoo.dylib", 0x1000, 0x2000));
656 let desc = cr.binary_images_description();
657 assert!(desc.starts_with("Binary Images:"));
658 assert!(desc.contains("libfoo.dylib"));
659 }
660 }
661
662 mod backtrace_methods {
666 use super::*;
667
668 fn make_frame(num: u32, sym: Option<&str>, addr: u64) -> BacktraceFrame {
669 BacktraceFrame {
670 frame_number: num,
671 image_name: "libfoo.dylib".into(),
672 address: addr,
673 symbol_name: sym.map(|s| s.into()),
674 symbol_offset: 42,
675 source_file: None,
676 source_line: None,
677 }
678 }
679
680 fn make_bt(
681 thread_num: u32,
682 is_crashed: bool,
683 frames: Vec<BacktraceFrame>,
684 ) -> ThreadBacktrace {
685 ThreadBacktrace {
686 thread_number: thread_num,
687 thread_name: None,
688 thread_id: None,
689 is_crashed,
690 frames,
691 }
692 }
693
694 #[test]
695 fn add_thread_backtrace_sets_crashed_on_is_crashed() {
696 let mut cr = make_test_cr();
697 cr.crashed_thread_number = -1;
698 let bt = make_bt(0, true, vec![make_frame(0, Some("main"), 0x1000)]);
699 cr.add_thread_backtrace(bt);
700 assert_eq!(cr.crashed_thread_number, 0);
701 }
702
703 #[test]
704 fn add_thread_backtrace_sets_crashed_on_thread_id_match() {
705 let mut cr = make_test_cr();
706 cr.crashed_thread_number = -1;
707 cr.thread_id = Some(999);
708 let mut bt = make_bt(0, false, vec![make_frame(0, Some("main"), 0x1000)]);
709 bt.thread_id = Some(999);
710 cr.add_thread_backtrace(bt);
711 assert_eq!(cr.crashed_thread_number, 0);
712 }
713
714 #[test]
715 fn add_thread_backtrace_exec_failure_pattern() {
716 let mut cr = make_test_cr();
717 cr.crashed_thread_number = -1;
718 let bt = make_bt(
719 0,
720 true,
721 vec![make_frame(
722 0,
723 Some("___NEW_PROCESS_COULD_NOT_BE_EXECD___"),
724 0x1000,
725 )],
726 );
727 cr.add_thread_backtrace(bt);
728 assert!(cr.exec_failure_error.is_some());
729 }
730
731 #[test]
732 fn add_thread_backtrace_objc_msgsend_pattern() {
733 let mut cr = make_test_cr();
734 cr.crashed_thread_number = -1;
735 let bt = make_bt(0, true, vec![make_frame(0, Some("objc_msgSend"), 0x1000)]);
736 cr.add_thread_backtrace(bt);
737 assert_eq!(cr.objc_selector_name, Some("objc_msgSend".into()));
738 }
739
740 #[test]
741 fn add_thread_backtrace_dyld_fatal_error_pattern() {
742 let mut cr = make_test_cr();
743 cr.crashed_thread_number = -1;
744 let bt = make_bt(
745 0,
746 true,
747 vec![make_frame(0, Some("dyld_fatal_error"), 0x1000)],
748 );
749 cr.add_thread_backtrace(bt);
750 assert!(cr.extract_legacy_dyld_error_string);
751 }
752
753 #[test]
754 fn add_thread_backtrace_sigabrt_abort_pattern() {
755 let mut cr = make_test_cr();
756 cr.crashed_thread_number = -1;
757 cr.signal = 6; let bt = make_bt(0, true, vec![make_frame(0, Some("abort"), 0x1000)]);
759 cr.add_thread_backtrace(bt);
760 assert_eq!(cr.crashed_thread_number, 0);
761 }
762
763 #[test]
764 fn backtrace_description_empty() {
765 let cr = make_test_cr();
766 assert!(cr.backtrace_description().contains("not available"));
767 }
768
769 #[test]
770 fn backtrace_description_single_thread() {
771 let mut cr = make_test_cr();
772 cr.crashed_thread_number = -1;
773 let bt = make_bt(0, false, vec![make_frame(0, Some("main"), 0x1000)]);
774 cr.add_thread_backtrace(bt);
775 let desc = cr.backtrace_description();
776 assert!(desc.contains("Thread 0:"));
777 assert!(desc.contains("main + 0x2a"));
778 }
779
780 #[test]
781 fn backtrace_description_crashed_thread_marker() {
782 let mut cr = make_test_cr();
783 cr.crashed_thread_number = -1;
784 let bt = make_bt(0, true, vec![make_frame(0, Some("crash_fn"), 0x1000)]);
785 cr.add_thread_backtrace(bt);
786 let desc = cr.backtrace_description();
787 assert!(desc.contains("Thread 0 Crashed:"));
788 }
789
790 #[test]
791 fn backtrace_description_32bit_addresses() {
792 let mut cr = make_test_cr();
793 cr.is_64_bit = false;
794 cr.crashed_thread_number = -1;
795 let bt = make_bt(0, false, vec![make_frame(0, Some("fn"), 0x1000)]);
796 cr.add_thread_backtrace(bt);
797 let desc = cr.backtrace_description();
798 assert!(desc.contains("0x0000001000"));
800 }
801
802 #[test]
803 fn backtrace_description_64bit_addresses() {
804 let mut cr = make_test_cr();
805 cr.crashed_thread_number = -1;
806 let bt = make_bt(0, false, vec![make_frame(0, Some("fn"), 0x1000)]);
807 cr.add_thread_backtrace(bt);
808 let desc = cr.backtrace_description();
809 assert!(desc.contains("0x000000000000001000"));
811 }
812
813 #[test]
814 fn backtrace_description_source_file_line() {
815 let mut cr = make_test_cr();
816 cr.crashed_thread_number = -1;
817 let mut frame = make_frame(0, Some("fn"), 0x1000);
818 frame.source_file = Some("main.c".into());
819 frame.source_line = Some(42);
820 let bt = make_bt(0, false, vec![frame]);
821 cr.add_thread_backtrace(bt);
822 let desc = cr.backtrace_description();
823 assert!(desc.contains("(main.c:42)"));
824 }
825
826 #[test]
827 fn thread_state_description_flavor7_sub1_32bit() {
828 let mut cr = make_test_cr();
829 cr.crashed_thread_number = 0;
830 let mut regs = vec![0u32; 18];
832 regs[0] = 1; regs[1] = 0; regs[2] = 0xAAAA_AAAA; cr.thread_state = ThreadState {
836 flavor: 7,
837 registers: regs,
838 };
839 let desc = cr.thread_state_description();
840 assert!(desc.contains("32-bit"));
841 assert!(desc.contains("eax: 0xaaaaaaaa"));
842 }
843
844 #[test]
845 fn thread_state_description_flavor7_64bit() {
846 let mut cr = make_test_cr();
847 cr.crashed_thread_number = 0;
848 let mut regs = vec![0u32; 44];
850 regs[0] = 4; regs[2] = 0xDEAD_BEEF; regs[3] = 0x0000_0001; cr.thread_state = ThreadState {
854 flavor: 7,
855 registers: regs,
856 };
857 let desc = cr.thread_state_description();
858 assert!(desc.contains("64-bit"));
859 assert!(desc.contains("rax: 0x00000001deadbeef"));
860 }
861
862 #[test]
863 fn thread_state_description_flavor1() {
864 let mut cr = make_test_cr();
865 cr.crashed_thread_number = 0;
866 let mut regs = vec![0u32; 16];
867 regs[0] = 0xBBBB_BBBB; cr.thread_state = ThreadState {
869 flavor: 1,
870 registers: regs,
871 };
872 let desc = cr.thread_state_description();
873 assert!(desc.contains("32-bit"));
874 assert!(desc.contains("eax: 0xbbbbbbbb"));
875 }
876
877 #[test]
878 fn thread_state_description_unknown_flavor() {
879 let mut cr = make_test_cr();
880 cr.crashed_thread_number = 0;
881 cr.thread_state = ThreadState {
882 flavor: 99,
883 registers: vec![],
884 };
885 let desc = cr.thread_state_description();
886 assert!(desc.contains("unknown flavor 99"));
887 }
888
889 #[test]
890 fn thread_state_description_arm64_flavor6() {
891 let mut cr = make_test_cr_arm64();
892 cr.crashed_thread_number = 0;
893 let mut regs = vec![0u32; 68];
896 regs[0] = 0xCAFE_BABE; regs[1] = 0x0000_0001; regs[64] = 0xDEAD_0000; regs[65] = 0x0000_FFFF; regs[66] = 0x8000_0000; cr.thread_state = ThreadState {
902 flavor: 6,
903 registers: regs,
904 };
905 let desc = cr.thread_state_description();
906 assert!(desc.contains("ARM Thread State (64-bit)"));
907 assert!(desc.contains("x0: 0x00000001cafebabe"));
908 assert!(desc.contains("pc: 0x0000ffffdead0000"));
909 assert!(desc.contains("cpsr: 0x80000000"));
910 }
911
912 #[test]
913 fn thread_state_description_arm_thread_state_sub2() {
914 let mut cr = make_test_cr_arm64();
915 cr.crashed_thread_number = 0;
916 let mut regs = vec![0u32; 70];
918 regs[0] = 2; regs[1] = 0; regs[2] = 0x1111_2222; regs[3] = 0x3333_4444; regs[68] = 0xAAAA_BBBB; cr.thread_state = ThreadState {
924 flavor: 1,
925 registers: regs,
926 };
927 let desc = cr.thread_state_description();
928 assert!(desc.contains("ARM Thread State (64-bit)"));
929 assert!(desc.contains("x0: 0x3333444411112222"));
930 assert!(desc.contains("cpsr: 0xaaaabbbb"));
931 }
932
933 #[test]
934 fn thread_state_description_arm32() {
935 let mut cr = make_test_cr();
936 cr.cpu_type = CpuType::ARM;
937 cr.crashed_thread_number = 0;
938 let mut regs = vec![0u32; 19];
940 regs[0] = 1; regs[1] = 0; regs[2] = 0xDEAD_BEEF; regs[17] = 0x1234_5678; regs[18] = 0x6000_0010; cr.thread_state = ThreadState {
946 flavor: 1,
947 registers: regs,
948 };
949 let desc = cr.thread_state_description();
950 assert!(desc.contains("ARM Thread State (32-bit)"));
951 assert!(desc.contains("r0: 0xdeadbeef"));
952 assert!(desc.contains("pc: 0x12345678"));
953 assert!(desc.contains("cpsr: 0x60000010"));
954 }
955 }
956}