use std::{
cell::UnsafeCell,
convert::{TryFrom, TryInto},
sync::RwLock,
};
use libc;
const PTHREAD_INTROSPECTION_THREAD_CREATE: libc::c_uint = 1;
const PTHREAD_INTROSPECTION_THREAD_START: libc::c_uint = 2;
const PTHREAD_INTROSPECTION_THREAD_TERMINATE: libc::c_uint = 3;
const PTHREAD_INTROSPECTION_THREAD_DESTROY: libc::c_uint = 4;
#[allow(non_camel_case_types)]
type pthread_introspection_hook_t = extern "C" fn(
event: libc::c_uint,
thread: libc::pthread_t,
addr: *const libc::c_void,
size: libc::size_t,
);
extern "C" {
fn pthread_introspection_hook_install(
hook: *const libc::c_void,
) -> pthread_introspection_hook_t;
}
struct PreviousHook(UnsafeCell<Option<pthread_introspection_hook_t>>);
impl PreviousHook {
pub fn dispatch(
&self,
event: libc::c_uint,
thread: libc::pthread_t,
addr: *const libc::c_void,
size: libc::size_t,
) {
let inner = unsafe { *self.0.get() };
if inner.is_none() {
return;
}
let inner = inner.unwrap();
inner(event, thread, addr, size);
}
pub fn set(&self, hook: pthread_introspection_hook_t) {
unsafe {
*self.0.get() = Some(hook);
}
}
pub fn reset(&self) {
let inner = unsafe { *self.0.get() };
if inner.is_none() {
unsafe {
pthread_introspection_hook_install(std::ptr::null());
}
return;
}
let inner = inner.unwrap();
unsafe {
*self.0.get() = None;
pthread_introspection_hook_install(inner as *const libc::c_void);
}
}
}
unsafe impl Sync for PreviousHook {}
#[allow(non_upper_case_globals)]
static PREVIOUS_HOOK: PreviousHook = PreviousHook(UnsafeCell::new(None));
static CURRENT_HOOK: RwLock<Option<PthreadIntrospectionHook>> = RwLock::new(None);
extern "C" fn pthread_introspection_hook(
event: libc::c_uint,
thread: libc::pthread_t,
addr: *const libc::c_void,
size: libc::size_t,
) {
if let Some(ref hook) = *CURRENT_HOOK.read().unwrap() {
hook(event.try_into().unwrap(), thread, addr, size);
}
PREVIOUS_HOOK.dispatch(event, thread, addr, size);
}
pub type PthreadIntrospectionHook =
Box<dyn Fn(EventType, libc::pthread_t, *const libc::c_void, libc::size_t) + Sync + Send>;
#[derive(Debug, PartialEq, Eq)]
pub enum EventType {
Create,
Start,
Terminate,
Destroy,
}
impl TryFrom<libc::c_uint> for EventType {
type Error = ();
fn try_from(value: libc::c_uint) -> Result<Self, Self::Error> {
match value {
PTHREAD_INTROSPECTION_THREAD_CREATE => Ok(Self::Create),
PTHREAD_INTROSPECTION_THREAD_START => Ok(Self::Start),
PTHREAD_INTROSPECTION_THREAD_TERMINATE => Ok(Self::Terminate),
PTHREAD_INTROSPECTION_THREAD_DESTROY => Ok(Self::Destroy),
_ => Err(()),
}
}
}
impl From<EventType> for libc::c_uint {
fn from(event: EventType) -> Self {
match event {
EventType::Create => PTHREAD_INTROSPECTION_THREAD_CREATE,
EventType::Start => PTHREAD_INTROSPECTION_THREAD_START,
EventType::Terminate => PTHREAD_INTROSPECTION_THREAD_TERMINATE,
EventType::Destroy => PTHREAD_INTROSPECTION_THREAD_DESTROY,
}
}
}
pub fn install<H>(hook: H)
where
H: Fn(EventType, libc::pthread_t, *const libc::c_void, libc::size_t) + Send + Sync + 'static,
{
let mut new_hook = CURRENT_HOOK.write().unwrap();
*new_hook = Some(Box::new(hook));
let prev = unsafe {
pthread_introspection_hook_install(pthread_introspection_hook as *const libc::c_void)
};
#[allow(clippy::fn_address_comparisons, clippy::fn_null_check)]
if !(prev as *const libc::c_void).is_null() && prev != pthread_introspection_hook {
PREVIOUS_HOOK.set(prev);
}
}
pub fn reset() {
PREVIOUS_HOOK.reset();
}
#[cfg(test)]
mod test {
use std::{
sync::{Arc, Mutex},
thread,
time::Duration,
};
use serial_test::serial;
#[test]
#[serial]
fn test_nohook_thread_create() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));
super::reset();
assert!(!*triggered.lock().unwrap());
}
#[test]
#[serial]
fn test_hook_thread_create() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
let inner_triggered = triggered.clone();
super::install(move |event, _, _, _| {
if event == super::EventType::Create {
let mut triggered = inner_triggered.lock().unwrap();
*triggered = true;
}
});
thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));
super::reset();
assert!(*triggered.lock().unwrap());
}
#[test]
#[serial]
fn test_hook_thread_start() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
let inner_triggered = triggered.clone();
super::install(move |event, _, _, _| {
if event == super::EventType::Start {
let mut triggered = inner_triggered.lock().unwrap();
*triggered = true;
}
});
thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));
super::reset();
assert!(*triggered.lock().unwrap());
}
#[test]
#[serial]
fn test_hook_reset() {
let triggered: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
let inner_triggered = triggered.clone();
super::install(move |event, _, _, _| {
if event == super::EventType::Start {
let mut triggered = inner_triggered.lock().unwrap();
*triggered = true;
}
});
super::reset();
thread::spawn(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(50));
assert!(!*triggered.lock().unwrap());
}
}