calltrace-rs 1.1.4

High-performance function call tracing library for C/C++ applications using GCC instrumentation with Rust safety guarantees
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
//! Crash Handler Module
//!
//! This module provides crash detection and detailed crash reporting functionality.
//! It installs signal handlers for common crash signals only if no existing handlers are present,
//! and generates comprehensive crash reports including the current call stack.

use once_cell::sync::Lazy;
use std::arch::asm;
use std::collections::HashMap;
use std::ffi::{c_void, CStr};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Once};

use libc::{
    dladdr, sigaction, sigaddset, sigemptyset, siginfo_t, ucontext_t, Dl_info, SA_NODEFER,
    SA_RESTART, SA_SIGINFO, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP,
};

use crate::error::{CallTraceError, Result};
use crate::json_output::JsonOutputGenerator;
use crate::register_reader::RegisterContext;

/// Global crash handler state
static CRASH_HANDLER_INIT: Once = Once::new();
static CRASH_HANDLER_ENABLED: AtomicBool = AtomicBool::new(false);

/// Storage for original signal handlers
static ORIGINAL_HANDLERS: Lazy<Arc<Mutex<HashMap<i32, libc::sigaction>>>> =
    once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));

/// Crash information structure
#[derive(Debug, Clone)]
pub struct CrashInfo {
    pub signal: i32,
    pub signal_name: String,
    pub thread_id: u64,
    pub fault_address: Option<u64>,
    pub instruction_pointer: Option<u64>,
    pub stack_pointer: Option<u64>,
    pub register_context: Option<RegisterContext>,
    pub backtrace: Vec<StackFrame>,
    pub call_tree_snapshot: Option<String>,
    pub crash_time: std::time::SystemTime,
}

/// Stack frame information
#[derive(Debug, Clone, serde::Serialize)]
pub struct StackFrame {
    pub address: u64,
    pub function_name: Option<String>,
    pub library_name: Option<String>,
    pub offset: Option<u64>,
}

/// Signals to handle for crash detection
const CRASH_SIGNALS: &[i32] = &[
    SIGSEGV, // Segmentation fault
    SIGABRT, // Abort signal
    SIGILL,  // Illegal instruction
    SIGFPE,  // Floating point exception
    SIGBUS,  // Bus error
    SIGTRAP, // Trace/breakpoint trap
];

/// Initialize crash handler
pub fn init_crash_handler() -> Result<()> {
    CRASH_HANDLER_INIT.call_once(|| {
        if let Err(e) = setup_crash_handlers() {
            eprintln!("CallTrace: Failed to initialize crash handler: {:?}", e);
        } else {
            CRASH_HANDLER_ENABLED.store(true, Ordering::Relaxed);
            if std::env::var("CALLTRACE_DEBUG").is_ok() {
                eprintln!("CallTrace: Crash handler initialized successfully");
            }
        }
    });
    Ok(())
}

/// Setup signal handlers for crash detection
fn setup_crash_handlers() -> Result<()> {
    let mut original_handlers = ORIGINAL_HANDLERS.lock().map_err(|_| {
        CallTraceError::InitializationError("Failed to lock original handlers".to_string())
    })?;

    for &signal in CRASH_SIGNALS {
        // Store the current handler for restoration during cleanup
        let mut existing_action: libc::sigaction = unsafe { std::mem::zeroed() };
        let result = unsafe { sigaction(signal, std::ptr::null(), &mut existing_action) };

        if result != 0 {
            continue; // Skip this signal if we can't query it
        }

        // Store the original handler for restoration later
        original_handlers.insert(signal, existing_action);

        // Install our crash handler (override any existing handler)
        let mut new_action: libc::sigaction = unsafe { std::mem::zeroed() };
        new_action.sa_sigaction = crash_signal_handler as *const () as usize;
        new_action.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER;

        // Initialize signal mask
        unsafe {
            sigemptyset(&mut new_action.sa_mask);
            for &other_signal in CRASH_SIGNALS {
                if other_signal != signal {
                    sigaddset(&mut new_action.sa_mask, other_signal);
                }
            }
        }

        let install_result = unsafe { sigaction(signal, &new_action, std::ptr::null_mut()) };

        if install_result != 0 {
            eprintln!("CallTrace: Failed to install handler for signal {}", signal);
        } else if std::env::var("CALLTRACE_DEBUG").is_ok() {
            eprintln!("CallTrace: Installed crash handler for signal {} (overriding any existing handler)", signal);
        }
    }

    Ok(())
}

/// Main crash signal handler
extern "C" fn crash_signal_handler(signal: i32, siginfo: *mut siginfo_t, context: *mut c_void) {
    // Avoid recursive crashes in the handler
    static HANDLER_RUNNING: AtomicBool = AtomicBool::new(false);

    if HANDLER_RUNNING.load(Ordering::Acquire) {
        unsafe {
            libc::_exit(128 + signal);
        }
    }
    HANDLER_RUNNING.store(true, Ordering::Release);

    // Generate crash report
    let crash_info = match generate_crash_info(signal, siginfo, context) {
        Ok(info) => info,
        Err(e) => {
            eprintln!("CallTrace: Failed to generate crash info: {:?}", e);
            unsafe {
                libc::_exit(128 + signal);
            }
        }
    };

    // Output crash report
    if let Err(e) = output_crash_report(&crash_info) {
        eprintln!("CallTrace: Failed to output crash report: {:?}", e);
    }

    // Restore original handler and re-raise the signal
    restore_and_reraise(signal);
}

/// Generate comprehensive crash information
fn generate_crash_info(
    signal: i32,
    siginfo: *mut siginfo_t,
    context: *mut c_void,
) -> Result<CrashInfo> {
    let signal_name = get_signal_name(signal);

    // Capture current thread ID
    let thread_id = unsafe {
        #[cfg(target_os = "linux")]
        {
            libc::gettid() as u64
        }
        #[cfg(not(target_os = "linux"))]
        {
            // Fallback to pthread thread ID on other systems
            libc::pthread_self() as u64
        }
    };

    // Extract fault address and context information
    let (fault_address, instruction_pointer, stack_pointer) =
        extract_context_info(siginfo, context);

    // Capture current register context (if possible)
    let register_context = unsafe { RegisterContext::capture().ok() };

    // Generate backtrace
    let backtrace = generate_backtrace()?;

    // Capture current call tree state
    let call_tree_snapshot = capture_call_tree_snapshot();

    Ok(CrashInfo {
        signal,
        signal_name,
        thread_id,
        fault_address,
        instruction_pointer,
        stack_pointer,
        register_context,
        backtrace,
        call_tree_snapshot,
        crash_time: std::time::SystemTime::now(),
    })
}

/// Extract context information from signal info and ucontext
fn extract_context_info(
    siginfo: *mut siginfo_t,
    context: *mut c_void,
) -> (Option<u64>, Option<u64>, Option<u64>) {
    if siginfo.is_null() || context.is_null() {
        return (None, None, None);
    }

    let fault_address = unsafe {
        let info = &*siginfo;
        if info.si_signo == SIGSEGV || info.si_signo == SIGBUS {
            // Access fault address from siginfo_t
            #[cfg(target_arch = "x86_64")]
            {
                // On x86_64, fault address is in si_addr field
                // For simplicity, we'll try to access it directly
                // This may not work on all platforms - a real implementation would use proper bindings
                None // Simplified for now - would need platform-specific offset calculation
            }
            #[cfg(not(target_arch = "x86_64"))]
            {
                None
            }
        } else {
            None
        }
    };

    let (instruction_pointer, stack_pointer) = unsafe {
        #[cfg(target_arch = "x86_64")]
        {
            let ucontext = &*(context as *const ucontext_t);
            // Access registers from ucontext - this is platform specific
            // On Linux x86_64, we can access them through mcontext
            let _mcontext_ptr = &ucontext.uc_mcontext as *const _ as *const u8;

            // These offsets are architecture and OS specific
            // For a production implementation, we'd use proper bindings
            // For now, we'll try a simpler approach
            (None, None) // Simplified for now
        }

        #[cfg(not(target_arch = "x86_64"))]
        {
            (None, None)
        }
    };

    (fault_address, instruction_pointer, stack_pointer)
}

/// Generate stack backtrace
/// This is a simplified implementation that captures basic stack information
fn generate_backtrace() -> Result<Vec<StackFrame>> {
    // For now, we'll implement a basic backtrace using frame pointers
    // In a production implementation, this would use libunwind or similar
    let mut frames = Vec::new();

    // Try to get some basic stack frames using unsafe frame pointer walking
    unsafe {
        let mut frame_ptr: *const *const c_void;

        // Get current frame pointer
        #[cfg(target_arch = "x86_64")]
        {
            asm!("mov {}, rbp", out(reg) frame_ptr, options(nomem, nostack));
        }

        #[cfg(not(target_arch = "x86_64"))]
        {
            // For non-x86_64, we can't easily walk the stack
            return Ok(frames);
        }

        // Walk up to 32 frames
        for _ in 0..32 {
            if frame_ptr.is_null() {
                break;
            }

            // Get return address (next pointer in frame)
            let return_addr_ptr = frame_ptr.offset(1);
            if return_addr_ptr.is_null() {
                break;
            }

            let return_addr = *return_addr_ptr as u64;
            if return_addr == 0 {
                break;
            }

            // Create frame info
            let frame = resolve_stack_frame(return_addr);
            frames.push(frame);

            // Move to next frame
            frame_ptr = *frame_ptr as *const *const c_void;

            // Safety check to prevent infinite loops
            if (frame_ptr as u64) < 0x1000 || (frame_ptr as u64) > 0x7fffffffffff {
                break;
            }
        }
    }

    Ok(frames)
}

/// Resolve stack frame information using dladdr
fn resolve_stack_frame(address: u64) -> StackFrame {
    let mut dl_info: Dl_info = unsafe { std::mem::zeroed() };
    let result = unsafe { dladdr(address as *const c_void, &mut dl_info) };

    let (function_name, library_name, offset) = if result != 0 {
        let func_name = if !dl_info.dli_sname.is_null() {
            let raw_name = unsafe {
                CStr::from_ptr(dl_info.dli_sname)
                    .to_string_lossy()
                    .into_owned()
            };

            // Try to demangle C++ function names
            Some(crate::dwarf_analyzer::demangle_function_name(&raw_name))
        } else {
            None
        };

        let lib_name = if !dl_info.dli_fname.is_null() {
            unsafe {
                Some(
                    CStr::from_ptr(dl_info.dli_fname)
                        .to_string_lossy()
                        .into_owned(),
                )
            }
        } else {
            None
        };

        let offset_val = if !dl_info.dli_saddr.is_null() {
            Some(address - (dl_info.dli_saddr as u64))
        } else {
            None
        };

        (func_name, lib_name, offset_val)
    } else {
        (None, None, None)
    };

    StackFrame {
        address,
        function_name,
        library_name,
        offset,
    }
}

/// Capture current call tree state as JSON string
fn capture_call_tree_snapshot() -> Option<String> {
    use crate::CALL_TREE_MANAGER;

    match JsonOutputGenerator::new().generate_output(&CALL_TREE_MANAGER) {
        Ok(trace_session) => serde_json::to_string_pretty(&trace_session).ok(),
        Err(_) => None,
    }
}

/// Output crash report to stderr and optional file
fn output_crash_report(crash_info: &CrashInfo) -> Result<()> {
    let report = format_crash_report(crash_info)?;

    // Always output human-readable format to stderr
    eprintln!("\n{}", "=".repeat(80));
    eprintln!("CALLTRACE CRASH REPORT");
    eprintln!("{}", "=".repeat(80));
    eprintln!("{}", report);
    eprintln!("{}", "=".repeat(80));

    // Write JSON crash report to file
    if let Some(base_filename) = crate::get_base_output_filename() {
        let crash_file = format!("{}.json", base_filename);

        // Convert to JSON format
        let crash_json = convert_crash_info_to_json(crash_info)?;

        // Serialize to JSON
        let json_content = match serde_json::to_string_pretty(&crash_json) {
            Ok(json) => json,
            Err(e) => {
                eprintln!("CallTrace: Failed to serialize crash info to JSON: {}", e);
                return Ok(());
            }
        };

        if let Err(e) = std::fs::write(&crash_file, &json_content) {
            eprintln!(
                "CallTrace: Failed to write crash report to {}: {}",
                crash_file, e
            );
        } else {
            eprintln!("CallTrace: Crash report written to {}", crash_file);
        }
    }

    Ok(())
}

/// Format crash report as human-readable text
fn format_crash_report(crash_info: &CrashInfo) -> Result<String> {
    let mut report = String::new();

    report.push_str(&format!(
        "Signal: {} ({})\n",
        crash_info.signal, crash_info.signal_name
    ));
    report.push_str(&format!("Thread ID: {}\n", crash_info.thread_id));
    report.push_str(&format!(
        "Time: {}\n",
        format_system_time(&crash_info.crash_time)
    ));

    if let Some(fault_addr) = crash_info.fault_address {
        report.push_str(&format!("Fault Address: 0x{:016x}\n", fault_addr));
    }

    if let Some(ip) = crash_info.instruction_pointer {
        report.push_str(&format!("Instruction Pointer: 0x{:016x}\n", ip));
    }

    if let Some(sp) = crash_info.stack_pointer {
        report.push_str(&format!("Stack Pointer: 0x{:016x}\n", sp));
    }

    report.push_str("\nStack Trace:\n");
    for (i, frame) in crash_info.backtrace.iter().enumerate() {
        report.push_str(&format!("  #{:2}: 0x{:016x}", i, frame.address));

        if let Some(ref func) = frame.function_name {
            report.push_str(&format!(" in {}", func));
            if let Some(offset) = frame.offset {
                report.push_str(&format!("+0x{:x}", offset));
            }
        }

        if let Some(ref lib) = frame.library_name {
            report.push_str(&format!(" ({})", lib));
        }

        report.push('\n');
    }

    if let Some(ref call_tree) = crash_info.call_tree_snapshot {
        report.push_str("\nCall Tree at Crash:\n");
        report.push_str(call_tree);
    }

    Ok(report)
}

/// Convert CrashInfo to JSON-serializable format with normal trace session data
fn convert_crash_info_to_json(crash_info: &CrashInfo) -> Result<crate::json_output::TraceSession> {
    // Generate normal trace session data first
    let mut trace_session = crate::JsonOutputGenerator::new()
        .generate_output(&crate::CALL_TREE_MANAGER)
        .map_err(|e| {
            CallTraceError::InitializationError(format!(
                "Failed to generate trace session: {:?}",
                e
            ))
        })?;

    // Format timestamp
    let (crash_time_str, crash_timestamp) = format_crash_time(&crash_info.crash_time);

    // Convert backtrace
    let backtrace_json = crash_info
        .backtrace
        .iter()
        .map(|frame| crate::json_output::StackFrame {
            address: format!("0x{:016x}", frame.address),
            function_name: frame.function_name.clone(),
            library_name: frame.library_name.clone(),
            offset: frame.offset.map(|off| format!("0x{:x}", off)),
        })
        .collect();

    // Create crash info
    let crash_info_json = crate::json_output::CrashInfo {
        signal: crash_info.signal,
        signal_name: crash_info.signal_name.clone(),
        thread_id: crash_info.thread_id,
        fault_address: crash_info
            .fault_address
            .map(|addr| format!("0x{:016x}", addr)),
        instruction_pointer: crash_info
            .instruction_pointer
            .map(|ip| format!("0x{:016x}", ip)),
        stack_pointer: crash_info.stack_pointer.map(|sp| format!("0x{:016x}", sp)),
        register_context: crash_info.register_context.clone(),
        backtrace: backtrace_json,
        crash_time: crash_time_str,
        crash_timestamp,
    };

    // Add crash info to trace session
    trace_session.crash = Some(crash_info_json);

    Ok(trace_session)
}

/// Format crash time for JSON output
fn format_crash_time(system_time: &std::time::SystemTime) -> (String, f64) {
    match system_time.duration_since(std::time::UNIX_EPOCH) {
        Ok(duration) => {
            let total_seconds = duration.as_secs();
            let microseconds = duration.subsec_micros();
            let timestamp_decimal = total_seconds as f64 + (microseconds as f64 / 1_000_000.0);

            // Get human readable time
            let readable_time = get_readable_time(total_seconds);

            (readable_time, timestamp_decimal)
        }
        Err(_) => (format!("{:?}", system_time), 0.0),
    }
}

/// Format SystemTime to human-readable string
fn format_system_time(system_time: &std::time::SystemTime) -> String {
    match system_time.duration_since(std::time::UNIX_EPOCH) {
        Ok(duration) => {
            let total_seconds = duration.as_secs();
            let microseconds = duration.subsec_micros();

            // Convert to local time using UNIX utilities approach
            // Format as: YYYY-MM-DD HH:MM:SS.uuuuuu (timestamp)
            let timestamp_decimal = format!("{}.{:06}", total_seconds, microseconds);

            // Try to get human readable time via system command
            let readable_time = get_readable_time(total_seconds);

            format!("{} ({})", readable_time, timestamp_decimal)
        }
        Err(_) => {
            // Fallback to debug format if time is before UNIX epoch
            format!("{:?}", system_time)
        }
    }
}

/// Get human-readable time string from UNIX timestamp
fn get_readable_time(timestamp: u64) -> String {
    use std::process::Command;

    // Try to use system 'date' command for proper timezone handling
    if let Ok(output) = Command::new("date")
        .arg("-d")
        .arg(format!("@{}", timestamp))
        .arg("+%Y-%m-%d %H:%M:%S %Z")
        .output()
    {
        if output.status.success() {
            if let Ok(time_str) = String::from_utf8(output.stdout) {
                return time_str.trim().to_string();
            }
        }
    }

    // Fallback: Simple UTC formatting
    let total_seconds = timestamp;
    let seconds_per_day = 86400;
    let seconds_per_hour = 3600;
    let seconds_per_minute = 60;

    // Calculate days since UNIX epoch (1970-01-01)
    let days_since_epoch = total_seconds / seconds_per_day;
    let seconds_in_day = total_seconds % seconds_per_day;

    let hours = seconds_in_day / seconds_per_hour;
    let minutes = (seconds_in_day % seconds_per_hour) / seconds_per_minute;
    let seconds = seconds_in_day % seconds_per_minute;

    // Simple date calculation (approximate, doesn't handle leap years perfectly)
    let years_since_1970 = days_since_epoch / 365;
    let remaining_days = days_since_epoch % 365;
    let months = remaining_days / 30; // Rough approximation
    let days = remaining_days % 30;

    format!(
        "{:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC",
        1970 + years_since_1970,
        1 + months,
        1 + days,
        hours,
        minutes,
        seconds
    )
}

/// Get human-readable signal name
fn get_signal_name(signal: i32) -> String {
    match signal {
        SIGSEGV => "SIGSEGV (Segmentation fault)".to_string(),
        SIGABRT => "SIGABRT (Abort)".to_string(),
        SIGILL => "SIGILL (Illegal instruction)".to_string(),
        SIGFPE => "SIGFPE (Floating point exception)".to_string(),
        SIGBUS => "SIGBUS (Bus error)".to_string(),
        SIGTRAP => "SIGTRAP (Trace/breakpoint trap)".to_string(),
        _ => format!("Signal {}", signal),
    }
}

/// Restore original signal handler and re-raise the signal
fn restore_and_reraise(signal: i32) {
    // Restore original handler
    if let Ok(original_handlers) = ORIGINAL_HANDLERS.lock() {
        if let Some(original_action) = original_handlers.get(&signal) {
            unsafe {
                sigaction(signal, original_action, std::ptr::null_mut());
            }
        }
    }

    // Re-raise the signal to trigger default behavior
    unsafe {
        libc::raise(signal);
    }

    // If raise didn't work, exit with error code
    unsafe {
        libc::_exit(128 + signal);
    }
}

/// Reinforce crash handlers (reinstall to override any handlers set after initialization)
pub fn reinforce_crash_handlers() -> Result<()> {
    if !CRASH_HANDLER_ENABLED.load(Ordering::Relaxed) {
        return Ok(()); // Not enabled, nothing to do
    }

    for &signal in CRASH_SIGNALS {
        // Install our crash handler (override any existing handler)
        let mut new_action: libc::sigaction = unsafe { std::mem::zeroed() };
        new_action.sa_sigaction = crash_signal_handler as *const () as usize;
        new_action.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER;

        // Initialize signal mask
        unsafe {
            sigemptyset(&mut new_action.sa_mask);
            for &other_signal in CRASH_SIGNALS {
                if other_signal != signal {
                    sigaddset(&mut new_action.sa_mask, other_signal);
                }
            }
        }

        let install_result = unsafe { sigaction(signal, &new_action, std::ptr::null_mut()) };

        if install_result == 0 {
            // Only print reinforcement messages during debug mode
            if std::env::var("CALLTRACE_DEBUG").is_ok() {
                eprintln!("CallTrace: Reinforced crash handler for signal {}", signal);
            }
        }
    }

    Ok(())
}

/// Check if crash handler is enabled
pub fn is_crash_handler_enabled() -> bool {
    CRASH_HANDLER_ENABLED.load(Ordering::Relaxed)
}

/// Cleanup crash handler (restore original handlers)
pub fn cleanup_crash_handler() {
    if let Ok(original_handlers) = ORIGINAL_HANDLERS.lock() {
        for (&signal, original_action) in original_handlers.iter() {
            unsafe {
                sigaction(signal, original_action, std::ptr::null_mut());
            }
        }
    }
    CRASH_HANDLER_ENABLED.store(false, Ordering::Relaxed);
}