keos_project2/loader/stack_builder.rs
1//! [`StackBuilder`], a utility for constructing a user-space stack layout.
2use crate::{mm_struct::MmStruct, pager::Pager};
3use keos::{KernelError, addressing::Va, mm::page_table::Permission};
4
5/// A utility for constructing a user-space stack layout.
6///
7/// [`StackBuilder`] provides methods to allocate, align, and push data onto
8/// a stack before mapping it into a user process. It is primarily used to
9/// prepare the initial stack for a new process, including setting up `argv`
10/// and other necessary data.
11///
12/// The stack starts at virtual address `0x4748_0000` and grows downward.
13///
14/// # Fields
15/// - `sp`: The current stack pointer, representing the top of the stack.
16/// - `pages`: A list of allocated pages that will back the stack.
17///
18/// # Usage
19/// 1. **Create a new stack** using [`StackBuilder::new`].
20/// 2. **Push data** (e.g., arguments, environment variables) onto the stack.
21/// 3. **Align the stack** for proper memory layout.
22/// 4. **Finalize the stack** using [`StackBuilder::finish`] to map it into the
23/// process's address space.
24pub struct StackBuilder<'a, P: Pager> {
25 sp: Va,
26 mm_state: &'a mut MmStruct<P>,
27}
28
29impl<'a, P: Pager> StackBuilder<'a, P> {
30 /// Creates a new [`StackBuilder`] instance for building a user-space stack.
31 ///
32 /// The stack is initialized at virtual address `0x4748_0000` and grows
33 /// downward as data is pushed onto it.
34 ///
35 /// # Returns
36 /// A new [`StackBuilder`] with an empty stack and no allocated pages.
37 pub fn new(mm_state: &'a mut MmStruct<P>) -> Result<Self, KernelError> {
38 mm_state
39 .do_mmap(
40 Va::new(0x4748_0000 - 0x10000).unwrap(),
41 0x10000,
42 Permission::READ | Permission::WRITE | Permission::USER,
43 None,
44 0,
45 )
46 .map(|_| Self {
47 sp: Va::new(0x4748_0000).unwrap(),
48 mm_state,
49 })
50 }
51
52 /// Consume the [`StackBuilder`] and return the stack pointer.
53 ///
54 ///
55 /// # Returns
56 /// - `Ok(Va)`: The final stack pointer after mapping.
57 /// - `Err(KernelError)`: If the stack mapping fails.
58 pub fn finish(self) -> Va {
59 self.sp
60 }
61
62 /// Returns the current stack pointer.
63 ///
64 /// The stack pointer (`sp`) indicates the top of the stack, where the next
65 /// value would be pushed. The stack grows downward, meaning the pointer
66 /// decreases as more data is pushed onto it.
67 ///
68 /// # Returns
69 /// - The current stack pointer as a virtual address ([`Va`]).
70 #[inline]
71 pub fn sp(&self) -> Va {
72 self.sp
73 }
74
75 /// Aligns the stack pointer to the given alignment.
76 ///
77 /// This function ensures that the stack pointer is aligned to the specified
78 /// byte boundary, which is useful for maintaining proper data alignment
79 /// when pushing values.
80 ///
81 /// # Parameters
82 /// - `align`: The byte alignment requirement.
83 ///
84 /// # Behavior
85 /// - If the stack pointer is not already aligned, it is adjusted downward
86 /// to meet the alignment requirement.
87 #[inline]
88 pub fn align(&mut self, align: usize) {
89 while !self.sp.into_usize().is_multiple_of(align) {
90 self.sp -= 1;
91 }
92 }
93
94 /// Pushes a byte array onto the stack.
95 ///
96 /// This function decreases the stack pointer to allocate space for the
97 /// value and stores it at the new top of the stack.
98 ///
99 /// # Parameters
100 /// - `v`: The `[u8]` value to be pushed onto the stack.
101 ///
102 /// # Returns
103 /// - The updated stack pointer after pushing the value.
104 pub fn push_bytes(&mut self, mut bytes: &[u8]) -> Va {
105 // HINT: use `get_user_page_and`
106 todo!();
107 self.sp()
108 }
109
110 /// Pushes a `usize` value onto the stack.
111 ///
112 /// This function decreases the stack pointer to allocate space for the
113 /// value and stores it at the new top of the stack.
114 ///
115 /// # Parameters
116 /// - `v`: The `usize` value to be pushed onto the stack.
117 ///
118 /// # Returns
119 /// - The updated stack pointer after pushing the value.
120 pub fn push_usize(&mut self, v: usize) -> Va {
121 self.push_bytes(&v.to_ne_bytes())
122 }
123
124 /// Pushes a string onto the stack as a C-style string (null-terminated).
125 ///
126 /// This function copies the given string onto the stack, appends a null
127 /// terminator (`\0`), and returns the virtual address ([`Va`]) where the
128 /// string is stored.
129 ///
130 /// # Parameters
131 /// - `s`: The string to push onto the stack.
132 ///
133 /// # Returns
134 /// - The virtual address ([`Va`]) pointing to the beginning of the stored
135 /// string in memory.
136 ///
137 /// # Behavior
138 /// - The stack pointer is adjusted downward to allocate space for the
139 /// string.
140 /// - The string is stored in memory in a null-terminated format, making it
141 /// compatible with C-style APIs.
142 #[inline]
143 pub fn push_str(&mut self, s: &str) -> Va {
144 // Make a space for null bytes ('\0').
145 self.sp -= 1;
146 // Push the string slice.
147 self.push_bytes(s.as_bytes())
148 }
149}