Skip to main content

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}