8000 Add threads and thread enumeration · Pull Request #213 · frida/frida-rust · GitHub
[go: up one dir, main page]

Skip to content
8000
Merged
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
1 change: 1 addition & 0 deletions frida-gum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ spin = { version = "0.10", default-features = false, features = [
"mutex",
"spin_mutex",
] }
bitflags = "2.9.1"

[dev-dependencies]
lazy_static = "1"
Expand Down
3 changes: 3 additions & 0 deletions frida-gum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ pub use range_details::*;
mod debug_symbol;
pub use debug_symbol::*;

mod thread;
pub use thread::*;

#[cfg(feature = "backtrace")]
#[cfg_attr(docsrs, doc(cfg(feature = "backtrace")))]
mod backtracer;
8000 Expand Down
30 changes: 29 additions & 1 deletion frida-gum/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
allow(clippy::unnecessary_cast)
)]

use crate::{FileMapping, Module, NativePointer};
use crate::{FileMapping, Module, NativePointer, Thread};

use {
crate::{Gum, PageProtection, RangeDetails},
Expand All @@ -18,6 +18,7 @@ use {
#[cfg(not(feature = "std"))]
use alloc::{string::String, string::ToString, vec::Vec};
use cstr_core::CString;
use frida_gum_sys::GumThreadFlags_GUM_THREAD_FLAGS_ALL;

#[cfg(target_os = "linux")]
extern "C" {
Expand Down Expand Up @@ -242,4 +243,31 @@ impl<'a> Process<'a> {

callback_data.modules
}

/// Enumerates process threads. May crash the application if called too early (if there are no non-frida threads)
pub fn enumerate_threads(&self) -> Vec<Thread> {
unsafe extern "C" fn enumerate_threads_callback(
details: *const gum_sys::GumThreadDetails,
user_data: gpointer,
) -> gboolean {
let res = &mut *(user_data as *mut Vec<Thread>);
res.push(Thread::from_raw(details));

// if this value is zero, the iteration ends
// subprojects/frida-gum/gum/gumthreadregistry.c
1
}

let callback_data = Vec::new();

unsafe {
gum_sys::gum_process_enumerate_threads(
Some(enumerate_threads_callback),
&callback_data as *const _ as *mut c_void,
GumThreadFlags_GUM_THREAD_FLAGS_ALL,
)
};

callback_data
}
}
124 changes: 124 additions & 0 deletions frida-gum/src/thread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use core::ffi::CStr;
use core::fmt::{self, Debug};

use bitflags::bitflags;
use frida_gum_sys::{
self as gum_sys, GumCpuContext, GumThreadFlags, GumThreadFlags_GUM_THREAD_FLAGS_ALL,
GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT,
GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_PARAMETER,
GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_ROUTINE, GumThreadFlags_GUM_THREAD_FLAGS_NAME,
GumThreadFlags_GUM_THREAD_FLAGS_NONE, GumThreadFlags_GUM_THREAD_FLAGS_STATE, GumThreadId,
GumThreadState_GUM_THREAD_HALTED, GumThreadState_GUM_THREAD_STOPPED,
GumThreadState_GUM_THREAD_UNINTERRUPTIBLE, GumThreadState_GUM_THREAD_WAITING,
};
use frida_gum_sys::{GumThreadDetails, GumThreadState_GUM_THREAD_RUNNING};
use num::FromPrimitive;

#[cfg(feature = "backtrace")]
use crate::Backtracer;
use crate::{CpuContext, NativePointer};

bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ThreadFlags: GumThreadFlags {
const NAME = GumThreadFlags_GUM_THREAD_FLAGS_NAME;
const STATE = GumThreadFlags_GUM_THREAD_FLAGS_STATE;
const CPU_CONTEXT = GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT;
const ENTRYPOINT_ROUTINE = GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_ROUTINE;
const ENTRYPOINT_PARAMETER = GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_PARAMETER;

const NONE = GumThreadFlags_GUM_THREAD_FLAGS_NONE;
const ALL = GumThreadFlags_GUM_THREAD_FLAGS_ALL;
}
}

#[derive(Debug, FromPrimitive)]
#[repr(u32)] // Repr GumThreadState (c_uint) not available
pub enum ThreadState {
Running = GumThreadState_GUM_THREAD_RUNNING as _,
Stopped = GumThreadState_GUM_THREAD_STOPPED as _,
Waiting = GumThreadState_GUM_THREAD_WAITING as _,
Uninterruptible = GumThreadState_GUM_THREAD_UNINTERRUPTIBLE as _,
Halted = GumThreadState_GUM_THREAD_HALTED as _,
}

#[derive(Debug)]
pub struct Entrypoint {
pub routine: NativePointer,
pub parameter: NativePointer,
}

pub struct Thread {
thread: *mut GumThreadDetails,
}

impl Thread {
pub(crate) fn from_raw(thread: *const GumThreadDetails) -> Self {
// I'm not sure if copying the threads details is needed, but it looks like that threads are not refcounted, so
// there is no guarantee that they won't be freed
let thread = unsafe { gum_sys::gum_thread_details_copy(thread) };

Self { thread }
}

pub fn flags(&self) -> ThreadFlags {
ThreadFlags::from_bits_truncate((unsafe { &*self.thread }).flags)
}

pub fn id(&self) -> GumThreadId {
unsafe { &*self.thread }.id
}

pub fn name(&self) -> &CStr {
unsafe { CStr::from_ptr(({ *self.thread }).name) }
}

pub fn state(&self) -> ThreadState {
ThreadState::from_u32(unsafe { ({ *self.thread }).state } as _).unwrap()
}

fn gum_cpu_context(&self) -> &GumCpuContext {
unsafe { &(*self.thread).cpu_context }
}

// todo: return an immutable reference instead of an owned object; without this multiple threads can obtain a Process instance,
// get the same CpuContexts and cause a data race
/// Modifying CpuContexts from multiple threads is unsound, as they may share the same pointer
pub fn cpu_context(&self) -> CpuContext<'_> {
CpuContext::from_raw(self.gum_cpu_context() as *const _ as *mut GumCpuContext)
}

pub fn entrypoint(&self) -> Entrypoint {
let entrypoint = unsafe { &(*self.thread).entrypoint };
Entrypoint {
routine: NativePointer(entrypoint.routine as *mut _),
parameter: NativePointer(entrypoint.parameter as *mut _),
}
}

#[cfg(feature = "backtrace")]
pub fn backtrace_accurate(&self) -> Vec<usize> {
Backtracer::accurate_with_context(self.gum_cpu_context())
}

#[cfg(feature = "backtrace")]
pub fn backtrace_fuzzy(&self) -> Vec<usize> {
Backtracer::fuzzy_with_context(self.gum_cpu_context())
}
}

impl Drop for Thread {
fn drop(&mut self) {
unsafe { gum_sys::gum_thread_details_free(self.thread) };
}
}

impl Debug for Thread {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Thread")
.field("id", &self.id())
.field("name", &self.name())
.field("state", &self.state())
.finish_non_exhaustive()
}
}
0