servo/components/background_hang_monitor/sampler_linux.rs
CarePackage17 a93d977020
Replace unwind-sys with backtrace crate on Linux (#37728)
This PR removes `unwind-sys` usage from background_hang_monitor on
Linux, replacing it with the [backtrace
crate](https://crates.io/crates/backtrace).

Testing: `hang_monitor-tests.rs` still pass after the change (on Ubuntu
24.04).
Fixes: https://github.com/servo/servo/issues/35063

---------

Signed-off-by: CarePackage17 <5157010+CarePackage17@users.noreply.github.com>
2025-06-27 12:20:10 +00:00

245 lines
7.1 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![allow(unsafe_code)]
use std::cell::UnsafeCell;
use std::{io, mem, process, thread};
use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
use crate::sampler::{NativeStack, Sampler};
struct UncheckedSyncUnsafeCell<T>(std::cell::UnsafeCell<T>);
/// Safety: dereferencing the pointer from `UnsafeCell::get` must involve external synchronization
unsafe impl<T> Sync for UncheckedSyncUnsafeCell<T> {}
static SHARED_STATE: UncheckedSyncUnsafeCell<SharedState> =
UncheckedSyncUnsafeCell(std::cell::UnsafeCell::new(SharedState {
msg2: None,
msg3: None,
msg4: None,
}));
type MonitoredThreadId = libc::pid_t;
struct SharedState {
// "msg1" is the signal.
msg2: Option<PosixSemaphore>,
msg3: Option<PosixSemaphore>,
msg4: Option<PosixSemaphore>,
}
fn clear_shared_state() {
// Safety: this is only called from the sampling thread (theres only one)
// Sampled threads only access SHARED_STATE in their signal handler.
// This signal and the semaphores in SHARED_STATE provide the necessary synchronization.
unsafe {
let shared_state = &mut *SHARED_STATE.0.get();
shared_state.msg2 = None;
shared_state.msg3 = None;
shared_state.msg4 = None;
}
}
fn reset_shared_state() {
// Safety: same as clear_shared_state
unsafe {
let shared_state = &mut *SHARED_STATE.0.get();
shared_state.msg2 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
shared_state.msg3 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
shared_state.msg4 = Some(PosixSemaphore::new(0).expect("valid semaphore"));
}
}
struct PosixSemaphore {
sem: UnsafeCell<libc::sem_t>,
}
impl PosixSemaphore {
pub fn new(value: u32) -> io::Result<Self> {
let mut sem = mem::MaybeUninit::uninit();
let r = unsafe {
libc::sem_init(sem.as_mut_ptr(), 0 /* not shared */, value)
};
if r == -1 {
return Err(io::Error::last_os_error());
}
Ok(PosixSemaphore {
sem: UnsafeCell::new(unsafe { sem.assume_init() }),
})
}
pub fn post(&self) -> io::Result<()> {
if unsafe { libc::sem_post(self.sem.get()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn wait(&self) -> io::Result<()> {
if unsafe { libc::sem_wait(self.sem.get()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
/// Retries the wait if it returned due to EINTR.
/// Returns Ok on success and the error on any other return value.
pub fn wait_through_intr(&self) -> io::Result<()> {
loop {
match self.wait() {
Err(os_error) => {
let err = os_error.raw_os_error().expect("no os error");
if err == libc::EINTR {
thread::yield_now();
continue;
}
return Err(os_error);
},
_ => return Ok(()),
}
}
}
}
unsafe impl Sync for PosixSemaphore {}
impl Drop for PosixSemaphore {
/// Destroys the semaphore.
fn drop(&mut self) {
unsafe { libc::sem_destroy(self.sem.get()) };
}
}
#[allow(dead_code)]
pub struct LinuxSampler {
thread_id: MonitoredThreadId,
old_handler: SigAction,
}
impl LinuxSampler {
#[allow(unsafe_code, dead_code)]
pub fn new_boxed() -> Box<dyn Sampler> {
let thread_id = unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t };
let handler = SigHandler::SigAction(sigprof_handler);
let action = SigAction::new(
handler,
SaFlags::SA_RESTART | SaFlags::SA_SIGINFO,
SigSet::empty(),
);
let old_handler =
unsafe { sigaction(Signal::SIGPROF, &action).expect("signal handler set") };
Box::new(LinuxSampler {
thread_id,
old_handler,
})
}
}
impl Sampler for LinuxSampler {
#[allow(unsafe_code)]
fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
// Warning: The "critical section" begins here.
// In the critical section:
// we must not do any dynamic memory allocation,
// nor try to acquire any lock
// or any other unshareable resource.
// first we reinitialize the semaphores
reset_shared_state();
// signal the thread, wait for it to tell us state was copied.
send_sigprof(self.thread_id);
// Safety: non-exclusive reference only
// since sampled threads are accessing this concurrently
let result;
{
let shared_state = unsafe { &*SHARED_STATE.0.get() };
shared_state
.msg2
.as_ref()
.unwrap()
.wait_through_intr()
.expect("msg2 failed");
let mut native_stack = NativeStack::new();
unsafe {
backtrace::trace_unsynchronized(|frame| {
let ip = frame.ip();
let sp = frame.sp();
//This return value here determines whether we proceed to the next stack frame or not.
native_stack.process_register(ip, sp).is_ok()
})
};
result = Ok(native_stack);
// signal the thread to continue.
shared_state
.msg3
.as_ref()
.unwrap()
.post()
.expect("msg3 failed");
// wait for thread to continue.
shared_state
.msg4
.as_ref()
.unwrap()
.wait_through_intr()
.expect("msg4 failed");
}
clear_shared_state();
// NOTE: End of "critical section".
result
}
}
impl Drop for LinuxSampler {
fn drop(&mut self) {
unsafe {
sigaction(Signal::SIGPROF, &self.old_handler).expect("previous signal handler restored")
};
}
}
extern "C" fn sigprof_handler(
sig: libc::c_int,
_info: *mut libc::siginfo_t,
_ctx: *mut libc::c_void,
) {
assert_eq!(sig, libc::SIGPROF);
// Safety: non-exclusive reference only
// since the sampling thread is accessing this concurrently
let shared_state = unsafe { &*SHARED_STATE.0.get() };
// Tell the sampler we copied the context.
shared_state.msg2.as_ref().unwrap().post().expect("posted");
// Wait for sampling to finish.
shared_state
.msg3
.as_ref()
.unwrap()
.wait_through_intr()
.expect("msg3 wait succeeded");
// OK we are done!
shared_state.msg4.as_ref().unwrap().post().expect("posted");
// DO NOT TOUCH shared state here onwards.
}
fn send_sigprof(to: libc::pid_t) {
unsafe {
libc::syscall(libc::SYS_tgkill, process::id(), to, libc::SIGPROF);
}
}