1extern 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#[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#[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
67pub mod abi {
69 use libc::c_char;
70 use std::mem;
71
72 #[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 #[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 #[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
99pub 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
115pub fn start() {
117 start_raw(include!(concat!(env!("OUT_DIR"), "/febug_socket.rs")))
118}
119
120#[cfg(febug_dont)]
122pub fn start_raw(_: &[u8]) {}
123#[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 }
161 #[cfg(target_os="netbsd")]
162 {
163 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 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 }; 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 }; 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
217pub fn install_handler() -> bool {
219 install_handler_signal(include!(concat!(env!("OUT_DIR"), "/febug_signum.rs")) as u8)
220}
221
222#[cfg(febug_dont)]
224pub fn install_handler_signal(_: u8) -> bool {
225 false
226}
227
228#[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#[cfg(febug_dont)]
249pub fn end() {}
250#[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
260pub 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
275pub 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
311pub 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 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 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 #[cfg(febug_dont)]
357 fn drop(&mut self) {}
358 #[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#[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
388pub static FORMATTERS: Lazy<Mutex<BTreeMap<Type, fn(&mut File, usize)>>> = Lazy::new(|| Mutex::new(BTreeMap::new()));
392
393
394#[cfg(febug_dont)]
396pub extern "C" fn debug_handler(_: c_int) {}
397#[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 }; 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 } else {
460 eprintln!("febug::debug_handler: cmsg: no fd");
461 }
462}