… and its underlying ecosystem.
To draw from the tagline for the Rust project
A language empowering everyone to build reliable and efficient software.
seL4 is exceptional with regards to reliability and efficiency. The goal here, is to bring the "empowering everyone" qualities of Rust to seL4, by way of seamlessly reaching into seL4 development from Rust.
Rust is highly attractive to the dev-community at large. Stack overflow surveys has it as "most loved" seven years running, and in 2022, it was surveyed as being "most wanted".
To make writing Rust for seL4 ejoyable and productive "out of the box" is to make seL4 development an attractive endeavour to a wider community
TODO:? Add motivations that explicitly outline the benefits of rust?
Currently, there are a lot of limitations and considerations needed in order to develop with Rust. The choice between C and Rust would ideally be solely based on language qualities.
This could be an RFC in and of itself. There are independant projects out there, each a demonstration that there is a will, desire, and feasability to making Rust a supported tool for building seL4 based systems. In way to move towards a goal, and what reaching that goal would look like, is ambiguous at this point.
The Rust ecosystem is a potentially powerful tool for improving the ability to organise, maintain, and make available, libraries and tooling. For specific examples.
Consider this (hypothetical) workflow. No other build dependancies required.
# Create your hello world
cargo new sel4_hello_world
cd sel4_hello_world
# Import the core sel4 library, opting into a
# rapid-prototyping utility library
cargo add --features utils sel4
# annotate the main function as the root server
sed -i '1i#[sel4::root]' src/main.rs
# Simulate a default(native-platform-based QEMU) "hello-world" in debug mode,
cargo run
# Simulate in release mode, using features provided by `utils` to write to tty
cargo run --release
In the above examples, we have set up a hello-world system with no more (surfaced) complexity than an async hello-world (i.e. the need to annotate the main function to bring in the required runtime), followed by running a simulation.
Rusts rich type-system, helpful compiler errors, lifetime management, and other toolings dramatically elevate the value of exposing an interface that conforms to rust idioms, take the for example:
// instead of
sel4::reply(42); // (valid where a CPtr is like `#define seL4_Word seL4_CPtr;`)
// use an interface based on:
pub struct ReplyCPtr(CPtr);
// With a reply interface like so:
sel4::reply(reply_cap_type);
Consider the following code in C:
int get_and_use_foo_struct() {
FooStruct *foo = malloc(sizeof(*foo));
if foo == 0 {
return 0;
}
let result = fallible_call_getter(foo);
if result != 0 {
return result;
// Continue on happy path
let extracted = extract_useful_non_zero(foo);
free(foo);
// and let's blow things up...
FooStruct *null_foo = 0;
// try to fill up with a null-pointer
int forgotten_result = fallible_call_getter(null_foo);
/* undefined behavior */
}
The rust equivilent might be:
// API use:
// inside a function with a return type:
// Result<usize, E> where E: From<FooError>
let usize_from_foo_struct = FooStruct::try_get_foo()
// Handle the happy-path with `.map`
// Use the conversion trait to get the usize
// Use `?` to early-return the error
.map(Into::into)?;
// why not log it, eh?
println!("got {}", usize_from_foo_struct);
// return the value
Ok(usize_from_foo_struct)
To make this possible, you would build an interface that might look like:
// Rust equivilent of Cs result enum. note lack of `Nil`
enum FooError {
GetError,
SomeError,
OtherError
}
// Use Rust result wrapping pattern
pub struct FooResult<T> = Result<T, FooError>;
// Make get-error handling a compiler-enforced requirement
impl FooStruct{
fn try_get_foo() -> FooResult<Self> {
Self::fallible_getter()
}
}
impl From<FooStruct> for usize { /* snip */ }
impl From<FooError> for $MORE_GENERAL_ERROR { /* snip */ }
If the rust ecosystem was already mature when seL4 rust development started, what would the project look like if Rust was considered the primary ecosystem for development?
There is already prior work done, where it seems this question has been asked, and work done to realise the vision that comes from this question:
Does $STRATEGY method of improving seL4 development in the Rust ecosystem increase the mantenance burden long term?
E.g. Having to maintain two libsel4 implementations (one for C, one for Rust), vs just having a Rust interface that can be called from an auto-generated C library, via an FFI.
Result
// Private helper-function
fn do_cnode_copy(/*snip */) -> Result<(), SyscallError> {/* snip */}
// api usage:
let cnode = sel4::CNode::/* constructor method */(/*snip */)?;
let mut copied_node: CNode = cnode.copy(/* snip */)?;
#[cfg(...)]
for conditional compilation/exporting// in src/arch/mod.rs
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "aarch64")]
pub use aarch64::*;
usize
instead of seL4_Word
The compiler auto-magically fills the same requirements of seL4_Word
definitions
Put public wrappers around private data, and use intenal implementations to ensure that invalid data will return an Err
instead of on Ok
// in src/types.rs
pub struct CPtr(usize); // equivilent to seL4_CPtr
// in TODO: module design for capability file locations
pub struct TCB(CPtr);
impl TCB {
// The only(?) way to provide a `usize` to the kernel interface when a TCB cability is required
pub fn create(/* snip */) -> Result<Self, /* snip */> {/* snip */}
}
TODO: (RFC author not objective)
A lot of work has already been done to make rust compatable with seL4, in many different projects.
The IceCap Framework provides a Rust runtime, Rust libraries, system composition tools, and a build system for constructing Rust userspaces for seL4-based systems. IceCap self-describes as opinionated. According to the authors, many of the design decisions which compose the IceCap Framework, across varying facets and scopes, were made with the design and engineering process of the IceCap Hypervisor in mind. However, the IceCap Framework is successful as the basis for the IceCap Hypervisor, which serves as a concrete, open-source example of a seL4-based system with a userspace written almost entirely in Rust.
See the (IceCap Framework Tutorial)[https://gitlab.com/arm-research/security/icecap/icecap/-/tree/main/examples#tutorial] for an introduction to some of the facets of the framework which relate to this discussion.
Perhaps example closest in scope/features to what this RFC is about. Following initial setup in README instructions, the following shell cli runs a hello-world:
cargo fel4 new my-project
cd my-project
cargo fel4 simulate
Some key contributions of this project to bringing seL4 into the rust ecosystem:
no_std
functionTODO
Originally an effort to grok the seL4 API, it became a PR to discus what a Rusty version of the API might look like.
A microkernel based OS written in rust. Though the seL4 kernel is entirely implemented in C, the Redox project could be considered a reference point regarding what the upper-bound might look like when it comes to ease of developing an OS with seL4 and Rust.
Where on the spectrum to set our ambitions. From "low-hanging fruit only" to "wildly ambitious". Some thoughts about what might be involved between the two
Wildly ambitious:
Low-hanging fruit:
Some where in between:
The matter of threading dependencies between cmake/kernel and rust build(s):
I’m not saying it (low-level lib) shouldn’t end up as a crate on crates.io, I think that might make sense, it’s just that the seL4 build should not depend on a Rust build, but only the other way around (Rust build can depend on or invoke a seL4 build).
a build.rs
file can handle that, perhaps. One question that might need answering,
Are we expecting cargo to invoke the seL4 build system and produce a seL4 binary to package with user space?
The answer to this question can vary. For the things that
how does that mesh with the crate structure Rust expects? Can seL4 releases + bottom-level (untyped) Rust libraries be generated and packaged as a crate other work can depend on? If yes, what would working on a seL4 dev version look like where maybe no crate is published yet? (This would be mostly for CI during seL4 development, not so much for people wanting to use seL4).
how does support for the different config options of seL4 work in Rust builds? Can everything be mapped to corresponding concepts in Rust builds?
For static/default configurations, you can use .cargo/config.toml
There is also the corrosion cmake is a tool that helps integrate rust into a cmake project
I presume target architecture, platform, etc would be selected in the cargo.toml file? What would an example look like?
// src/arch/mod.rs
#[cfg(target = "aarch64")]
mod aarch64;
#[cfg(target = "aarch64")]
pub use aarch64::*;
#[cfg(target = "x86_64")]
mod x86_64;
#[cfg(target = "x86_64")]
pub use x86_64::*;