use libc::{dladdr, Dl_info};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Once;
use std::time::Duration;
use crate::error::Result;
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);
const VALIDATION_TIMEOUT_MS: u64 = 200;
#[derive(Debug)]
pub struct BuildValidation {
pub finstrument_functions_ok: bool,
pub rdynamic_ok: bool,
pub validation_complete: bool,
pub error_messages: Vec<String>,
}
pub fn init_build_validation() -> Result<()> {
VALIDATION_INIT.call_once(|| {
test_symbol_resolution();
std::thread::spawn(|| {
validate_build_requirements();
});
std::thread::spawn(|| {
std::thread::sleep(Duration::from_millis(100));
let _preliminary_result = get_build_validation_result();
});
});
Ok(())
}
fn test_symbol_resolution() {
let mut symbol_found = false;
unsafe {
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;
}
}
if !symbol_found {
if let Ok(exe_path) = std::env::current_exe() {
if let Some(_exe_path_str) = exe_path.to_str() {
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);
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);
}
pub fn record_function_hook_call() {
HOOK_CALL_COUNT.fetch_add(1, Ordering::Relaxed);
FUNCTION_HOOKS_DETECTED.store(true, Ordering::Relaxed);
}
fn validate_build_requirements() {
std::thread::sleep(Duration::from_millis(VALIDATION_TIMEOUT_MS));
let _validation_result = get_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,
}
}
pub fn is_validation_complete() -> bool {
VALIDATION_INIT.is_completed()
}
#[cfg(test)]
pub fn force_validation_check() -> BuildValidation {
test_symbol_resolution();
get_build_validation_result()
}