libdd_crashtracker/collector/crash_handler.rs
1// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4#![cfg(unix)]
5
6use super::collector_manager::Collector;
7use super::receiver_manager::Receiver;
8use super::signal_handler_manager::chain_signal_handler;
9use crate::crash_info::Metadata;
10use crate::shared::configuration::CrashtrackerConfiguration;
11use libc::{c_void, siginfo_t, ucontext_t};
12use libdd_common::timeout::TimeoutManager;
13use std::ptr;
14use std::sync::atomic::Ordering::SeqCst;
15use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64};
16
17// Note that this file makes use the following async-signal safe functions in a signal handler.
18// <https://man7.org/linux/man-pages/man7/signal-safety.7.html>
19// - clock_gettime
20// - close (although Rust may call `free` because we call the higher-level nix interface)
21// - dup2
22// - fork (on MacOS; Linux calls `fork()` directly as syscall)
23// - kill
24// - poll
25// - raise
26// - read
27// - sigaction
28// - write
29
30// These represent data used by the crashtracker.
31// Using mutexes inside a signal handler is not allowed, so use `AtomicPtr`
32// instead to get atomicity.
33// These should always be either: null_mut, or `Box::into_raw()`
34// This means that we can always clean up the memory inside one of these using
35// `Box::from_raw` to recreate the box, then dropping it.
36static METADATA: AtomicPtr<(Metadata, String)> = AtomicPtr::new(ptr::null_mut());
37static CONFIG: AtomicPtr<(CrashtrackerConfiguration, String)> = AtomicPtr::new(ptr::null_mut());
38
39#[derive(Debug, thiserror::Error)]
40pub enum CrashHandlerError {
41 #[error("No crashtracking config available")]
42 NoConfig,
43 #[error("No crashtracking metadata available")]
44 NoMetadata,
45 #[error("Failed to spawn receiver: {0}")]
46 ReceiverSpawnError(#[from] super::receiver_manager::ReceiverError),
47 #[error("Failed to spawn collector: {0}")]
48 CollectorSpawnError(#[from] super::collector_manager::CollectorSpawnError),
49}
50
51/// Updates the crashtracker metadata for this process
52/// Metadata is stored in a global variable and sent to the crashtracking
53/// receiver when a crash occurs.
54///
55/// PRECONDITIONS:
56/// None
57/// SAFETY:
58/// Crash-tracking functions are not guaranteed to be reentrant.
59/// No other crash-handler functions should be called concurrently.
60/// ATOMICITY:
61/// This function uses a swap on an atomic pointer.
62pub fn update_metadata(metadata: Metadata) -> anyhow::Result<()> {
63 let metadata_string = serde_json::to_string(&metadata)?;
64 let box_ptr = Box::into_raw(Box::new((metadata, metadata_string)));
65 let old = METADATA.swap(box_ptr, SeqCst);
66 if !old.is_null() {
67 // Safety: This can only come from a box above.
68 unsafe {
69 std::mem::drop(Box::from_raw(old));
70 }
71 }
72 Ok(())
73}
74
75/// Updates the crashtracker config for this process
76/// Config is stored in a global variable and sent to the crashtracking
77/// receiver when a crash occurs.
78///
79/// PRECONDITIONS:
80/// None
81/// SAFETY:
82/// Crash-tracking functions are not guaranteed to be reentrant.
83/// No other crash-handler functions should be called concurrently.
84/// ATOMICITY:
85/// This function uses a swap on an atomic pointer.
86pub fn update_config(config: CrashtrackerConfiguration) -> anyhow::Result<()> {
87 let config_string = serde_json::to_string(&config)?;
88 let box_ptr = Box::into_raw(Box::new((config, config_string)));
89 let old = CONFIG.swap(box_ptr, SeqCst);
90 if !old.is_null() {
91 // Safety: This can only come from a box above.
92 unsafe {
93 std::mem::drop(Box::from_raw(old));
94 }
95 }
96 Ok(())
97}
98
99pub(crate) extern "C" fn handle_posix_sigaction(
100 signum: i32,
101 sig_info: *mut siginfo_t,
102 ucontext: *mut c_void,
103) {
104 // Handle the signal. Note this has a guard to ensure that we only generate
105 // one crash report per process.
106 let _ = handle_posix_signal_impl(sig_info, ucontext as *mut ucontext_t);
107 // SAFETY: No preconditions.
108 unsafe { chain_signal_handler(signum, sig_info, ucontext) };
109}
110
111static ENABLED: AtomicBool = AtomicBool::new(true);
112
113/// Disables the crashtracker.
114/// Note that this does not restore the old signal handlers, but rather turns crash-tracking into a
115/// no-op, and then chains the old handlers. This means that handlers registered after the
116/// crashtracker will continue to work as expected.
117///
118/// # Preconditions
119/// None
120/// # Safety
121/// None
122/// # Atomicity
123/// This function is atomic and idempotent. Calling it multiple times is allowed.
124pub fn disable() {
125 ENABLED.store(false, SeqCst);
126}
127
128/// Enables the crashtracker, if had been previously disabled.
129/// If crashtracking has not been initialized, this function will have no effect.
130///
131/// # Preconditions
132/// None
133/// # Safety
134/// None
135/// # Atomicity
136/// This function is atomic and idempotent. Calling it multiple times is allowed.
137pub fn enable() {
138 ENABLED.store(true, SeqCst);
139}
140
141fn handle_posix_signal_impl(
142 sig_info: *const siginfo_t,
143 ucontext: *const ucontext_t,
144) -> Result<(), CrashHandlerError> {
145 if !ENABLED.load(SeqCst) {
146 return Ok(());
147 }
148
149 // If this code hits a stack overflow, then it will result in a segfault. That situation is
150 // protected by the one-time guard.
151
152 // One-time guard to guarantee at most one crash per process
153 static NUM_TIMES_CALLED: AtomicU64 = AtomicU64::new(0);
154 if NUM_TIMES_CALLED.fetch_add(1, SeqCst) > 0 {
155 // In the case where some lower-level signal handler recovered the error
156 // we don't want to spam the system with calls. Make this one shot.
157 return Ok(());
158 }
159
160 // Leak config and metadata to avoid calling `drop` during a crash
161 // Note that these operations also replace the global states. When the one-time guard is
162 // passed, all global configuration and metadata becomes invalid.
163 let config_ptr = CONFIG.swap(ptr::null_mut(), SeqCst);
164 if config_ptr.is_null() {
165 return Err(CrashHandlerError::NoConfig);
166 }
167 let (config, config_str) = unsafe { &*config_ptr };
168
169 let metadata_ptr = METADATA.swap(ptr::null_mut(), SeqCst);
170 if metadata_ptr.is_null() {
171 return Err(CrashHandlerError::NoMetadata);
172 }
173 let (_metadata, metadata_string) = unsafe { &*metadata_ptr };
174
175 let timeout_manager = TimeoutManager::new(config.timeout());
176
177 // Optionally, create the receiver. This all hinges on whether or not the configuration has a
178 // non-null unix domain socket specified. If it doesn't, then we need to check the receiver
179 // configuration. If it does, then we just connect to the socket.
180 let unix_socket_path = config.unix_socket_path().as_deref().unwrap_or_default();
181
182 let receiver = if unix_socket_path.is_empty() {
183 Receiver::spawn_from_stored_config()?
184 } else {
185 Receiver::from_socket(unix_socket_path)?
186 };
187
188 let collector = Collector::spawn(
189 &receiver,
190 config,
191 config_str,
192 metadata_string,
193 sig_info,
194 ucontext,
195 )?;
196
197 // We're done. Wrap up our interaction with the receiver.
198 collector.finish(&timeout_manager);
199 receiver.finish(&timeout_manager);
200
201 Ok(())
202}