From 5852f775c2ffa55a24da469b0b73457ac3bc3fa6 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Tue, 16 Jun 2026 07:54:01 +0000 Subject: [PATCH] feat: expose Unwinder::is_address_in_module Add a public predicate reporting whether an address falls within any known module's range, wrapping the existing private find_module_for_address. Frame-pointer unwinding -- framehop's fallback for code without unwind info (e.g. Go runtime nosplit functions, whose binaries ship no .eh_frame), as well as external FP callchains -- can mistake a stack-local value for a saved return address, yielding a "frame" whose address points into no module. Callers had no way to detect this: find_module_for_address was private and the only public hint, max_known_code_address, is a loose upper bound that a small bogus value trivially passes. Exposing the precise check lets a caller (e.g. samply) trim such frames so they don't surface as unresolved root frames. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/aarch64/unwinder.rs | 4 ++++ src/unwinder.rs | 14 ++++++++++++++ src/x86_64/unwinder.rs | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/src/aarch64/unwinder.rs b/src/aarch64/unwinder.rs index e8fafb6..b89ae57 100644 --- a/src/aarch64/unwinder.rs +++ b/src/aarch64/unwinder.rs @@ -51,6 +51,10 @@ impl, P: AllocationPolicy> Unwinder for UnwinderAarch64< self.0.max_known_code_address() } + fn is_address_in_module(&self, address: u64) -> bool { + self.0.is_address_in_module(address) + } + fn unwind_frame( &self, address: FrameAddress, diff --git a/src/unwinder.rs b/src/unwinder.rs index c17b71a..cd712a0 100644 --- a/src/unwinder.rs +++ b/src/unwinder.rs @@ -60,6 +60,16 @@ pub trait Unwinder: Clone { /// to make an educated guess at a pointer authentication mask for Aarch64 return addresses. fn max_known_code_address(&self) -> u64; + /// Returns whether `address` falls within the address range of any known + /// module, i.e. whether it points into code we have a module for. + /// + /// A genuine code address (an instruction pointer or a return address) + /// always lies in a known module. This lets a caller reject frames produced + /// by unreliable unwinding — e.g. frame-pointer walks over code without + /// unwind info, which can mistake a stack-local value for a saved return + /// address — that would otherwise surface as unresolved root frames. + fn is_address_in_module(&self, address: u64) -> bool; + /// Unwind a single frame, to recover return address and caller register values. /// This is the main entry point for unwinding. fn unwind_frame( @@ -284,6 +294,10 @@ impl, A: Unwinding, P: AllocationPolicy> UnwinderInterna self.modules.last().map_or(0, |m| m.avma_range.end) } + pub fn is_address_in_module(&self, address: u64) -> bool { + self.find_module_for_address(address).is_some() + } + fn find_module_for_address(&self, address: u64) -> Option<(usize, u32)> { let (module_index, module) = match self .modules diff --git a/src/x86_64/unwinder.rs b/src/x86_64/unwinder.rs index bb98d60..fd7e0e9 100644 --- a/src/x86_64/unwinder.rs +++ b/src/x86_64/unwinder.rs @@ -53,6 +53,10 @@ impl, P: AllocationPolicy> Unwinder for UnwinderX86_64 bool { + self.0.is_address_in_module(address) + } + fn unwind_frame( &self, address: FrameAddress,