Skip to content

Commit

Permalink
Add methods for returning thread activity
Browse files Browse the repository at this point in the history
As part of #92, this adds methods
to determine if a thread is idle. This also refactors threads into a struct
with common methods across operating systems (id, active, lock etc).
  • Loading branch information
benfred committed Mar 3, 2019
1 parent 3222375 commit ddc3b12
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 171 deletions.
2 changes: 1 addition & 1 deletion remoteprocess/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
remoteproces
remoteprocess
=====

This crate provides a cross platform way of querying information about other processes running on
Expand Down
25 changes: 7 additions & 18 deletions remoteprocess/examples/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,18 @@ extern crate nix;
fn get_backtrace(pid: remoteprocess::Pid) -> Result<(), remoteprocess::Error> {
// Create a new handle to the process
let process = remoteprocess::Process::new(pid)?;

// lock the process to get a consistent snapshot. Unwinding will fail otherwise
let _lock = process.lock()?;

// Create a stack unwind object, and use it to get the stack for each thread
let unwinder = process.unwinder()?;
for (i, thread) in process.threads()?.iter().enumerate() {
let thread = *thread;
#[cfg(unix)]
println!("Thread {} ({})", i, thread);

// TODO: normalize this using GetThreadId or something
#[cfg(windows)]
println!("Thread {}", i);
for thread in process.threads()?.iter() {
println!("Thread 0x{:0x} - {}", thread.id()?, if thread.active()? { "running" } else { "idle" });

/* TODO: cross pross thread status
let threadid = get_thread_identifier_info(thread)?;
let threadstatus = get_thread_basic_info(thread)?;
println!("status: {:?} id {:?}", threadstatus, threadid);
*/
// lock the thread to get a consistent snapshot (unwinding will fail otherwise)
// Note: the thread will appear idle when locked, so wee are calling
// thread.active() before this
let _lock = thread.lock()?;

// Iterate over the callstack for the current thread
for ip in unwinder.cursor(thread)? {
for ip in unwinder.cursor(&thread)? {
let ip = ip?;

// Lookup the current stack frame containing a filename/function/linenumber etc
Expand Down
4 changes: 3 additions & 1 deletion remoteprocess/examples/validate_libunwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ fn libunwind_compare(pid: remoteprocess::Pid) -> Result<(), remoteprocess::Error

let _lock = process.lock()?;

let mut gimli_cursor = unwinder.cursor(nix::unistd::Pid::from_raw(pid))?;
let thread = remoteprocess::Thread::new(pid);

let mut gimli_cursor = unwinder.cursor(&thread)?;
let mut libunwind_cursor = libunwinder.cursor(pid)?;

loop {
Expand Down
2 changes: 1 addition & 1 deletion remoteprocess/src/dwarf_unwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl UnwindInfo {
#[cfg(target_os="macos")]
fn get_fde(&self, pc: u64) -> gimli::Result<&FrameDescriptionEntry> {
// Binary search frame description table to get the FDE on osx
if self.frame_descriptions.len() == 0 {
if self.frame_descriptions.is_empty() {
return Err(gimli::Error::NoUnwindInfoForAddress);
}
let fde = match self.frame_descriptions.binary_search_by(|e| e.0.cmp(&pc)) {
Expand Down
23 changes: 3 additions & 20 deletions remoteprocess/src/linux/gimli_unwinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ use std::rc::Rc;
use std::cell::RefCell;
use std::collections::BTreeMap;

use libc::c_void;
use read_process_memory::{TryIntoProcessHandle, ProcessHandle, copy_address};
use goblin::Object;
use goblin::error::Error as GoblinError;
use memmap::Mmap;
use nix;
use nix::sys::ptrace;
use proc_maps;

use gimli::{EhFrame, BaseAddresses, Pointer, NativeEndian, EhFrameHdr};
Expand All @@ -25,7 +22,7 @@ use dwarf_unwind::{UnwindInfo, Registers};

use linux::symbolication::{SymbolData};
use super::super::StackFrame;
use super::{Tid, Pid};
use super::{Pid, Thread};

pub struct Unwinder {
binaries: BTreeMap<u64, BinaryInfo>,
Expand Down Expand Up @@ -165,9 +162,8 @@ impl Unwinder {
Ok(())
}

pub fn cursor(&self, tid: Tid) -> Result<Cursor, nix::Error> {
let registers = get_regs(tid)?;
Ok(Cursor{registers, parent: self, initial_frame: true})
pub fn cursor(&self, thread: &Thread) -> Result<Cursor, Error> {
Ok(Cursor{registers: thread.registers()?, parent: self, initial_frame: true})
}

pub fn symbolicate(&self, addr: u64, callback: &mut FnMut(&StackFrame)) -> gimli::Result<()> {
Expand Down Expand Up @@ -272,16 +268,3 @@ impl BinaryInfo {
addr >= self.address && addr < (self.address + self.size)
}
}

fn get_regs(pid: nix::unistd::Pid) -> Result<Registers, nix::Error> {
unsafe {
let mut data: Registers = std::mem::zeroed();
// nix has marked this as deprecated (in favour of specific functions like attach)
// but hasn't yet exposed PTRACE_GETREGS as it's own function
#[allow(deprecated)]
ptrace::ptrace(ptrace::Request::PTRACE_GETREGS, pid,
std::ptr::null_mut(),
&mut data as *mut _ as * mut c_void)?;
Ok(data)
}
}
86 changes: 76 additions & 10 deletions remoteprocess/src/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
pub mod libunwind;
mod gimli_unwinder;
mod symbolication;
use libc::c_void;

use goblin::error::Error as GoblinError;

use nix::{self, sys::wait, {sched::{setns, CloneFlags}}};
use nix::{self, sys::wait, sys::ptrace, {sched::{setns, CloneFlags}}};
use std::io::Read;
use std::os::unix::io::AsRawFd;
use std::fs::File;

use dwarf_unwind::Registers;
use super::Error;
pub use self::gimli_unwinder::*;
pub use self::symbolication::*;
pub use self::libunwind::{LibUnwind};

pub use read_process_memory::{Pid, ProcessHandle};
pub use nix::unistd::Pid as Tid;

pub struct Process {
pub pid: Pid,
}

#[derive(Eq, PartialEq, Hash, Copy, Clone)]
pub struct Thread {
tid: nix::unistd::Pid
}

impl Process {
pub fn new(pid: Pid) -> Result<Process, Error> {
Ok(Process{pid})
Expand Down Expand Up @@ -48,9 +55,10 @@ impl Process {
// created
while !done {
done = true;
for threadid in self.threads()? {
for thread in self.threads()? {
let threadid = thread.id()?;
if !locked.contains(&threadid) {
locks.push(ThreadLock::new(threadid).map_err(|e| Error::Other(format!("Failed to lock {:?}", e)))?);
locks.push(thread.lock()?);
locked.insert(threadid);
done = false;
}
Expand All @@ -60,7 +68,7 @@ impl Process {
Ok(Lock{locks})
}

pub fn threads(&self) -> Result<Vec<Tid>, Error> {
pub fn threads(&self) -> Result<Vec<Thread>, Error> {
let mut ret = Vec::new();
let path = format!("/proc/{}/task", self.pid);
let tasks = std::fs::read_dir(path)?;
Expand All @@ -73,7 +81,7 @@ impl Process {
};

if let Ok(threadid) = thread.parse::<i32>() {
ret.push(Tid::from_raw(threadid));
ret.push(Thread{tid: nix::unistd::Pid::from_raw(threadid)});
}
}
Ok(ret)
Expand All @@ -84,6 +92,43 @@ impl Process {
}
}

impl Thread {
pub fn new(threadid: i32) -> Thread{
Thread{tid: nix::unistd::Pid::from_raw(threadid)}
}

pub fn lock(&self) -> Result<ThreadLock, Error> {
Ok(ThreadLock::new(self.tid)?)
}

pub fn registers(&self) -> Result<Registers, Error> {
unsafe {
let mut data: Registers = std::mem::zeroed();
// nix has marked this as deprecated (in favour of specific functions like attach)
// but hasn't yet exposed PTRACE_GETREGS as it's own function
#[allow(deprecated)]
ptrace::ptrace(ptrace::Request::PTRACE_GETREGS, self.tid,
std::ptr::null_mut(),
&mut data as *mut _ as * mut c_void)?;
Ok(data)
}
}

pub fn id(&self) -> Result<u64, Error> {
Ok(self.tid.as_raw() as u64)
}

pub fn active(&self) -> Result<bool, Error> {
let mut file = File::open(format!("/proc/{}/stat", self.tid))?;

let mut buf=[0u8; 512];
file.read(&mut buf)?;
match get_active_status(&buf) {
Some(stat) => Ok(stat == b'R'),
None => Err(Error::Other(format!("Failed to parse /proc/{}/stat", self.tid)))
}
}
}

/// This locks a target process using ptrace, and prevents it from running while this
/// struct is alive
Expand All @@ -92,12 +137,12 @@ pub struct Lock {
locks: Vec<ThreadLock>
}

struct ThreadLock {
tid: Tid
pub struct ThreadLock {
tid: nix::unistd::Pid
}

impl ThreadLock {
fn new(tid: Tid) -> Result<ThreadLock, nix::Error> {
fn new(tid: nix::unistd::Pid) -> Result<ThreadLock, nix::Error> {
nix::sys::ptrace::attach(tid)?;
wait::waitpid(tid, Some(wait::WaitPidFlag::WSTOPPED))?;
debug!("attached to thread {}", tid);
Expand Down Expand Up @@ -144,4 +189,25 @@ impl Drop for Namespace {
info!("Restored process namespace");
}
}
}
}

fn get_active_status(stat: &[u8]) -> Option<u8> {
// find the first ')' character, and return the active status
// field which comes after it
let mut iter = stat.iter().skip_while(|x| **x != b')');
match (iter.next(), iter.next(), iter.next()) {
(Some(b')'), Some(b' '), ret) => ret.map(|x| *x),
_ => None
}
}

#[test]
fn test_parse_stat() {
assert_eq!(get_active_status(b"1234 (bash) S 1233"), Some(b'S'));
assert_eq!(get_active_status(b"1234 (with space) R 1233"), Some(b'R'));
assert_eq!(get_active_status(b"1234"), None);
assert_eq!(get_active_status(b")"), None);
assert_eq!(get_active_status(b")))"), None);
assert_eq!(get_active_status(b"1234 (bash)S"), None);
assert_eq!(get_active_status(b"1234)SSSS"), None);
}
Loading

0 comments on commit ddc3b12

Please sign in to comment.