From 789db56b74730e727cfc057cae97d7ca91f67bf6 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Tue, 13 Oct 2026 16:57:31 +0200 Subject: [PATCH 1/3] feat(linux): support frame-pointer-only recording Add --unwinding-mode fp to record kernel callchains without copying user stacks or registers. Keep the existing DWARF recording behavior as the default, including on Android. Apply kernel callchain exclusion after initializing perf flags. Cover CLI selection and callchain-only stack conversion, and document frame-pointer build requirements. --- README.md | 10 ++ samply/src/cli.rs | 65 ++++++++++- samply/src/linux/perf_event.rs | 10 +- samply/src/linux/profiler.rs | 30 ++++- samply/src/linux_shared/converter.rs | 164 +++++++++++++++++++++++++++ samply/src/shared/prop_types.rs | 8 ++ 6 files changed, 273 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 50c856a33..ea0f26931 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ On Linux, samply uses perf events. You can grant temporary access by running: echo '-1' | sudo tee /proc/sys/kernel/perf_event_paranoid ``` +On Linux, use `--unwinding-mode fp` to record kernel frame-pointer callchains without copying user stack bytes or registers: + +```sh +samply record --unwinding-mode fp ./my-application my-arguments +``` + +The default, `--unwinding-mode dwarf`, keeps stack/register capture and framehop unwinding with the kernel frame-pointer callchain as a fallback. + +Frame-pointer recording requires the application and its libraries to preserve frame pointers. For Rust, build with `RUSTFLAGS="-C force-frame-pointers=yes"`; for C/C++, use `-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer`. Missing frame pointers can truncate the stack. Kernel callchain support depends on the target architecture and kernel, and callchain depth is bounded by `/proc/sys/kernel/perf_event_max_stack`. + On Windows, you can use `samply record -a` to record all processes. You'll usually also want to use some symbol servers, most importantly the Microsoft Symbol Server so that you can see symbols for Windows libraries. Here's a command which supports symbols for Windows, Firefox and Chrome: ``` diff --git a/samply/src/cli.rs b/samply/src/cli.rs index 456e30394..6a2f34cc0 100644 --- a/samply/src/cli.rs +++ b/samply/src/cli.rs @@ -10,7 +10,7 @@ use super::server::{PortSelection, ServerProps}; use super::shared::included_processes::IncludedProcesses; use super::shared::prop_types::{ CoreClrProfileProps, ImportProps, ProcessLaunchProps, ProfileCreationProps, RecordingMode, - RecordingProps, SymbolProps, + RecordingProps, SymbolProps, UnwindingMode, }; #[derive(Debug, Parser)] @@ -133,6 +133,11 @@ pub struct RecordArgs { #[arg(short, long, default_value = "1000")] pub rate: f64, + /// Stack unwinding method (Linux only). + #[cfg(target_os = "linux")] + #[arg(long, value_enum, default_value = "dwarf")] + pub unwinding_mode: UnwindingMode, + /// Limit the recorded time to the specified number of seconds #[arg(short, long)] pub duration: Option, @@ -456,6 +461,10 @@ impl RecordArgs { output_file: self.output.clone(), time_limit, interval, + #[cfg(target_os = "linux")] + unwinding_mode: self.unwinding_mode, + #[cfg(not(target_os = "linux"))] + unwinding_mode: UnwindingMode::Dwarf, perf_events, gfx: self.gfx, browsers: self.browsers, @@ -648,4 +657,58 @@ mod test { let opt_res = Opt::try_parse_from(["samply", "record", "-p", "1234", "rustup"]); assert!(opt_res.is_err()); } + + #[cfg(target_os = "linux")] + #[test] + fn record_fp_unwinding_with_launched_command() { + let opt = Opt::try_parse_from([ + "samply", + "record", + "--unwinding-mode", + "fp", + "rustup", + "--version", + ]) + .unwrap(); + let Action::Record(args) = opt.action else { + panic!("expected record action"); + }; + assert_eq!(args.unwinding_mode, UnwindingMode::Fp); + assert_eq!(args.command, ["rustup", "--version"]); + assert!(matches!(args.recording_mode(), RecordingMode::Launch(_))); + } + + #[cfg(target_os = "linux")] + #[test] + fn record_fp_unwinding_with_pid_attach() { + let opt = Opt::try_parse_from([ + "samply", + "record", + "--unwinding-mode", + "fp", + "--pid", + "1234", + ]) + .unwrap(); + let Action::Record(args) = opt.action else { + panic!("expected record action"); + }; + assert_eq!(args.unwinding_mode, UnwindingMode::Fp); + assert!(matches!(args.recording_mode(), RecordingMode::Pid(1234))); + } + + #[cfg(target_os = "linux")] + #[test] + fn record_rejects_unsupported_unwinding_mode() { + let error = Opt::try_parse_from([ + "samply", + "record", + "--unwinding-mode", + "unsupported", + "--pid", + "1234", + ]) + .unwrap_err(); + assert_eq!(error.kind(), clap::error::ErrorKind::InvalidValue); + } } diff --git a/samply/src/linux/perf_event.rs b/samply/src/linux/perf_event.rs index a34cf4bad..4fa7e4366 100644 --- a/samply/src/linux/perf_event.rs +++ b/samply/src/linux/perf_event.rs @@ -505,13 +505,9 @@ impl PerfBuilder { attr.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID; } - // Always request callchain so the kernel walks frame pointers at sample - // time. The user portion serves as a fallback when DWARF unwinding - // truncates (e.g. due to the 32 KB captured stack window). + // FP-only recording uses the callchain directly; DWARF recording uses + // its user portion as a fallback when the captured stack is too short. attr.sample_type |= PERF_SAMPLE_CALLCHAIN; - if exclude_kernel { - attr.flags |= PERF_ATTR_FLAG_EXCLUDE_CALLCHAIN_KERNEL; - } attr.sample_regs_user = reg_mask; attr.sample_stack_user = stack_size; @@ -533,7 +529,7 @@ impl PerfBuilder { } if exclude_kernel { - attr.flags |= PERF_ATTR_FLAG_EXCLUDE_KERNEL; + attr.flags |= PERF_ATTR_FLAG_EXCLUDE_KERNEL | PERF_ATTR_FLAG_EXCLUDE_CALLCHAIN_KERNEL; } if inherit { diff --git a/samply/src/linux/profiler.rs b/samply/src/linux/profiler.rs index 271e01cdf..e1286686d 100644 --- a/samply/src/linux/profiler.rs +++ b/samply/src/linux/profiler.rs @@ -26,7 +26,7 @@ use crate::linux_shared::{ }; use crate::shared::ctrl_c::CtrlC; use crate::shared::prop_types::{ - ProcessLaunchProps, ProfileCreationProps, RecordingMode, RecordingProps, + ProcessLaunchProps, ProfileCreationProps, RecordingMode, RecordingProps, UnwindingMode, }; #[cfg(target_arch = "x86_64")] @@ -93,6 +93,7 @@ pub fn run( // Launch the observer thread. This thread will manage the perf events. let interval = recording_props.interval; + let unwinding_mode = recording_props.unwinding_mode; let time_limit = recording_props.time_limit; // Resolve the extra event names before launching anything, so that a // configuration error aborts immediately. @@ -117,7 +118,14 @@ pub fn run( }; // Create the perf events, setting ENABLE_ON_EXEC. - let perf_group = init_profiler(interval, pid, attach_mode, extra_events, &mut converter); + let perf_group = init_profiler( + interval, + unwinding_mode, + pid, + attach_mode, + extra_events, + &mut converter, + ); // Tell the main thread to tell the child process to begin executing. profile_another_pid_reply_sender.send(true).unwrap(); @@ -281,8 +289,14 @@ fn start_profiling_pid( else { panic!("The first message should be a StartProfilingAnotherProcess") }; - let perf_group = - init_profiler(interval, pid, attach_mode, extra_events, &mut converter); + let perf_group = init_profiler( + interval, + recording_props.unwinding_mode, + pid, + attach_mode, + extra_events, + &mut converter, + ); // Tell the main thread that we are now executing. profile_another_pid_reply_sender.send(true).unwrap(); @@ -403,6 +417,7 @@ fn resolve_extra_events(specs: &[String]) -> Vec { fn init_profiler( interval: Duration, + unwinding_mode: UnwindingMode, pid: u32, attach_mode: AttachMode, extra_events: Vec, @@ -417,8 +432,11 @@ fn init_profiler( }; let frequency = (1_000_000_000 / interval_nanos) as u32; - let stack_size = 32000; - let regs_mask = ConvertRegsNative::regs_mask(); + const DWARF_STACK_SIZE: u32 = 32_000; + let (stack_size, regs_mask) = match unwinding_mode { + UnwindingMode::Dwarf => (DWARF_STACK_SIZE, ConvertRegsNative::regs_mask()), + UnwindingMode::Fp => (0, 0), + }; let mut perf = PerfGroup::open( pid, diff --git a/samply/src/linux_shared/converter.rs b/samply/src/linux_shared/converter.rs index 120020d43..09e70e59b 100644 --- a/samply/src/linux_shared/converter.rs +++ b/samply/src/linux_shared/converter.rs @@ -2159,3 +2159,167 @@ impl Marker for MmapMarker { self.0 } } + +#[cfg(test)] +mod tests { + use framehop::x86_64::{CacheX86_64, UnwinderX86_64}; + use framehop::{ExplicitModuleSectionInfo, Module, Unwinder}; + use linux_perf_data::linux_perf_event_reader; + use linux_perf_event_reader::constants::{PERF_CONTEXT_KERNEL, PERF_CONTEXT_USER}; + use linux_perf_event_reader::{CpuMode, RawData, RawDataU64, SampleRecord}; + + use super::{Converter, MmapRangeOrVec}; + use crate::linux_shared::convert_regs::ConvertRegsX86_64; + use crate::shared::types::{StackFrame, StackMode}; + + fn callchain_only_stack( + ip: u64, + cpu_mode: CpuMode, + callchain: Option<&[u64]>, + ) -> Vec { + let callchain_bytes = callchain.map(|addresses| { + addresses + .iter() + .flat_map(|address| address.to_le_bytes()) + .collect::>() + }); + let sample = SampleRecord { + id: None, + addr: None, + stream_id: None, + raw: None, + ip: Some(ip), + timestamp: None, + pid: Some(1), + tid: Some(1), + cpu: None, + period: None, + read: None, + user_regs: None, + user_stack: None, + callchain: callchain_bytes.as_ref().map(|bytes| { + RawDataU64::from_raw_data::(RawData::Single(bytes)) + }), + phys_addr: None, + data_page_size: None, + code_page_size: None, + intr_regs: None, + cpu_mode, + }; + + let mut unwinder = UnwinderX86_64::::new(); + // Known code addresses keep root trimming from discarding valid FP frames. + unwinder.add_module(Module::new( + "test-module".to_string(), + 0x1000..0x2000, + 0x1000, + ExplicitModuleSectionInfo { + base_svma: 0, + text_svma: None, + text: None, + stubs_svma: None, + stub_helper_svma: None, + got_svma: None, + unwind_info: None, + eh_frame_svma: None, + eh_frame: None, + eh_frame_hdr_svma: None, + eh_frame_hdr: None, + debug_frame: None, + text_segment_svma: None, + text_segment: None, + }, + )); + let mut stack = Vec::new(); + Converter::>::get_sample_stack::( + &sample, + &unwinder, + &mut CacheX86_64::new(), + &mut std::collections::HashMap::new(), + &mut stack, + false, + false, + ); + stack + } + + #[test] + fn fp_only_callchain_preserves_user_frame_order_without_duplicating_sampled_ip() { + let stack = callchain_only_stack( + 0x1010, + CpuMode::User, + Some(&[PERF_CONTEXT_USER, 0x1010, 0x1020, 0x1030]), + ); + + assert_eq!( + stack, + vec![ + StackFrame::InstructionPointer(0x1010, StackMode::User), + StackFrame::ReturnAddress(0x1020, StackMode::User), + StackFrame::ReturnAddress(0x1030, StackMode::User), + ] + ); + } + + #[test] + fn fp_only_callchain_preserves_kernel_to_user_context_transition() { + let kernel_ip = 0xffff_8000_0000_0010; + let kernel_caller = 0xffff_8000_0000_0020; + let stack = callchain_only_stack( + kernel_ip, + CpuMode::Kernel, + Some(&[ + PERF_CONTEXT_KERNEL, + kernel_ip, + kernel_caller, + PERF_CONTEXT_USER, + 0x1010, + 0x1020, + ]), + ); + + assert_eq!( + stack, + vec![ + StackFrame::InstructionPointer(kernel_ip, StackMode::Kernel), + StackFrame::ReturnAddress(kernel_caller, StackMode::Kernel), + StackFrame::ReturnAddress(0x1010, StackMode::User), + StackFrame::ReturnAddress(0x1020, StackMode::User), + ] + ); + } + + #[test] + fn fp_only_leaf_callchain_keeps_one_sampled_ip() { + let stack = callchain_only_stack(0x1010, CpuMode::User, Some(&[PERF_CONTEXT_USER, 0x1010])); + + assert_eq!( + stack, + vec![StackFrame::InstructionPointer(0x1010, StackMode::User)] + ); + } + + #[test] + fn fp_only_empty_callchains_fall_back_to_sampled_ip() { + let callchains: [Option<&[u64]>; 3] = [None, Some(&[]), Some(&[PERF_CONTEXT_USER])]; + for callchain in callchains { + let stack = callchain_only_stack(0x1010, CpuMode::User, callchain); + + assert_eq!( + stack, + vec![StackFrame::InstructionPointer(0x1010, StackMode::User)], + "callchain: {callchain:?}" + ); + } + } + + #[test] + fn fp_only_unmapped_leaf_callchain_falls_back_to_sampled_ip() { + let stack = callchain_only_stack(0x9000, CpuMode::User, Some(&[PERF_CONTEXT_USER, 0x9000])); + + assert_eq!( + stack, + vec![StackFrame::InstructionPointer(0x9000, StackMode::User)] + ); + } +} diff --git a/samply/src/shared/prop_types.rs b/samply/src/shared/prop_types.rs index 4b901841e..ea3168987 100644 --- a/samply/src/shared/prop_types.rs +++ b/samply/src/shared/prop_types.rs @@ -25,6 +25,12 @@ impl CoreClrProfileProps { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)] +pub enum UnwindingMode { + Dwarf, + Fp, +} + /// Properties which are meaningful both for recording a fresh process /// as well as for recording an existing process. #[derive(Debug, Clone)] @@ -33,6 +39,8 @@ pub struct RecordingProps { pub output_file: PathBuf, pub time_limit: Option, pub interval: Duration, + #[allow(dead_code)] // Only used on Linux and Android + pub unwinding_mode: UnwindingMode, /// Names of extra perf events to record alongside the main sampling /// event. Only used on Linux. #[allow(dead_code)] From 22347cd5f4590b521ae95519e58b5bb9eabbecf5 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Tue, 13 Oct 2026 17:36:46 +0200 Subject: [PATCH 2/3] fix(mac): initialize task profilers before resuming processes Wait for the sampler to finish task initialization before releasing the preload handshake or resuming an attached task. Otherwise short-lived root processes can exit while their initial threads and libraries are still being read, causing the sampler to panic. Reuse one rendezvous channel for task initialization. Release the accepter after each initialization attempt, including failures, so processes do not remain suspended when sampling stops. --- samply/src/mac/profiler.rs | 17 +++++++++++++---- samply/src/mac/sampler.rs | 16 +++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/samply/src/mac/profiler.rs b/samply/src/mac/profiler.rs index 124cf02e2..cd714bc76 100644 --- a/samply/src/mac/profiler.rs +++ b/samply/src/mac/profiler.rs @@ -4,7 +4,7 @@ use std::process::ExitStatus; use std::thread; use std::time::Duration; -use crossbeam_channel::unbounded; +use crossbeam_channel::{bounded, unbounded}; use fxprof_processed_profile::Profile; use super::error::SamplingError; @@ -76,9 +76,15 @@ pub fn run( }; let (task_sender, task_receiver) = unbounded(); + let (task_initialized_sender, task_initialized_receiver) = bounded(0); let sampler_thread = thread::spawn(move || { - let sampler = Sampler::new(task_receiver, recording_props, profile_creation_props); + let sampler = Sampler::new( + task_receiver, + task_initialized_sender, + recording_props, + profile_creation_props, + ); sampler.run() }); @@ -108,8 +114,11 @@ pub fn run( path_receiver, })); path_senders_per_pid.insert(pid, path_sender); - if send_result.is_err() { - // The sampler has already shut down. This task arrived too late. + if send_result.is_ok() { + // Keep the task suspended until the sampler has read its + // initial threads and libraries; short-lived tasks can + // otherwise exit before TaskProfiler::new finishes. + task_initialized_receiver.recv().ok(); } accepted_task.start_execution(); } diff --git a/samply/src/mac/sampler.rs b/samply/src/mac/sampler.rs index 4ac1fdf26..c3a3d8b4c 100644 --- a/samply/src/mac/sampler.rs +++ b/samply/src/mac/sampler.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::time::{Duration, SystemTime}; use std::{mem, thread}; -use crossbeam_channel::Receiver; +use crossbeam_channel::{Receiver, Sender}; use fxprof_processed_profile::{ Category, CategoryColor, PlatformSpecificReferenceTimestamp, Profile, ReferenceTimestamp, SubcategoryHandle, @@ -41,6 +41,7 @@ pub struct TaskInit { pub struct Sampler { task_receiver: Receiver, + task_initialized_sender: Sender<()>, recording_props: Arc, profile_creation_props: Arc, } @@ -48,11 +49,13 @@ pub struct Sampler { impl Sampler { pub fn new( task_receiver: Receiver, + task_initialized_sender: Sender<()>, recording_props: RecordingProps, profile_creation_props: ProfileCreationProps, ) -> Self { Sampler { task_receiver, + task_initialized_sender, recording_props: Arc::new(recording_props), profile_creation_props: Arc::new(profile_creation_props), } @@ -110,8 +113,9 @@ impl Sampler { &mut profile, process_recycler.as_mut(), self.profile_creation_props.clone(), - ) - .expect("couldn't create root TaskProfiler"); + ); + self.task_initialized_sender.send(()).ok(); + let root_task = root_task.expect("couldn't create root TaskProfiler"); let mut process_sample_datas = Vec::new(); let mut stack_scratch_buffer = Vec::new(); @@ -144,14 +148,16 @@ impl Sampler { break; } }; - if let Ok(new_task) = TaskProfiler::new( + let new_task = TaskProfiler::new( task_init, timestamp_converter, &self.profile_creation_props.fallback_profile_name, &mut profile, process_recycler.as_mut(), self.profile_creation_props.clone(), - ) { + ); + self.task_initialized_sender.send(()).ok(); + if let Ok(new_task) = new_task { live_tasks.push(new_task); } else { // The task is probably already dead again. We get here for tasks which are From ad8f2861b7f71c285937385c8a66cacf064c027b Mon Sep 17 00:00:00 2001 From: not-matthias Date: Tue, 13 Oct 2026 17:45:51 +0200 Subject: [PATCH 3/3] feat(linux): add --unwinding-stack-size option The user stack copied per sample for DWARF unwinding was fixed at 32000 bytes. Expose it as --unwinding-stack-size (default unchanged) so callers can trade profile size for deeper stacks. Values are validated against the kernel's sample_stack_user constraints (8-byte aligned, below 65536). --- samply/src/cli.rs | 46 ++++++++++++++++++++++++++++++++- samply/src/linux/profiler.rs | 7 +++-- samply/src/shared/prop_types.rs | 6 +++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/samply/src/cli.rs b/samply/src/cli.rs index 6a2f34cc0..9b892c3c5 100644 --- a/samply/src/cli.rs +++ b/samply/src/cli.rs @@ -10,7 +10,7 @@ use super::server::{PortSelection, ServerProps}; use super::shared::included_processes::IncludedProcesses; use super::shared::prop_types::{ CoreClrProfileProps, ImportProps, ProcessLaunchProps, ProfileCreationProps, RecordingMode, - RecordingProps, SymbolProps, UnwindingMode, + RecordingProps, SymbolProps, UnwindingMode, DEFAULT_UNWINDING_STACK_SIZE, }; #[derive(Debug, Parser)] @@ -138,6 +138,12 @@ pub struct RecordArgs { #[arg(long, value_enum, default_value = "dwarf")] pub unwinding_mode: UnwindingMode, + /// Bytes of user stack copied with each sample for DWARF unwinding (Linux only). + /// Must be a multiple of 8 and below 65536. Ignored with `--unwinding-mode fp`. + #[cfg(target_os = "linux")] + #[arg(long, default_value_t = DEFAULT_UNWINDING_STACK_SIZE, value_parser = parse_unwinding_stack_size)] + pub unwinding_stack_size: u32, + /// Limit the recorded time to the specified number of seconds #[arg(short, long)] pub duration: Option, @@ -465,6 +471,10 @@ impl RecordArgs { unwinding_mode: self.unwinding_mode, #[cfg(not(target_os = "linux"))] unwinding_mode: UnwindingMode::Dwarf, + #[cfg(target_os = "linux")] + unwinding_stack_size: self.unwinding_stack_size, + #[cfg(not(target_os = "linux"))] + unwinding_stack_size: DEFAULT_UNWINDING_STACK_SIZE, perf_events, gfx: self.gfx, browsers: self.browsers, @@ -623,6 +633,17 @@ impl SymbolArgs { } } +/// The kernel rejects `sample_stack_user` values that are not 8-byte aligned +/// or do not fit in a `u16`. +#[cfg(target_os = "linux")] +fn parse_unwinding_stack_size(s: &str) -> Result { + let size: u32 = s.parse().map_err(|e| format!("{e}"))?; + if size == 0 || size % 8 != 0 || size > u32::from(u16::MAX) { + return Err("must be a non-zero multiple of 8 below 65536".to_string()); + } + Ok(size) +} + #[cfg(test)] mod test { use super::*; @@ -711,4 +732,27 @@ mod test { .unwrap_err(); assert_eq!(error.kind(), clap::error::ErrorKind::InvalidValue); } + + #[cfg(target_os = "linux")] + #[test] + fn record_unwinding_stack_size() { + let parse = |size: &str| { + Opt::try_parse_from([ + "samply", + "record", + "--unwinding-stack-size", + size, + "--pid", + "1234", + ]) + }; + let Action::Record(args) = parse("65528").unwrap().action else { + panic!("expected record action"); + }; + assert_eq!(args.unwinding_stack_size, 65528); + for invalid in ["0", "1001", "65536"] { + let error = parse(invalid).unwrap_err(); + assert_eq!(error.kind(), clap::error::ErrorKind::ValueValidation); + } + } } diff --git a/samply/src/linux/profiler.rs b/samply/src/linux/profiler.rs index e1286686d..9bf95f6d4 100644 --- a/samply/src/linux/profiler.rs +++ b/samply/src/linux/profiler.rs @@ -94,6 +94,7 @@ pub fn run( // Launch the observer thread. This thread will manage the perf events. let interval = recording_props.interval; let unwinding_mode = recording_props.unwinding_mode; + let unwinding_stack_size = recording_props.unwinding_stack_size; let time_limit = recording_props.time_limit; // Resolve the extra event names before launching anything, so that a // configuration error aborts immediately. @@ -121,6 +122,7 @@ pub fn run( let perf_group = init_profiler( interval, unwinding_mode, + unwinding_stack_size, pid, attach_mode, extra_events, @@ -292,6 +294,7 @@ fn start_profiling_pid( let perf_group = init_profiler( interval, recording_props.unwinding_mode, + recording_props.unwinding_stack_size, pid, attach_mode, extra_events, @@ -418,6 +421,7 @@ fn resolve_extra_events(specs: &[String]) -> Vec { fn init_profiler( interval: Duration, unwinding_mode: UnwindingMode, + unwinding_stack_size: u32, pid: u32, attach_mode: AttachMode, extra_events: Vec, @@ -432,9 +436,8 @@ fn init_profiler( }; let frequency = (1_000_000_000 / interval_nanos) as u32; - const DWARF_STACK_SIZE: u32 = 32_000; let (stack_size, regs_mask) = match unwinding_mode { - UnwindingMode::Dwarf => (DWARF_STACK_SIZE, ConvertRegsNative::regs_mask()), + UnwindingMode::Dwarf => (unwinding_stack_size, ConvertRegsNative::regs_mask()), UnwindingMode::Fp => (0, 0), }; diff --git a/samply/src/shared/prop_types.rs b/samply/src/shared/prop_types.rs index ea3168987..4018156c4 100644 --- a/samply/src/shared/prop_types.rs +++ b/samply/src/shared/prop_types.rs @@ -31,6 +31,9 @@ pub enum UnwindingMode { Fp, } +/// Default bytes of user stack copied per sample for DWARF unwinding. +pub const DEFAULT_UNWINDING_STACK_SIZE: u32 = 32_000; + /// Properties which are meaningful both for recording a fresh process /// as well as for recording an existing process. #[derive(Debug, Clone)] @@ -41,6 +44,9 @@ pub struct RecordingProps { pub interval: Duration, #[allow(dead_code)] // Only used on Linux and Android pub unwinding_mode: UnwindingMode, + /// Bytes of user stack copied per sample for DWARF unwinding. + #[allow(dead_code)] // Only used on Linux + pub unwinding_stack_size: u32, /// Names of extra perf events to record alongside the main sampling /// event. Only used on Linux. #[allow(dead_code)]