calltrace-rs 1.1.4

High-performance function call tracing library for C/C++ applications using GCC instrumentation with Rust safety guarantees
Documentation
//! Build Validation Module
//!
//! This module validates that the target application was built with the required
//! compilation flags for CallTrace to function correctly.

use libc::{dladdr, Dl_info};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Once;
use std::time::Duration;

use crate::error::Result;

/// Global validation state
static VALIDATION_INIT: Once = Once::new();
static FUNCTION_HOOKS_DETECTED: AtomicBool = AtomicBool::new(false);
static SYMBOL_RESOLUTION_AVAILABLE: AtomicBool = AtomicBool::new(false);
static HOOK_CALL_COUNT: AtomicUsize = AtomicUsize::new(0);

/// Timeout for build validation (in milliseconds)
const VALIDATION_TIMEOUT_MS: u64 = 200;

/// Build validation result
#[derive(Debug)]
pub struct BuildValidation {
    pub finstrument_functions_ok: bool,
    pub rdynamic_ok: bool,
    pub validation_complete: bool,
    pub error_messages: Vec<String>,
}

/// Initialize build validation
pub fn init_build_validation() -> Result<()> {
    VALIDATION_INIT.call_once(|| {
        // Test symbol resolution capability immediately
        test_symbol_resolution();

        // Schedule validation check with a delay to allow function hooks to be detected
        std::thread::spawn(|| {
            validate_build_requirements();
        });

        // Also run an immediate preliminary check (but don't exit on failure)
        std::thread::spawn(|| {
            std::thread::sleep(Duration::from_millis(100));
            let _preliminary_result = get_build_validation_result();
            // Note: Previously would check and exit on missing rdynamic, now just continue
        });
    });

    Ok(())
}

/// Test if symbol resolution is available (rdynamic check)
fn test_symbol_resolution() {
    // Try to find a symbol from the main executable
    // Use dladdr with various known addresses to test symbol resolution
    let mut symbol_found = false;

    unsafe {
        // Try with main function symbol lookup
        let main_sym = libc::dlsym(
            libc::RTLD_DEFAULT,
            b"main\0".as_ptr() as *const libc::c_char,
        );
        if !main_sym.is_null() {
            let mut dl_info: Dl_info = std::mem::zeroed();
            let result = dladdr(main_sym, &mut dl_info);

            if result != 0 && !dl_info.dli_sname.is_null() {
                symbol_found = true;
            }
        }

        // Alternative test: Try to get symbol info for any executable symbol
        if !symbol_found {
            // Get the executable path and try to find any symbol from it
            if let Ok(exe_path) = std::env::current_exe() {
                if let Some(_exe_path_str) = exe_path.to_str() {
                    // Try common function names that might exist
                    let func_names = [c"main", c"_start", c"__libc_start_main"];
                    for func_name in func_names {
                        let sym = libc::dlsym(libc::RTLD_DEFAULT, func_name.as_ptr());
                        if !sym.is_null() {
                            let mut dl_info: Dl_info = std::mem::zeroed();
                            let result = dladdr(sym, &mut dl_info);

                            // Check if the symbol comes from the main executable
                            if result != 0 && !dl_info.dli_fname.is_null() {
                                let fname =
                                    std::ffi::CStr::from_ptr(dl_info.dli_fname).to_string_lossy();
                                if fname.contains(
                                    &exe_path.file_name().unwrap().to_string_lossy().to_string(),
                                ) {
                                    symbol_found = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    SYMBOL_RESOLUTION_AVAILABLE.store(symbol_found, Ordering::Relaxed);
}

/// Record that a function hook was called (called from instrumentation hooks)
pub fn record_function_hook_call() {
    HOOK_CALL_COUNT.fetch_add(1, Ordering::Relaxed);
    FUNCTION_HOOKS_DETECTED.store(true, Ordering::Relaxed);
}

/// Main build validation logic
fn validate_build_requirements() {
    // Wait briefly for function hooks to be called, then check
    std::thread::sleep(Duration::from_millis(VALIDATION_TIMEOUT_MS));

    // Perform validation (but don't exit on failure)
    let _validation_result = get_build_validation_result();

    // Note: Previously would display error and exit here, but now we just continue silently
}

/// Get current build validation result
pub fn get_build_validation_result() -> BuildValidation {
    let finstrument_ok = FUNCTION_HOOKS_DETECTED.load(Ordering::Relaxed);
    let rdynamic_ok = SYMBOL_RESOLUTION_AVAILABLE.load(Ordering::Relaxed);
    let hook_count = HOOK_CALL_COUNT.load(Ordering::Relaxed);

    let mut error_messages = Vec::new();

    if !finstrument_ok {
        error_messages.push(format!(
            "Function instrumentation not detected (received {} hook calls). \
            Application was not built with -finstrument-functions flag.",
            hook_count
        ));
    }

    if !rdynamic_ok {
        error_messages.push(
            "Symbol resolution not available. \
            Application was not built with -rdynamic flag."
                .to_string(),
        );
    }

    BuildValidation {
        finstrument_functions_ok: finstrument_ok,
        rdynamic_ok,
        validation_complete: true,
        error_messages,
    }
}

/// Check if build validation has been completed
pub fn is_validation_complete() -> bool {
    VALIDATION_INIT.is_completed()
}

/// Force validation check (for testing purposes)
#[cfg(test)]
pub fn force_validation_check() -> BuildValidation {
    test_symbol_resolution();
    get_build_validation_result()
}