extern crate libc;
extern crate once_cell;
#[cfg(not(febug_dont))]
use libc::{getenv, close, c_char, c_uint};
#[cfg(all(not(febug_dont), target_os="netbsd"))]
use libc::{recv, ssize_t};
use libc::{strerror, c_void, c_int};
#[cfg(not(febug_dont))]
use std::os::unix::io::FromRawFd;
use std::sync::atomic::AtomicI32;
#[cfg(not(febug_dont))]
use std::sync::atomic::Ordering;
use std::collections::BTreeMap;
use std::marker::PhantomData;
#[cfg(not(febug_dont))]
use std::io::{Cursor, Write};
#[cfg(not(febug_dont))]
use std::{slice, cmp, ptr};
#[cfg(not(febug_dont))]
use std::mem::MaybeUninit;
use once_cell::sync::Lazy;
use std::sync::Mutex;
use std::any::TypeId;
use std::{fmt, mem};
use std::ffi::CStr;
use std::fs::File;
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
use libc::__errno as errno_location;
#[cfg(any(target_os = "linux"))]
use libc::__errno_location as errno_location;
#[cfg(any(target_os = "freebsd"))]
use libc::__error as errno_location;
pub mod abi {
use libc::c_char;
use std::mem;
#[repr(packed)]
pub struct FebugMessage {
pub variable_id: u64,
pub variable_type: u64,
pub signal: u8,
pub name: [c_char; 4096 - 8 - 8 - 1],
}
const _FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<FebugMessage>() - 4096] = [];
#[repr(packed)]
pub struct StopFebugMessage {
pub variable_id: u64,
}
const _STOP_FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<StopFebugMessage>() - 8] = [];
#[repr(packed)]
pub struct AttnFebugMessage {
pub variable_id: u64,
pub variable_type: u64,
}
const _ATTN_FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<AttnFebugMessage>() - 16] = [];
}
pub static GLOBAL_CONTROLLED_SOCKET: AtomicI32 = AtomicI32::new(-1);
const _ILP32_ASSERT: [(); mem::size_of::<AtomicI32>() - mem::size_of::<c_int>()] = [];
struct Strerror;
impl fmt::Display for Strerror {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(unsafe { CStr::from_ptr(strerror(*errno_location())) }.to_str().unwrap_or("<strerror not UTF-8?>"))
}
}
pub fn start() {
start_raw(include!(concat!(env!("OUT_DIR"), "/febug_socket.rs")))
}
#[cfg(febug_dont)]
pub fn start_raw(_: &[u8]) {}
#[cfg(not(febug_dont))]
pub fn start_raw(mut path: &[u8]) {
if unsafe { getenv(b"FEBUG_DONT\0".as_ptr() as *const c_char) } != ptr::null_mut() {
return;
}
let sock = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET, 0) };
if sock == -1 {
eprintln!("febug::start_raw: socket: {}", Strerror);
return;
}
let fs = unsafe { getenv(b"FEBUG_SOCKET\0".as_ptr() as *const _) };
if fs != ptr::null_mut() {
path = unsafe { CStr::from_ptr(fs) }.to_bytes();
}
let path = unsafe { slice::from_raw_parts(path.as_ptr() as *const c_char, path.len()) };
let mut addr: libc::sockaddr_un = unsafe { MaybeUninit::zeroed().assume_init() };
addr.sun_family = libc::AF_UNIX as libc::sa_family_t;
let path_strlen = cmp::min(addr.sun_path.len() - 1, path.len());
addr.sun_path[0..path_strlen].copy_from_slice(&path[0..path_strlen]);
if unsafe { libc::connect(sock, &addr as *const libc::sockaddr_un as *const libc::sockaddr, mem::size_of_val(&addr) as u32) } == -1 {
eprintln!("febug::start_raw: connect: {}", Strerror);
unsafe { close(sock) };
return;
}
#[cfg(any(target_os="linux", target_os="openbsd"))]
{
}
#[cfg(target_os="netbsd")]
{
let mut sync_msg = abi::AttnFebugMessage {
variable_id: 0,
variable_type: 0,
};
if unsafe { recv(sock, &mut sync_msg as *mut _ as *mut c_void, mem::size_of_val(&sync_msg), 0) } != mem::size_of_val(&sync_msg) as ssize_t {
eprintln!("febug::start_raw: recv: {}", Strerror);
unsafe { close(sock) };
return;
}
}
#[cfg(not(any(target_os="linux", target_os="openbsd", target_os="netbsd")))]
{
let mut cmsgbuf = [0u8; mem::align_of::<libc::cmsghdr>() * 2 + mem::size_of::<libc::cmsgcred>() * 2];
let cmsgbuf_len = unsafe { libc::CMSG_SPACE(mem::size_of::<libc::cmsgcred>() as c_uint) as usize };
assert!(cmsgbuf.len() >= cmsgbuf_len, "{} >= {}", cmsgbuf.len(), cmsgbuf_len);
let mut msg = libc::msghdr {
msg_name: ptr::null_mut(),
msg_namelen: 0,
msg_iov: ptr::null_mut(),
msg_iovlen: 0,
msg_control: cmsgbuf.as_mut_ptr() as *mut _,
msg_controllen: cmsgbuf.len() as c_uint,
msg_flags: 0,
};
let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
unsafe { (*cmsg).cmsg_level = libc::SOL_SOCKET };
unsafe { (*cmsg).cmsg_type = libc::SCM_CREDS };
unsafe { (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<libc::cmsgcred>() as c_uint) };
msg.msg_controllen = unsafe { (*cmsg).cmsg_len };
if unsafe { libc::sendmsg(sock, &msg, 0) } == -1 {
eprintln!("febug::start_raw: sendmsg: {}", Strerror);
unsafe { close(sock) };
return;
}
}
GLOBAL_CONTROLLED_SOCKET.store(sock, Ordering::Relaxed);
}
pub fn install_handler() -> bool {
install_handler_signal(include!(concat!(env!("OUT_DIR"), "/febug_signum.rs")) as u8)
}
#[cfg(febug_dont)]
pub fn install_handler_signal(_: u8) -> bool {
false
}
#[cfg(not(febug_dont))]
pub fn install_handler_signal(signal: u8) -> bool {
if GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed) != -1 {
let mut act: libc::sigaction = unsafe { MaybeUninit::zeroed().assume_init() };
act.sa_sigaction = debug_handler as usize;
if unsafe { libc::sigaction(signal as c_int, &act, ptr::null_mut()) } == -1 {
eprintln!("febug::install_handler: sigaction: {}", Strerror);
false
} else {
true
}
} else {
false
}
}
#[cfg(febug_dont)]
pub fn end() {}
#[cfg(not(febug_dont))]
pub fn end() {
let sock = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
if sock != -1 {
unsafe { close(sock) };
GLOBAL_CONTROLLED_SOCKET.store(-1, Ordering::Relaxed);
}
}
pub trait Wrappable {
fn wrap_type(&self, tp: u64, name: fmt::Arguments) -> Wrapper<Self>;
fn wrap_type_signal(&self, tp: u64, signal: u8, name: fmt::Arguments) -> Wrapper<Self>;
}
impl<T: ?Sized> Wrappable for T {
fn wrap_type(&self, tp: u64, name: fmt::Arguments) -> Wrapper<T> {
Wrapper::new(tp, self, name)
}
fn wrap_type_signal(&self, tp: u64, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
Wrapper::new_signal(tp, self, signal, name)
}
}
pub trait StaticWrappable: 'static {
fn wrap(&self, name: fmt::Arguments) -> Wrapper<Self>;
fn wrap_signal(&self, signal: u8, name: fmt::Arguments) -> Wrapper<Self>;
}
impl<T: ?Sized + 'static> StaticWrappable for T {
fn wrap(&self, name: fmt::Arguments) -> Wrapper<T> {
Wrapper::new(unsafe { mem::transmute::<_, TypeIdButStronger>(TypeId::of::<T>()).t }, self, name)
}
fn wrap_signal(&self, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
Wrapper::new_signal(unsafe { mem::transmute::<_, TypeIdButStronger>(TypeId::of::<T>()).t }, self, signal, name)
}
}
struct TypeIdButStronger {
t: u64,
}
const _TYPE_ID_SIZE_ASSERT: [(); mem::size_of::<TypeId>() - mem::size_of::<TypeIdButStronger>()] = [];
const _TYPE_ID_ALIGN_ASSERT: [(); mem::align_of::<TypeId>() - mem::align_of::<TypeIdButStronger>()] = [];
pub struct Wrapper<T: ?Sized> {
#[cfg_attr(febug_dont, allow(dead_code))]
id: u64,
tee: PhantomData<T>,
}
impl<T: ?Sized> Wrapper<T> {
pub fn new(tp: u64, data: &T, name: fmt::Arguments) -> Wrapper<T> {
Wrapper::new_signal(tp, data, include!(concat!(env!("OUT_DIR"), "/febug_signum.rs")) as u8, name)
}
pub fn new_signal(tp: u64, data: &T, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
let id = data as *const T as *const c_void as usize as u64;
#[cfg(febug_dont)]
let _ = (tp, signal, name);
#[cfg(not(febug_dont))]
{
let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
if s != -1 {
let mut msg = abi::FebugMessage {
variable_id: id,
variable_type: tp,
signal: signal,
name: unsafe { MaybeUninit::zeroed().assume_init() },
};
let _ = Cursor::new(unsafe { slice::from_raw_parts_mut(msg.name.as_mut_ptr() as *mut u8, msg.name.len()) }).write_fmt(name);
unsafe { libc::send(s, &msg as *const _ as *const c_void, mem::size_of_val(&msg), 0) };
}
}
Wrapper {
id: id,
tee: PhantomData,
}
}
}
impl<T: ?Sized> Drop for Wrapper<T> {
#[cfg(febug_dont)]
fn drop(&mut self) {}
#[cfg(not(febug_dont))]
fn drop(&mut self) {
let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
if s != -1 {
let msg = abi::StopFebugMessage { variable_id: self.id };
unsafe { libc::send(s, &msg as *const _ as *const c_void, mem::size_of_val(&msg), 0) };
}
}
}
pub static FORMATTERS: Lazy<Mutex<BTreeMap<TypeId, fn(&mut File, usize)>>> = Lazy::new(|| Mutex::new(BTreeMap::new()));
#[cfg(febug_dont)]
pub extern "C" fn debug_handler(_: c_int) {}
#[cfg(not(febug_dont))]
pub extern "C" fn debug_handler(_: c_int) {
let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
if s == -1 {
return;
}
let mut afmsg = abi::AttnFebugMessage {
variable_id: 0,
variable_type: 0,
};
let mut buf_i = libc::iovec {
iov_base: &mut afmsg as *mut _ as *mut c_void,
iov_len: mem::size_of_val(&afmsg),
};
let mut retpipe: c_int = -1;
let mut cmsgbuf = [0u8; mem::align_of::<libc::cmsghdr>() * 2 + mem::size_of::<c_int>() * 8];
let cmsgbuf_len = unsafe { libc::CMSG_SPACE(mem::size_of_val(&retpipe) as c_uint) as usize };
assert!(cmsgbuf.len() >= cmsgbuf_len, "{} >= {}", cmsgbuf.len(), cmsgbuf_len);
let mut msg = libc::msghdr {
msg_name: ptr::null_mut(),
msg_namelen: 0,
msg_iov: &mut buf_i,
msg_iovlen: 1,
msg_control: cmsgbuf.as_mut_ptr() as *mut _,
#[cfg(target_os="linux")]
msg_controllen: cmsgbuf_len,
#[cfg(not(target_os="linux"))]
msg_controllen: cmsgbuf_len as c_uint,
msg_flags: 0,
};
if unsafe { libc::recvmsg(s, &mut msg, 0) } == -1 {
eprintln!("febug::debug_handler: recvmsg: {}", Strerror);
return;
}
let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
if cmsg != ptr::null_mut() && unsafe { (*cmsg).cmsg_type } == libc::SCM_RIGHTS {
unsafe { ptr::copy_nonoverlapping(libc::CMSG_DATA(cmsg), &mut retpipe as *mut _ as *mut u8, mem::size_of_val(&retpipe)) };
let mut retpipe = unsafe { File::from_raw_fd(retpipe) };
let tp = afmsg.variable_type;
let ti = unsafe { mem::transmute::<_, TypeId>(TypeIdButStronger { t: tp }) };
let id = afmsg.variable_id;
match FORMATTERS.lock() {
Ok(fmts) => {
match fmts.get(&ti) {
Some(fmt) => fmt(&mut retpipe, afmsg.variable_id as usize),
None => {
let _ = writeln!(retpipe, "Unknown variable type {} with ID {}", tp, id);
}
}
}
Err(e) => {
let _ = writeln!(retpipe, "Can't see variable {} with ID {}: poisoned: {}!", tp, id, e);
}
};
} else {
eprintln!("febug::debug_handler: cmsg: no fd");
}
}