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}