Skip to content

Commit

Permalink
Add implementation of PTRACE_{GET,SET}REGSET
Browse files Browse the repository at this point in the history
Also added `PTRACE_{GET,SET}REGS` for most platforms other than x86
using aforementioned implementation.
  • Loading branch information
hack3ric committed Mar 31, 2024
1 parent 4ab23c3 commit f605fba
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 6 deletions.
115 changes: 113 additions & 2 deletions src/sys/ptrace/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ pub type AddressType = *mut ::libc::c_void;
target_arch = "x86_64",
any(target_env = "gnu", target_env = "musl")
),
all(target_arch = "x86", target_env = "gnu")
)
all(target_arch = "x86", target_env = "gnu"),
all(target_arch = "aarch64", target_env = "gnu"),
all(target_arch = "riscv64", target_env = "gnu"),
),
))]
use libc::user_regs_struct;

Expand Down Expand Up @@ -170,6 +172,29 @@ libc_enum! {
}
}

libc_enum! {
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
#[repr(i32)]
/// Defining a specific register set, as used in [`getregset`] and [`setregset`].
#[non_exhaustive]
pub enum RegisterSet {
NT_PRSTATUS,
NT_PRFPREG,
NT_PRPSINFO,
NT_TASKSTRUCT,
NT_AUXV,
}
}

libc_bitflags! {
/// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request.
/// See `man ptrace` for more details.
Expand Down Expand Up @@ -231,6 +256,48 @@ pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
ptrace_get_data::<user_regs_struct>(Request::PTRACE_GETREGS, pid)
}

/// Get user registers, as with `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)`
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
getregset(pid, RegisterSet::NT_PRSTATUS)
}

/// Get a particular set of user registers, as with `ptrace(PTRACE_GETREGSET, ...)`
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
pub fn getregset(pid: Pid, set: RegisterSet) -> Result<user_regs_struct> {
let request = Request::PTRACE_GETREGSET;
let mut data = mem::MaybeUninit::<user_regs_struct>::uninit();
let mut iov = libc::iovec {
iov_base: data.as_mut_ptr() as *mut _,
iov_len: mem::size_of::<user_regs_struct>(),
};
unsafe {
ptrace_other(
request,
pid,
set as i32 as AddressType,
&mut iov as *mut _ as *mut c_void,
)?;
};
Ok(unsafe { data.assume_init() })
}

/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)`
#[cfg(all(
target_os = "linux",
Expand All @@ -254,6 +321,50 @@ pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
Errno::result(res).map(drop)
}

/// Set user registers, as with `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)`
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
setregset(pid, RegisterSet::NT_PRSTATUS, regs)
}

/// Set a particular set of user registers, as with `ptrace(PTRACE_SETREGSET, ...)`
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
pub fn setregset(
pid: Pid,
set: RegisterSet,
regs: user_regs_struct,
) -> Result<()> {
let iov = libc::iovec {
iov_base: &regs as *const _ as *mut c_void,
iov_len: mem::size_of::<user_regs_struct>(),
};
unsafe {
ptrace_other(
Request::PTRACE_SETREGSET,
pid,
set as i32 as AddressType,
&iov as *const _ as *mut c_void,
)?;
}
Ok(())
}

/// Function for ptrace requests that return values from the data field.
/// Some ptrace get requests populate structs or larger elements than `c_long`
/// and therefore use the data field to return values. This function handles these
Expand Down
32 changes: 28 additions & 4 deletions test/sys/test_ptrace.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#[cfg(all(
target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86"),
target_env = "gnu"
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
use memoffset::offset_of;
use nix::errno::Errno;
Expand Down Expand Up @@ -179,8 +184,13 @@ fn test_ptrace_interrupt() {
// ptrace::{setoptions, getregs} are only available in these platforms
#[cfg(all(
target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86"),
target_env = "gnu"
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
#[test]
fn test_ptrace_syscall() {
Expand Down Expand Up @@ -226,14 +236,28 @@ fn test_ptrace_syscall() {
let get_syscall_id =
|| ptrace::getregs(child).unwrap().orig_eax as libc::c_long;

#[cfg(target_arch = "aarch64")]
let get_syscall_id =
|| ptrace::getregs(child).unwrap().regs[8] as libc::c_long;

#[cfg(target_arch = "riscv64")]
let get_syscall_id =
|| ptrace::getregs(child).unwrap().a7 as libc::c_long;

// this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`.
#[cfg(target_arch = "x86_64")]
let rax_offset = offset_of!(libc::user_regs_struct, orig_rax);
#[cfg(target_arch = "x86")]
let rax_offset = offset_of!(libc::user_regs_struct, orig_eax);
#[cfg(target_arch = "aarch64")]
let rax_offset = offset_of!(libc::user_regs_struct, regs)
+ 8 * mem::size_of::<libc::c_ulonglong>();
#[cfg(target_arch = "riscv64")]
let rax_offset = offset_of!(libc::user_regs_struct, a7);

let get_syscall_from_user_area = || {
// Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
let rax_offset = offset_of!(libc::user, regs) + rax_offset;
ptrace::read_user(child, rax_offset as _).unwrap()
as libc::c_long
Expand Down

0 comments on commit f605fba

Please sign in to comment.