@@ -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 ) ]
4751pub 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