Skip to content

Commit 0c52c5f

Browse files
authored
Add param to use alternate stack and restore old signal handler (#276)
* try keep old_sigaction Signed-off-by: VGalaxies <[email protected]> * fixup Signed-off-by: VGalaxies <[email protected]> * add comments Signed-off-by: VGalaxies <[email protected]> * add on_stack param Signed-off-by: VGalaxies <[email protected]> * enable on_stack under frame-pointer feature Signed-off-by: VGalaxies <[email protected]> * fix variable does not need to be mutable by shadowing Signed-off-by: VGalaxies <[email protected]> --------- Signed-off-by: VGalaxies <[email protected]>
1 parent c2d6e7b commit 0c52c5f

File tree

1 file changed

+55
-14
lines changed

1 file changed

+55
-14
lines changed

src/profiler.rs

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ pub struct Profiler {
3232
pub(crate) data: Collector<UnresolvedFrames>,
3333
sample_counter: i32,
3434

35+
old_sigaction: Option<signal::SigAction>,
3536
running: bool,
3637

38+
#[cfg(feature = "frame-pointer")]
39+
on_stack: bool,
40+
3741
#[cfg(any(
3842
target_arch = "x86_64",
3943
target_arch = "aarch64",
@@ -46,6 +50,10 @@ pub struct Profiler {
4650
#[derive(Clone)]
4751
pub struct ProfilerGuardBuilder {
4852
frequency: c_int,
53+
54+
#[cfg(feature = "frame-pointer")]
55+
on_stack: bool,
56+
4957
#[cfg(any(
5058
target_arch = "x86_64",
5159
target_arch = "aarch64",
@@ -60,6 +68,9 @@ impl Default for ProfilerGuardBuilder {
6068
ProfilerGuardBuilder {
6169
frequency: 99,
6270

71+
#[cfg(feature = "frame-pointer")]
72+
on_stack: false,
73+
6374
#[cfg(any(
6475
target_arch = "x86_64",
6576
target_arch = "aarch64",
@@ -76,6 +87,21 @@ impl ProfilerGuardBuilder {
7687
Self { frequency, ..self }
7788
}
7889

90+
#[cfg(feature = "frame-pointer")]
91+
/// Sets whether to use an alternate signal stack via `SA_ONSTACK`.
92+
///
93+
/// This is only available and only works correctly when the `frame-pointer` feature is enabled.
94+
///
95+
/// The `backtrace-rs` unwinder ignores the signal context and unwinds the current stack. Using
96+
/// an alternate stack with it would produce meaningless results. The `frame-pointer` unwinder,
97+
/// however, uses the provided `ucontext` to correctly walk the original application stack.
98+
///
99+
/// This should be enabled when the profiler is used in an environment
100+
/// with small stacks (e.g., inside a Go program) to prevent stack overflow.
101+
pub fn on_stack(self, on_stack: bool) -> Self {
102+
Self { on_stack, ..self }
103+
}
104+
79105
#[cfg(any(
80106
target_arch = "x86_64",
81107
target_arch = "aarch64",
@@ -126,6 +152,11 @@ impl ProfilerGuardBuilder {
126152
Err(Error::CreatingError)
127153
}
128154
Ok(profiler) => {
155+
#[cfg(feature = "frame-pointer")]
156+
{
157+
profiler.on_stack = self.on_stack;
158+
}
159+
129160
#[cfg(any(
130161
target_arch = "x86_64",
131162
target_arch = "aarch64",
@@ -387,8 +418,12 @@ impl Profiler {
387418
Ok(Profiler {
388419
data: Collector::new()?,
389420
sample_counter: 0,
421+
old_sigaction: None,
390422
running: false,
391423

424+
#[cfg(feature = "frame-pointer")]
425+
on_stack: false,
426+
392427
#[cfg(any(
393428
target_arch = "x86_64",
394429
target_arch = "aarch64",
@@ -448,24 +483,30 @@ impl Profiler {
448483
}
449484
}
450485

451-
fn register_signal_handler(&self) -> Result<()> {
486+
fn register_signal_handler(&mut self) -> Result<()> {
452487
let handler = signal::SigHandler::SigAction(perf_signal_handler);
453-
let sigaction = signal::SigAction::new(
454-
handler,
455-
// SA_RESTART will only restart a syscall when it's safe to do so,
456-
// e.g. when it's a blocking read(2) or write(2). See man 7 signal.
457-
signal::SaFlags::SA_SIGINFO | signal::SaFlags::SA_RESTART,
458-
signal::SigSet::empty(),
459-
);
460-
unsafe { signal::sigaction(signal::SIGPROF, &sigaction) }?;
461-
488+
// SA_RESTART will only restart a syscall when it's safe to do so,
489+
// e.g. when it's a blocking read(2) or write(2). See man 7 signal.
490+
let flags = signal::SaFlags::SA_SIGINFO | signal::SaFlags::SA_RESTART;
491+
#[cfg(feature = "frame-pointer")]
492+
let flags = if self.on_stack {
493+
// SA_ONSTACK will deliver the signal on an alternate stack. This is crucial
494+
// to prevent a stack overflow if the signal arrives at a thread with
495+
// a small stack, which is common when use pprof-rs in Go runtimes.
496+
flags | signal::SaFlags::SA_ONSTACK
497+
} else {
498+
flags
499+
};
500+
let sigaction = signal::SigAction::new(handler, flags, signal::SigSet::empty());
501+
let old_action = unsafe { signal::sigaction(signal::SIGPROF, &sigaction) }?;
502+
self.old_sigaction = Some(old_action);
462503
Ok(())
463504
}
464505

465-
fn unregister_signal_handler(&self) -> Result<()> {
466-
let handler = signal::SigHandler::SigIgn;
467-
unsafe { signal::signal(signal::SIGPROF, handler) }?;
468-
506+
fn unregister_signal_handler(&mut self) -> Result<()> {
507+
if let Some(old_action) = self.old_sigaction.take() {
508+
unsafe { signal::sigaction(signal::SIGPROF, &old_action) }?;
509+
}
469510
Ok(())
470511
}
471512

0 commit comments

Comments
 (0)