diff --git a/Cargo.toml b/Cargo.toml index 19f5c1c..7448f9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +members = ["macros"] + [package] name = "origin" version = "0.12.1" @@ -21,6 +24,9 @@ memoffset = { version = "0.9.0", optional = true } log = { version = "0.4.14", default-features = false, optional = true } rustix-futex-sync = "0.1.0" +# Provides "origin::main" macro +origin-macros = { version = "0.1.0", path = "./macros", optional = true } + # Optional logging backends. You can use any external logger, but using these # features allows origin to initialize the logger before main, so that you can # see the log messages emitted before main is called. @@ -55,6 +61,8 @@ rustc-dep-of-std = [ "libc/rustc-dep-of-std", ] +macros = ["dep:origin-macros"] + # Use origin's implementation of program startup and shutdown. origin-program = [] diff --git a/example-crates/origin-start-lto/Cargo.toml b/example-crates/origin-start-lto/Cargo.toml index bd5496e..1eb5f57 100644 --- a/example-crates/origin-start-lto/Cargo.toml +++ b/example-crates/origin-start-lto/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] # Origin can be depended on just like any other crate. For no_std, disable # the default features, and the desired features. -origin = { path = "../..", default-features = false, features = ["origin-program", "origin-thread", "origin-start"] } +origin = { path = "../..", default-features = false, features = ["origin-program", "origin-thread", "origin-start", "macros"] } # Crates to help writing no_std code. atomic-dbg = { version = "0.1.8", default-features = false } diff --git a/example-crates/origin-start-lto/src/main.rs b/example-crates/origin-start-lto/src/main.rs index a998553..e35b361 100644 --- a/example-crates/origin-start-lto/src/main.rs +++ b/example-crates/origin-start-lto/src/main.rs @@ -5,6 +5,7 @@ #![allow(internal_features)] #![feature(lang_items)] #![feature(core_intrinsics)] +#![feature(naked_functions)] extern crate alloc; extern crate compiler_builtins; @@ -26,8 +27,8 @@ extern "C" fn eh_personality() {} #[global_allocator] static GLOBAL_ALLOCATOR: rustix_dlmalloc::GlobalDlmalloc = rustix_dlmalloc::GlobalDlmalloc; -#[no_mangle] -extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { +#[origin::main] +fn main() -> i32 { eprintln!("Hello from main thread"); at_exit(Box::new(|| eprintln!("Hello from an at_exit handler"))); @@ -53,5 +54,6 @@ extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } eprintln!("Goodbye from main"); - exit(0); + + 0 } diff --git a/example-crates/origin-start-no-alloc/Cargo.toml b/example-crates/origin-start-no-alloc/Cargo.toml index b6a71ae..4948cd9 100644 --- a/example-crates/origin-start-no-alloc/Cargo.toml +++ b/example-crates/origin-start-no-alloc/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] # Origin can be depended on just like any other crate. For no_std, disable # the default features, and the desired features. -origin = { path = "../..", default-features = false, features = ["origin-program", "origin-start"] } +origin = { path = "../..", default-features = false, features = ["origin-program", "origin-start", "macros"] } # Crates to help writing no_std code. atomic-dbg = { version = "0.1.8", default-features = false } diff --git a/example-crates/origin-start-no-alloc/src/main.rs b/example-crates/origin-start-no-alloc/src/main.rs index 8500ec1..b0b6515 100644 --- a/example-crates/origin-start-no-alloc/src/main.rs +++ b/example-crates/origin-start-no-alloc/src/main.rs @@ -5,11 +5,11 @@ #![allow(internal_features)] #![feature(lang_items)] #![feature(core_intrinsics)] +#![feature(naked_functions)] extern crate compiler_builtins; use atomic_dbg::{dbg, eprintln}; -use origin::program::*; #[panic_handler] fn panic(panic: &core::panic::PanicInfo<'_>) -> ! { @@ -20,12 +20,12 @@ fn panic(panic: &core::panic::PanicInfo<'_>) -> ! { #[lang = "eh_personality"] extern "C" fn eh_personality() {} -#[no_mangle] -extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { +#[origin::main] +fn start() -> i32 { eprintln!("Hello!"); // Unlike origin-start, this example can't create threads because origin's // thread support requires an allocator. - exit(0); + 0 } diff --git a/example-crates/origin-start-tiny/Cargo.toml b/example-crates/origin-start-tiny/Cargo.toml index 9616543..6e7fea1 100644 --- a/example-crates/origin-start-tiny/Cargo.toml +++ b/example-crates/origin-start-tiny/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] # Origin can be depended on just like any other crate. For no_std, disable # the default features, and the desired features. -origin = { path = "../..", default-features = false, features = ["origin-program", "origin-start"] } +origin = { path = "../..", default-features = false, features = ["origin-program", "origin-start", "macros"] } # Crates to help writing no_std code. compiler_builtins = { version = "0.1.101", features = ["mem"] } diff --git a/example-crates/origin-start-tiny/src/main.rs b/example-crates/origin-start-tiny/src/main.rs index 526028d..18d5e0a 100644 --- a/example-crates/origin-start-tiny/src/main.rs +++ b/example-crates/origin-start-tiny/src/main.rs @@ -5,9 +5,10 @@ #![allow(internal_features)] #![feature(lang_items)] #![feature(core_intrinsics)] +#![feature(naked_functions)] -extern crate origin; extern crate compiler_builtins; +extern crate origin; #[panic_handler] fn panic(_panic: &core::panic::PanicInfo<'_>) -> ! { @@ -17,7 +18,7 @@ fn panic(_panic: &core::panic::PanicInfo<'_>) -> ! { #[lang = "eh_personality"] extern "C" fn eh_personality() {} -#[no_mangle] -extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { +#[origin::start] +fn main() -> i32 { 42 } diff --git a/example-crates/origin-start/Cargo.toml b/example-crates/origin-start/Cargo.toml index 6182e2b..55e55a7 100644 --- a/example-crates/origin-start/Cargo.toml +++ b/example-crates/origin-start/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] # Origin can be depended on just like any other crate. For no_std, disable # the default features, and the desired features. -origin = { path = "../..", default-features = false, features = ["origin-program", "origin-thread", "origin-start"] } +origin = { path = "../..", default-features = false, features = ["origin-program", "origin-thread", "origin-start", "macros"] } # Crates to help writing no_std code. atomic-dbg = { version = "0.1.8", default-features = false } diff --git a/example-crates/origin-start/src/main.rs b/example-crates/origin-start/src/main.rs index a998553..89280ab 100644 --- a/example-crates/origin-start/src/main.rs +++ b/example-crates/origin-start/src/main.rs @@ -5,6 +5,7 @@ #![allow(internal_features)] #![feature(lang_items)] #![feature(core_intrinsics)] +#![feature(naked_functions)] extern crate alloc; extern crate compiler_builtins; @@ -26,8 +27,8 @@ extern "C" fn eh_personality() {} #[global_allocator] static GLOBAL_ALLOCATOR: rustix_dlmalloc::GlobalDlmalloc = rustix_dlmalloc::GlobalDlmalloc; -#[no_mangle] -extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { +#[origin::main] +fn main() -> i32 { eprintln!("Hello from main thread"); at_exit(Box::new(|| eprintln!("Hello from an at_exit handler"))); diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..2ea5a08 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "origin-macros" +version = "0.1.0" +edition = "2021" +authors = [ + "Dan Gohman ", + "Federico Maria Morrone ", +] +description = "Macros for origin crate" +documentation = "https://docs.rs/origin-macros" +license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" +repository = "https://github.com/sunfishcode/origin" +keywords = ["linux"] +categories = ["no-std"] +include = ["src", "Cargo.toml", "../COPYRIGHT", "../LICENSE*", "README.md"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.66" +quote = "1.0.33" +syn = { version = "2.0.31", features = ["full"] } diff --git a/macros/README.md b/macros/README.md new file mode 100644 index 0000000..cdc6a98 --- /dev/null +++ b/macros/README.md @@ -0,0 +1 @@ +# Origin Macros diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..4e19830 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,88 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +#[proc_macro_attribute] +pub fn main(_attr: TokenStream, input: TokenStream) -> TokenStream { + let main_fn: ItemFn = parse_macro_input!(input); + let main_fn_ident = &main_fn.sig.ident; + let asm_impl = asm_impl(); + + quote! { + #main_fn + + #[naked] + #[no_mangle] + unsafe extern "C" fn _start() -> ! { + unsafe fn entry(mem: *mut usize) -> ! { + let (argc, argv, envp) = origin::program::compute_args(mem); + origin::program::init_runtime(mem, envp); + origin::program::exit(#main_fn_ident()) + } + + #asm_impl + } + } + .into() +} + +/// Provides the asm implementation to start the program +fn asm_impl() -> TokenStream2 { + quote! { + // Jump to `entry`, passing it the initial stack pointer value as an + // argument, a null return address, a null frame pointer, and an aligned + // stack pointer. On many architectures, the incoming frame pointer is + // already null. + + #[cfg(target_arch = "x86_64")] + core::arch::asm!( + "mov rdi, rsp", // Pass the incoming `rsp` as the arg to `entry`. + "push rbp", // Set the return address to zero. + "jmp {entry}", // Jump to `entry`. + entry = sym entry, + options(noreturn), + ); + + #[cfg(target_arch = "aarch64")] + core::arch::asm!( + "mov x0, sp", // Pass the incoming `sp` as the arg to `entry`. + "mov x30, xzr", // Set the return address to zero. + "b {entry}", // Jump to `entry`. + entry = sym entry, + options(noreturn), + ); + + #[cfg(target_arch = "arm")] + core::arch::asm!( + "mov r0, sp\n", // Pass the incoming `sp` as the arg to `entry`. + "mov lr, #0", // Set the return address to zero. + "b {entry}", // Jump to `entry`. + entry = sym entry, + options(noreturn), + ); + + #[cfg(target_arch = "riscv64")] + core::arch::asm!( + "mv a0, sp", // Pass the incoming `sp` as the arg to `entry`. + "mv ra, zero", // Set the return address to zero. + "mv fp, zero", // Set the frame address to zero. + "tail {entry}", // Jump to `entry`. + entry = sym entry, + options(noreturn), + ); + + #[cfg(target_arch = "x86")] + core::arch::asm!( + "mov eax, esp", // Save the incoming `esp` value. + "push ebp", // Pad for alignment. + "push ebp", // Pad for alignment. + "push ebp", // Pad for alignment. + "push eax", // Pass saved the incoming `esp` as the arg to `entry`. + "push ebp", // Set the return address to zero. + "jmp {entry}", // Jump to `entry`. + entry = sym entry, + options(noreturn), + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 68d17ee..4f85f2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,9 @@ #![deny(lossy_provenance_casts)] #![no_std] +#![allow(dead_code)] // FIXME: this is just so tests pass while I implement everything + + #[cfg(all(feature = "alloc", not(feature = "rustc-dep-of-std")))] extern crate alloc; @@ -40,76 +43,8 @@ pub mod thread; #[cfg_attr(target_arch = "arm", path = "arch/arm.rs")] mod arch; -/// The program entry point. -/// -/// # Safety -/// -/// This function should never be called explicitly. It is the first thing -/// executed in the program, and it assumes that memory is laid out according -/// to the operating system convention for starting a new program. -#[cfg(feature = "origin-program")] -#[cfg(feature = "origin-start")] -#[naked] -#[no_mangle] -unsafe extern "C" fn _start() -> ! { - use core::arch::asm; - use program::entry; - - // Jump to `entry`, passing it the initial stack pointer value as an - // argument, a null return address, a null frame pointer, and an aligned - // stack pointer. On many architectures, the incoming frame pointer is - // already null. - - #[cfg(target_arch = "x86_64")] - asm!( - "mov rdi, rsp", // Pass the incoming `rsp` as the arg to `entry`. - "push rbp", // Set the return address to zero. - "jmp {entry}", // Jump to `entry`. - entry = sym entry, - options(noreturn), - ); - - #[cfg(target_arch = "aarch64")] - asm!( - "mov x0, sp", // Pass the incoming `sp` as the arg to `entry`. - "mov x30, xzr", // Set the return address to zero. - "b {entry}", // Jump to `entry`. - entry = sym entry, - options(noreturn), - ); - - #[cfg(target_arch = "arm")] - asm!( - "mov r0, sp\n", // Pass the incoming `sp` as the arg to `entry`. - "mov lr, #0", // Set the return address to zero. - "b {entry}", // Jump to `entry`. - entry = sym entry, - options(noreturn), - ); - - #[cfg(target_arch = "riscv64")] - asm!( - "mv a0, sp", // Pass the incoming `sp` as the arg to `entry`. - "mv ra, zero", // Set the return address to zero. - "mv fp, zero", // Set the frame address to zero. - "tail {entry}", // Jump to `entry`. - entry = sym entry, - options(noreturn), - ); - - #[cfg(target_arch = "x86")] - asm!( - "mov eax, esp", // Save the incoming `esp` value. - "push ebp", // Pad for alignment. - "push ebp", // Pad for alignment. - "push ebp", // Pad for alignment. - "push eax", // Pass saved the incoming `esp` as the arg to `entry`. - "push ebp", // Set the return address to zero. - "jmp {entry}", // Jump to `entry`. - entry = sym entry, - options(noreturn), - ); -} +#[cfg(feature = "macros")] +pub use origin_macros::main; /// A program entry point similar to `_start`, but which is meant to be called /// by something else in the program rather than the OS. diff --git a/src/program.rs b/src/program.rs index 5b75f95..4c4bc6e 100644 --- a/src/program.rs +++ b/src/program.rs @@ -79,8 +79,9 @@ pub(super) unsafe extern "C" fn entry(mem: *mut usize) -> ! { exit(status) } +/// TODO: docs #[cfg(any(feature = "origin-start", feature = "external-start"))] -unsafe fn compute_args(mem: *mut usize) -> (i32, *mut *mut u8, *mut *mut u8) { +pub unsafe fn compute_args(mem: *mut usize) -> (i32, *mut *mut u8, *mut *mut u8) { use linux_raw_sys::ctypes::c_uint; let argc = *mem as c_int; @@ -95,9 +96,10 @@ unsafe fn compute_args(mem: *mut usize) -> (i32, *mut *mut u8, *mut *mut u8) { (argc, argv, envp) } +/// TODO: docs #[cfg(any(feature = "origin-start", feature = "external-start"))] #[allow(unused_variables)] -unsafe fn init_runtime(mem: *mut usize, envp: *mut *mut u8) { +pub unsafe fn init_runtime(mem: *mut usize, envp: *mut *mut u8) { // Explicitly initialize `rustix` so that we can control the initialization // order. #[cfg(feature = "param")] @@ -114,9 +116,10 @@ unsafe fn init_runtime(mem: *mut usize, envp: *mut *mut u8) { initialize_main_thread(mem.cast()); } +/// TODO: docs #[cfg(any(feature = "origin-start", feature = "external-start"))] #[allow(unused_variables)] -unsafe fn call_user_code(argc: c_int, argv: *mut *mut u8, envp: *mut *mut u8) -> i32 { +pub unsafe fn call_user_code(argc: c_int, argv: *mut *mut u8, envp: *mut *mut u8) -> i32 { extern "C" { fn main(argc: c_int, argv: *mut *mut u8, envp: *mut *mut u8) -> c_int; } @@ -140,7 +143,7 @@ unsafe fn call_user_code(argc: c_int, argv: *mut *mut u8, envp: *mut *mut u8) -> /// Call the constructors in the `.init_array` section. #[cfg(any(feature = "origin-start", feature = "external-start"))] #[cfg(feature = "init-fini-arrays")] -unsafe fn call_ctors(argc: c_int, argv: *mut *mut u8, envp: *mut *mut u8) { +pub unsafe fn call_ctors(argc: c_int, argv: *mut *mut u8, envp: *mut *mut u8) { use core::ffi::c_void; extern "C" {