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;
static CRASH_HANDLER_INIT: Once = Once::new();
static CRASH_HANDLER_ENABLED: AtomicBool = AtomicBool::new(false);
static ORIGINAL_HANDLERS: Lazy<Arc<Mutex<HashMap<i32, libc::sigaction>>>> =
once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
#[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,
}
#[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>,
}
const CRASH_SIGNALS: &[i32] = &[
SIGSEGV, SIGABRT, SIGILL, SIGFPE, SIGBUS, SIGTRAP, ];
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(())
}
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 {
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; }
original_handlers.insert(signal, existing_action);
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;
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(())
}
extern "C" fn crash_signal_handler(signal: i32, siginfo: *mut siginfo_t, context: *mut c_void) {
static HANDLER_RUNNING: AtomicBool = AtomicBool::new(false);
if HANDLER_RUNNING.load(Ordering::Acquire) {
unsafe {
libc::_exit(128 + signal);
}
}
HANDLER_RUNNING.store(true, Ordering::Release);
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);
}
}
};
if let Err(e) = output_crash_report(&crash_info) {
eprintln!("CallTrace: Failed to output crash report: {:?}", e);
}
restore_and_reraise(signal);
}
fn generate_crash_info(
signal: i32,
siginfo: *mut siginfo_t,
context: *mut c_void,
) -> Result<CrashInfo> {
let signal_name = get_signal_name(signal);
let thread_id = unsafe {
#[cfg(target_os = "linux")]
{
libc::gettid() as u64
}
#[cfg(not(target_os = "linux"))]
{
libc::pthread_self() as u64
}
};
let (fault_address, instruction_pointer, stack_pointer) =
extract_context_info(siginfo, context);
let register_context = unsafe { RegisterContext::capture().ok() };
let backtrace = generate_backtrace()?;
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(),
})
}
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 {
#[cfg(target_arch = "x86_64")]
{
None }
#[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);
let _mcontext_ptr = &ucontext.uc_mcontext as *const _ as *const u8;
(None, None) }
#[cfg(not(target_arch = "x86_64"))]
{
(None, None)
}
};
(fault_address, instruction_pointer, stack_pointer)
}
fn generate_backtrace() -> Result<Vec<StackFrame>> {
let mut frames = Vec::new();
unsafe {
let mut frame_ptr: *const *const c_void;
#[cfg(target_arch = "x86_64")]
{
asm!("mov {}, rbp", out(reg) frame_ptr, options(nomem, nostack));
}
#[cfg(not(target_arch = "x86_64"))]
{
return Ok(frames);
}
for _ in 0..32 {
if frame_ptr.is_null() {
break;
}
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;
}
let frame = resolve_stack_frame(return_addr);
frames.push(frame);
frame_ptr = *frame_ptr as *const *const c_void;
if (frame_ptr as u64) < 0x1000 || (frame_ptr as u64) > 0x7fffffffffff {
break;
}
}
}
Ok(frames)
}
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()
};
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,
}
}
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,
}
}
fn output_crash_report(crash_info: &CrashInfo) -> Result<()> {
let report = format_crash_report(crash_info)?;
eprintln!("\n{}", "=".repeat(80));
eprintln!("CALLTRACE CRASH REPORT");
eprintln!("{}", "=".repeat(80));
eprintln!("{}", report);
eprintln!("{}", "=".repeat(80));
if let Some(base_filename) = crate::get_base_output_filename() {
let crash_file = format!("{}.json", base_filename);
let crash_json = convert_crash_info_to_json(crash_info)?;
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(())
}
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)
}
fn convert_crash_info_to_json(crash_info: &CrashInfo) -> Result<crate::json_output::TraceSession> {
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
))
})?;
let (crash_time_str, crash_timestamp) = format_crash_time(&crash_info.crash_time);
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();
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,
};
trace_session.crash = Some(crash_info_json);
Ok(trace_session)
}
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);
let readable_time = get_readable_time(total_seconds);
(readable_time, timestamp_decimal)
}
Err(_) => (format!("{:?}", system_time), 0.0),
}
}
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();
let timestamp_decimal = format!("{}.{:06}", total_seconds, microseconds);
let readable_time = get_readable_time(total_seconds);
format!("{} ({})", readable_time, timestamp_decimal)
}
Err(_) => {
format!("{:?}", system_time)
}
}
}
fn get_readable_time(timestamp: u64) -> String {
use std::process::Command;
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();
}
}
}
let total_seconds = timestamp;
let seconds_per_day = 86400;
let seconds_per_hour = 3600;
let seconds_per_minute = 60;
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;
let years_since_1970 = days_since_epoch / 365;
let remaining_days = days_since_epoch % 365;
let months = remaining_days / 30; let days = remaining_days % 30;
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC",
1970 + years_since_1970,
1 + months,
1 + days,
hours,
minutes,
seconds
)
}
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),
}
}
fn restore_and_reraise(signal: i32) {
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());
}
}
}
unsafe {
libc::raise(signal);
}
unsafe {
libc::_exit(128 + signal);
}
}
pub fn reinforce_crash_handlers() -> Result<()> {
if !CRASH_HANDLER_ENABLED.load(Ordering::Relaxed) {
return Ok(()); }
for &signal in CRASH_SIGNALS {
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;
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 {
if std::env::var("CALLTRACE_DEBUG").is_ok() {
eprintln!("CallTrace: Reinforced crash handler for signal {}", signal);
}
}
}
Ok(())
}
pub fn is_crash_handler_enabled() -> bool {
CRASH_HANDLER_ENABLED.load(Ordering::Relaxed)
}
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);
}