diff --git a/.gitignore b/.gitignore index 088ba6b..1c7b3d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +# ignore built APE and debug executables +*.com +*.com.dbg +# download cosmo amalgamation +/libcosmo/* +!/libcosmo/README.md + # Generated by Cargo # will have compiled files and executables /target/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..624cb06 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hello_world" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd9493b --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# Actually Portable Executables with Cosmopolitan Libc and Rust + +This repository contains a simple `Hello world!` example in the [Rust][rust] +programming language, that builds with [Cosmopolitan Libc][cosmo]. + +I created a [custom compilation target][custom-target] for Rust, called +`x86_64-unknown-linux-cosmo`, to provide a build process the Cosmopolitan Libc +amalgamation and `cargo`. I followed the documentation in the [Rust +Embedonomicon][custom-embed] to create the target. + +An alternative method to build APEs with Rust would be to avoid `cargo`, just +use `rustc` or equivalent compiler to generate `.o` files, and then write a +shell script that does the linking with the expected flags. I have not tried +this method. + +## Build steps + +1. Download the Cosmopolitan Libc [amalgamation][amalg-download] into the `libcosmo` folder: + +```bash +cd libcosmo +wget https://justine.lol/cosmopolitan/cosmopolitan.zip +unzip cosmopolitan.zip +cd ../ +``` + +2. Download the necessary toolchain(s) and source code for Rust: + +```bash +rustup toolchain install nightly-x86_64-unknown-linux-gnu +rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu +``` + +3. run `cargo build` to get the debug executable. This uses a bash script that + removes unnecessary linker arguments. A recent version of `gcc` and `ld.bfd` + is required. + +```bash +cargo +nightly build -Zbuild-std=core,libc --target=./x86_64-unknown-linux-cosmo.json +``` + +4. run `objcopy` on the debug binary to obtain the APE: + +```bash +objcopy -SO binary ./target/x86_64-unknown-linux-cosmo/debug/hello_world.com.dbg ./hello_world.com +``` + +## What about the `std` crate? + +The `std` crate compiles successfully, but fails at the linker stage. Here's how +that can be tested: + +1. Change the source code in `src/main.rs` to use the commented out `main` + function and `#![restricted_std]`. + +2. In the source code for Rust's `std` crate, change a `cfg_if` in + `$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/backtrace/src/backtrace/mod.rs` + to not use the `noop` trace instead of depending on `libunwind`. + +```diff +--- mod.rs 2022-06-21 12:52:21.724053459 +0530 ++++ mod.rs 2022-06-21 13:05:50.948777093 +0530 +@@ -132,7 +132,7 @@ + pub(crate) mod miri; + use self::miri::trace as trace_imp; + pub(crate) use self::miri::Frame as FrameImp; +- } else if #[cfg( ++ } /* else if #[cfg( + any( + all( + unix, +@@ -154,7 +154,7 @@ + pub(crate) use self::dbghelp::Frame as FrameImp; + #[cfg(target_env = "msvc")] // only used in dbghelp symbolize + pub(crate) use self::dbghelp::StackFrame; +- } else { ++ } */ else { + mod noop; + use self::noop::trace as trace_imp; + pub(crate) use self::noop::Frame as FrameImp; +``` + +I haven't figured out what config I should give to `cargo` so I don't need to do +this. I find it surprising that `std` cannot be built without relying on +`libunwind`. + +3. The build command now changes to + +```bash +cargo +nightly build -Zbuild-std=core,alloc,panic_abort,libc,std -Zbuild-std-features= --target=./x86_64-unknown-linux-cosmo.json +``` + +4. At the linker stage you might find some of the following symbols are missing: + + - If `Unwind_Backtrace` or similar is missing, you need to check if step 2 is + done properly. + + - `open64`, `stat64`, `fstat64`, `__xpg_strerror_r`: these can be added to + Cosmopolitan Libc via aliases of the existing functions. + + - `pthread_key_create`,`pthread_setspecific`,`pthread_key_delete`,`pthread_getspecific` + -- these functions should not be needed in a single-threaded program, but + somehow they are still linked. Support for threads is currently being added + to Cosmopolitan Libc, but possibly the related code in the `std` crate can + be changed to avoid this issue. + +[rust]: https://rust-lang.org +[cosmo]: https://github.com/jart/cosmopolitan +[amalg-download]: https://justine.lol/cosmopolitan/download.html +[custom-target]: https://doc.rust-lang.org/rustc/targets/custom.html +[custom-embed]: https://docs.rust-embedded.org/embedonomicon/custom-target.html diff --git a/gcc-linker-wrapper.bash b/gcc-linker-wrapper.bash new file mode 100755 index 0000000..85cd605 --- /dev/null +++ b/gcc-linker-wrapper.bash @@ -0,0 +1,21 @@ +#!/bin/bash +set -eu + +declare -a args +args=() +for o in "$@" ; do + case $o in + "-lunwind") continue;; + "-lutil") continue;; + "-lrt") continue;; + "-lc") continue;; + "-lm") continue;; + "-lpthread") continue;; + "-lgcc") continue;; + "-Wl,-Bdynamic") continue;; + "-Wl,-Bstatic") continue;; + esac + args+=("$o") +done + +gcc "${args[@]}" diff --git a/libcosmo/README.md b/libcosmo/README.md new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..983b7d9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,31 @@ +#![no_main] +#![no_std] +#![feature(rustc_private)] + +extern crate libc; + +#[no_mangle] +pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize { + // Since we are passing a C string the final null character is mandatory + const HELLO: &'static str = "Hello, world! %d + %d = %d\n\0"; + let x: i32 = 1; + let y: i32 = 2; + unsafe { + libc::printf(HELLO.as_ptr() as *const _, x, y, x+y); + } + 0 +} + +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +/* std crate requires some external changes + +#![feature(restricted_std)] + +pub fn main() { + println!("hello pls cosmo rust APE"); +} +*/ diff --git a/x86_64-unknown-linux-cosmo.json b/x86_64-unknown-linux-cosmo.json new file mode 100644 index 0000000..63a9a7a --- /dev/null +++ b/x86_64-unknown-linux-cosmo.json @@ -0,0 +1,62 @@ +{ + "llvm-target": "x86_64-unknown-linux-musl", + "target-pointer-width": "64", + "arch": "x86_64", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", + "cpu": "x86-64", + "os":"linux", + "env": "musl", + "panic-strategy":"abort", + "requires-uwtable":false, + "dynamic-linking": false, + "executables": true, + "exe-suffix": ".com.dbg", + "emit-debug-gdb-scripts":false, + "crt-static-default": true, + "crt-static-respected": false, + "linker-is-gnu":true, + "allows-weak-linkage":true, + "has-rpath": false, + "has-thread-local": false, + "is-builtin": false, + "trap-unreachable":true, + "position-independent-executables": false, + "static-position-independent-executables": false, + "relocation-model": "static", + "disable-redzone":true, + "frame-pointer":"always", + "requires-lto":false, + "eh-frame-header":false, + "no-default-libraries":true, + "max-atomic-width":64, + "linker-flavor":"gcc", + "linker": "./gcc-linker-wrapper.bash", + "late-link-args": { + "gcc": [] + }, + "pre-link-args": { + "gcc": [ + "-static", + "-nostdinc", + "-nostdlib", + "-pg", + "-mnop-mcount" + ] + }, + "post-link-args": { + "gcc": [ + "-Wl,--gc-sections", + "-fuse-ld=bfd", + "-Wl,-T,./libcosmo/ape.lds", + "./libcosmo/crt.o", + "./libcosmo/ape-no-modify-self.o", + "./libcosmo/cosmopolitan.a" + ] + }, + "stack-probes": { + "kind": "none" + }, + "target-family": [ + "unix" + ] +}