Skip to main content

calltrace/
build_validator.rs

1//! Build Validation Module
2//!
3//! This module validates that the target application was built with the required
4//! compilation flags for CallTrace to function correctly.
5
6use libc::{dladdr, Dl_info};
7use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
8use std::sync::Once;
9use std::time::Duration;
10
11use crate::error::Result;
12
13/// Global validation state
14static VALIDATION_INIT: Once = Once::new();
15static FUNCTION_HOOKS_DETECTED: AtomicBool = AtomicBool::new(false);
16static SYMBOL_RESOLUTION_AVAILABLE: AtomicBool = AtomicBool::new(false);
17static HOOK_CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
18
19/// Timeout for build validation (in milliseconds)
20const VALIDATION_TIMEOUT_MS: u64 = 200;
21
22/// Build validation result
23#[derive(Debug)]
24pub struct BuildValidation {
25    pub finstrument_functions_ok: bool,
26    pub rdynamic_ok: bool,
27    pub validation_complete: bool,
28    pub error_messages: Vec<String>,
29}
30
31/// Initialize build validation
32pub fn init_build_validation() -> Result<()> {
33    VALIDATION_INIT.call_once(|| {
34        // Test symbol resolution capability immediately
35        test_symbol_resolution();
36
37        // Schedule validation check with a delay to allow function hooks to be detected
38        std::thread::spawn(|| {
39            validate_build_requirements();
40        });
41
42        // Also run an immediate preliminary check (but don't exit on failure)
43        std::thread::spawn(|| {
44            std::thread::sleep(Duration::from_millis(100));
45            let _preliminary_result = get_build_validation_result();
46            // Note: Previously would check and exit on missing rdynamic, now just continue
47        });
48    });
49
50    Ok(())
51}
52
53/// Test if symbol resolution is available (rdynamic check)
54fn test_symbol_resolution() {
55    // Try to find a symbol from the main executable
56    // Use dladdr with various known addresses to test symbol resolution
57    let mut symbol_found = false;
58
59    unsafe {
60        // Try with main function symbol lookup
61        let main_sym = libc::dlsym(
62            libc::RTLD_DEFAULT,
63            b"main\0".as_ptr() as *const libc::c_char,
64        );
65        if !main_sym.is_null() {
66            let mut dl_info: Dl_info = std::mem::zeroed();
67            let result = dladdr(main_sym, &mut dl_info);
68
69            if result != 0 && !dl_info.dli_sname.is_null() {
70                symbol_found = true;
71            }
72        }
73
74        // Alternative test: Try to get symbol info for any executable symbol
75        if !symbol_found {
76            // Get the executable path and try to find any symbol from it
77            if let Ok(exe_path) = std::env::current_exe() {
78                if let Some(_exe_path_str) = exe_path.to_str() {
79                    // Try common function names that might exist
80                    let func_names = [c"main", c"_start", c"__libc_start_main"];
81                    for func_name in func_names {
82                        let sym = libc::dlsym(libc::RTLD_DEFAULT, func_name.as_ptr());
83                        if !sym.is_null() {
84                            let mut dl_info: Dl_info = std::mem::zeroed();
85                            let result = dladdr(sym, &mut dl_info);
86
87                            // Check if the symbol comes from the main executable
88                            if result != 0 && !dl_info.dli_fname.is_null() {
89                                let fname =
90                                    std::ffi::CStr::from_ptr(dl_info.dli_fname).to_string_lossy();
91                                if fname.contains(
92                                    &exe_path.file_name().unwrap().to_string_lossy().to_string(),
93                                ) {
94                                    symbol_found = true;
95                                    break;
96                                }
97                            }
98                        }
99                    }
100                }
101            }
102        }
103    }
104
105    SYMBOL_RESOLUTION_AVAILABLE.store(symbol_found, Ordering::Relaxed);
106}
107
108/// Record that a function hook was called (called from instrumentation hooks)
109pub fn record_function_hook_call() {
110    HOOK_CALL_COUNT.fetch_add(1, Ordering::Relaxed);
111    FUNCTION_HOOKS_DETECTED.store(true, Ordering::Relaxed);
112}
113
114/// Main build validation logic
115fn validate_build_requirements() {
116    // Wait briefly for function hooks to be called, then check
117    std::thread::sleep(Duration::from_millis(VALIDATION_TIMEOUT_MS));
118
119    // Perform validation (but don't exit on failure)
120    let _validation_result = get_build_validation_result();
121
122    // Note: Previously would display error and exit here, but now we just continue silently
123}
124
125/// Get current build validation result
126pub fn get_build_validation_result() -> BuildValidation {
127    let finstrument_ok = FUNCTION_HOOKS_DETECTED.load(Ordering::Relaxed);
128    let rdynamic_ok = SYMBOL_RESOLUTION_AVAILABLE.load(Ordering::Relaxed);
129    let hook_count = HOOK_CALL_COUNT.load(Ordering::Relaxed);
130
131    let mut error_messages = Vec::new();
132
133    if !finstrument_ok {
134        error_messages.push(format!(
135            "Function instrumentation not detected (received {} hook calls). \
136            Application was not built with -finstrument-functions flag.",
137            hook_count
138        ));
139    }
140
141    if !rdynamic_ok {
142        error_messages.push(
143            "Symbol resolution not available. \
144            Application was not built with -rdynamic flag."
145                .to_string(),
146        );
147    }
148
149    BuildValidation {
150        finstrument_functions_ok: finstrument_ok,
151        rdynamic_ok,
152        validation_complete: true,
153        error_messages,
154    }
155}
156
157/// Check if build validation has been completed
158pub fn is_validation_complete() -> bool {
159    VALIDATION_INIT.is_completed()
160}
161
162/// Force validation check (for testing purposes)
163#[cfg(test)]
164pub fn force_validation_check() -> BuildValidation {
165    test_symbol_resolution();
166    get_build_validation_result()
167}