pub use leaks::*; macro_rules! track { ($client:expr, $rc:expr) => { $rc.tracker.register($client.id); }; } #[cfg(not(feature = "rc_tracking"))] mod leaks { use {crate::client::ClientId, std::marker::PhantomData}; pub fn init() { // nothing } pub fn log_leaked() { // nothing } pub struct Tracker { _phantom: PhantomData, } impl Tracker { pub fn register(&self, _client: ClientId) { // nothing } } impl Default for Tracker { fn default() -> Self { Self { _phantom: Default::default(), } } } } #[cfg(feature = "rc_tracking")] mod leaks { use { crate::{ client::ClientId, utils::{ ptr_ext::{MutPtrExt, PtrExt}, windows::WindowsExt, }, }, ahash::{AHashMap, AHashSet}, backtrace::Backtrace, std::{ alloc::{GlobalAlloc, Layout}, any, marker::PhantomData, mem, ptr, }, uapi::c, }; #[thread_local] static mut MAP: *mut AHashMap = ptr::null_mut(); #[thread_local] static mut ID: u64 = 0; pub fn init() { unsafe { if INITIALIZED { return; } MAP = Box::into_raw(Box::new(AHashMap::new())); ALLOCATIONS = Box::into_raw(Box::new(AHashMap::new())); IN_ALLOCATOR = 0; INITIALIZED = true; } } fn log_containers( prefix: &str, allocation: &mut Allocation, offset: usize, logged: &mut AHashSet<*mut u8>, ) { log::info!( "{}Contained in allocation {:?} at offset {}. Backtrace:", prefix, allocation.addr, offset ); allocation.backtrace.resolve(); let bt = format!("{:?}", allocation.backtrace); for line in bt.lines() { log::info!("{} {}", prefix, line); } if !logged.insert(allocation.addr) { log::error!("{} LOOP", prefix); } else { let containers = find_allocations_pointing_to(allocation.addr); if containers.is_empty() { let mut frames = vec![]; backtrace::trace(|frame| { frames.push((frame.ip() as usize, frame.sp() as usize)); true }); let mut frames2 = vec![]; for [l, r] in frames.array_windows_ext::<2>() { frames2.push((l.0, l.1, r.1)); } let mut referenced_on_stack = false; for (ip, lo, hi) in frames2 { if lo % 8 != 0 { log::error!("lo % 8 != 0"); } let slice = unsafe { std::slice::from_raw_parts( lo as *const *mut u8, (hi - lo) / mem::size_of::(), ) }; for addr in slice { if *addr == allocation.addr { let mut name = String::new(); backtrace::resolve(ip as _, |sym| { let symname = match sym.name() { Some(s) => s.to_string(), _ => String::new(), }; name = format!("{} {:?}:{:?}", symname, sym.filename(), sym.lineno()) }); if !name.starts_with("jay::leaks::") { log::info!("{} REFERENCED ON THE STACK: {}", prefix, name); referenced_on_stack = true; } } } } if !referenced_on_stack { log::error!("{} NO REFERENCES", prefix); } } let new_prefix = format!("{} ", prefix); for (mut allocation, offset) in containers { log_containers(&new_prefix, &mut allocation, offset, logged); } logged.remove(&allocation.addr); } } pub fn log_leaked() { unsafe { IN_ALLOCATOR += 1; let mut map: AHashMap> = AHashMap::new(); for (id, obj) in MAP.deref_mut().drain() { map.entry(obj.client).or_default().push((id, obj)); } if map.is_empty() { log::info!("No leaks"); } for (_, mut objs) in map.drain() { if objs.len() == 0 { continue; } objs.sort_by_key(|o| o.0); log::info!("Client {} leaked {} objects", objs[0].1.client, objs.len()); for (_, obj) in objs { let time = chrono::NaiveDateTime::from_timestamp(obj.time.0, obj.time.1); log::info!(" [{}] {}", time.format("%H:%M:%S%.3f"), obj.ty,); match find_allocation_containing(obj.addr) { Some(mut alloc) => { log_containers(" ", &mut alloc, 0, &mut AHashSet::new()) } _ => log::error!(" Not contained in any allocation??"), } } } IN_ALLOCATOR -= 1; } } // // pub fn log_allocations(w: &mut dyn Write) { // log::info!("remaining allocations:"); // unsafe { // IN_ALLOCATOR += 1; // for (_, a) in ALLOCATIONS.deref() { // let mut bt = a.backtrace.clone(); // bt.resolve(); // write!(w, "[{:?}, {:?}), allocated at\n{:?}", a.addr, a.addr.add(a.len), bt); // } // IN_ALLOCATOR -= 1; // } // } #[derive(Copy, Clone)] struct Tracked { addr: usize, client: ClientId, ty: &'static str, time: (i64, u32), } pub struct Tracker { id: u64, _phantom: PhantomData, } impl Default for Tracker { fn default() -> Self { Self { id: unsafe { let id = ID; ID += 1; id }, _phantom: Default::default(), } } } impl Tracker { pub fn register(&self, client_id: ClientId) { unsafe { let mut time = c::timespec { tv_sec: 0, tv_nsec: 0, }; uapi::clock_gettime(c::CLOCK_REALTIME, &mut time).unwrap(); IN_ALLOCATOR += 1; MAP.deref_mut().insert( self.id, Tracked { addr: self as *const _ as usize, client: client_id, ty: any::type_name::(), time: (time.tv_sec as i64, time.tv_nsec as u32), }, ); IN_ALLOCATOR -= 1; } } } impl Drop for Tracker { fn drop(&mut self) { unsafe { MAP.deref_mut().remove(&self.id); } } } struct TracingAllocator; #[global_allocator] static GLOBAL: TracingAllocator = TracingAllocator; #[derive(Clone)] struct Allocation { pub addr: *mut u8, pub len: usize, pub backtrace: Backtrace, } #[thread_local] static mut ALLOCATIONS: *mut AHashMap<*mut u8, Allocation> = ptr::null_mut(); #[thread_local] static mut IN_ALLOCATOR: u32 = 1; #[thread_local] static mut INITIALIZED: bool = false; unsafe impl GlobalAlloc for TracingAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { let res = c::calloc(layout.size(), 1) as *mut u8; if IN_ALLOCATOR == 0 { IN_ALLOCATOR = 1; ALLOCATIONS.deref_mut().insert( res, Allocation { addr: res, len: layout.size(), backtrace: Backtrace::new_unresolved(), }, ); // log::info!("allocated [0x{:x}, 0x{:x})", res as usize, res as usize + layout.size()); IN_ALLOCATOR = 0; } res } unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { if INITIALIZED { ALLOCATIONS.deref_mut().remove(&ptr); } // c::memset(ptr as _, 0, layout.size()); c::free(ptr as _); } } fn find_allocations_pointing_to(addr: *mut u8) -> Vec<(Allocation, usize)> { unsafe { IN_ALLOCATOR += 1; let mut res = vec![]; for allocation in ALLOCATIONS.deref().values() { let num = allocation.len / mem::size_of::(); let elements = std::slice::from_raw_parts(allocation.addr as *const *mut u8, num); for (offset, pos) in elements.iter().enumerate() { if *pos == addr { res.push((allocation.clone(), offset * mem::size_of::())); break; } } } IN_ALLOCATOR -= 1; res } } fn find_allocation_containing(addr: usize) -> Option { unsafe { IN_ALLOCATOR += 1; let mut res = None; for allocation in ALLOCATIONS.deref().values() { let aaddr = allocation.addr as usize; if aaddr <= addr && addr < aaddr + allocation.len { res = Some(allocation.clone()); break; } } IN_ALLOCATOR -= 1; res } } }