lx/
start.rs

1//! On startup, Linux calls the entry point function with the application arguments, environment
2//! variables and ELF auxiliary header on the stack.
3//!
4//! This does not follow a calling convention that Rust understands so it is not possible to write
5//! a pure Rust function that accesses these things.
6//!
7//! This modules provides a `start` macro to define an entry point that calls into a user provided
8//! Rust function with an argument slice and a `EnvVars` object so that it can read the information
9//! from Linux.
10//!
11//! Once the application has finished reading the arguments with the `Args` object, it can call
12//! `into_env_vars` to get a `EnvVars` object to read the environment variables. Similarly, once
13//! the application has finished reading the environment variables with the `EnvVars` object, it
14//! can call `into_elf_aux_table` to get a `ElfAuxTable` object to read the ELF auxiliary table.
15//!
16//! The reading is done this way because except for the program arguments, arrays are not length
17//! prefixed but rather null-terminated so there is no way to seek to the interesting data and the
18//! data must always be read in order.
19//!
20//! If the program does not care about these things, it can create its own `_start` function.
21//!
22//! In both case, please note that the program's entry point is not supposed to return. Instead,
23//! the program should call `exit`.
24
25use core::ffi::c_char;
26
27// ELF auxiliary entry keys.
28pub const AT_NULL: usize = 0;
29pub const AT_IGNORE: usize = 1;
30pub const AT_EXECFD: usize = 2;
31pub const AT_PHDR: usize = 3;
32pub const AT_PHENT: usize = 4;
33pub const AT_PHNUM: usize = 5;
34pub const AT_PAGESZ: usize = 6;
35pub const AT_BASE: usize = 7;
36pub const AT_FLAGS: usize = 8;
37pub const AT_ENTRY: usize = 9;
38pub const AT_NOTELF: usize = 10;
39pub const AT_UID: usize = 11;
40pub const AT_EUID: usize = 12;
41pub const AT_GID: usize = 13;
42pub const AT_EGID: usize = 14;
43pub const AT_PLATFORM: usize = 15;
44pub const AT_HWCAP: usize = 16;
45pub const AT_CLKTCK: usize = 17;
46pub const AT_SECURE: usize = 23;
47pub const AT_BASE_PLATFORM: usize = 24;
48pub const AT_RANDOM: usize = 25;
49pub const AT_HWCAP2: usize = 26;
50pub const AT_EXECFN: usize = 31;
51pub const AT_MINSIGSTKSZ: usize = 51;
52
53/// Environment variable iterator.
54///
55/// # Invariants
56///
57/// `curr` must be properly aligned.
58///
59/// There must exist a `n` such that `curr.add(n)` is valid for reads and `*curr.offset(n)` is
60/// `ptr::null()`, indicating the end of the array.
61///
62/// Pointers of the form `curr.add(i)` with `i` from zero included to `n` excluded must be valid
63/// for reads.
64///
65/// There must be a valid ELF auxiliary table at `curr.add(n + 1)`. That is,
66/// `ElfAuxTable { curr: curr.add(n + 1) as *const ElfAuxEntry }` must follow `ElfAuxTable`'s
67/// invariants.
68pub struct EnvVars {
69    curr: *const *const c_char,
70}
71
72impl EnvVars {
73    /// Creates a new environment variable iterator from a pointer to a Linux-like environment
74    /// variable array.
75    ///
76    /// # Safety
77    ///
78    /// `start` must be properly aligned.
79    ///
80    /// There must exist a `n` such that `start.add(n)` is valid for reads and `*start.offset(n)`
81    /// is `ptr::null()`, indicating the end of the array.
82    ///
83    /// Pointers of the form `start.add(i)` with `i` from zero included to `n` excluded must be
84    /// valid for reads.
85    ///
86    /// There must be a valid ELF auxiliary table at `start.add(n + 1)`. That is,
87    /// `ElfAuxTable { curr: start.add(n + 1) as *const ElfAuxEntry }` must follow `ElfAuxTable`'s
88    /// invariants.
89    pub unsafe fn new(start: *const *const c_char) -> Self {
90        Self { curr: start }
91    }
92
93    pub fn into_elf_aux_table(mut self) -> ElfAuxTable {
94        // Skip remaining variables.
95        while self.next().is_some() {}
96        let start = unsafe { self.curr.offset(1) as *const ElfAuxEntry };
97        ElfAuxTable { curr: start }
98    }
99}
100
101impl Iterator for EnvVars {
102    type Item = *const c_char;
103
104    fn next(&mut self) -> Option<Self::Item> {
105        let entry = unsafe { *self.curr };
106        if entry.is_null() {
107            return None;
108        }
109        self.curr = unsafe { self.curr.offset(1) };
110        Some(entry)
111    }
112}
113
114/// An ELF auxiliary table entry.
115#[repr(C)]
116#[derive(Clone, Copy)]
117pub struct ElfAuxEntry {
118    pub key: usize,
119    pub val: usize,
120}
121
122/// An ELF auxiliary table. See the `AT_*` constants for entries that can be found inside.
123///
124/// # Invariants
125///
126/// `curr` must be aligned properly.
127///
128/// There must exist a `n` such that `curr.add(n)` is valid for reads and `(*curr.add(n)).key` is
129/// `AT_NULL`, indicating the end of the table.
130///
131/// Pointers of the form `curr.offset(i)` with `i` from zero included to `n` excluded must be valid
132/// for reads.
133pub struct ElfAuxTable {
134    curr: *const ElfAuxEntry,
135}
136
137impl Iterator for ElfAuxTable {
138    type Item = ElfAuxEntry;
139
140    fn next(&mut self) -> Option<Self::Item> {
141        let entry = unsafe { *self.curr };
142        if entry.key == AT_NULL {
143            return None;
144        }
145        self.curr = unsafe { self.curr.offset(1) };
146        Some(entry)
147    }
148}
149
150#[macro_export]
151macro_rules! def_start {
152    ($user_entry:ident) => {
153        ::core::arch::global_asm!(
154            "
155        .section .text
156        .global _start
157        _start:
158            mov rdi, rsp
159            jmp rust_start
160        "
161        );
162
163        #[no_mangle]
164        unsafe extern "C" fn rust_start(mut stack: *const usize) -> ! {
165            use ::core::ffi::c_char;
166
167            let argc = *stack;
168            stack = stack.offset(1);
169
170            let args = ::core::slice::from_raw_parts(stack as *const *const c_char, argc);
171            stack = stack.add(argc);
172            stack = stack.offset(1);
173
174            let env_vars = $crate::EnvVars::new(stack as *const *const c_char);
175
176            $user_entry(args, env_vars)
177        }
178    };
179}