1#![cfg(unix)]
5
6use super::collector_manager::Collector;
7use super::receiver_manager::Receiver;
8use super::saguard::{SaGuard, SuppressionMode};
9use super::signal_handler_manager::chain_signal_handler;
10use crate::crash_info::Metadata;
11use crate::shared::configuration::CrashtrackerConfiguration;
12use crate::StackTrace;
13use core::ptr;
14use core::sync::atomic::Ordering::{Acquire, Relaxed, SeqCst};
15use core::sync::atomic::{AtomicBool, AtomicI32, AtomicPtr, AtomicU64};
16use errno::{errno, set_errno};
17use libc::{c_void, pid_t, siginfo_t, ucontext_t};
18use libdd_common::timeout::TimeoutManager;
19use std::os::fd::OwnedFd;
20use std::os::unix::io::{AsRawFd, FromRawFd};
21use std::os::unix::net::UnixStream;
22use std::panic;
23use std::panic::PanicHookInfo;
24
25static METADATA: AtomicPtr<(Metadata, String)> = AtomicPtr::new(ptr::null_mut());
45static CONFIG: AtomicPtr<(CrashtrackerConfiguration, String)> = AtomicPtr::new(ptr::null_mut());
46static PANIC_MESSAGE: AtomicPtr<String> = AtomicPtr::new(ptr::null_mut());
47
48type PanicHook = Box<dyn Fn(&PanicHookInfo<'_>) + Send + Sync>;
49static PREVIOUS_PANIC_HOOK: AtomicPtr<PanicHook> = AtomicPtr::new(ptr::null_mut());
50
51static EXPECTED_RECEIVER_PID: AtomicI32 = AtomicI32::new(0);
55
56pub fn set_expected_receiver_pid(pid: pid_t) {
69 EXPECTED_RECEIVER_PID.store(pid, Relaxed);
70}
71
72pub fn get_expected_receiver_pid() -> pid_t {
74 EXPECTED_RECEIVER_PID.load(Relaxed)
75}
76
77#[derive(Debug, thiserror::Error)]
78pub enum CrashHandlerError {
79 #[error("No crashtracking config available")]
80 NoConfig,
81 #[error("No crashtracking metadata available")]
82 NoMetadata,
83 #[error("Failed to spawn receiver: {0}")]
84 ReceiverSpawnError(#[from] super::receiver_manager::ReceiverError),
85 #[error("Failed to spawn collector: {0}")]
86 CollectorSpawnError(#[from] super::collector_manager::CollectorSpawnError),
87}
88
89pub fn update_metadata(metadata: Metadata) -> anyhow::Result<()> {
101 let metadata_string = serde_json::to_string(&metadata)?;
102 let box_ptr = Box::into_raw(Box::new((metadata, metadata_string)));
103 let old = METADATA.swap(box_ptr, SeqCst);
104 if !old.is_null() {
105 unsafe {
107 core::mem::drop(Box::from_raw(old));
108 }
109 }
110 Ok(())
111}
112
113fn format_message(
115 category: &str,
116 panic_message: &str,
117 location: Option<&panic::Location>,
118) -> String {
119 let base = if panic_message.is_empty() {
120 format!("Process panicked with {}", category)
121 } else {
122 format!("Process panicked with {} \"{}\"", category, panic_message)
123 };
124
125 match location {
126 Some(loc) => format!("{} ({}:{}:{})", base, loc.file(), loc.line(), loc.column()),
127 None => base,
128 }
129}
130
131pub fn register_panic_hook() -> anyhow::Result<()> {
142 if !PREVIOUS_PANIC_HOOK.load(SeqCst).is_null() {
144 return Ok(());
145 }
146
147 let old_hook = panic::take_hook();
148 let old_hook_ptr = Box::into_raw(Box::new(old_hook));
149 PREVIOUS_PANIC_HOOK.swap(old_hook_ptr, SeqCst);
150 panic::set_hook(Box::new(|panic_info| {
151 let message = if let Some(&s) = panic_info.payload().downcast_ref::<&str>() {
153 format_message("message", s, panic_info.location())
154 } else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
155 format_message("message", s.as_str(), panic_info.location())
156 } else {
157 format_message("unknown type", "", panic_info.location())
159 };
160
161 let message_ptr = PANIC_MESSAGE.swap(Box::into_raw(Box::new(message)), SeqCst);
163 if !message_ptr.is_null() {
165 unsafe {
166 core::mem::drop(Box::from_raw(message_ptr));
167 }
168 }
169
170 call_previous_panic_hook(panic_info);
171 }));
172 Ok(())
173}
174
175fn call_previous_panic_hook(panic_info: &PanicHookInfo<'_>) {
184 let old_hook_ptr = PREVIOUS_PANIC_HOOK.load(SeqCst);
185 if !old_hook_ptr.is_null() {
186 unsafe {
189 let old_hook = &*old_hook_ptr;
190 old_hook(panic_info);
191 }
192 }
193}
194
195pub fn update_config(config: CrashtrackerConfiguration) -> anyhow::Result<()> {
207 let config_string = serde_json::to_string(&config)?;
208 let box_ptr = Box::into_raw(Box::new((config, config_string)));
209 let old = CONFIG.swap(box_ptr, SeqCst);
210 if !old.is_null() {
211 unsafe {
213 core::mem::drop(Box::from_raw(old));
214 }
215 }
216 Ok(())
217}
218
219pub(crate) extern "C" fn handle_posix_sigaction(
220 signum: i32,
221 sig_info: *mut siginfo_t,
222 ucontext: *mut c_void,
223) {
224 let errno = errno();
226
227 let _ = handle_posix_signal_impl(sig_info, ucontext as *mut ucontext_t);
230
231 set_errno(errno);
233 unsafe { chain_signal_handler(signum, sig_info, ucontext) };
236}
237
238static ENABLED: AtomicBool = AtomicBool::new(true);
239
240pub fn disable() {
252 ENABLED.store(false, SeqCst);
253}
254
255pub fn enable() {
265 ENABLED.store(true, SeqCst);
266}
267
268fn handle_posix_signal_impl(
269 sig_info: *const siginfo_t,
270 ucontext: *const ucontext_t,
271) -> Result<(), CrashHandlerError> {
272 if !ENABLED.load(SeqCst) {
273 return Ok(());
274 }
275
276 static NUM_TIMES_CALLED: AtomicU64 = AtomicU64::new(0);
281 if NUM_TIMES_CALLED.fetch_add(1, SeqCst) > 0 {
282 return Ok(());
285 }
286
287 #[cfg(target_os = "linux")]
288 {
289 super::api::mark_preload_logger_collector();
290 }
291
292 let _sa_guard = SaGuard::new_with_modes(&[
296 (
297 nix::sys::signal::Signal::SIGCHLD,
298 SuppressionMode::BlockOnly,
299 ),
300 (
301 nix::sys::signal::Signal::SIGPIPE,
302 SuppressionMode::IgnoreAndBlock,
303 ),
304 ]);
305
306 let config_ptr = take_config_ptr();
311 if config_ptr.is_null() {
312 return Err(CrashHandlerError::NoConfig);
313 }
314 let (config, config_str) = unsafe { &*config_ptr };
315
316 let metadata_ptr = take_metadata_ptr();
317 if metadata_ptr.is_null() {
318 return Err(CrashHandlerError::NoMetadata);
319 }
320 let (_metadata, metadata_string) = unsafe { &*metadata_ptr };
321
322 let panic_message_ptr = PANIC_MESSAGE.swap(ptr::null_mut(), Acquire);
326
327 let message: Option<&str> = if !panic_message_ptr.is_null() {
330 Some(unsafe { &*panic_message_ptr })
333 } else {
334 #[cfg(all(target_os = "linux", target_pointer_width = "64"))]
335 {
336 super::assert_interceptor::take_assert_message()
337 }
338 #[cfg(not(all(target_os = "linux", target_pointer_width = "64")))]
339 {
340 None
341 }
342 };
343
344 let timeout_manager = TimeoutManager::new(config.timeout());
345
346 let receiver = Receiver::from_crashtracker_config(config)?;
347
348 #[cfg(target_os = "linux")]
353 if config.collect_all_threads() {
354 grant_ptracer_permission(&receiver);
355 }
356
357 let collector = Collector::spawn(
358 &receiver,
359 config,
360 config_str,
361 metadata_string,
362 message,
363 sig_info,
364 ucontext,
365 )?;
366
367 collector.finish(&timeout_manager);
369 receiver.finish(&timeout_manager);
370
371 Ok(())
372}
373
374fn take_metadata_ptr() -> *mut (crate::crash_info::Metadata, String) {
380 METADATA.swap(ptr::null_mut(), SeqCst)
381}
382
383fn take_config_ptr() -> *mut (
389 crate::shared::configuration::CrashtrackerConfiguration,
390 String,
391) {
392 CONFIG.swap(ptr::null_mut(), SeqCst)
393}
394
395fn take_metadata() -> Option<(crate::crash_info::Metadata, String)> {
399 let ptr = take_metadata_ptr();
400 if ptr.is_null() {
401 None
402 } else {
403 Some(*unsafe { Box::from_raw(ptr) })
405 }
406}
407
408fn take_config() -> Option<(
412 crate::shared::configuration::CrashtrackerConfiguration,
413 String,
414)> {
415 let ptr = take_config_ptr();
416 if ptr.is_null() {
417 None
418 } else {
419 Some(*unsafe { Box::from_raw(ptr) })
421 }
422}
423
424#[cfg(target_os = "linux")]
432fn grant_ptracer_permission(receiver: &Receiver) {
433 let ptracer_pid = match receiver.handle.pid {
434 Some(pid) => pid,
435 None => {
436 let expected_pid = get_expected_receiver_pid();
437 if expected_pid <= 0 {
438 0
439 } else {
440 let mut cred: libc::ucred = unsafe { core::mem::zeroed() };
441 let mut len = core::mem::size_of::<libc::ucred>() as libc::socklen_t;
442 let ret = unsafe {
444 libc::getsockopt(
445 receiver.handle.uds_fd,
446 libc::SOL_SOCKET,
447 libc::SO_PEERCRED,
448 &mut cred as *mut _ as *mut libc::c_void,
449 &mut len,
450 )
451 };
452 if ret == 0 && cred.pid == expected_pid {
453 cred.pid
454 } else {
455 0
456 }
457 }
458 }
459 };
460 if ptracer_pid > 0 {
461 unsafe {
463 libc::prctl(libc::PR_SET_PTRACER, ptracer_pid as libc::c_ulong);
464 }
465 }
466}
467
468pub fn report_unhandled_exception(
484 exception_type: Option<&str>,
485 exception_message: Option<&str>,
486 stacktrace: StackTrace,
487) -> Result<(), CrashHandlerError> {
488 disable();
507
508 let (config, config_str) = take_config().ok_or(CrashHandlerError::NoConfig)?;
509 let (_metadata, metadata_str) = take_metadata().ok_or(CrashHandlerError::NoMetadata)?;
510
511 let receiver = Receiver::from_crashtracker_config(&config)?;
512
513 #[cfg(target_os = "linux")]
514 if config.collect_all_threads() {
515 grant_ptracer_permission(&receiver);
516 }
517
518 let timeout_manager = TimeoutManager::new(config.timeout());
519
520 let pid = unsafe { libc::getpid() };
521 let tid = libdd_common::threading::get_current_thread_id() as libc::pid_t;
522
523 let error_type_str = exception_type
531 .unwrap_or("<unknown>")
532 .replace('\n', "\\n")
533 .replace('\r', "\\r");
534 let error_message_str = exception_message
535 .unwrap_or("<no message>")
536 .replace('\n', "\\n")
537 .replace('\r', "\\r");
538 let message = format!(
539 "Process was terminated due to an unhandled exception of type '{error_type_str}'. \
540 Message: {error_message_str}"
541 );
542
543 let poll_fd = unsafe { OwnedFd::from_raw_fd(libc::dup(receiver.handle.uds_fd)) };
550 let receiver_pid = receiver.handle.pid;
551
552 {
553 let mut unix_stream = unsafe { UnixStream::from_raw_fd(receiver.handle.uds_fd) };
554 let _ = super::emitters::emit_crashreport(
555 &mut unix_stream,
556 &config,
557 &config_str,
558 &metadata_str,
559 Some(message.as_str()),
560 super::emitters::CrashKindData::UnhandledException { stacktrace },
561 pid,
562 tid,
563 );
564 }
567
568 let finish_handle =
571 super::process_handle::ProcessHandle::new(poll_fd.as_raw_fd(), receiver_pid);
572 finish_handle.finish(&timeout_manager);
573
574 Ok(())
575}
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use core::time::Duration;
580
581 fn make_test_metadata() -> Metadata {
582 Metadata {
583 library_name: "test-lib".to_string(),
584 library_version: "1.0.0".to_string(),
585 family: "test-family".to_string(),
586 tags: vec![],
587 }
588 }
589
590 fn make_test_config() -> CrashtrackerConfiguration {
591 let builder = CrashtrackerConfiguration::builder();
592 builder.timeout(Duration::from_secs(1)).build().unwrap()
593 }
594
595 fn clear_metadata() {
597 let ptr = METADATA.swap(ptr::null_mut(), SeqCst);
598 if !ptr.is_null() {
599 unsafe { drop(Box::from_raw(ptr)) };
600 }
601 }
602
603 fn clear_config() {
605 let ptr = CONFIG.swap(ptr::null_mut(), SeqCst);
606 if !ptr.is_null() {
607 unsafe { drop(Box::from_raw(ptr)) };
608 }
609 }
610
611 #[test]
612 fn test_register_panic_hook() {
613 assert!(PREVIOUS_PANIC_HOOK.load(SeqCst).is_null());
614
615 let result = register_panic_hook();
616 assert!(result.is_ok());
617
618 assert!(!PREVIOUS_PANIC_HOOK.load(SeqCst).is_null());
619 }
620
621 #[test]
622 fn test_panic_message_storage_and_retrieval() {
623 let test_message = "test panic message".to_string();
625 let message_ptr = Box::into_raw(Box::new(test_message.clone()));
626
627 let old_ptr = PANIC_MESSAGE.swap(message_ptr, SeqCst);
629 assert!(old_ptr.is_null()); let retrieved_ptr = PANIC_MESSAGE.swap(ptr::null_mut(), SeqCst);
633 assert!(!retrieved_ptr.is_null());
634
635 unsafe {
636 let retrieved_message = *Box::from_raw(retrieved_ptr);
637 assert_eq!(retrieved_message, test_message);
638 }
639 }
640
641 #[test]
642 fn test_panic_message_null_handling() {
643 PANIC_MESSAGE.store(ptr::null_mut(), SeqCst);
645
646 let message_ptr = PANIC_MESSAGE.load(SeqCst);
647 assert!(message_ptr.is_null());
648
649 let old_ptr = PANIC_MESSAGE.swap(ptr::null_mut(), SeqCst);
651 assert!(old_ptr.is_null());
652 }
653
654 #[test]
655 fn test_panic_message_replacement() {
656 let message1 = "first message".to_string();
658 let message2 = "second message".to_string();
659
660 let ptr1 = Box::into_raw(Box::new(message1));
661 let ptr2 = Box::into_raw(Box::new(message2.clone()));
662
663 PANIC_MESSAGE.store(ptr1, SeqCst);
664 let old_ptr = PANIC_MESSAGE.swap(ptr2, SeqCst);
665
666 assert_eq!(old_ptr, ptr1);
668
669 unsafe {
671 drop(Box::from_raw(old_ptr));
672 let final_ptr = PANIC_MESSAGE.swap(ptr::null_mut(), SeqCst);
673 let final_message = *Box::from_raw(final_ptr);
674 assert_eq!(final_message, message2);
675 }
676 }
677
678 #[test]
679 fn test_metadata_update_atomic() {
680 let metadata = Metadata {
682 library_name: "test".to_string(),
683 library_version: "1.0.0".to_string(),
684 family: "test_family".to_string(),
685 tags: vec![],
686 };
687
688 let result = update_metadata(metadata.clone());
689 assert!(result.is_ok());
690
691 let metadata_ptr = METADATA.load(SeqCst);
693 assert!(!metadata_ptr.is_null());
694
695 unsafe {
696 let (stored_metadata, _) = &*metadata_ptr;
697 assert_eq!(stored_metadata.library_name, "test");
698 }
699 }
700
701 #[test]
702 fn test_format_message_with_message_and_location() {
703 let location = panic::Location::caller();
704 let result = format_message("message", "test panic", Some(location));
705
706 assert!(result.starts_with("Process panicked with message \"test panic\" ("));
707 assert!(result.contains(&format!("{}:", location.file())));
708 assert!(result.contains(&format!(":{}", location.line())));
709 assert!(result.ends_with(&format!("{})", location.column())));
710 }
711
712 #[test]
713 fn test_format_message_with_message_no_location() {
714 let result = format_message("message", "test panic", None);
715 assert_eq!(result, "Process panicked with message \"test panic\"");
716 }
717
718 #[test]
719 fn test_format_message_empty_message_with_location() {
720 let location = panic::Location::caller();
721 let result = format_message("unknown type", "", Some(location));
722
723 assert!(result.starts_with("Process panicked with unknown type ("));
724 assert!(result.contains(&format!("{}:", location.file())));
725 assert!(result.ends_with(&format!("{})", location.column())));
726 }
727
728 #[test]
729 fn test_format_message_empty_message_no_location() {
730 let result = format_message("unknown type", "", None);
731 assert_eq!(result, "Process panicked with unknown type");
732 }
733
734 #[test]
735 fn test_format_message_different_categories() {
736 let result1 = format_message("message", "test", None);
737 assert_eq!(result1, "Process panicked with message \"test\"");
738
739 let result2 = format_message("unknown type", "", None);
740 assert_eq!(result2, "Process panicked with unknown type");
741
742 let result3 = format_message("custom category", "content", None);
743 assert_eq!(result3, "Process panicked with custom category \"content\"");
744 }
745
746 #[test]
747 fn test_format_message_with_special_characters() {
748 let result = format_message("message", "test \"quoted\" 'text'", None);
749 assert_eq!(
750 result,
751 "Process panicked with message \"test \"quoted\" 'text'\""
752 );
753 }
754
755 #[test]
758 fn test_take_metadata_ptr_returns_null_when_unset() {
759 clear_metadata();
760 assert!(take_metadata_ptr().is_null());
761 }
762
763 #[test]
764 fn test_take_metadata_ptr_takes_value_and_leaves_null() {
765 clear_metadata();
766 update_metadata(make_test_metadata()).unwrap();
767
768 let ptr = take_metadata_ptr();
769 assert!(!ptr.is_null());
770
771 assert!(take_metadata_ptr().is_null());
773
774 unsafe { drop(Box::from_raw(ptr)) };
776 }
777
778 #[test]
779 fn test_take_metadata_ptr_preserves_data() {
780 clear_metadata();
781 let metadata = make_test_metadata();
782 update_metadata(metadata.clone()).unwrap();
783
784 let ptr = take_metadata_ptr();
785 assert!(!ptr.is_null());
786
787 let (stored_metadata, stored_json) = unsafe { &*ptr };
788 assert_eq!(stored_metadata.library_name, metadata.library_name);
789 assert_eq!(stored_metadata.library_version, metadata.library_version);
790 assert_eq!(stored_metadata.family, metadata.family);
791 assert!(!stored_json.is_empty());
793 assert!(serde_json::from_str::<serde_json::Value>(stored_json).is_ok());
794
795 unsafe { drop(Box::from_raw(ptr)) };
796 }
797
798 #[test]
801 fn test_take_config_ptr_returns_null_when_unset() {
802 clear_config();
803 assert!(take_config_ptr().is_null());
804 }
805
806 #[test]
807 fn test_take_config_ptr_takes_value_and_leaves_null() {
808 clear_config();
809 update_config(make_test_config()).unwrap();
810
811 let ptr = take_config_ptr();
812 assert!(!ptr.is_null());
813
814 assert!(take_config_ptr().is_null());
816
817 unsafe { drop(Box::from_raw(ptr)) };
818 }
819
820 #[test]
823 fn test_take_metadata_returns_none_when_unset() {
824 clear_metadata();
825 assert!(take_metadata().is_none());
826 }
827
828 #[test]
829 fn test_take_metadata_returns_value_and_leaves_none() {
830 clear_metadata();
831 let metadata = make_test_metadata();
832 update_metadata(metadata.clone()).unwrap();
833
834 let (taken_metadata, taken_json) = take_metadata().expect("should return Some");
835 assert_eq!(taken_metadata.library_name, metadata.library_name);
836 assert_eq!(taken_metadata.library_version, metadata.library_version);
837 assert_eq!(taken_metadata.family, metadata.family);
838 assert!(!taken_json.is_empty());
839
840 assert!(take_metadata().is_none());
842 }
843
844 #[test]
847 fn test_take_config_returns_none_when_unset() {
848 clear_config();
849 assert!(take_config().is_none());
850 }
851
852 #[test]
853 fn test_take_config_returns_value_and_leaves_none() {
854 clear_config();
855 let config = make_test_config();
856 update_config(config.clone()).unwrap();
857
858 let (taken_config, taken_json) = take_config().expect("should return Some");
859 assert_eq!(taken_config, config);
860 assert!(!taken_json.is_empty());
861 assert!(serde_json::from_str::<serde_json::Value>(&taken_json).is_ok());
862
863 assert!(take_config().is_none());
865 }
866}