x86/boundary/
context.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5use core::fmt::{self, Display, Formatter};
6use core::mem::size_of;
7use core::ptr;
8
9use crate::registers::irq::EXCEPTIONS;
10
11/// Stored CPU state of a user-mode app
12///
13/// This struct stores the complete CPU state of a user-mode Tock application on x86.
14///
15/// We access this struct from several assembly routines to perform context switching between user
16/// and kernel mode. For this reason, it is **critical** that the struct have a deterministic layout
17/// in memory. We use `#[repr(C)]` for this.
18#[repr(C)]
19#[derive(Default)]
20pub struct UserContext {
21    pub eax: u32,    // Offset:  0
22    pub ebx: u32,    // Offset:  4
23    pub ecx: u32,    // Offset:  8
24    pub edx: u32,    // Offset: 12
25    pub esi: u32,    // Offset: 16
26    pub edi: u32,    // Offset: 20
27    pub ebp: u32,    // Offset: 24
28    pub esp: u32,    // Offset: 28
29    pub eip: u32,    // Offset: 32
30    pub eflags: u32, // Offset: 36
31    pub cs: u32,     // Offset: 40
32    pub ss: u32,     // Offset: 44
33    pub ds: u32,     // Offset: 48
34    pub es: u32,     // Offset: 52
35    pub fs: u32,     // Offset: 56
36    pub gs: u32,     // Offset: 60
37
38    /// If the process triggers a CPU exception, this field will be populated with the
39    /// exception number. Otherwise this field must remain as zero.
40    pub exception: u8,
41
42    /// If the process triggers a CPU exception with an associated error code, this field will be
43    /// populated with the error code value. Otherwise this field must remain zero.
44    pub err_code: u32,
45}
46
47impl UserContext {
48    /// Pushes a value onto the user stack.
49    ///
50    /// Returns an `Err` if the new stack value would fall outside of valid memory.
51    ///
52    /// ## Safety
53    ///
54    /// The memory region described by `accessible_memory_start` and `app_brk` must point to memory
55    /// of the user process. This function will write to that memory.
56    pub unsafe fn push_stack(
57        &mut self,
58        value: u32,
59        accessible_memory_start: *const u8,
60        app_brk: *const u8,
61    ) -> Result<(), ()> {
62        let new_esp = self.esp - 4;
63
64        if new_esp < accessible_memory_start as u32 {
65            return Err(());
66        }
67
68        if new_esp + 4 > app_brk as u32 {
69            return Err(());
70        }
71
72        // Safety: We have validated above that new_esp lies within the specified memory region, and
73        //         the caller has guaranteed that this region is valid.
74        unsafe { ptr::write_volatile(new_esp as *mut u32, value) };
75
76        self.esp = new_esp;
77
78        Ok(())
79    }
80
81    /// Reads a value from `offset` relative to the current user stack pointer.
82    ///
83    /// `offset` is a DWORD offset (i.e. 4 bytes), not bytes.
84    ///
85    /// Returns an `Err` if the specified location falls outside of valid memory.
86    ///
87    /// ## Safety
88    ///
89    /// The memory region described by `accessible_memory_start` and `app_brk` must point to memory
90    /// of the user process. This function will read from that memory.
91    pub unsafe fn read_stack(
92        &self,
93        offset: u32,
94        accessible_memory_start: *const u8,
95        app_brk: *const u8,
96    ) -> Result<u32, ()> {
97        let stack_addr = self.esp + (offset * 4);
98
99        if stack_addr < accessible_memory_start as u32 {
100            return Err(());
101        }
102
103        if stack_addr + 4 > app_brk as u32 {
104            return Err(());
105        }
106
107        // Safety: We have validated above that stack_addr lies within the specified memory region,
108        //         and the caller has guaranteed that this region is valid.
109        let val = unsafe { ptr::read_volatile(stack_addr as *mut u32) };
110
111        Ok(val)
112    }
113
114    /// Writes a value to `offset` relative to the current user stack pointer.
115    ///
116    /// `offset` is a DWORD offset (i.e. 4 bytes), not bytes.
117    ///
118    /// Returns an `Err` if the specified location falls outside of valid memory.
119    ///
120    /// ## Safety
121    ///
122    /// The memory region described by `accessible_memory_start` and `app_brk` must point to memory
123    /// of the user process. This function will write to that memory.
124    pub unsafe fn write_stack(
125        &self,
126        offset: u32,
127        value: u32,
128        accessible_memory_start: *const u8,
129        app_brk: *const u8,
130    ) -> Result<(), ()> {
131        let stack_addr = self.esp + (offset * size_of::<usize>() as u32);
132
133        if stack_addr < accessible_memory_start as u32 {
134            return Err(());
135        }
136
137        if stack_addr + 4 > app_brk as u32 {
138            return Err(());
139        }
140
141        // Safety: We have validated above that stack_addr lies within the specified memory region,
142        //         and the caller has guaranteed that this region is valid.
143        unsafe { ptr::write_volatile(stack_addr as *mut u32, value) };
144
145        Ok(())
146    }
147}
148
149impl Display for UserContext {
150    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
151        writeln!(f)?;
152        writeln!(f, " CPU Registers:")?;
153        writeln!(f)?;
154        writeln!(f, "  EAX: {:#010x}      EBX: {:#010x}", self.eax, self.ebx)?;
155        writeln!(f, "  ECX: {:#010x}      EDX: {:#010x}", self.ecx, self.edx)?;
156        writeln!(f, "  ESI: {:#010x}      EDI: {:#010x}", self.esi, self.edi)?;
157        writeln!(f, "  EBP: {:#010x}      ESP: {:#010x}", self.ebp, self.esp)?;
158        writeln!(
159            f,
160            "  EIP: {:#010x}   EFLAGS: {:#010x}",
161            self.eip, self.eflags
162        )?;
163        writeln!(
164            f,
165            "   CS:     {:#06x}       SS:     {:#06x}",
166            self.cs, self.ss
167        )?;
168        writeln!(
169            f,
170            "   DS:     {:#06x}       ES:     {:#06x}",
171            self.ds, self.es
172        )?;
173        writeln!(
174            f,
175            "   FS:     {:#06x}       GS:     {:#06x}",
176            self.fs, self.gs
177        )?;
178
179        if self.exception != 0 || self.err_code != 0 {
180            writeln!(f)?;
181            if let Some(msg) = EXCEPTIONS.get(self.exception as usize) {
182                writeln!(f, " Exception: {}", msg)?;
183            } else {
184                writeln!(f, " Exception Number: {}", self.exception)?;
185            }
186            writeln!(f, " Error code: {:#010x}", self.err_code)?;
187        }
188        Ok(())
189    }
190}