Add `libtock_runtime` startup code. This includes: 1. `rust_start`: The first Rust code to run in a process 1. `set_main!`: Used to specify `main` in a process binary 1. `stack_size!`: Used to specify the stack size in a process binary 1. Documentation of the startup sequence, including the 3 above steps. The documentation refers to a two things that haven't been implemented yet: 1. Debug syscalls performed by `rust_start` -- unimplemented because `memop` is still unimplemented. 1. The `exit` system call.
diff --git a/core/runtime/src/lib.rs b/core/runtime/src/lib.rs index b0daf03..7e2e1ca 100644 --- a/core/runtime/src/lib.rs +++ b/core/runtime/src/lib.rs
@@ -21,6 +21,8 @@ #![feature(asm)] #![no_std] +mod startup; + /// TockSyscalls implements `libtock_platform::Syscalls`. pub struct TockSyscalls;
diff --git a/core/runtime/src/startup.rs b/core/runtime/src/startup.rs new file mode 100644 index 0000000..f99d402 --- /dev/null +++ b/core/runtime/src/startup.rs
@@ -0,0 +1,61 @@ +//! Runtime components related to process startup. + +/// `set_main!` is used to tell `libtock_runtime` where the process binary's +/// `main` function is. The process binary's `main` function must have the +/// signature `FnOnce() -> !`. +/// +/// # Example +/// ``` +/// libtock_runtime::set_main!{main}; +/// +/// fn main() -> ! { /* Omitted */ } +/// ``` +// set_main! generates a function called `libtock_unsafe_main`, which is called +// by `rust_start`. The function has `unsafe` in its name because implementing +// it is `unsafe` (it *must* have the signature `libtock_unsafe_main() -> !`), +// but there is no way to enforce the use of `unsafe` through the type system. +// This function calls the client-provided function, which enforces its type +// signature. +#[macro_export] +macro_rules! set_main { + {$name:ident} => { + #[no_mangle] + fn libtock_unsafe_main() -> ! { + $name() + } + } +} + +/// Executables must specify their stack size by using the `stack_size!` macro. +/// It takes a single argument, the desired stack size in bytes. Example: +/// ``` +/// stack_size!{0x400} +/// ``` +// stack_size works by putting a symbol equal to the size of the stack in the +// .stack_buffer section. The linker script uses the .stack_buffer section to +// size the stack. flash.sh looks for the symbol by name (hence #[no_mangle]) to +// determine the size of the stack to pass to elf2tab. +#[macro_export] +macro_rules! stack_size { + {$size:expr} => { + #[no_mangle] + #[link_section = ".stack_buffer"] + pub static mut STACK_MEMORY: [u8; $size] = [0; $size]; + } +} + +// rust_start is the first Rust code to execute in the process. It is called +// from start, which is written directly in assembly. +#[no_mangle] +extern "C" fn rust_start() -> ! { + // TODO: Call memop() to inform the kernel of the stack and heap sizes + + // locations. Also, perhaps we should support calling a heap initialization + // function? + + extern "Rust" { + fn libtock_unsafe_main() -> !; + } + unsafe { + libtock_unsafe_main(); + } +}
diff --git a/doc/Startup.md b/doc/Startup.md new file mode 100644 index 0000000..2e7c050 --- /dev/null +++ b/doc/Startup.md
@@ -0,0 +1,60 @@ +Startup +======= + +This document describes the `libtock_runtime` startup process, up until the +process binary's `main` starts executing. + +## Step 1: `start` assembly + +The first code to start executing is in a symbol called `start`, which is +written in handwritten assembly. These implementations are specific to each +architecture, and live in `runtime/asm`. This assembly does the following: + +1. Checks the initial program counter value against the correct `start` address. + This verifies the process was deployed at the direct address in non-volatile + storage. This is necessary because `libtock-rs` apps are statically-linked, + and an incorrect location would cause undefined behavior. If this check + fails, an error may be reported (if the `low_level_debug` capsule is present) + and the process terminates. +1. Moves the process break to make room for the stack, `.data`, and `.bss`. The + process break is the top of the process-accessible RAM. The process break is + initially moved to be shortly after the end of the `.bss` section (depending + on alignment constraints). +1. Initialize the stack. The initial stack pointer value is provided by the + linker script, which calculates it using a symbol called `STACK_MEMORY` in + the `.stack_buffer` section. +1. Copies `.data` from non-volatile storage into RAM. The .data section contains + read-write global variables (e.g. `static mut` values) that have nonzero + initial values. +1. Zeroes out `.bss`. `.bss` contains read-write global variables that have zero + initial values. +1. Calls `rust_start`. + +## Step 2: `rust_start` + +`rust_start` is the first Rust code to execute in a process. It is defined in +the `libtock_runtime::startup` module. It runs some higher-level initialization, +such as giving debug information (stack and heap addresses) to the kernel. +`rust_start` then calls `libtock_unsafe_main`. + +## Step 3: `libtock_unsafe_main` + +`libtock_unsafe_main` is a shim used to direct execution from `libtock_runtime` +to the process binary's `main` function. It is generated by the +`libtock_runtime::set_main!` macro. `libtock_unsafe_main` just calls the +user-provided `main` function. + +## Step 4: `main` + +At this point, the user's `main` starts executing, and `libtock_runtime` is no +longer in control. Note that unlike most Rust programs, `main` is expected to +*not* return. Process binaries should use the `exit` system call to terminate +when they are done executing (which may be done as the last statement of +`main`). + +## Appendix: Why `#![no_main]`? + +Writing a `#![no_std]` `bin` crate currently requires using either `#![no_main]` +or the `start` unstable feature. Because we want to move `libtock-rs` to stable +Rust eventually (hopefully soon after `asm` is stabilized), `libtock-rs` expects +process binaries to be `#![no_main]`.