Skip to main content

proot/tracee/
regs.rs

1//! Architecture view of a tracee's registers at a syscall-entry stop.
2//!
3//! Two hosts are supported: x86_64 (also serving i386 tracees) and aarch64.
4//! Both expose the same [`Regs`] API; the differences are confined to the
5//! `arch` module selected at compile time.
6
7use crate::abi::Abi;
8use crate::sys::{RED_ZONE_SIZE, Result};
9
10#[cfg(target_arch = "x86_64")]
11mod arch {
12    use super::super::ptrace;
13    use crate::abi::Abi;
14    use crate::sys::Result;
15
16    pub type Raw = libc::user_regs_struct;
17
18    pub fn fetch(pid: i32) -> Result<Raw> {
19        ptrace::getregs(pid)
20    }
21
22    pub fn store(pid: i32, cur: &Raw, _orig: &Raw) -> Result<()> {
23        ptrace::setregs(pid, cur)
24    }
25
26    /// `0x23` is the 32-bit compat code segment, `0x33` the 64-bit one.
27    pub fn abi(r: &Raw) -> Abi {
28        if r.cs == 0x23 { Abi::I386 } else { Abi::X86_64 }
29    }
30
31    pub fn sysnum(r: &Raw) -> i64 {
32        r.orig_rax as i64
33    }
34
35    pub fn set_sysnum(r: &mut Raw, nr: i64) {
36        r.orig_rax = nr as u64;
37    }
38
39    /// The result a skipped syscall returns: `rax`, `eax` for i386.
40    pub fn set_ret(r: &mut Raw, v: i64) {
41        r.rax = v as u64;
42    }
43
44    pub fn arg(r: &Raw, i: usize) -> u64 {
45        match abi(r) {
46            Abi::I386 => {
47                (match i {
48                    0 => r.rbx,
49                    1 => r.rcx,
50                    2 => r.rdx,
51                    3 => r.rsi,
52                    4 => r.rdi,
53                    _ => r.rbp,
54                }) & 0xffff_ffff
55            }
56            _ => match i {
57                0 => r.rdi,
58                1 => r.rsi,
59                2 => r.rdx,
60                3 => r.r10,
61                4 => r.r8,
62                _ => r.r9,
63            },
64        }
65    }
66
67    pub fn set_arg(r: &mut Raw, i: usize, v: u64) {
68        match abi(r) {
69            Abi::I386 => {
70                let v = v & 0xffff_ffff;
71                match i {
72                    0 => r.rbx = v,
73                    1 => r.rcx = v,
74                    2 => r.rdx = v,
75                    3 => r.rsi = v,
76                    4 => r.rdi = v,
77                    _ => r.rbp = v,
78                }
79            }
80            _ => match i {
81                0 => r.rdi = v,
82                1 => r.rsi = v,
83                2 => r.rdx = v,
84                3 => r.r10 = v,
85                4 => r.r8 = v,
86                _ => r.r9 = v,
87            },
88        }
89    }
90
91    pub fn sp(r: &Raw) -> u64 {
92        r.rsp
93    }
94
95    pub fn ip(r: &Raw) -> u64 {
96        r.rip
97    }
98
99    pub fn same(a: &Raw, b: &Raw) -> bool {
100        let n = std::mem::size_of::<Raw>();
101        unsafe {
102            std::slice::from_raw_parts(a as *const _ as *const u8, n)
103                == std::slice::from_raw_parts(b as *const _ as *const u8, n)
104        }
105    }
106}
107
108#[cfg(target_arch = "aarch64")]
109mod arch {
110    use super::super::ptrace;
111    use crate::abi::Abi;
112    use crate::sys::Result;
113
114    /// `struct user_pt_regs` for a 64-bit tracee, or the 18-word `struct
115    /// user_regs` (r0..r15, cpsr, orig_r0) the kernel reports for a 32-bit one.
116    #[repr(C)]
117    #[derive(Clone, Copy, PartialEq)]
118    pub struct Raw {
119        pub regs: [u64; 31],
120        pub sp: u64,
121        pub pc: u64,
122        pub pstate: u64,
123        /// The tracee is AArch32: `regs[0..18]` hold the 32-bit view instead.
124        pub compat: bool,
125        /// Syscall number override, written through `NT_ARM_SYSTEM_CALL` on store.
126        pub sysnum_override: Option<i32>,
127    }
128
129    const NT_PRSTATUS: u32 = 1;
130    const NT_ARM_SYSTEM_CALL: u32 = 0x404;
131    const REGS64_LEN: usize = 34 * 8;
132    const REGS32_LEN: usize = 18 * 4;
133
134    pub fn fetch(pid: i32) -> Result<Raw> {
135        let mut r = Raw {
136            regs: [0; 31],
137            sp: 0,
138            pc: 0,
139            pstate: 0,
140            compat: false,
141            sysnum_override: None,
142        };
143        let mut buf = [0u8; REGS64_LEN];
144        let n = ptrace::getregset(pid, NT_PRSTATUS, buf.as_mut_ptr(), REGS64_LEN)?;
145        if n == REGS32_LEN {
146            r.compat = true;
147            for (i, w) in buf[..REGS32_LEN].as_chunks::<4>().0.iter().enumerate() {
148                r.regs[i] = u32::from_ne_bytes(*w) as u64;
149            }
150            r.sp = r.regs[13];
151            r.pc = r.regs[15];
152            r.pstate = r.regs[16];
153        } else {
154            for (i, w) in buf[..31 * 8].as_chunks::<8>().0.iter().enumerate() {
155                r.regs[i] = u64::from_ne_bytes(*w);
156            }
157            r.sp = u64::from_ne_bytes(buf[248..256].try_into().unwrap());
158            r.pc = u64::from_ne_bytes(buf[256..264].try_into().unwrap());
159            r.pstate = u64::from_ne_bytes(buf[264..272].try_into().unwrap());
160        }
161        Ok(r)
162    }
163
164    pub fn store(pid: i32, cur: &Raw, orig: &Raw) -> Result<()> {
165        if let Some(nr) = cur.sysnum_override {
166            let mut v = nr;
167            ptrace::setregset(pid, NT_ARM_SYSTEM_CALL, &mut v as *mut i32 as *mut u8, 4)?;
168        }
169        if cur.regs != orig.regs || cur.sp != orig.sp || cur.pc != orig.pc {
170            if cur.compat {
171                let mut buf = [0u8; REGS32_LEN];
172                for i in 0..18 {
173                    buf[i * 4..i * 4 + 4].copy_from_slice(&(cur.regs[i] as u32).to_ne_bytes());
174                }
175                ptrace::setregset(pid, NT_PRSTATUS, buf.as_mut_ptr(), REGS32_LEN)?;
176            } else {
177                let mut buf = [0u8; REGS64_LEN];
178                for i in 0..31 {
179                    buf[i * 8..i * 8 + 8].copy_from_slice(&cur.regs[i].to_ne_bytes());
180                }
181                buf[248..256].copy_from_slice(&cur.sp.to_ne_bytes());
182                buf[256..264].copy_from_slice(&cur.pc.to_ne_bytes());
183                buf[264..272].copy_from_slice(&cur.pstate.to_ne_bytes());
184                ptrace::setregset(pid, NT_PRSTATUS, buf.as_mut_ptr(), REGS64_LEN)?;
185            }
186        }
187        Ok(())
188    }
189
190    pub fn abi(r: &Raw) -> Abi {
191        if r.compat { Abi::Arm } else { Abi::Aarch64 }
192    }
193
194    /// The syscall number lives in x8 (AArch64) or r7 (AArch32) at entry.
195    pub fn sysnum(r: &Raw) -> i64 {
196        if let Some(n) = r.sysnum_override {
197            return n as i64;
198        }
199        if r.compat {
200            r.regs[7] as i64
201        } else {
202            r.regs[8] as i64
203        }
204    }
205
206    pub fn set_sysnum(r: &mut Raw, nr: i64) {
207        r.sysnum_override = Some(nr as i32);
208    }
209
210    /// The result a skipped syscall returns: `x0`, `r0` for a compat tracee.
211    pub fn set_ret(r: &mut Raw, v: i64) {
212        r.regs[0] = if r.compat {
213            v as u64 & 0xffff_ffff
214        } else {
215            v as u64
216        };
217    }
218
219    /// Arguments are x0..x5 or r0..r5 on both ABIs.
220    pub fn arg(r: &Raw, i: usize) -> u64 {
221        let v = r.regs[i.min(5)];
222        if r.compat { v & 0xffff_ffff } else { v }
223    }
224
225    pub fn set_arg(r: &mut Raw, i: usize, v: u64) {
226        r.regs[i.min(5)] = if r.compat { v & 0xffff_ffff } else { v };
227        if r.compat && i == 0 {
228            // orig_r0 is what the kernel re-reads for a restarted syscall.
229            r.regs[17] = v & 0xffff_ffff;
230        }
231    }
232
233    pub fn sp(r: &Raw) -> u64 {
234        r.sp
235    }
236
237    pub fn ip(r: &Raw) -> u64 {
238        r.pc
239    }
240
241    pub fn same(a: &Raw, b: &Raw) -> bool {
242        a == b
243    }
244}
245
246/// Registers of a tracee stopped at syscall entry, with pending edits.
247#[derive(Clone)]
248pub struct Regs {
249    pid: i32,
250    orig: arch::Raw,
251    cur: arch::Raw,
252    /// Bytes already handed out below the red zone by [`Regs::alloc_stack`].
253    scratch_used: u64,
254}
255
256impl Regs {
257    /// Fetches the registers of `pid`.
258    pub fn fetch(pid: i32) -> Result<Regs> {
259        let r = arch::fetch(pid)?;
260        Ok(Regs {
261            pid,
262            orig: r,
263            cur: r,
264            scratch_used: 0,
265        })
266    }
267
268    /// Thread id these registers belong to.
269    pub fn pid(&self) -> i32 {
270        self.pid
271    }
272
273    /// ABI the tracee made this syscall through.
274    pub fn abi(&self) -> Abi {
275        arch::abi(&self.cur)
276    }
277
278    /// Syscall number being entered, in the tracee's own ABI numbering.
279    pub fn sysnum(&self) -> i64 {
280        arch::sysnum(&self.cur)
281    }
282
283    /// Skips the syscall and makes it fail with `errno`: a tracer that sets the
284    /// number to `-1` at the entry or seccomp stop has the tracee see whatever
285    /// it put in the result register (seccomp(2), `SECCOMP_RET_TRACE`), which
286    /// is `ENOSYS` left alone.
287    pub fn fail(&mut self, errno: i32) {
288        arch::set_sysnum(&mut self.cur, -1);
289        arch::set_ret(&mut self.cur, -i64::from(errno));
290    }
291
292    /// Syscall argument `i` (0..6), in the register order of the tracee's ABI.
293    pub fn arg(&self, i: usize) -> u64 {
294        arch::arg(&self.cur, i)
295    }
296
297    /// Sets syscall argument `i` (0..6) in the register the tracee's ABI uses.
298    pub fn set_arg(&mut self, i: usize, v: u64) {
299        arch::set_arg(&mut self.cur, i, v);
300    }
301
302    /// Current stack pointer.
303    pub fn sp(&self) -> u64 {
304        arch::sp(&self.cur)
305    }
306
307    /// Current instruction pointer.
308    pub fn ip(&self) -> u64 {
309        arch::ip(&self.cur)
310    }
311
312    /// Reserves `size` bytes of scratch space below the tracee's stack pointer.
313    ///
314    /// The stack pointer itself is left untouched: we take no exit stop that
315    /// could restore it, and the memory below `sp` minus the red zone is
316    /// unused while the thread sits in the syscall instruction.  (aarch64 has
317    /// no red zone; skipping one is harmless.)  Successive reservations stack
318    /// downwards.  For `execve` the kernel discards the whole stack anyway.
319    ///
320    /// # Arguments
321    /// * `size` - Bytes to reserve; the result is 16-byte aligned.
322    ///
323    /// # Returns
324    /// The address of the reserved block in the tracee.
325    pub fn alloc_stack(&mut self, size: u64) -> u64 {
326        let sp = self.sp();
327        let base = sp.saturating_sub(RED_ZONE_SIZE + self.scratch_used);
328        let addr = base.saturating_sub(size) & !0xf;
329        self.scratch_used = sp - addr;
330        addr
331    }
332
333    /// Writes the modified registers back if anything changed.
334    pub fn commit(&self) -> Result<()> {
335        if arch::same(&self.cur, &self.orig) {
336            return Ok(());
337        }
338        arch::store(self.pid, &self.cur, &self.orig)
339    }
340}