Bootstrap a Rust application
Using shield::shield_main!() for Bare-Metal / Sentry Targets
When building Rust applications for Camelot OS / Sentry, you are not running on a traditional operating system like Linux or macOS. Instead, your program runs directly on top of the Sentry kernel, without a standard C runtime or OS loader.
This is where the following construct comes into play:
#[cfg(target_os = "none")] shield::shield_main!();Let’s break down what this does, when you need it, and how it works with sentry-uapi.
Why target_os="none" ?
In Rust, the compilation target determines what runtime assumptions are made. For example:
| Target | Meaning |
|---|---|
linux, macos | Full OS with libc, process loader, syscalls |
none | Bare-metal / no operating system |
Camelot OS applications are compiled with:
This tells Rust:
No libc
No default
mainentrypointNo OS startup code
You must provide your own entrypoint
The #[cfg(target_os = "none")] attribute ensures the code is only compiled for bare-metal Sentry targets, and not for local development or tests.
What Is shield::shield_main!() ?
shield::shield_main!() is a procedural macro provided by the shield runtime crate. Its job is to:
Define the program entrypoint
Set up the execution environment
Connect Rust code to the Sentry kernel ABI
Safely call your Rust
main()function
In other words, it replaces what crt0, the ELF loader, and libc would normally do on a traditional OS.
Without this macro, your program would not start on Camelot OS.
What the Macro Does (Conceptually)
While the macro expands to target-specific assembly and Rust glue code, conceptually it does the following:
Defines a
_startsymbol (the kernel entrypoint)Initializes stack and registers
Sets up the Sentry syscall interface
Jumps into your Rust
main()functionHandles exit back to the kernel
This makes it possible for sentry-uapi syscalls to work correctly, because the kernel expects a well-defined ABI and calling convention.
Typical Usage in a Camelot Rust App
A minimal main.rs for a Sentry application usually looks like this:
#![no_std]
#![no_main]
use shield::prelude::*;
#[cfg(target_os = "none")]
shield::shield_main!();
fn main() {
// Your application logic
}Key points:
#![no_std]→ no standard library#![no_main]→ disable Rust’s default runtimeshield_main!()→ installs the Sentry entrypointmain()→ still written like normal Rust
The macro bridges the gap between the kernel and your Rust code.
Relationship to sentry-uapi
sentry-uapi provides:
Raw syscalls
Shared kernel types
Exchange buffer helpers
But it assumes:
A valid entrypoint exists
The process ABI matches the Sentry kernel
Syscall registers and stack are correctly initialized
shield::shield_main!() ensures all of those assumptions are true.
Think of the roles like this:
| Component | Responsibility |
|---|---|
shield_main!() | Program startup & ABI |
sentry-uapi | Kernel communication |
| Your app | Business logic |
They are designed to work together.
Why the cfg Attribute Matters
Using:
Local testing on Linux/macOS
Unit tests
Deployment on Camelot OS
On non-Sentry targets:
The macro is not compiled
You can still build and test normally
This pattern is strongly recommended for Sentry-based applications.
Common Mistakes to Avoid
- Forgetting
#![no_main] - Calling
shield_main!()unconditionally - Using
stdon anonetarget - Expecting
main()to be the ELF entrypoint
If your app “builds but never starts,” the missing or misconfigured shield_main!() macro is often the cause.
Summary
target_os = "none"means bare-metal / Sentryshield::shield_main!()provides the program entrypointIt replaces the OS loader and C runtime
It is required for
sentry-uapisyscalls to functionThe
cfgguard allows cross-platform development
This macro is the foundation that allows Rust applications to run safely and predictably on Camelot OS.
