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}