vexide_startup/lib.rs
1//! User program startup and runtime support.
2//!
3//! This crate provides runtime infrastructure for booting vexide programs from Rust code. This
4//! infrastructure includes a more optimized heap allocator, differential uploading support, and a
5//! panic hook that draws panic messages to the screen and captures backtraces.
6//!
7//! - User code begins at an assembly routine called `_vexide_boot`, which sets up the stack section
8//! before jumping to the `_start` routine defined in libstd, which then calls the `main`
9//! function.
10//!
11//! - From there, consumers must call the [`startup`] function to finish the startup process by
12//! applying differential upload patches, claiming heap space, and setting up this crate's custom
13//! panic hook.
14//!
15//! This crate does NOT provide a `libc` [crt0 implementation]. No `libc`-style global constructors
16//! are called. This means that the [`__libc_init_array`] function must be explicitly called if you
17//! wish to link to C libraries.
18//!
19//! [crt0 implementation]: https://en.wikipedia.org/wiki/Crt0
20//! [`__libc_init_array`]: https://maskray.me/blog/2021-11-07-init-ctors-init-array
21//!
22//! # Example
23//!
24//! This is an example of a minimal user program that boots without using the main vexide runtime or
25//! the `#[vexide::main]` macro.
26//!
27//! ```
28//! use vexide_core::program::{CodeSignature, ProgramOptions, ProgramOwner, ProgramType};
29//!
30//! // SAFETY: This symbol is unique and is being used to start the runtime.
31//! fn main() {
32//! // Setup the heap, zero bss, apply patches, etc...
33//! unsafe {
34//! vexide_startup::startup();
35//! }
36//!
37//! // Rust code goes here!
38//! }
39//!
40//! // Program header (placed at the first 20 bytes on the binary).
41//! #[unsafe(link_section = ".code_signature")]
42//! #[used] // This is needed to prevent the linker from removing this object in release builds
43//! static CODE_SIG: CodeSignature = CodeSignature::new(
44//! ProgramType::User,
45//! ProgramOwner::Partner,
46//! ProgramOptions::empty(),
47//! );
48//! ```
49
50#[cfg(feature = "allocator")]
51pub mod allocator;
52pub mod banner;
53
54#[cfg(all(target_os = "vexos", feature = "abort-handler"))]
55mod abort_handler;
56mod error_report;
57#[cfg(feature = "panic-hook")]
58mod panic_hook;
59#[cfg(target_os = "vexos")]
60mod patcher;
61mod sdk;
62
63// Linkerscript Symbols
64//
65// All of these external symbols are defined by either Rust's armv7a-vex-v5 linkerscript, our ours
66// (see link/vexide.ld). These symbols don't have real types or values, but a pointer to them points
67// to the address of their location defined in the linkerscript.
68#[cfg(target_os = "vexos")]
69unsafe extern "C" {
70 static mut __heap_start: u8;
71 static mut __heap_end: u8;
72
73 static mut __user_ram_start: u8;
74
75 static mut __linked_file_start: u8;
76 static mut __linked_file_end: u8;
77
78 static mut __bss_start: u32;
79 static mut __bss_end: u32;
80}
81
82/// vexide's first-stage boot routine.
83///
84/// This is the true entrypoint of vexide, containing the first instructions of user code executed
85/// before anything else. This is written in assembly to ensure that it stays the same across
86/// compilations (a requirement of the patcher),
87///
88/// This routine loads the stack pointer to the stack region specified in our linkerscript, makes a
89/// copy of program memory for the patcher if needed, then branches to the Rust entrypoint (_start)
90/// defined in libstd.
91#[unsafe(link_section = ".vexide_boot")]
92#[unsafe(no_mangle)]
93#[unsafe(naked)]
94#[cfg(target_os = "vexos")]
95unsafe extern "C" fn _vexide_boot() {
96 core::arch::naked_asm!(
97 // Load the stack pointer to point to our stack section.
98 //
99 // This technically isn't required, as VEXos already sets up a stack for CPU1, but that
100 // stack is relatively small and we have more than enough memory available to us for this.
101 //
102 // SAFETY: Doing this should be safe, since VEXos doesn't seem to use its existing stack
103 // after calling user code. This operation is safe assuming that the variables on the
104 // previous stack are never read or written to during execution of the program.
105 "ldr sp, =__stack_top",
106 // Before any Rust code runs, we need to memcpy the currently running program in memory to
107 // the `.patcher_base` section if a patch file needs to be applied.
108 //
109 // We do this since we need an unmodified copy of the base binary to run the patcher
110 // correctly. Since Rust code will modify the binary by writing to `.data` and `.bss`, we
111 // can't reference program memory in the patcher so we must make a carbon copy of it before
112 // any Rust code gets the chance to modify these sections.
113
114 // Check if a patch file is loaded into memory by reading the first four bytes at the
115 // expected location (0x07A00000) and checking if they equal the magic header value of
116 // 0xB1DF.
117 "ldr r0, =__patcher_patch_start",
118 "ldr r0, [r0]",
119 "ldr r1, ={patch_magic}",
120 "cmp r0, r1", // r0 == 0xB1DF?
121 // Prepare to memcpy binary to 0x07C00000
122 "ldr r0, =__patcher_base_start", // memcpy dest -> r0
123 "ldr r1, =__user_ram_start", // memcpy src -> r1
124 "ldr r2, =__patcher_patch_start+12", // Base binary len is stored as metadata in the patch.
125 "ldr r2, [r2]", // memcpy size -> r2
126 // Do the memcpy if patch magic is present (we checked this in our `cmp` instruction).
127 "bleq __overwriter_aeabi_memcpy",
128 // Jump to the Rust entrypoint.
129 "b _start",
130 patch_magic = const patcher::PATCH_MAGIC,
131 )
132}
133
134/// vexide runtime initialization.
135///
136/// This function performs some prerequisites to allow vexide programs to properly run. It must be
137/// called once at startup before any heap allocation is done. When using `vexide`, this function is
138/// already called for you by the `#[vexide::main]` macro, so there's no need to call it yourself
139/// (doing so would cause **undefined behavior**).
140///
141/// This function does the following initialization:
142///
143/// - Sets up the global heap allocator by [claiming](crate::allocator::claim) the default heap
144/// region if the `allocator` feature is specified.
145/// - Applies [differential upload patches] to the program if a patch file exists in memory and
146/// restarts the program if necessary.
147/// - Registers a custom [panic hook] to allow panic messages to be drawn to the screen and
148/// backtrace to be collected. This can be enabled/disabled using the `panic-hook` and `backtrace`
149/// features.
150///
151/// [differential upload patches]: https://vexide.dev/docs/building-uploading/#uploading-strategies
152/// [panic hook]: https://doc.rust-lang.org/std/panic/fn.set_hook.html
153///
154/// # Examples
155///
156/// ```
157/// // Not using the `#[vexide::main]` macro here.
158/// fn main() {
159/// unsafe {
160/// vexide_startup::startup(); // Call this once at the start of main.
161/// }
162///
163/// println!("Hi.");
164/// }
165/// ```
166///
167/// # Safety
168///
169/// Must be called *once and only once* at the start of program execution.
170#[inline]
171#[allow(clippy::needless_doctest_main)]
172pub unsafe fn startup() {
173 #[cfg(target_os = "vexos")]
174 unsafe {
175 // Initialize the heap allocator in our heap region defined in the linkerscript
176 #[cfg(feature = "allocator")]
177 crate::allocator::claim(&raw mut __heap_start, &raw mut __heap_end);
178
179 // If this link address is 0x03800000, this implies we were uploaded using differential
180 // uploads by cargo-v5 and may have a patch to apply.
181 if vexide_core::program::linked_file().addr() == (&raw const __user_ram_start).addr() {
182 patcher::patch();
183 }
184
185 // Reclaim 6mb memory region occupied by patches and program copies as heap space.
186 #[cfg(feature = "allocator")]
187 crate::allocator::claim(&raw mut __linked_file_start, &raw mut __linked_file_end);
188
189 #[cfg(feature = "abort-handler")]
190 abort_handler::install_vector_table();
191 }
192
193 // Register custom panic hook if needed.
194 #[cfg(feature = "panic-hook")]
195 std::panic::set_hook(Box::new(panic_hook::hook));
196}