Skip to content

Commit 1c2cad8

Browse files
hack3ricSteveLauC
andauthored
Add implementation of PTRACE_{GET,SET}REGSET (#2044)
* Add implementation of `PTRACE_{GET,SET}REGSET` Also added `PTRACE_{GET,SET}REGS` for most platforms other than x86 using aforementioned implementation. * test: remove unused import on aarch64 and riscv64 * chore: changelog --------- Co-authored-by: Steve Lau <[email protected]>
1 parent ba66e13 commit 1c2cad8

File tree

3 files changed

+227
-7
lines changed

3 files changed

+227
-7
lines changed

changelog/2044.added.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add `getregset()/setregset()` for Linux/glibc/x86/x86_64/aarch64/riscv64 and
2+
`getregs()/setregs()` for Linux/glibc/aarch64/riscv64

src/sys/ptrace/linux.rs

Lines changed: 132 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.
@@ -217,6 +242,12 @@ fn ptrace_peek(
217242
}
218243

219244
/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)`
245+
///
246+
/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]),
247+
/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
248+
/// on aarch64 and riscv64.
249+
///
250+
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
220251
#[cfg(all(
221252
target_os = "linux",
222253
any(
@@ -231,7 +262,58 @@ pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
231262
ptrace_get_data::<user_regs_struct>(Request::PTRACE_GETREGS, pid)
232263
}
233264

265+
/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)`
266+
///
267+
/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]),
268+
/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
269+
/// on aarch64 and riscv64.
270+
///
271+
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
272+
#[cfg(all(
273+
target_os = "linux",
274+
target_env = "gnu",
275+
any(target_arch = "aarch64", target_arch = "riscv64")
276+
))]
277+
pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
278+
getregset(pid, RegisterSet::NT_PRSTATUS)
279+
}
280+
281+
/// Get a particular set of user registers, as with `ptrace(PTRACE_GETREGSET, ...)`
282+
#[cfg(all(
283+
target_os = "linux",
284+
target_env = "gnu",
285+
any(
286+
target_arch = "x86_64",
287+
target_arch = "x86",
288+
target_arch = "aarch64",
289+
target_arch = "riscv64",
290+
)
291+
))]
292+
pub fn getregset(pid: Pid, set: RegisterSet) -> Result<user_regs_struct> {
293+
let request = Request::PTRACE_GETREGSET;
294+
let mut data = mem::MaybeUninit::<user_regs_struct>::uninit();
295+
let mut iov = libc::iovec {
296+
iov_base: data.as_mut_ptr().cast(),
297+
iov_len: mem::size_of::<user_regs_struct>(),
298+
};
299+
unsafe {
300+
ptrace_other(
301+
request,
302+
pid,
303+
set as i32 as AddressType,
304+
(&mut iov as *mut libc::iovec).cast(),
305+
)?;
306+
};
307+
Ok(unsafe { data.assume_init() })
308+
}
309+
234310
/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)`
311+
///
312+
/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]),
313+
/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
314+
/// on aarch64 and riscv64.
315+
///
316+
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
235317
#[cfg(all(
236318
target_os = "linux",
237319
any(
@@ -248,12 +330,59 @@ pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
248330
Request::PTRACE_SETREGS as RequestType,
249331
libc::pid_t::from(pid),
250332
ptr::null_mut::<c_void>(),
251-
&regs as *const _ as *const c_void,
333+
&regs as *const user_regs_struct as *const c_void,
252334
)
253335
};
254336
Errno::result(res).map(drop)
255337
}
256338

339+
/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)`
340+
///
341+
/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]),
342+
/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
343+
/// on aarch64 and riscv64.
344+
///
345+
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
346+
#[cfg(all(
347+
target_os = "linux",
348+
target_env = "gnu",
349+
any(target_arch = "aarch64", target_arch = "riscv64")
350+
))]
351+
pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
352+
setregset(pid, RegisterSet::NT_PRSTATUS, regs)
353+
}
354+
355+
/// Set a particular set of user registers, as with `ptrace(PTRACE_SETREGSET, ...)`
356+
#[cfg(all(
357+
target_os = "linux",
358+
target_env = "gnu",
359+
any(
360+
target_arch = "x86_64",
361+
target_arch = "x86",
362+
target_arch = "aarch64",
363+
target_arch = "riscv64",
364+
)
365+
))]
366+
pub fn setregset(
367+
pid: Pid,
368+
set: RegisterSet,
369+
mut regs: user_regs_struct,
370+
) -> Result<()> {
371+
let mut iov = libc::iovec {
372+
iov_base: (&mut regs as *mut user_regs_struct).cast(),
373+
iov_len: mem::size_of::<user_regs_struct>(),
374+
};
375+
unsafe {
376+
ptrace_other(
377+
Request::PTRACE_SETREGSET,
378+
pid,
379+
set as i32 as AddressType,
380+
(&mut iov as *mut libc::iovec).cast(),
381+
)?;
382+
}
383+
Ok(())
384+
}
385+
257386
/// Function for ptrace requests that return values from the data field.
258387
/// Some ptrace get requests populate structs or larger elements than `c_long`
259388
/// and therefore use the data field to return values. This function handles these

test/sys/test_ptrace.rs

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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(target_arch = "x86_64", target_arch = "x86")
55
))]
66
use memoffset::offset_of;
77
use nix::errno::Errno;
@@ -179,8 +179,13 @@ fn test_ptrace_interrupt() {
179179
// ptrace::{setoptions, getregs} are only available in these platforms
180180
#[cfg(all(
181181
target_os = "linux",
182-
any(target_arch = "x86_64", target_arch = "x86"),
183-
target_env = "gnu"
182+
target_env = "gnu",
183+
any(
184+
target_arch = "x86_64",
185+
target_arch = "x86",
186+
target_arch = "aarch64",
187+
target_arch = "riscv64",
188+
)
184189
))]
185190
#[test]
186191
fn test_ptrace_syscall() {
@@ -226,12 +231,21 @@ fn test_ptrace_syscall() {
226231
let get_syscall_id =
227232
|| ptrace::getregs(child).unwrap().orig_eax as libc::c_long;
228233

234+
#[cfg(target_arch = "aarch64")]
235+
let get_syscall_id =
236+
|| ptrace::getregs(child).unwrap().regs[8] as libc::c_long;
237+
238+
#[cfg(target_arch = "riscv64")]
239+
let get_syscall_id =
240+
|| ptrace::getregs(child).unwrap().a7 as libc::c_long;
241+
229242
// this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`.
230243
#[cfg(target_arch = "x86_64")]
231244
let rax_offset = offset_of!(libc::user_regs_struct, orig_rax);
232245
#[cfg(target_arch = "x86")]
233246
let rax_offset = offset_of!(libc::user_regs_struct, orig_eax);
234247

248+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
235249
let get_syscall_from_user_area = || {
236250
// Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)
237251
let rax_offset = offset_of!(libc::user, regs) + rax_offset;
@@ -246,6 +260,7 @@ fn test_ptrace_syscall() {
246260
Ok(WaitStatus::PtraceSyscall(child))
247261
);
248262
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
263+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
249264
assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
250265

251266
// kill exit
@@ -255,6 +270,7 @@ fn test_ptrace_syscall() {
255270
Ok(WaitStatus::PtraceSyscall(child))
256271
);
257272
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
273+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
258274
assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);
259275

260276
// receive signal
@@ -273,3 +289,76 @@ fn test_ptrace_syscall() {
273289
}
274290
}
275291
}
292+
293+
#[cfg(all(
294+
target_os = "linux",
295+
target_env = "gnu",
296+
any(
297+
target_arch = "x86_64",
298+
target_arch = "x86",
299+
target_arch = "aarch64",
300+
target_arch = "riscv64",
301+
)
302+
))]
303+
#[test]
304+
fn test_ptrace_regsets() {
305+
use nix::sys::ptrace::{self, getregset, setregset, RegisterSet};
306+
use nix::sys::signal::*;
307+
use nix::sys::wait::{waitpid, WaitStatus};
308+
use nix::unistd::fork;
309+
use nix::unistd::ForkResult::*;
310+
311+
require_capability!("test_ptrace_regsets", CAP_SYS_PTRACE);
312+
313+
let _m = crate::FORK_MTX.lock();
314+
315+
match unsafe { fork() }.expect("Error: Fork Failed") {
316+
Child => {
317+
ptrace::traceme().unwrap();
318+
// As recommended by ptrace(2), raise SIGTRAP to pause the child
319+
// until the parent is ready to continue
320+
loop {
321+
raise(Signal::SIGTRAP).unwrap();
322+
}
323+
}
324+
325+
Parent { child } => {
326+
assert_eq!(
327+
waitpid(child, None),
328+
Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
329+
);
330+
let mut regstruct =
331+
getregset(child, RegisterSet::NT_PRSTATUS).unwrap();
332+
333+
#[cfg(target_arch = "x86_64")]
334+
let reg = &mut regstruct.r15;
335+
#[cfg(target_arch = "x86")]
336+
let reg = &mut regstruct.edx;
337+
#[cfg(target_arch = "aarch64")]
338+
let reg = &mut regstruct.regs[16];
339+
#[cfg(target_arch = "riscv64")]
340+
let reg = &mut regstruct.regs[16];
341+
342+
*reg = 0xdeadbeefu32 as _;
343+
let _ = setregset(child, RegisterSet::NT_PRSTATUS, regstruct);
344+
regstruct = getregset(child, RegisterSet::NT_PRSTATUS).unwrap();
345+
346+
#[cfg(target_arch = "x86_64")]
347+
let reg = regstruct.r15;
348+
#[cfg(target_arch = "x86")]
349+
let reg = regstruct.edx;
350+
#[cfg(target_arch = "aarch64")]
351+
let reg = regstruct.regs[16];
352+
#[cfg(target_arch = "riscv64")]
353+
let reg = regstruct.regs[16];
354+
assert_eq!(reg, 0xdeadbeefu32 as _);
355+
356+
ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
357+
match waitpid(child, None) {
358+
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
359+
if pid == child => {}
360+
_ => panic!("The process should have been killed"),
361+
}
362+
}
363+
}
364+
}

0 commit comments

Comments
 (0)