diff --git a/crates/mipsevm/src/mips/instrumented.rs b/crates/mipsevm/src/mips/instrumented.rs index 4ebb99b..0762013 100644 --- a/crates/mipsevm/src/mips/instrumented.rs +++ b/crates/mipsevm/src/mips/instrumented.rs @@ -227,6 +227,38 @@ mod test { } } + #[test] + fn test_hello_rs_willem() { + let elf_bytes = include_bytes!("../../../../example/bin/hello-willem-rs.elf"); + let mut state = load_elf(elf_bytes).unwrap(); + patch::patch_go(elf_bytes, &mut state).unwrap(); + patch::patch_stack(&mut state).unwrap(); + + let out = BufWriter::new(Vec::default()); + let err = BufWriter::new(Vec::default()); + let mut ins = + InstrumentedState::new(state, StaticOracle::new(b"hello world".to_vec()), out, err); + + for _ in 0..400_000 { + if ins.state.exited { + break; + } + ins.step(false).unwrap(); + } + + assert!(ins.state.exited, "must exit"); + assert_eq!(ins.state.exit_code, 0, "must exit with 0"); + + assert_eq!( + String::from_utf8(ins.std_out.buffer().to_vec()).unwrap(), + "hello world!\n" + ); + assert_eq!( + String::from_utf8(ins.std_err.buffer().to_vec()).unwrap(), + "" + ); + } + #[test] fn test_hello() { let elf_bytes = include_bytes!("../../../../example/bin/hello.elf"); diff --git a/example/Makefile b/example/Makefile index 6712cd0..8dd54e3 100644 --- a/example/Makefile +++ b/example/Makefile @@ -6,6 +6,13 @@ elf: $(patsubst %/go.mod,bin/%.elf,$(wildcard */go.mod)) .PHONY: dump dump: $(patsubst %/go.mod,bin/%.dump,$(wildcard */go.mod)) +# .PHONY: ex-rs +# ex-rs: +# cd hello-rs && \ +# RUSTFLAGS='-C link-arg=-no-pie -C target-cpu=mips32 -C target-feature=-mips32r2,-fpxx,-nooddspreg,+mips32,+crt-static,+soft-float' \ +# cross build --release --target ../mips-unknown-linux-gnu.json -Z build-std -Z build-std-features=panic-unwind && \ +# cp ../../target/mips-unknown-linux-gnu/release/hello-rs ../bin/hello-rs.elf + bin: mkdir bin diff --git a/example/bin/hello-willem-rs.elf b/example/bin/hello-willem-rs.elf new file mode 100644 index 0000000..fe449fc Binary files /dev/null and b/example/bin/hello-willem-rs.elf differ diff --git a/example/hello-rs-willem/.gitignore b/example/hello-rs-willem/.gitignore new file mode 100644 index 0000000..d8d0e32 --- /dev/null +++ b/example/hello-rs-willem/.gitignore @@ -0,0 +1,2 @@ +# Rust target dir +target diff --git a/example/hello-rs-willem/Cargo.lock b/example/hello-rs-willem/Cargo.lock new file mode 100644 index 0000000..45ac9d8 --- /dev/null +++ b/example/hello-rs-willem/Cargo.lock @@ -0,0 +1,50 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "hello-rs-willem" +version = "0.0.1" +dependencies = [ + "linked_list_allocator", +] + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] diff --git a/example/hello-rs-willem/Cargo.toml b/example/hello-rs-willem/Cargo.toml new file mode 100644 index 0000000..a12552e --- /dev/null +++ b/example/hello-rs-willem/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "hello-rs-willem" +edition = "2021" +version = "0.0.1" +authors = ["Willem Olding"] + +[dependencies] +linked_list_allocator = "0.10.5" + +[profile.release] +panic = "abort" diff --git a/example/hello-rs-willem/README.md b/example/hello-rs-willem/README.md new file mode 100644 index 0000000..ce5fe64 --- /dev/null +++ b/example/hello-rs-willem/README.md @@ -0,0 +1,14 @@ +# [`Cannon-rs`][cannon-rs-willem] example program + +This is an example Rust program for Cannon that uses Willem Olding's [program template][program-template-willem]. + +## Building + +The program can be built using Badboilabs' provided container: + +```sh +docker run --rm --platform linux/amd64 -v `pwd`/:/code -w="/code" ghcr.io/badboilabs/cannon-rs/builder:main cargo build --release -Zbuild-std +``` + +[cannon-rs-willem]: https://github.com/BadBoiLabs/Cannon-rs +[program-template-willem]: https://github.com/BadBoiLabs/Cannon-rs/tree/main/project-template diff --git a/example/hello-rs-willem/mips-unknown-linux-gnu.json b/example/hello-rs-willem/mips-unknown-linux-gnu.json new file mode 100644 index 0000000..650ef42 --- /dev/null +++ b/example/hello-rs-willem/mips-unknown-linux-gnu.json @@ -0,0 +1,29 @@ +{ + "arch": "mips", + "cpu": "mips32", + "crt-objects-fallback": "false", + "crt-static-respected": true, + "data-layout": "E-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64", + "dynamic-linking": true, + "env": "gnu", + "features": "-mips32r2,-fpxx,-nooddspreg,+mips32,+crt-static,+soft-float", + "has-rpath": true, + "has-thread-local": true, + "linker-flavor": "gnu-cc", + "llvm-target": "mips-unknown-linux-gnu", + "max-atomic-width": 32, + "os": "linux", + "position-independent-executables": true, + "relro-level": "full", + "supported-split-debuginfo": [ + "packed", + "unpacked", + "off" + ], + "target-endian": "big", + "target-family": [ + "unix" + ], + "target-mcount": "_mcount", + "target-pointer-width": "32" +} diff --git a/example/hello-rs-willem/src/heap.rs b/example/hello-rs-willem/src/heap.rs new file mode 100644 index 0000000..26c225e --- /dev/null +++ b/example/hello-rs-willem/src/heap.rs @@ -0,0 +1,52 @@ +///! This is actually just a wrapper around linked_list_allocator that allows it to work in our environment +///! Different allocator can be used if desired +use core::alloc::{GlobalAlloc, Layout}; +use core::cell::RefCell; +use core::mem::MaybeUninit; +use core::ptr::{self, NonNull}; +struct Alloc { + heap: RefCell, +} + +impl Alloc { + const fn new() -> Self { + Self { + heap: RefCell::new(linked_list_allocator::Heap::empty()), + } + } +} + +unsafe impl GlobalAlloc for Alloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.heap + .borrow_mut() + .allocate_first_fit(layout) + .ok() + .map_or(ptr::null_mut(), |allocation| allocation.as_ptr()) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.heap + .borrow_mut() + .deallocate(NonNull::new_unchecked(ptr), layout) + } +} + +#[global_allocator] +static mut ALLOCATOR: Alloc = Alloc::new(); + +pub unsafe fn init(heap: &mut [MaybeUninit]) { + ALLOCATOR + .heap + .borrow_mut() + .init(heap.as_mut_ptr() as *mut u8, heap.len()) +} + +#[macro_export] +macro_rules! init_heap { + ( $x:expr ) => {{ + use core::mem::MaybeUninit; + static mut HEAP: [MaybeUninit; $x] = [MaybeUninit::uninit(); $x]; + unsafe { crate::heap::init(&mut HEAP) } + }}; +} diff --git a/example/hello-rs-willem/src/main.rs b/example/hello-rs-willem/src/main.rs new file mode 100644 index 0000000..5c5eb95 --- /dev/null +++ b/example/hello-rs-willem/src/main.rs @@ -0,0 +1,204 @@ +//! Frankensteined version of the `cannon-rs` program template, provided by Willem Olding in +//! the other [`Cannon-rs`](https://github.com/BadBoiLabs/Cannon-rs/tree/main). + +#![no_std] +#![no_main] +#![feature(core_intrinsics)] +#![feature(alloc_error_handler)] +#![feature(asm_experimental_arch)] + +extern crate alloc; + +mod heap; + +use syscalls::*; + +/// Defines the size of the heap in bytes +/// Changing this will change the size of the resulting json file built by converting the elf file +/// How big you can make this depends on the program size but it should be possible to make it very large (close to 4GB). +/// See https://image1.slideserve.com/3443033/memory-map-l.jpg +const HEAP_SIZE: usize = 0x400000; + +/// Main entrypoint for a verifiable computation +#[no_mangle] +pub extern "C" fn _start() { + init_heap!(HEAP_SIZE); + + print("hello world!\n"); + + exit(0); // 0 code indicates success +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let msg = alloc::format!("Panic: {}", info); + let _ = print(&msg); + exit(2); +} + +#[alloc_error_handler] +fn alloc_error_handler(_layout: alloc::alloc::Layout) -> ! { + let _ = print("alloc error! (probably out of memory)"); + exit(3); +} + +mod raw { + use core::arch::asm; + + /// Issues a raw system call with 1 argument. (e.g. exit) + #[inline] + pub unsafe fn syscall1(n: u32, arg1: u32) -> u32 { + let mut err: u32; + let mut ret: u32; + asm!( + "syscall", + inlateout("$2") n => ret, + lateout("$7") err, + in("$4") arg1, + // All temporary registers are always clobbered + lateout("$8") _, + lateout("$9") _, + lateout("$10") _, + lateout("$11") _, + lateout("$12") _, + lateout("$13") _, + lateout("$14") _, + lateout("$15") _, + lateout("$24") _, + lateout("$25") _, + options(nostack, preserves_flags) + ); + if err == 0 { + ret + } else { + ret.wrapping_neg() + } + } + + /// Issues a raw system call with 3 arguments. (e.g. read, write) + #[inline] + unsafe fn syscall3raw(n: u32, arg1: u32, arg2: u32, arg3: u32) -> u32 { + let mut err: u32; + let mut ret: u32; + asm!( + "syscall", + inlateout("$2") n => ret, + lateout("$7") err, + in("$4") arg1, + in("$5") arg2, + in("$6") arg3, + // All temporary registers are always clobbered + lateout("$8") _, + lateout("$9") _, + lateout("$10") _, + lateout("$11") _, + lateout("$12") _, + lateout("$13") _, + lateout("$14") _, + lateout("$15") _, + lateout("$24") _, + lateout("$25") _, + options(nostack, preserves_flags) + ); + if err == 0 { + ret + } else { + ret.wrapping_neg() + } + } + + /// Same as above but handles the error code and wraps it in a Result. + #[inline] + pub unsafe fn syscall3(nr: u32, a1: u32, a2: u32, a3: u32) -> Result { + let value = syscall3raw(nr, a1, a2, a3); + if value > -4096isize as u32 { + // Truncation of the error value is guaranteed to never occur due to + // the above check. This is the same check that musl uses: + // https://git.musl-libc.org/cgit/musl/tree/src/internal/syscall_ret.c?h=v1.1.15 + Err(-(value as i32)) + } else { + Ok(value) + } + } +} + +mod syscalls { + use super::raw::{syscall1, syscall3}; + + enum FileDescriptor { + StdOut = 1, + HintRead = 3, + HintWrite = 4, + PreimageRead = 5, + PreimageWrite = 6, + } + enum SyscallNo { + Exit = 4246, + Read = 4003, + Write = 4004, + } + + #[derive(Debug)] + pub enum SyscallError { + Code(u32), + } + + impl From for SyscallError { + fn from(code: i32) -> Self { + SyscallError::Code(code as u32) + } + } + + type Result = core::result::Result; + + pub fn print(s: &str) -> Result { + write(FileDescriptor::StdOut, s.as_bytes()) + } + + pub fn write_preimage(key: &[u8]) -> Result { + write(FileDescriptor::PreimageWrite, key) + } + + pub fn read_preimage(out: &mut [u8]) -> Result { + read(FileDescriptor::PreimageRead, out) + } + + pub fn write_hint(key: [u8; 32]) -> Result { + write(FileDescriptor::HintWrite, &key) + } + + pub fn read_hint(out: &mut [u8]) -> Result { + read(FileDescriptor::HintRead, out) + } + + pub fn exit(code: u8) -> ! { + unsafe { + syscall1(SyscallNo::Exit as u32, code.into()); + panic!() // just to get the correct never return type + } + } + + fn write(fd: FileDescriptor, buf: &[u8]) -> Result { + let result = unsafe { + syscall3( + SyscallNo::Write as u32, + fd as u32, + buf.as_ptr() as u32, + buf.len() as u32, + ) + }; + result.map_err(SyscallError::from) + } + + fn read(fd: FileDescriptor, buf: &mut [u8]) -> Result { + let result = unsafe { + syscall3( + SyscallNo::Read as u32, + fd as u32, + buf.as_ptr() as u32, + buf.len() as u32, + ) + }; + result.map_err(SyscallError::from) + } +}