Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/dw/dwfl/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ pub struct FindElf(
impl FindElf {
/// A standard callback used with `Register::linux_proc`.
pub const LINUX_PROC: FindElf = FindElf(crate::dw_sys::dwfl_linux_proc_find_elf);

/// A callback that caches Elf lookups via a `ProcessTracker`.
pub const TRACKER_LINUX_PROC: FindElf =
FindElf(crate::dw_sys::dwflst_tracker_linux_proc_find_elf);
}

/// The callback responsible for locating the debuginfo of a process.
Expand Down
2 changes: 2 additions & 0 deletions src/dw/dwfl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ pub use self::frame::*;
pub use self::handle::*;
pub use self::module::*;
pub use self::thread::*;
pub use self::tracker::*;

mod callbacks;
mod error;
mod frame;
mod handle;
mod module;
mod thread;
mod tracker;

fn cvt(r: c_int) -> Result<(), Error> {
if r == 0 {
Expand Down
114 changes: 114 additions & 0 deletions src/dw/dwfl/tracker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// Copyright (c) 2026 Basil Crow
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use foreign_types::ForeignTypeRef;
use libc::pid_t;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::ptr;

use super::{cvt, Callbacks, DwflRef, Error};

/// A process tracker that caches Elf data across multiple libdwfl sessions.
///
/// The callbacks must outlive the tracker, as libdwfl stores a pointer to them.
pub struct ProcessTracker<'a> {
ptr: *mut crate::dw_sys::Dwflst_Process_Tracker,
_callbacks: PhantomData<&'a Callbacks>,
}

impl<'a> ProcessTracker<'a> {
/// Creates a new process tracker with the specified callbacks.
///
/// The callbacks must remain at a stable address for the lifetime of the
/// tracker (e.g. a `static` or `LazyLock`).
pub fn new(callbacks: &'a Callbacks) -> Result<ProcessTracker<'a>, Error> {
unsafe {
let ptr = crate::dw_sys::dwflst_tracker_begin(callbacks.as_ptr());
if ptr.is_null() {
Err(Error::new())
} else {
Ok(ProcessTracker {
ptr,
_callbacks: PhantomData,
})
}
}
}

/// Creates a Dwfl for the given PID, reports its modules, ends the report,
/// and attaches to the process.
pub fn attach_process(&self, pid: u32) -> Result<TrackerDwfl<'_>, Error> {
unsafe {
let dwfl = crate::dw_sys::dwflst_tracker_dwfl_begin(self.ptr);
if dwfl.is_null() {
return Err(Error::new());
}

let r = crate::dw_sys::dwfl_linux_proc_report(dwfl, pid as pid_t);
if r < 0 {
return Err(Error::new());
}

let r = crate::dw_sys::dwfl_report_end(dwfl, None, ptr::null_mut());
if r != 0 {
return Err(Error::new());
}

cvt(crate::dw_sys::dwfl_linux_proc_attach(
dwfl,
pid as pid_t,
false,
))?;

Ok(TrackerDwfl {
ptr: dwfl,
_marker: PhantomData,
})
}
}
}

impl Drop for ProcessTracker<'_> {
fn drop(&mut self) {
unsafe {
crate::dw_sys::dwflst_tracker_end(self.ptr);
}
}
}

/// A Dwfl session owned by a `ProcessTracker`.
///
/// Unlike `Dwfl`, this is not independently freed — it is cleaned up when the
/// owning tracker is dropped.
pub struct TrackerDwfl<'a> {
ptr: *mut crate::dw_sys::Dwfl,
_marker: PhantomData<&'a ProcessTracker<'a>>,
}

impl<'a> Deref for TrackerDwfl<'a> {
type Target = DwflRef<'a>;

fn deref(&self) -> &DwflRef<'a> {
unsafe { DwflRef::from_ptr(self.ptr) }
}
}

impl<'a> DerefMut for TrackerDwfl<'a> {
fn deref_mut(&mut self) -> &mut DwflRef<'a> {
unsafe { DwflRef::from_ptr_mut(self.ptr) }
}
}
52 changes: 52 additions & 0 deletions src/dw_sys/elfutils/libdwfl_stacktrace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Copyright (c) 2026 Basil Crow
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use libc::{c_char, c_int, c_void, pid_t};

use super::super::*;
use super::libdwfl::{Dwfl, Dwfl_Callbacks, Dwfl_Module};

pub enum Dwflst_Process_Tracker {}

extern "C" {
pub fn dwflst_tracker_begin(callbacks: *const Dwfl_Callbacks) -> *mut Dwflst_Process_Tracker;

pub fn dwflst_tracker_dwfl_begin(tracker: *mut Dwflst_Process_Tracker) -> *mut Dwfl;

pub fn dwflst_tracker_find_pid(
tracker: *mut Dwflst_Process_Tracker,
pid: pid_t,
callback: Option<
unsafe extern "C" fn(
tracker: *mut Dwflst_Process_Tracker,
pid: pid_t,
arg: *mut c_void,
) -> *mut Dwfl,
>,
arg: *mut c_void,
) -> *mut Dwfl;

pub fn dwflst_tracker_linux_proc_find_elf(
mod_: *mut Dwfl_Module,
userdata: *mut *mut c_void,
module_name: *const c_char,
base: Dwarf_Addr,
file_name: *mut *mut c_char,
elfp: *mut *mut Elf,
) -> c_int;

pub fn dwflst_tracker_end(tracker: *mut Dwflst_Process_Tracker);
}
2 changes: 2 additions & 0 deletions src/dw_sys/elfutils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
pub use self::gelf::*;
pub use self::libdw::*;
pub use self::libdwfl::*;
pub use self::libdwfl_stacktrace::*;

mod gelf;
mod libdw;
mod libdwelf;
mod libdwfl;
mod libdwfl_stacktrace;
99 changes: 57 additions & 42 deletions src/stack/dw.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2017 Steven Fackler
// Copyright (c) 2026 Basil Crow
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -15,62 +16,76 @@
//

pub use crate::dw::dwfl::Error;
use crate::dw::dwfl::{Callbacks, Dwfl, FindDebuginfo, FindElf};
use crate::dw::dwfl::{Callbacks, FindDebuginfo, FindElf, ProcessTracker};
use std::fs;
use std::sync::LazyLock;

use super::{Frame, Symbol, TraceOptions, TracedThread};
use super::{Frame, Symbol, Thread, TraceOptions};

static CALLBACKS: LazyLock<Callbacks> =
LazyLock::new(|| Callbacks::new(FindElf::LINUX_PROC, FindDebuginfo::STANDARD));
LazyLock::new(|| Callbacks::new(FindElf::TRACKER_LINUX_PROC, FindDebuginfo::STANDARD));

pub struct State(Dwfl<'static>);
pub struct State(ProcessTracker<'static>);

impl State {
pub fn new(pid: u32) -> Result<State, Error> {
let mut dwfl = Dwfl::begin(&CALLBACKS)?;
dwfl.report().linux_proc(pid)?;
dwfl.linux_proc_attach(pid, true)?;
Ok(State(dwfl))
pub fn new() -> Result<State, Error> {
Ok(State(ProcessTracker::new(&CALLBACKS)?))
}
}

impl TracedThread {
pub fn dump_inner(
&self,
dwfl: &mut State,
options: &TraceOptions,
frames: &mut Vec<Frame>,
) -> Result<(), Error> {
dwfl.0.thread_frames(self.id, |frame| {
let mut is_signal = false;
let ip = frame.pc(Some(&mut is_signal))?;
pub fn trace(&self, pid: u32, options: &TraceOptions) -> Result<Vec<Thread>, Error> {
let mut dwfl = self.0.attach_process(pid)?;
let mut threads = vec![];

dwfl.threads(|thread| {
let tid = thread.tid();
let name = if options.thread_names {
let path = format!("/proc/{}/task/{}/comm", pid, tid);
fs::read_to_string(&path).ok().map(|s| s.trim().to_string())
} else {
None
};

let mut symbol = None;
if options.symbols {
let signal_adjust = if is_signal { 0 } else { 1 };
let mut frames = vec![];
thread.frames(|frame| {
let mut is_signal = false;
let ip = frame.pc(Some(&mut is_signal))?;

if let Ok(i) = frame
.thread()
.dwfl()
.addr_module(ip - signal_adjust)
.and_then(|module| module.addr_info(ip - signal_adjust))
{
symbol = Some(Symbol {
name: i.name().to_string_lossy().into_owned(),
offset: i.offset() + signal_adjust,
address: i.bias() + i.symbol().value(),
size: i.symbol().size(),
});
let mut symbol = None;
if options.symbols {
let signal_adjust = if is_signal { 0 } else { 1 };

if let Ok(i) = frame
.thread()
.dwfl()
.addr_module(ip - signal_adjust)
.and_then(|module| module.addr_info(ip - signal_adjust))
{
symbol = Some(Symbol {
name: i.name().to_string_lossy().into_owned(),
offset: i.offset() + signal_adjust,
address: i.bias() + i.symbol().value(),
size: i.symbol().size(),
});
}
}
}

frames.push(Frame {
ip,
is_signal,
symbol,
});
frames.push(Frame {
ip,
is_signal,
symbol,
});

Ok(())
})?;

threads.push(Thread {
id: tid,
name,
frames,
});
Ok(())
})
})?;

Ok(threads)
}
}
Loading
Loading