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}