Skip to content

Commit 7c0d740

Browse files
committed
Add implementation of PTRACE_{GET,SET}REGSET
Also added `PTRACE_{GET,SET}REGS` for most platforms other than x86 using aforementioned implementation.
1 parent 4ab23c3 commit 7c0d740

File tree

2 files changed

+209
-7
lines changed

2 files changed

+209
-7
lines changed

src/sys/ptrace/linux.rs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ pub type AddressType = *mut ::libc::c_void;
1717
target_arch = "x86_64",
1818
any(target_env = "gnu", target_env = "musl")
1919
),
20-
all(target_arch = "x86", target_env = "gnu")
21-
)
20+
all(target_arch = "x86", target_env = "gnu"),
21+
all(target_arch = "aarch64", target_env = "gnu"),
22+
all(target_arch = "riscv64", target_env = "gnu"),
23+
),
2224
))]
2325
use libc::user_regs_struct;
2426

@@ -170,6 +172,29 @@ libc_enum! {
170172
}
171173
}
172174

175+
libc_enum! {
176+
#[cfg(all(
177+
target_os = "linux",
178+
target_env = "gnu",
179+
any(
180+
target_arch = "x86_64",
181+
target_arch = "x86",
182+
target_arch = "aarch64",
183+
target_arch = "riscv64",
184+
)
185+
))]
186+
#[repr(i32)]
187+
/// Defining a specific register set, as used in [`getregset`] and [`setregset`].
188+
#[non_exhaustive]
189+
pub enum RegisterSet {
190+
NT_PRSTATUS,
191+
NT_PRFPREG,
192+
NT_PRPSINFO,
193+
NT_TASKSTRUCT,
194+
NT_AUXV,
195+
}
196+
}
197+
173198
libc_bitflags! {
174199
/// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request.
175200
/// See `man ptrace` for more details.
@@ -231,6 +256,45 @@ pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
231256
ptrace_get_data::<user_regs_struct>(Request::PTRACE_GETREGS, pid)
232257
}
233258

259+
/// Get user registers, as with `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)`
260+
#[cfg(all(
261+
target_os = "linux",
262+
target_env = "gnu",
263+
any(target_arch = "aarch64", target_arch = "riscv64",)
264+
))]
265+
pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
266+
getregset(pid, RegisterSet::NT_PRSTATUS)
267+
}
268+
269+
/// Get a particular set of user registers, as with `ptrace(PTRACE_GETREGSET, ...)`
270+
#[cfg(all(
271+
target_os = "linux",
272+
target_env = "gnu",
273+
any(
274+
target_arch = "x86_64",
275+
target_arch = "x86",
276+
target_arch = "aarch64",
277+
target_arch = "riscv64",
278+
)
279+
))]
280+
pub fn getregset(pid: Pid, set: RegisterSet) -> Result<user_regs_struct> {
281+
let request = Request::PTRACE_GETREGSET;
282+
let mut data = mem::MaybeUninit::<user_regs_struct>::uninit();
283+
let mut iov = libc::iovec {
284+
iov_base: data.as_mut_ptr().cast(),
285+
iov_len: mem::size_of::<user_regs_struct>(),
286+
};
287+
unsafe {
288+
ptrace_other(
289+
request,
290+
pid,
291+
set as i32 as AddressType,
292+
(&mut iov as *mut libc::iovec).cast(),
293+
)?;
294+
};
295+
Ok(unsafe { data.assume_init() })
296+
}
297+
234298
/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)`
235299
#[cfg(all(
236300
target_os = "linux",
@@ -248,12 +312,53 @@ pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
248312
Request::PTRACE_SETREGS as RequestType,
249313
libc::pid_t::from(pid),
250314
ptr::null_mut::<c_void>(),
251-
&regs as *const _ as *const c_void,
315+
&regs as *const user_regs_struct as *const c_void,
252316
)
253317
};
254318
Errno::result(res).map(drop)
255319
}
256320

321+
/// Set user registers, as with `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)`
322+
#[cfg(all(
323+
target_os = "linux",
324+
target_env = "gnu",
325+
any(target_arch = "aarch64", target_arch = "riscv64",)
326+
))]
327+
pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
328+
setregset(pid, RegisterSet::NT_PRSTATUS, regs)
329+
}
330+
331+
/// Set a particular set of user registers, as with `ptrace(PTRACE_SETREGSET, ...)`
332+
#[cfg(all(
333+
target_os = "linux",
334+
target_env = "gnu",
335+
any(
336+
target_arch = "x86_64",
337+
target_arch = "x86",
338+
target_arch = "aarch64",
339+
target_arch = "riscv64",
340+
)
341+
))]
342+
pub fn setregset(
343+
pid: Pid,
344+
set: RegisterSet,
345+
mut regs: user_regs_struct,
346+
) -> Result<()> {
347+
let mut iov = libc::iovec {
348+
iov_base: (&mut regs as *mut user_regs_struct).cast(),
349+
iov_len: mem::size_of::<user_regs_struct>(),
350+
};
351+
unsafe {
352+
ptrace_other(
353+
Request::PTRACE_SETREGSET,
354+
pid,
355+
set as i32 as AddressType,
356+
(&mut iov as *mut libc::iovec).cast(),
357+
)?;
358+
}
359+
Ok(())
360+
}
361+
257362
/// Function for ptrace requests that return values from the data field.
258363
/// Some ptrace get requests populate structs or larger elements than `c_long`
259364
/// and therefore use the data field to return values. This function handles these

test/sys/test_ptrace.rs

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
#[cfg(all(
22
target_os = "linux",
3-
any(target_arch = "x86_64", target_arch = "x86"),
4-
target_env = "gnu"
3+
target_env = "gnu",
4+
any(
5+
target_arch = "x86_64",
6+
target_arch = "x86",
7+
target_arch = "aarch64",
8+
target_arch = "riscv64",
9+
)
510
))]
611
use memoffset::offset_of;
712
use nix::errno::Errno;
@@ -179,8 +184,13 @@ fn test_ptrace_interrupt() {
179184
// ptrace::{setoptions, getregs} are only available in these platforms
180185
#[cfg(all(
181186
target_os = "linux",
182-
any(target_arch = "x86_64", target_arch = "x86"),
183-
target_env = "gnu"
187+
target_env = "gnu",
188+
any(
189+
target_arch = "x86_64",
190+
target_arch = "x86",
191+
target_arch = "aarch64",
192+
target_arch = "riscv64",
193+
)
184194
))]
185195
#[test]
186196
fn test_ptrace_syscall() {
@@ -226,14 +236,28 @@ fn test_ptrace_syscall() {
226236
let get_syscall_id =
227237
|| ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
228238

239+
#[cfg(target_arch = "aarch64")]
240+
let get_syscall_id =
241+
|| ptrace::getregs(child).unwrap().regs[8] as libc::c_long;
242+
243+
#[cfg(target_arch = "riscv64")]
244+
let get_syscall_id =
245+
|| ptrace::getregs(child).unwrap().a7 as libc::c_long;
246+
229247
// this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`.
230248
#[cfg(target_arch = "x86_64")]
231249
let rax_offset = offset_of!(libc::user_regs_struct, orig_rax);
232250
#[cfg(target_arch = "x86")]
233251
let rax_offset = offset_of!(libc::user_regs_struct, orig_eax);
252+
#[cfg(target_arch = "aarch64")]
253+
let rax_offset = offset_of!(libc::user_regs_struct, regs)
254+
+ 8 * mem::size_of::<libc::c_ulonglong>();
255+
#[cfg(target_arch = "riscv64")]
256+
let rax_offset = offset_of!(libc::user_regs_struct, a7);
234257

235258
let get_syscall_from_user_area = || {
236259
// Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)
260+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
237261
let rax_offset = offset_of!(libc::user, regs) + rax_offset;
238262
ptrace::read_user(child, rax_offset as _).unwrap()
239263
as libc::c_long
@@ -273,3 +297,76 @@ fn test_ptrace_syscall() {
273297
}
274298
}
275299
}
300+
301+
#[cfg(all(
302+
target_os = "linux",
303+
target_env = "gnu",
304+
any(
305+
target_arch = "x86_64",
306+
target_arch = "x86",
307+
target_arch = "aarch64",
308+
target_arch = "riscv64",
309+
)
310+
))]
311+
#[test]
312+
fn test_ptrace_regsets() {
313+
use nix::sys::ptrace::{self, getregset, setregset, RegisterSet};
314+
use nix::sys::signal::*;
315+
use nix::sys::wait::{waitpid, WaitStatus};
316+
use nix::unistd::fork;
317+
use nix::unistd::ForkResult::*;
318+
319+
require_capability!("test_ptrace_regsets", CAP_SYS_PTRACE);
320+
321+
let _m = crate::FORK_MTX.lock();
322+
323+
match unsafe { fork() }.expect("Error: Fork Failed") {
324+
Child => {
325+
ptrace::traceme().unwrap();
326+
// As recommended by ptrace(2), raise SIGTRAP to pause the child
327+
// until the parent is ready to continue
328+
loop {
329+
raise(Signal::SIGTRAP).unwrap();
330+
}
331+
}
332+
333+
Parent { child } => {
334+
assert_eq!(
335+
waitpid(child, None),
336+
Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
337+
);
338+
let mut regstruct =
339+
getregset(child, RegisterSet::NT_PRSTATUS).unwrap();
340+
341+
#[cfg(target_arch = "x86_64")]
342+
let reg = &mut regstruct.r15;
343+
#[cfg(target_arch = "x86")]
344+
let reg = &mut regstruct.edx;
345+
#[cfg(target_arch = "aarch64")]
346+
let reg = &mut regstruct.regs[16];
347+
#[cfg(target_arch = "riscv64")]
348+
let reg = &mut regstruct.regs[16];
349+
350+
*reg = 0xdeadbeef;
351+
let _ = setregset(child, RegisterSet::NT_PRSTATUS, regstruct);
352+
regstruct = getregset(child, RegisterSet::NT_PRSTATUS).unwrap();
353+
354+
#[cfg(target_arch = "x86_64")]
355+
let reg = regstruct.r15;
356+
#[cfg(target_arch = "x86")]
357+
let reg = regstruct.edx;
358+
#[cfg(target_arch = "aarch64")]
359+
let reg = regstruct.regs[16];
360+
#[cfg(target_arch = "riscv64")]
361+
let reg = regstruct.regs[16];
362+
assert_eq!(0xdeadbeef, reg);
363+
364+
ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
365+
match waitpid(child, None) {
366+
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
367+
if pid == child => {}
368+
_ => panic!("The process should have been killed"),
369+
}
370+
}
371+
}
372+
}

0 commit comments

Comments
 (0)