ptools 0.2.23

Utilities for inspecting Linux processes
//
//   Copyright (c) 2017 Steven Fackler
//
//   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 std::any::Any;
use std::borrow::Cow;
use std::ffi::CStr;
use std::panic::AssertUnwindSafe;
use std::panic::{self};
use std::ptr::null;
use std::ptr::{self};

use foreign_types::foreign_type;
use foreign_types::ForeignType;
use foreign_types::ForeignTypeRef;
use libc::c_int;
use libc::c_void;
use libc::pid_t;

use super::cvt;
use super::Callbacks;
use super::Error;
use super::FrameRef;
use super::ModuleRef;
use super::ThreadRef;

foreign_type! {
    /// The base type used when interacting with libdwfl.
    pub unsafe type Dwfl<'a> {
        type CType = crate::dw_sys::Dwfl;
        type PhantomData = &'a ();
        fn drop = crate::dw_sys::dwfl_end;
    }
}

impl<'a> Dwfl<'a> {
    /// Creates a new `Dwfl` which will use the specified callbacks.
    pub fn begin(callbacks: &'a Callbacks) -> Result<Dwfl<'a>, Error> {
        unsafe {
            let ptr = crate::dw_sys::dwfl_begin(callbacks.as_ptr());
            if ptr.is_null() {
                Err(Error::new())
            } else {
                Ok(Dwfl::from_ptr(ptr))
            }
        }
    }
}

impl<'a> DwflRef<'a> {
    /// Returns a string describing the version of libdw used.
    pub fn version(&self) -> Cow<'_, str> {
        unsafe {
            let p = crate::dw_sys::dwfl_version(self.as_ptr());
            CStr::from_ptr(p).to_string_lossy()
        }
    }

    /// Starts a "reporting" session used to register new segments and modules.
    ///
    /// Existing segments and modules will be removed.
    pub fn report(&mut self) -> Report<'_, 'a> {
        unsafe {
            crate::dw_sys::dwfl_report_begin(self.as_ptr());
            Report(self)
        }
    }

    /// Starts a "reporting" session used to register new segments and modules.
    ///
    /// Unlike the `report` method, this will not remove existing segments and modules.
    pub fn report_add(&mut self) -> Report<'_, 'a> {
        unsafe {
            crate::dw_sys::dwfl_report_begin_add(self.as_ptr());
            Report(self)
        }
    }

    /// Configures the session to unwind the threads of a remote process via ptrace and the `/proc` pseudo-filesystem.
    ///
    /// Normally, the session will ptrace attach to threads being unwound, but if `assume_ptrace_stopped` is set to
    /// `true`, this will not happen. It's then the responsibility of the caller to ensure that the thread is already
    /// attached and stopped.
    pub fn linux_proc_attach(
        &mut self,
        pid: u32,
        assume_ptrace_stopped: bool,
    ) -> Result<(), Error> {
        unsafe {
            cvt(crate::dw_sys::dwfl_linux_proc_attach(
                self.as_ptr(),
                pid as pid_t,
                assume_ptrace_stopped,
            ))
        }
    }

    /// Iterates through the threads of the attached process.
    ///
    /// The callback will be invoked for each thread in turn.
    pub fn threads<F>(&mut self, callback: F) -> Result<(), Error>
    where
        F: FnMut(&mut ThreadRef) -> Result<(), Error>,
    {
        unsafe {
            let mut state = ThreadsCallbackState {
                callback,
                panic: None,
                error: None,
            };
            let r = crate::dw_sys::dwfl_getthreads(
                self.as_ptr(),
                Some(threads_cb::<F>),
                &mut state as *mut _ as *mut c_void,
            );

            if let Some(payload) = state.panic {
                panic::resume_unwind(payload);
            }
            if let Some(error) = state.error {
                return Err(error);
            }

            cvt(r)
        }
    }

    /// Iterates through the frames of a specific thread of the attached process.
    ///
    /// The callback will be invoked for each stack frame of the thread in turn.
    pub fn thread_frames<F>(&mut self, tid: u32, callback: F) -> Result<(), Error>
    where
        F: FnMut(&mut FrameRef) -> Result<(), Error>,
    {
        unsafe {
            let mut state = FramesCallbackState {
                callback,
                panic: None,
                error: None,
            };
            let r = crate::dw_sys::dwfl_getthread_frames(
                self.as_ptr(),
                tid as pid_t,
                Some(frames_cb::<F>),
                &mut state as *mut _ as *mut c_void,
            );

            if let Some(payload) = state.panic {
                panic::resume_unwind(payload);
            }
            if let Some(e) = state.error {
                return Err(e);
            }

            cvt(r)
        }
    }

    /// Attaches to threads described in an ELF core file.
    ///
    /// # Safety
    ///
    /// The `elf` pointer must remain valid for the lifetime of the Dwfl session.
    pub unsafe fn core_file_attach(&mut self, elf: *mut crate::dw_sys::Elf) -> Result<(), Error> {
        let r = crate::dw_sys::dwfl_core_file_attach(self.as_ptr(), elf);
        if r < 0 {
            Err(Error::new())
        } else {
            Ok(())
        }
    }

    /// Returns the PID of the attached process or core dump.
    pub fn pid(&self) -> u32 {
        unsafe { crate::dw_sys::dwfl_pid(self.as_ptr()) as u32 }
    }

    /// Iterates through all modules in the session.
    ///
    /// The callback will be invoked for each module in turn.
    pub fn modules<F>(&mut self, mut callback: F) -> Result<(), Error>
    where
        F: FnMut(&ModuleRef) -> Result<(), Error>,
    {
        unsafe {
            let mut state = ModulesCallbackState {
                callback: &mut callback,
                panic: None,
                error: None,
            };
            let mut offset: libc::ptrdiff_t = 0;
            loop {
                offset = crate::dw_sys::dwfl_getmodules(
                    self.as_ptr(),
                    Some(modules_cb),
                    &mut state as *mut _ as *mut c_void,
                    offset,
                );
                if let Some(payload) = state.panic.take() {
                    panic::resume_unwind(payload);
                }
                if let Some(error) = state.error.take() {
                    return Err(error);
                }
                if offset <= 0 {
                    break;
                }
            }
            Ok(())
        }
    }

    /// Looks up the module containing the address.
    pub fn addr_module(&self, address: u64) -> Result<&ModuleRef, Error> {
        unsafe {
            let ptr = crate::dw_sys::dwfl_addrmodule(self.as_ptr(), address);
            if ptr.is_null() {
                Err(Error::new())
            } else {
                Ok(ModuleRef::from_ptr(ptr))
            }
        }
    }
}

/// A type used to register segments and modules with a DWFL session.
pub struct Report<'a, 'b>(&'a mut DwflRef<'b>);

impl<'a, 'b> Drop for Report<'a, 'b> {
    fn drop(&mut self) {
        unsafe {
            crate::dw_sys::dwfl_report_end(self.0.as_ptr(), None, ptr::null_mut());
        }
    }
}

impl<'a, 'b> Report<'a, 'b> {
    /// Uses the `/proc` pseudo-filesystem to register the information for a specific running process.
    ///
    /// The `FindElf::LINUX_PROC` callback should be used with this method.
    pub fn linux_proc(&mut self, pid: u32) -> Result<(), Error> {
        unsafe {
            cvt(crate::dw_sys::dwfl_linux_proc_report(
                self.0.as_ptr(),
                pid as pid_t,
            ))
        }
    }

    /// Registers modules from an ELF core file.
    ///
    /// The `FindElf::BUILD_ID` callback should be used with this method.
    ///
    /// # Safety
    ///
    /// The `elf` pointer must remain valid for the lifetime of the Dwfl session.
    pub unsafe fn core_file(&mut self, elf: *mut crate::dw_sys::Elf) -> Result<(), Error> {
        let r = crate::dw_sys::dwfl_core_file_report(self.0.as_ptr(), elf, null());
        if r < 0 {
            Err(Error::new())
        } else {
            Ok(())
        }
    }
}

struct ModulesCallbackState<'a> {
    callback: &'a mut dyn FnMut(&ModuleRef) -> Result<(), Error>,
    panic: Option<Box<dyn Any + Send>>,
    error: Option<Error>,
}

unsafe extern "C" fn modules_cb(
    module: *mut crate::dw_sys::Dwfl_Module,
    _userdata: *mut *mut c_void,
    _name: *const libc::c_char,
    _addr: crate::dw_sys::Dwarf_Addr,
    arg: *mut c_void,
) -> c_int {
    let state = &mut *(arg as *mut ModulesCallbackState);
    let module = ModuleRef::from_ptr(module);

    match panic::catch_unwind(AssertUnwindSafe(|| (state.callback)(module))) {
        Ok(Ok(())) => crate::dw_sys::DWARF_CB_OK,
        Ok(Err(e)) => {
            state.error = Some(e);
            crate::dw_sys::DWARF_CB_ABORT
        }
        Err(e) => {
            state.panic = Some(e);
            crate::dw_sys::DWARF_CB_ABORT
        }
    }
}

struct ThreadsCallbackState<F> {
    callback: F,
    panic: Option<Box<dyn Any + Send>>,
    error: Option<Error>,
}

unsafe extern "C" fn threads_cb<F>(
    thread: *mut crate::dw_sys::Dwfl_Thread,
    arg: *mut c_void,
) -> c_int
where
    F: FnMut(&mut ThreadRef) -> Result<(), Error>,
{
    let state = &mut *(arg as *mut ThreadsCallbackState<F>);
    let thread = ThreadRef::from_ptr_mut(thread);

    match panic::catch_unwind(AssertUnwindSafe(|| (state.callback)(thread))) {
        Ok(Ok(())) => crate::dw_sys::DWARF_CB_OK,
        Ok(Err(e)) => {
            state.error = Some(e);
            crate::dw_sys::DWARF_CB_ABORT
        }
        Err(e) => {
            state.panic = Some(e);
            crate::dw_sys::DWARF_CB_ABORT
        }
    }
}

struct FramesCallbackState<F> {
    callback: F,
    panic: Option<Box<dyn Any + Send>>,
    error: Option<Error>,
}

unsafe extern "C" fn frames_cb<F>(frame: *mut crate::dw_sys::Dwfl_Frame, arg: *mut c_void) -> c_int
where
    F: FnMut(&mut FrameRef) -> Result<(), Error>,
{
    let state = &mut *(arg as *mut FramesCallbackState<F>);
    let frame = FrameRef::from_ptr_mut(frame);

    match panic::catch_unwind(AssertUnwindSafe(|| (state.callback)(frame))) {
        Ok(Ok(())) => crate::dw_sys::DWARF_CB_OK,
        Ok(Err(e)) => {
            state.error = Some(e);
            crate::dw_sys::DWARF_CB_ABORT
        }
        Err(e) => {
            state.panic = Some(e);
            crate::dw_sys::DWARF_CB_ABORT
        }
    }
}