febug/
lib.rs

1// SPDX-License-Identifier: MIT
2
3//! Rust wrapper library for the [user-space debugfs](https://git.sr.ht/~nabijaczleweli/febug) ABI.
4//!
5//! Full documentation at
6//! [libfebug.rs(3)](https://ra.ws.co.ls/~nabijaczleweli/febug/blob/man/Debian/libfebug.rs.3.html),
7//! and of the rest of febug at [index(0)](https://ra.ws.co.ls/~nabijaczleweli/febug/blob/man/Debian/index.0.html).
8//!
9//! Set `$FEBUG_DONT` at build-time to turn everything into a no-op,
10//! set `$FEBUG_SIGNUM` at build-time to override the default signal (`SIGUSR2`),
11//! set `$FEBUG_SOCKET` at build-time to override the default location (`[/var]/run/febug.sock`).
12//!
13//! Set `$FEBUG_DONT` at run-time to not connect (and, hence, make everything a no-op),
14//! set `$FEBUG_SOCKET` at run-time to set an alternate location.
15//!
16//! See [`examples/string-sorts.rs`](https://git.sr.ht/~nabijaczleweli/febug/tree/trunk/item/examples/string-sorts.rs)
17//! for a usage example.
18
19
20extern crate libc;
21extern crate once_cell;
22
23
24use std::hash::{BuildHasher, Hasher, Hash};
25#[cfg(not(febug_dont))]
26use libc::{getenv, close, c_char, c_uint};
27#[cfg(all(not(febug_dont), target_os="netbsd"))]
28use libc::{recv, ssize_t};
29use libc::{strerror, c_void, c_int};
30#[cfg(not(febug_dont))]
31use std::os::unix::io::FromRawFd;
32use std::sync::atomic::AtomicI32;
33#[cfg(not(febug_dont))]
34use std::sync::atomic::Ordering;
35use std::collections::BTreeMap;
36use std::marker::PhantomData;
37#[cfg(not(febug_dont))]
38use std::io::{Cursor, Write};
39#[cfg(not(febug_dont))]
40use std::{slice, cmp, ptr};
41use std::convert::TryInto;
42#[cfg(not(febug_dont))]
43use std::mem::MaybeUninit;
44use once_cell::sync::Lazy;
45use std::sync::Mutex;
46use std::any::TypeId;
47use std::{fmt, mem};
48use std::ffi::CStr;
49use std::fs::File;
50
51// Borrowed from https://github.com/rust-random/getrandom/blob/2b03b0e0b8a65ec5272b867311d3004cea73f381/src/util_libc.rs
52#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
53use libc::__errno as errno_location;
54#[cfg(any(target_os = "linux"))]
55use libc::__errno_location as errno_location;
56#[cfg(any(target_os = "freebsd", target_os = "macos"))]
57use libc::__error as errno_location;
58
59
60// GUESS WHAT?? DARWIN DEFINES SOCK_SEQPACKET BUT DOESN'T ACTUALLY FUCKING SUPPORT IT (i.e. socket(2) returns EPROTONOSUPPORT). WHY? BECAUSE FUCK YOU.
61#[cfg(all(not(febug_dont), target_os = "macos"))]
62use libc::SOCK_STREAM as SOCK_SEQPACKET;
63#[cfg(all(not(febug_dont), not(target_os = "macos")))]
64use libc::SOCK_SEQPACKET as SOCK_SEQPACKET;
65
66
67/// [`febug-abi(5)`](https://ra.ws.co.ls/~nabijaczleweli/febug/blob/man/Debian/febug-abi.5.html)
68pub mod abi {
69    use libc::c_char;
70    use std::mem;
71
72    /// `febug_message`
73    #[repr(packed)]
74    pub struct FebugMessage {
75        pub variable_id: u64,
76        pub variable_type: u64,
77        pub signal: u8,
78        pub name: [c_char; 4096 - 8 - 8 - 1],
79    }
80    const _FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<FebugMessage>() - 4096] = [];
81
82    /// `stop_febug_message`
83    #[repr(packed)]
84    pub struct StopFebugMessage {
85        pub variable_id: u64,
86    }
87    const _STOP_FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<StopFebugMessage>() - 8] = [];
88
89    /// `attn_febug_message`
90    #[repr(packed)]
91    pub struct AttnFebugMessage {
92        pub variable_id: u64,
93        pub variable_type: u64,
94    }
95    const _ATTN_FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<AttnFebugMessage>() - 16] = [];
96}
97
98
99/// FD for the connection to [`febug(8)`](https://ra.ws.co.ls/~nabijaczleweli/febug/blob/man/Debian/febug.8.html), or
100/// -1 if none.
101///
102/// c_int is universally i32 (on platforms that we support, anyway)
103pub static GLOBAL_CONTROLLED_SOCKET: AtomicI32 = AtomicI32::new(-1);
104const _ILP32_ASSERT: [(); mem::size_of::<AtomicI32>() - mem::size_of::<c_int>()] = [];
105
106
107struct Strerror;
108impl fmt::Display for Strerror {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        f.write_str(unsafe { CStr::from_ptr(strerror(*errno_location())) }.to_str().unwrap_or("<strerror not UTF-8?>"))
111    }
112}
113
114
115/// Call `start_raw()` with the default socket location for this platform, overridable with `FEBUG_SOCKET`
116pub fn start() {
117    start_raw(include!(concat!(env!("OUT_DIR"), "/febug_socket.rs")))
118}
119
120/// Disabled
121#[cfg(febug_dont)]
122pub fn start_raw(_: &[u8]) {}
123/// If `$FEBUG_DONT` isn't set, dial
124/// [`febug(8)`](https://ra.ws.co.ls/~nabijaczleweli/febug/blob/man/Debian/febug.8.html) at the specified path,
125/// or `$FEBUG_SOCKET`
126#[cfg(not(febug_dont))]
127pub fn start_raw(mut path: &[u8]) {
128    if unsafe { getenv(b"FEBUG_DONT\0".as_ptr() as *const c_char) } != ptr::null_mut() {
129        return;
130    }
131
132    let sock = unsafe { libc::socket(libc::AF_UNIX, SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0) };
133    if sock == -1 {
134        eprintln!("febug::start_raw: socket: {}", Strerror);
135        return;
136    }
137
138    let fs = unsafe { getenv(b"FEBUG_SOCKET\0".as_ptr() as *const _) };
139    if fs != ptr::null_mut() {
140        path = unsafe { CStr::from_ptr(fs) }.to_bytes();
141    }
142    let path = unsafe { slice::from_raw_parts(path.as_ptr() as *const c_char, path.len()) };
143
144    let mut addr: libc::sockaddr_un = unsafe { MaybeUninit::zeroed().assume_init() };
145    addr.sun_family = libc::AF_UNIX as libc::sa_family_t;
146
147    let path_strlen = cmp::min(addr.sun_path.len() - 1, path.len());
148    addr.sun_path[0..path_strlen].copy_from_slice(&path[0..path_strlen]);
149    if unsafe { libc::connect(sock, &addr as *const libc::sockaddr_un as *const libc::sockaddr, mem::size_of_val(&addr) as u32) } == -1 {
150        eprintln!("febug::start_raw: connect: {}", Strerror);
151        unsafe { close(sock) };
152        return;
153    }
154
155        #[cfg(any(target_os="linux", target_os="openbsd", target_os="macos"))]
156    {
157        // Handled automatically with SO_PASSCRED, also the manual variant didn't work for some reason
158        // Only way is getsockopt(SO_PEERCRED)
159        // Only way is getsockopt(LOCAL_PEERCRED)+getsockopt(LOCAL_GETPEEREPID)
160    }
161        #[cfg(target_os="netbsd")]
162    {
163        // Correct way is automatically via LOCAL_CREDS
164        // However, the message /must/ be sent after the peer sets it; use a sync message from the server for this,
165        // otherwise we sent the first febug_message too quickly sometimes
166        let mut sync_msg = abi::AttnFebugMessage {
167            variable_id: 0,
168            variable_type: 0,
169        };
170        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 {
171            eprintln!("febug::start_raw: recv: {}", Strerror);
172            unsafe { close(sock) };
173            return;
174        }
175    }
176        #[cfg(not(any(target_os="linux", target_os="openbsd", target_os="macos", target_os="netbsd")))]
177    {
178        // From FreeBSD 12.1-RELEASE-p7 /usr/include/socket.h:
179        //
180        // Credentials structure, used to verify the identity of a peer
181        // process that has sent us a message. This is allocated by the
182        // peer process but filled in by the kernel. This prevents the
183        // peer from lying about its identity. (Note that cmcred_groups[0]
184        // is the effective GID.)
185        //
186
187        let mut cmsgbuf = [0u8; mem::align_of::<libc::cmsghdr>() * 2 + mem::size_of::<libc::cmsgcred>() * 2];
188        let cmsgbuf_len = unsafe { libc::CMSG_SPACE(mem::size_of::<libc::cmsgcred>() as c_uint) as usize }; // This is a macro in C. Not so here. Alas!
189        assert!(cmsgbuf.len() >= cmsgbuf_len, "{} >= {}", cmsgbuf.len(), cmsgbuf_len);
190
191        let mut msg = libc::msghdr {
192            msg_name: ptr::null_mut(),
193            msg_namelen: 0,
194            msg_iov: ptr::null_mut(),
195            msg_iovlen: 0,
196            msg_control: cmsgbuf.as_mut_ptr() as *mut _,
197            msg_controllen: cmsgbuf.len() as c_uint,
198            msg_flags: 0,
199        };
200
201        let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
202        unsafe { (*cmsg).cmsg_level = libc::SOL_SOCKET };
203        unsafe { (*cmsg).cmsg_type = libc::SCM_CREDS };
204        unsafe { (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<libc::cmsgcred>() as c_uint) };
205        msg.msg_controllen = unsafe { (*cmsg).cmsg_len }; // total size of all control blocks
206
207        if unsafe { libc::sendmsg(sock, &msg, 0) } == -1 {
208            eprintln!("febug::start_raw: sendmsg: {}", Strerror);
209            unsafe { close(sock) };
210            return;
211        }
212    }
213
214    GLOBAL_CONTROLLED_SOCKET.store(sock, Ordering::Relaxed);
215}
216
217/// Call `debug_handler()` with `FEBUG_SIGNUM` (`SIGUSR2` by default)
218pub fn install_handler() -> bool {
219    install_handler_signal(include!(concat!(env!("OUT_DIR"), "/febug_signum.rs")) as u8)
220}
221
222/// Disabled
223#[cfg(febug_dont)]
224pub fn install_handler_signal(_: u8) -> bool {
225    false
226}
227
228/// Install `debug_handler()` as a handler for the specified signal
229///
230/// Returns `true` if became installed
231#[cfg(not(febug_dont))]
232pub fn install_handler_signal(signal: u8) -> bool {
233    if GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed) != -1 {
234        let mut act: libc::sigaction = unsafe { MaybeUninit::zeroed().assume_init() };
235        act.sa_sigaction = debug_handler as usize;
236        if unsafe { libc::sigaction(signal as c_int, &act, ptr::null_mut()) } == -1 {
237            eprintln!("febug::install_handler: sigaction: {}", Strerror);
238            false
239        } else {
240            true
241        }
242    } else {
243        false
244    }
245}
246
247/// Disabled
248#[cfg(febug_dont)]
249pub fn end() {}
250/// Hang up on [`febug(8)`](https://ra.ws.co.ls/~nabijaczleweli/febug/blob/man/Debian/febug.8.html)
251#[cfg(not(febug_dont))]
252pub fn end() {
253    let sock = GLOBAL_CONTROLLED_SOCKET.swap(-1, Ordering::Relaxed);
254    if sock != -1 {
255        unsafe { close(sock) };
256    }
257}
258
259
260/// Helper trait for constructing `Wrapper<T>`s
261pub trait Wrappable {
262    fn wrap_type(&self, tp: u64, name: fmt::Arguments) -> Wrapper<Self>;
263    fn wrap_type_signal(&self, tp: u64, signal: u8, name: fmt::Arguments) -> Wrapper<Self>;
264}
265impl<T: ?Sized> Wrappable for T {
266    fn wrap_type(&self, tp: u64, name: fmt::Arguments) -> Wrapper<T> {
267        Wrapper::new(tp, self, name)
268    }
269
270    fn wrap_type_signal(&self, tp: u64, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
271        Wrapper::new_signal(tp, self, signal, name)
272    }
273}
274
275/// Helper trait for constructing `Wrapper<T>`s with type equal to `TypeId::of::<Self>()`
276pub trait StaticWrappable: 'static {
277    fn wrap(&self, name: fmt::Arguments) -> Wrapper<Self>;
278    fn wrap_signal(&self, signal: u8, name: fmt::Arguments) -> Wrapper<Self>;
279}
280impl<T: ?Sized + 'static> StaticWrappable for T {
281    fn wrap(&self, name: fmt::Arguments) -> Wrapper<T> {
282        Wrapper::new(Type::from(TypeId::of::<T>()).0, self, name)
283    }
284
285    fn wrap_signal(&self, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
286        Wrapper::new_signal(Type::from(TypeId::of::<T>()).0, self, signal, name)
287    }
288}
289
290struct NullHasher(u64);
291impl Hasher for NullHasher {
292    fn finish(&self) -> u64 {
293        self.0
294    }
295    fn write(&mut self, bytes: &[u8]) {
296        match bytes.try_into() {
297            Ok(eight) => self.0 = u64::from_ne_bytes(eight),
298            Err(_) => unreachable!()
299        }
300    }
301}
302impl BuildHasher for NullHasher {
303    type Hasher = NullHasher;
304
305    fn build_hasher(&self) -> Self::Hasher {
306        NullHasher(0)
307    }
308}
309
310
311/// Create this to register a variable to be debugged
312pub struct Wrapper<T: ?Sized> {
313    #[cfg_attr(febug_dont, allow(dead_code))]
314    id: u64,
315    tee: PhantomData<T>,
316}
317
318impl<T: ?Sized> Wrapper<T> {
319    /// Call `new_signal()` with `FEBUG_SIGNUM` (`SIGUSR2` by default)
320    pub fn new(tp: u64, data: &T, name: fmt::Arguments) -> Wrapper<T> {
321        Wrapper::new_signal(tp, data, include!(concat!(env!("OUT_DIR"), "/febug_signum.rs")) as u8, name)
322    }
323
324    /// Register the specified variable of the specified type with the specified name to be notified (if not `SIGKILL`) on the
325    /// specified signal to format it
326    pub fn new_signal(tp: u64, data: &T, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
327        let id = data as *const T as *const c_void as usize as u64;
328
329        #[cfg(febug_dont)]
330        let _ = (tp, signal, name);
331        #[cfg(not(febug_dont))]
332        {
333
334            let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
335            if s != -1 {
336                let mut msg = abi::FebugMessage {
337                    variable_id: id,
338                    variable_type: tp,
339                    signal: signal,
340                    name: unsafe { MaybeUninit::zeroed().assume_init() },
341                };
342                let _ = Cursor::new(unsafe { slice::from_raw_parts_mut(msg.name.as_mut_ptr() as *mut u8, msg.name.len()) }).write_fmt(name);
343                unsafe { libc::send(s, &msg as *const _ as *const c_void, mem::size_of_val(&msg), 0) };
344            }
345        }
346
347        Wrapper {
348            id: id,
349            tee: PhantomData,
350        }
351    }
352}
353
354impl<T: ?Sized> Drop for Wrapper<T> {
355    /// Disabled
356    #[cfg(febug_dont)]
357    fn drop(&mut self) {}
358    /// Deregister the variable
359    #[cfg(not(febug_dont))]
360    fn drop(&mut self) {
361        let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
362        if s != -1 {
363            let msg = abi::StopFebugMessage { variable_id: self.id };
364            unsafe { libc::send(s, &msg as *const _ as *const c_void, mem::size_of_val(&msg), 0) };
365        }
366    }
367}
368
369
370/// Desugars a `TypeId` to its hash, or an explicit type name
371///
372/// Required since `TypeId`s are non-reconstructible since Rust 1.72, cf. <https://101010.pl/@nabijaczleweli/111474899766816762>
373#[derive(Clone, Hash, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
374pub struct Type(pub u64);
375impl From<TypeId> for Type {
376    fn from(t: TypeId) -> Type {
377        let mut hash = NullHasher(0);
378        t.hash(&mut hash);
379        Type(hash.0)
380    }
381}
382impl From<u64> for Type {
383    fn from(u: u64) -> Type {
384        Type(u)
385    }
386}
387
388/// Register formatters for your variable types here;
389///
390/// type -> fn(return pipe, ID)
391pub static FORMATTERS: Lazy<Mutex<BTreeMap<Type, fn(&mut File, usize)>>> = Lazy::new(|| Mutex::new(BTreeMap::new()));
392
393
394/// Disabled
395#[cfg(febug_dont)]
396pub extern "C" fn debug_handler(_: c_int) {}
397/// Register this as a signal handler or call this from an event loop to format variables to be inspected
398#[cfg(not(febug_dont))]
399pub extern "C" fn debug_handler(_: c_int) {
400    let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
401    if s == -1 {
402        return;
403    }
404
405    let mut afmsg = abi::AttnFebugMessage {
406        variable_id: 0,
407        variable_type: 0,
408    };
409    let mut buf_i = libc::iovec {
410        iov_base: &mut afmsg as *mut _ as *mut c_void,
411        iov_len: mem::size_of_val(&afmsg),
412    };
413
414    let mut retpipe: c_int = -1;
415    let mut cmsgbuf = [0u8; mem::align_of::<libc::cmsghdr>() * 2 + mem::size_of::<c_int>() * 8];
416    let cmsgbuf_len = unsafe { libc::CMSG_SPACE(mem::size_of_val(&retpipe) as c_uint) as usize }; // This is a macro in C. Not so here. Alas!
417    assert!(cmsgbuf.len() >= cmsgbuf_len, "{} >= {}", cmsgbuf.len(), cmsgbuf_len);
418
419    let mut msg = libc::msghdr {
420        msg_name: ptr::null_mut(),
421        msg_namelen: 0,
422        msg_iov: &mut buf_i,
423        msg_iovlen: 1,
424        msg_control: cmsgbuf.as_mut_ptr() as *mut _,
425        #[cfg(target_os="linux")]
426        msg_controllen: cmsgbuf_len,
427        #[cfg(not(target_os="linux"))]
428        msg_controllen: cmsgbuf_len as c_uint,
429        msg_flags: 0,
430    };
431
432    if unsafe { libc::recvmsg(s, &mut msg, 0) } == -1 {
433        eprintln!("febug::debug_handler: recvmsg: {}", Strerror);
434        return;
435    }
436
437    let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
438    if cmsg != ptr::null_mut() && unsafe { (*cmsg).cmsg_type } == libc::SCM_RIGHTS {
439        unsafe { ptr::copy_nonoverlapping(libc::CMSG_DATA(cmsg), &mut retpipe as *mut _ as *mut u8, mem::size_of_val(&retpipe)) };
440        let mut retpipe = unsafe { File::from_raw_fd(retpipe) };
441
442        let tp = afmsg.variable_type;
443        let id = afmsg.variable_id;
444        match FORMATTERS.lock() {
445            Ok(fmts) => {
446                match fmts.get(&Type(tp)) {
447                    Some(fmt) => fmt(&mut retpipe, afmsg.variable_id as usize),
448                    None => {
449                        let _ = writeln!(retpipe, "Unknown variable type {} with ID {}", tp, id);
450                    }
451                }
452            }
453            Err(e) => {
454                let _ = writeln!(retpipe, "Can't see variable {} with ID {}: poisoned: {}!", tp, id, e);
455            }
456        };
457
458        // closed by drop
459    } else {
460        eprintln!("febug::debug_handler: cmsg: no fd");
461    }
462}