#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use once_cell::sync::Lazy;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::ffi::c_void;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, Once};
pub mod build_validator;
pub mod call_tree;
pub mod crash_handler;
pub mod cyg_profile;
pub mod dwarf_analyzer;
pub mod error;
pub mod json_output;
pub mod register_reader;
pub use cyg_profile::{__cyg_profile_func_enter, __cyg_profile_func_exit};
use call_tree::CallTreeManager;
use dwarf_analyzer::DwarfAnalyzer;
use json_output::JsonOutputGenerator;
static CALL_TREE_MANAGER: Lazy<Arc<CallTreeManager>> =
Lazy::new(|| Arc::new(CallTreeManager::new()));
static DWARF_ANALYZER: Lazy<Arc<Mutex<Option<DwarfAnalyzer>>>> =
Lazy::new(|| Arc::new(Mutex::new(None)));
static CONFIG: Lazy<Arc<Mutex<CallTraceConfig>>> =
Lazy::new(|| Arc::new(Mutex::new(CallTraceConfig::default())));
static INIT: Once = Once::new();
static TRACING_ENABLED: AtomicBool = AtomicBool::new(true);
static ARGUMENT_CAPTURE_ENABLED: AtomicBool = AtomicBool::new(false);
static FUNCTION_CALL_COUNT: AtomicU64 = AtomicU64::new(0);
static ARGUMENT_CAPTURE_COUNT: AtomicU64 = AtomicU64::new(0);
thread_local! {
static LOCAL_COUNTERS: RefCell<LocalCounters> = RefCell::new(LocalCounters::new());
}
#[derive(Debug)]
struct LocalCounters {
function_calls: u64,
argument_captures: u64,
batch_size: u64,
}
impl LocalCounters {
fn new() -> Self {
Self {
function_calls: 0,
argument_captures: 0,
batch_size: 100, }
}
#[inline]
fn increment_function_calls(&mut self) -> u64 {
self.function_calls += 1;
if self.function_calls % self.batch_size == 0 {
let batch = self.function_calls;
self.function_calls = 0;
FUNCTION_CALL_COUNT.fetch_add(batch, Ordering::Relaxed) + batch
} else {
FUNCTION_CALL_COUNT.load(Ordering::Relaxed) + self.function_calls
}
}
#[inline]
fn increment_argument_captures(&mut self) {
self.argument_captures += 1;
if self.argument_captures % self.batch_size == 0 {
let batch = self.argument_captures;
self.argument_captures = 0;
ARGUMENT_CAPTURE_COUNT.fetch_add(batch, Ordering::Relaxed);
}
}
fn flush(&mut self) {
if self.function_calls > 0 {
FUNCTION_CALL_COUNT.fetch_add(self.function_calls, Ordering::Relaxed);
self.function_calls = 0;
}
if self.argument_captures > 0 {
ARGUMENT_CAPTURE_COUNT.fetch_add(self.argument_captures, Ordering::Relaxed);
self.argument_captures = 0;
}
}
}
type FunctionInfoCache = Lazy<Arc<Mutex<HashMap<u64, Option<dwarf_analyzer::FunctionInfo>>>>>;
static FUNCTION_INFO_CACHE: FunctionInfoCache = Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
static STRING_INTERN_POOL: Lazy<Arc<Mutex<HashSet<String>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashSet::new())));
thread_local! {
static ARGUMENT_BUFFER_POOL: RefCell<Vec<Vec<register_reader::CapturedArgument>>> =
const { RefCell::new(Vec::new()) };
static FORMAT_BUFFER: RefCell<String> = RefCell::new(String::with_capacity(64));
}
#[derive(Debug, Clone)]
pub struct CallTraceConfig {
pub enabled: bool,
pub capture_arguments: bool,
pub output_file: Option<String>,
pub max_call_depth: usize,
pub pretty_json: bool,
}
impl Default for CallTraceConfig {
fn default() -> Self {
Self {
enabled: true,
capture_arguments: std::env::var("CALLTRACE_CAPTURE_ARGS")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false),
output_file: std::env::var("CALLTRACE_OUTPUT")
.ok()
.or_else(generate_default_output_filename),
max_call_depth: std::env::var("CALLTRACE_MAX_DEPTH")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(100),
pretty_json: std::env::var("CALLTRACE_PRETTY_JSON")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(true),
}
}
}
#[no_mangle]
pub extern "C" fn calltrace_init() -> i32 {
INIT.call_once(|| {
if let Err(e) = init_global_state() {
eprintln!("CallTrace initialization failed: {:?}", e);
}
});
0
}
#[no_mangle]
pub extern "C" fn calltrace_cleanup() {
if std::panic::catch_unwind(|| {
LOCAL_COUNTERS.with(|counters| counters.borrow_mut().flush());
})
.is_ok()
{
}
if let Err(e) = write_final_output() {
eprintln!("CallTrace cleanup failed: {:?}", e);
}
crash_handler::cleanup_crash_handler();
}
fn init_global_state() -> Result<(), Box<dyn std::error::Error>> {
if let Err(e) = build_validator::init_build_validation() {
eprintln!("CallTrace: Build validation initialization failed: {:?}", e);
}
let config = CONFIG.lock().unwrap();
TRACING_ENABLED.store(config.enabled, Ordering::Relaxed);
ARGUMENT_CAPTURE_ENABLED.store(config.capture_arguments, Ordering::Relaxed);
let exe_path = std::env::current_exe()
.or_else(|_| std::fs::read_link("/proc/self/exe"))
.unwrap_or_else(|_| "/proc/self/exe".into());
if let Some(exe_path_str) = exe_path.to_str() {
match DwarfAnalyzer::new(exe_path_str) {
Ok(analyzer) => {
*DWARF_ANALYZER.lock().unwrap() = Some(analyzer);
if std::env::var("CALLTRACE_DEBUG").is_ok() {
eprintln!("CallTrace: DWARF analyzer initialized for symbol resolution");
}
}
Err(e) => {
eprintln!("CallTrace: DWARF analyzer initialization failed: {:?}", e);
eprintln!("CallTrace: Continuing with limited symbol resolution");
}
}
}
if let Err(e) = crash_handler::init_crash_handler() {
eprintln!("CallTrace: Crash handler initialization failed: {:?}", e);
eprintln!("CallTrace: Continuing without crash detection");
}
if std::env::var("CALLTRACE_DEBUG").is_ok() {
eprintln!("CallTrace: Library initialized successfully");
}
Ok(())
}
#[inline]
pub(crate) fn intern_string(s: &str) -> String {
if s.len() <= 64
&& (s.starts_with("int")
|| s.starts_with("char")
|| s.starts_with("float")
|| s.starts_with("double")
|| s.starts_with("void")
|| s.starts_with("struct")
|| s.starts_with("0x"))
{
if let Ok(mut pool) = STRING_INTERN_POOL.lock() {
if let Some(interned) = pool.get(s) {
return interned.clone();
} else {
let owned = s.to_string();
pool.insert(owned.clone());
return owned;
}
}
}
s.to_string()
}
#[inline]
fn get_argument_buffer() -> Vec<register_reader::CapturedArgument> {
ARGUMENT_BUFFER_POOL.with(|pool| {
let mut pool_ref = pool.borrow_mut();
if let Some(mut buffer) = pool_ref.pop() {
buffer.clear();
buffer
} else {
Vec::with_capacity(16) }
})
}
#[inline]
pub(crate) fn format_address(addr: u64) -> String {
format!("0x{:x}", addr)
}
#[inline]
pub(crate) fn format_address_with_prefix(prefix: &str, addr: u64) -> String {
format!("{}_0x{:x}", prefix, addr)
}
mod string_constants {
pub const NULL_ADDRESS: &str = "0x0";
pub const NULL_STRING: &str = "NULL";
pub const CAPTURE_FAILED: &str = "Capture failed";
pub const PTHREAD_CREATE: &str = "pthread_create";
pub const X86_64: &str = "x86_64";
pub const UNKNOWN_ERROR: &str = "Unknown error";
}
#[inline]
fn get_cached_function_info(func_addr: u64) -> Option<dwarf_analyzer::FunctionInfo> {
if let Ok(cache) = FUNCTION_INFO_CACHE.lock() {
if let Some(cached_result) = cache.get(&func_addr) {
return cached_result.clone();
}
}
let function_info = if let Some(ref mut analyzer) = DWARF_ANALYZER.lock().unwrap().as_mut() {
analyzer.get_function_info(func_addr).ok()
} else {
None
};
if let Ok(mut cache) = FUNCTION_INFO_CACHE.lock() {
cache.insert(func_addr, function_info.clone());
const MAX_CACHE_SIZE: usize = 1024;
if cache.len() > MAX_CACHE_SIZE {
let keys_to_remove: Vec<u64> = cache.keys().take(MAX_CACHE_SIZE / 4).copied().collect();
for key in keys_to_remove {
cache.remove(&key);
}
}
}
function_info
}
fn write_final_output() -> Result<(), Box<dyn std::error::Error>> {
let config = CONFIG.lock().unwrap().clone();
if let Some(output_file) = &config.output_file {
let generator = JsonOutputGenerator::new();
let trace_session = generator.generate_output(&CALL_TREE_MANAGER)?;
generator.write_to_file(&trace_session, output_file)?;
eprintln!("CallTrace: Output written to {}", output_file);
}
Ok(())
}
fn generate_default_output_filename() -> Option<String> {
let exe_path = std::env::current_exe()
.or_else(|_| std::fs::read_link("/proc/self/exe"))
.ok()?;
let file_stem = exe_path.file_stem()?.to_str()?;
let exe_dir = exe_path.parent()?;
let output_path = exe_dir.join(format!("{}.json", file_stem));
output_path.to_str().map(str::to_string)
}
pub(crate) fn get_base_output_filename() -> Option<String> {
if let Ok(config) = CONFIG.lock() {
if let Some(ref output_file) = config.output_file {
if output_file.ends_with(".json") {
return Some(output_file[..output_file.len() - 5].to_string());
} else {
return Some(output_file.clone());
}
}
}
let exe_path = std::env::current_exe()
.or_else(|_| std::fs::read_link("/proc/self/exe"))
.ok()?;
let file_stem = exe_path.file_stem()?.to_str()?;
let exe_dir = exe_path.parent()?;
let base_path = exe_dir.join(file_stem);
base_path.to_str().map(str::to_string)
}
#[inline]
pub(crate) fn handle_function_enter_internal(
func_address: *mut c_void,
call_site: *mut c_void,
) -> Result<(), error::CallTraceError> {
build_validator::record_function_hook_call();
if !TRACING_ENABLED.load(Ordering::Relaxed) {
return Ok(());
}
let func_addr = func_address as u64;
let call_site_addr = call_site as u64;
let call_count =
LOCAL_COUNTERS.with(|counters| counters.borrow_mut().increment_function_calls());
let arg_capture_enabled = ARGUMENT_CAPTURE_ENABLED.load(Ordering::Relaxed);
if !arg_capture_enabled {
let _node_id = CALL_TREE_MANAGER.function_enter_fast_path(func_addr, call_site_addr)?;
if (call_count < 100 && call_count % 20 == 0) || call_count % 2000 == 0 {
let _ = crash_handler::reinforce_crash_handlers();
}
return Ok(());
}
let should_reinforce = if call_count < 100 {
call_count % 10 == 0 } else {
call_count % 1000 == 0 };
if should_reinforce {
let _ = crash_handler::reinforce_crash_handlers();
}
let function_info = get_cached_function_info(func_addr);
LOCAL_COUNTERS.with(|counters| counters.borrow_mut().increment_argument_captures());
let arguments = if let Some(ref func_info) = function_info {
let register_context = unsafe { register_reader::RegisterContext::capture().ok() };
if let Some(ref context) = register_context {
capture_function_arguments(func_info, context)
} else {
Vec::new()
}
} else {
Vec::new()
};
let _node_id = CALL_TREE_MANAGER.function_enter(
func_addr,
call_site_addr,
function_info,
arguments,
None, )?;
Ok(())
}
#[inline]
pub(crate) fn handle_function_exit_internal(
func_address: *mut c_void,
_call_site: *mut c_void,
) -> Result<(), error::CallTraceError> {
if !TRACING_ENABLED.load(Ordering::Relaxed) {
return Ok(());
}
let func_addr = func_address as u64;
if !ARGUMENT_CAPTURE_ENABLED.load(Ordering::Relaxed) {
CALL_TREE_MANAGER.function_exit_fast_path(func_addr)?;
return Ok(());
}
let return_value = {
let return_context = unsafe { register_reader::capture_return_values().ok() };
if let Some(ref context) = return_context {
if let Some(func_info) = get_cached_function_info(func_addr) {
register_reader::extract_return_value(context, func_info.return_type.as_ref())
} else {
if context.return_valid {
Some(register_reader::ArgumentValue::Integer(context.return_rax))
} else {
None
}
}
} else {
None
}
};
CALL_TREE_MANAGER.function_exit_with_return_value(func_addr, return_value)?;
Ok(())
}
#[inline]
fn capture_function_arguments(
function_info: &dwarf_analyzer::FunctionInfo,
register_context: ®ister_reader::RegisterContext,
) -> Vec<register_reader::CapturedArgument> {
let param_count = function_info.parameters.len();
if param_count == 0 {
return Vec::new();
}
let mut arguments = get_argument_buffer();
if arguments.capacity() < param_count {
arguments.reserve(param_count - arguments.capacity());
}
const MAX_ARGS: usize = 16;
let max_args = std::cmp::min(param_count, MAX_ARGS);
for (i, param) in function_info.parameters.iter().enumerate().take(max_args) {
let location = register_reader::classify_argument(
¶m.type_info.name,
param.type_info.size.unwrap_or(8) as usize,
i,
);
let value = if param.type_info.is_struct
|| param.type_info.is_array
|| (param.type_info.is_pointer && param.type_info.base_type.is_some())
{
register_reader::extract_argument_with_type_info(
register_context,
&location,
¶m.type_info,
param,
)
} else {
register_reader::extract_argument(
register_context,
&location,
¶m.type_info.name,
param.type_info.is_pointer,
)
};
let captured_arg = register_reader::CapturedArgument {
name: intern_string(¶m.name),
type_name: intern_string(¶m.type_info.name),
location,
value: match &value {
Ok(v) => v.clone(),
Err(_) => register_reader::ArgumentValue::Unknown {
type_name: intern_string(¶m.type_info.name),
raw_data: Vec::new(),
error: Some(string_constants::CAPTURE_FAILED.to_string()),
},
},
valid: value.is_ok(),
error: value.err().map(|e| format!("{:?}", e)),
};
arguments.push(captured_arg);
}
arguments
}
#[ctor::ctor]
fn library_init() {
calltrace_init();
}
#[ctor::dtor]
fn library_cleanup() {
calltrace_cleanup();
}