diff --git a/rs-hello/.cargo/config.toml b/rs-hello/.cargo/config.toml new file mode 100644 index 00000000..5f4ec049 --- /dev/null +++ b/rs-hello/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = ["x86_64-unknown-none"] diff --git a/rs-hello/.gitignore b/rs-hello/.gitignore new file mode 100644 index 00000000..9e607ad9 --- /dev/null +++ b/rs-hello/.gitignore @@ -0,0 +1,2 @@ +/workdir/ +/target diff --git a/rs-hello/Cargo.lock b/rs-hello/Cargo.lock new file mode 100644 index 00000000..511fcc25 --- /dev/null +++ b/rs-hello/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "rs-hello" +version = "0.1.0" diff --git a/rs-hello/Cargo.toml b/rs-hello/Cargo.toml new file mode 100644 index 00000000..f54a8f4b --- /dev/null +++ b/rs-hello/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rs-hello" +version = "0.1.0" +edition = "2021" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +[dependencies] diff --git a/rs-hello/Makefile b/rs-hello/Makefile new file mode 100644 index 00000000..7c08368a --- /dev/null +++ b/rs-hello/Makefile @@ -0,0 +1,13 @@ +UK_ROOT ?= $(PWD)/workdir/unikraft +UK_BUILD ?= $(PWD)/workdir/build +UK_APP ?= $(PWD) +LIBS_BASE = $(PWD)/workdir/libs +UK_LIBS ?= + +.PHONY: all + +all: + @$(MAKE) -C $(UK_ROOT) L=$(UK_LIBS) A=$(UK_APP) O=$(UK_BUILD) + +$(MAKECMDGOALS): + @$(MAKE) -C $(UK_ROOT) L=$(UK_LIBS) A=$(UK_APP) O=$(UK_BUILD) $(MAKECMDGOALS) diff --git a/rs-hello/Makefile.uk b/rs-hello/Makefile.uk new file mode 100644 index 00000000..954489e9 --- /dev/null +++ b/rs-hello/Makefile.uk @@ -0,0 +1,3 @@ +$(eval $(call addlib,apprshello)) + +APPRSHELLO_OBJS-y += $(APPRSHELLO_BASE)/target/x86_64-unknown-none/debug/deps/rs_hello-*.o diff --git a/rs-hello/README.md b/rs-hello/README.md new file mode 100644 index 00000000..bd5f3f04 --- /dev/null +++ b/rs-hello/README.md @@ -0,0 +1,195 @@ +# Rust Hello on Unikraft + +Build and run a `std` independent Rust Hello program on Unikraft. +Follow the instructions below to set up, configure, build and run Rust Hello. +Make sure you installed the [requirements](../README.md#requirements) and the [Rust toolchain channel through Rustup](https://www.rust-lang.org/tools/install). + +## Quick Setup (aka TLDR) + +For a quick setup, run the commands below. +Note that you still need to install the [requirements](../README.md#requirements) and the [Rust toolchain](https://www.rust-lang.org/tools/install). +Before everything, make sure you run the [top-level `setup.sh` script](../setup.sh). + +To build and run the application for `x86_64`, use the commands below: + +```console +./setup.sh +make distclean +rustup target add x86_64-unknown-none +cargo +stable rustc -- --emit=obj +wget -O /tmp/defconfig https://raw.githubusercontent.com/unikraft/catalog-core/refs/heads/scripts/rs-hello/scripts/defconfig/qemu.x86_64 +UK_DEFCONFIG=/tmp/defconfig make defconfig +make -j $(nproc) +qemu-system-x86_64 -nographic -m 8 -cpu max -kernel workdir/build/rs-hello_qemu-x86_64 +``` + +This will configure, build and run the application, resulting in a `Hello from Unikraft!` message being printed. + +Information about every step is detailed below. + +## Set Up + +Set up the required repositories. +For this, you have two options: + +1. Use the `setup.sh` script: + + ```console + ./setup.sh + ``` + + It will create symbolic links to the required repositories in `../repos/`. + Be sure to run the [top-level `setup.sh` script](../setup.sh). + + If you want use a custom variant of repositories (e.g. apply your own patch, make modifications), update it accordingly in the `../repos/` directory. + +1. Have your custom setup of repositories in the `workdir/` directory. + Clone, update and customize repositories to your own needs. + +## Clean + +While not strictly required, it is safest to clean the previous build artifacts: + +```console +make distclean +``` + +## Compile Rust source file + +To generate the object file of the Rust source file, use: + +``` +rustup target add x86_64-unknown-none +cargo +stable rustc -- --emit=obj +``` + +## Configure + +To configure the kernel, use: + +```console +make menuconfig +``` + +In the console menu interface, choose the target architecture (x86_64) and platform (KVM/QEMU). + +The end result will be the creation of the `.config` configuration file. + +## Build + +Build the application for the current configuration: + +```console +make -j $(nproc) +``` + +This results in the creation of the `workdir/build/` directory storing the build artifacts. +The unikernel application image file is `workdir/build/rs-hello_-`, where `` is the platform name (`qemu`), and `` is the architecture (`x86_64`). + +### Use a Different Compiler + +If you want to use a different compiler, such as a Clang or a different GCC version, pass the `CC` variable to `make`. + +To build with Clang, use the commands below: + +```console +make properclean +make CC=clang -j $(nproc) +``` + +Note that Clang >= 14 is required to build Unikraft. + +To build with another GCC version, use the commands below: + +```console +make properclean +make CC=gcc- -j $(nproc) +``` + +where `` is the GCC version, such as `11`, `12`. + +Note that GCC >= 8 is required to build Unikraft. + +## Run + +Run the resulting image using the corresponding platform tool. + +A successful run will show a message such as the one below: + +```text +Booting from ROM..Powered by +o. .o _ _ __ _ +Oo Oo ___ (_) | __ __ __ _ ' _) :_ +oO oO ' _ `| | |/ / _)' _` | |_| _) +oOo oOO| | | | | (| | | (_) | _) :_ + OoOoO ._, ._:_:_,\_._, .__,_:_, \___) + Helene 0.18.0~12072b5f +Hello from Unikraft! +``` + +### Run on QEMU/x86_64 + +```console +qemu-system-x86_64 -nographic -m 8 -cpu max -kernel workdir/build/rs-hello_qemu-x86_64 +``` + +## Clean Up + +Doing a new configuration, or a new build may require cleaning up the configuration and build artifacts. + +In order to remove the build artifacts, use: + +```console +make clean +``` + +In order to remove fetched files also, that is the removal of the `workdir/build/` directory, use: + +```console +make properclean +``` + +In order to remove the generated `.config` file as well, use: + +```console +make distclean +``` + +In order to remove the generated files by Cargo in the `target` directory, use: + +```console +cargo clean +``` +## Customize + +Rust Hello is the simplest application to be run with Unikraft. +This makes it ideal as a minimal testing ground for new features: it builds fast, it doesn't have dependencies. + +### Update the Unikraft Core Code + +If updating the Unikraft core code in the `./workdir/unikraft/` directory, you then go through the [configure](#configure), [build](#build) and [run](#run) steps. + +### Add Other Object Files + +The current configuration use a object file. +If looking to add another file to the build, update the [`Makefile.uk`](Makefile.uk) file. + +For example, to add a new object file `support.o` to the build, update the [`Makefile.uk`](Makefile.uk) file to: + +```make +$(eval $(call addlib,apprshello)) + +APPRSHELLO_OBJS-y += $(APPRSHELLO_BASE)/target/x86_64-unknown-none/debug/deps/rs_hello-*.o +APPRSHELLO_OBJS-y += $(APPRSHELLO_BASE)/support.o +``` + +To include a Rust library, such as `librs_hello.rlib`, update the [`Makefile.uk`](Makefile.uk) file to: + +```make +$(eval $(call addlib,apprshello)) + +APPRSHELLO_OBJS-y += $(APPRSHELLO_BASE)/target/x86_64-unknown-none/debug/deps/rs_hello-*.o +APPRSHELLO_ALIBS-y += $(APPRSHELLO_BASE)/librs_hello.rlib +``` + +Then go through the [configure](#configure), [build](#build) and [run](#run) steps. diff --git a/rs-hello/scripts/README.md b/rs-hello/scripts/README.md new file mode 100644 index 00000000..880810f0 --- /dev/null +++ b/rs-hello/scripts/README.md @@ -0,0 +1,44 @@ +# Scripts for Rust Hello on Unikraft + +These are companions instruction to the main instructions in the [`README`](README.md). + +Use scripts as quick actions for building and running Rust Hello on Unikraft. + +**Note**: Run scripts from the application directory. + +## Build for / : + +```console +./scripts/build/. +``` + +e.g.: + +```console +./scripts/build/qemu.x86_64 +``` + +## Build for / using a different compiler + +```console +CC=/path/to/compiler ./scripts/build/. +``` + +e.g. + +```console +CC=/usr/bin/gcc-12 ./scripts/build/qemu.x86_64 +CC=/usr/bin/clang ./scripts/build/qemu.x86_64 +``` + +## Run on / + +```console +./scripts/run/. +``` + +e.g. + +```console +./scripts/run/qemu.x86_64 +``` diff --git a/rs-hello/scripts/build/qemu.x86_64 b/rs-hello/scripts/build/qemu.x86_64 new file mode 100755 index 00000000..e2f3c3d1 --- /dev/null +++ b/rs-hello/scripts/build/qemu.x86_64 @@ -0,0 +1,15 @@ +#!/bin/sh + +make distclean +cargo clean +rustup target add x86_64-unknown-none +cargo +stable rustc -- --emit=obj +UK_DEFCONFIG=$(pwd)/scripts/defconfig/qemu.x86_64 make defconfig +touch Makefile.uk +make prepare +if test -z "$CC"; then + make -j $(nproc) +else + make CC="$CC" -j $(nproc) +fi + diff --git a/rs-hello/scripts/defconfig/qemu.x86_64 b/rs-hello/scripts/defconfig/qemu.x86_64 new file mode 100644 index 00000000..a98e5109 --- /dev/null +++ b/rs-hello/scripts/defconfig/qemu.x86_64 @@ -0,0 +1,4 @@ +CONFIG_PLAT_KVM=y +CONFIG_KVM_VMM_QEMU=y +CONFIG_ARCH_X86_64=y + diff --git a/rs-hello/scripts/run/qemu.x86_64 b/rs-hello/scripts/run/qemu.x86_64 new file mode 100755 index 00000000..80a832e4 --- /dev/null +++ b/rs-hello/scripts/run/qemu.x86_64 @@ -0,0 +1,14 @@ +#!/bin/sh + +if test ! -f "workdir/build/rs-hello_qemu-x86_64"; then + echo "No kernel file workdir/build/rs-hello_qemu-x86_64." 1>&2 + echo "Did you run ./build.qemu.x86_64 ?" 1>&2 + exit 1 +fi + +qemu-system-x86_64 \ + -nographic \ + -m 8 \ + -cpu max \ + -kernel workdir/build/rs-hello_qemu-x86_64 + diff --git a/rs-hello/scripts/test/.gitignore b/rs-hello/scripts/test/.gitignore new file mode 100644 index 00000000..04aa9423 --- /dev/null +++ b/rs-hello/scripts/test/.gitignore @@ -0,0 +1,2 @@ +/log/ + diff --git a/rs-hello/scripts/test/README.md b/rs-hello/scripts/test/README.md new file mode 100644 index 00000000..41cf02b6 --- /dev/null +++ b/rs-hello/scripts/test/README.md @@ -0,0 +1,15 @@ +# Testing Rust Hello on Unikraft + +These are companion instructions to the main instructions in the [`README`](../../README.md) and to the scripted run instructions in the [`scripts/README`](../README.md). +Use these scripts to test Rust Hello on Unikraft. + +**Note**: Run scripts from the application directory. + +Use the `all.sh` script to test all builds and all available runs: + +```console +./scripts/test/all.sh +``` + +Logs are stored in the `./scripts/test/log/` directory. + diff --git a/rs-hello/scripts/test/all.sh b/rs-hello/scripts/test/all.sh new file mode 100755 index 00000000..0935430c --- /dev/null +++ b/rs-hello/scripts/test/all.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_build() +{ + printf "%-46s ... " build."$1" + ./scripts/build/"$1" > ./scripts/test/log/build."$1" 2>&1 + if test $? -eq 0; then + echo "PASSED" + else + echo "FAILED" + fi +} + +test_build_run() +{ + printf "%-46s ... " build."$1" + ./scripts/build/"$1" > ./scripts/test/log/build."$1" 2>&1 + if test $? -eq 0; then + echo "PASSED" + else + echo "FAILED" + fi + + printf " %-42s ... " run."$1" + ./scripts/test/wrapper.sh ./scripts/run/"$1" 2> ./scripts/test/log/run."$1" +} + +./setup.sh +test -d ./scripts/test/log || mkdir ./scripts/test/log +test_build_run qemu.x86_64 + diff --git a/rs-hello/scripts/test/common.sh b/rs-hello/scripts/test/common.sh new file mode 100755 index 00000000..0a97d06e --- /dev/null +++ b/rs-hello/scripts/test/common.sh @@ -0,0 +1,135 @@ +#!/bin/bash + +if test -z "$log_file"; then + log_file="/dev/null" +fi + +clean_up() +{ + { + # Clean up any previous instances. + sudo pkill -9 qemu-system + sudo pkill -9 firecracker + kraft stop --all + kraft rm --all + sudo kraft stop --all + sudo kraft rm --all + + # Remove previously created network interfaces. + sudo ip link set dev tap0 down + sudo ip link del dev tap0 + sudo ip link set dev virbr0 down + sudo ip link del dev virbr0 + } > /dev/null 2>&1 +} + +start_instance() +{ + # Start instance. + setsid --fork "$start_command" 1>&2 & + if test $? -ne 0; then + echo "Cannot start instance" 1>&2 + echo "FAILED" + clean_up + exit 1 + fi +} + +test_ping() +{ + host="$1" + + # Connect to instance. + ping -c 1 "$host" 1>&2 + if test $? -ne 0; then + echo "Cannot ping $host" 1>&2 + echo "FAILED" + clean_up + exit 1 + fi +} + +test_curl_connect() +{ + host="$1" + port="$2" + + # Query instance. + curl --retry 1 --connect-timeout 1 --max-time 10 "$host":"$port" 1>&2 + if test $? -ne 0; then + echo "Cannot connect to $host:$port" 1>&2 + echo "FAILED" + clean_up + exit 1 + fi +} + +test_curl_check_reply() +{ + host="$1" + port="$2" + message="$3" + + # Check server message contents. + curl --retry 1 --connect-timeout 1 --max-time 10 "$host":"$port" | grep "$message" 1>&2 + if test $? -ne 0; then + echo "Wrong message from $host:$port" 1>&2 + echo "FAILED" + clean_up + exit 1 + fi +} + +test_netcat_connect() +{ + host="$1" + port="$2" + + # Check connection. + netcat -w 3 "$host" "$port" < /dev/null 1>&2 + if test $? -ne 0; then + echo "Cannot connect to $host:$port" 1>&2 + echo "FAILED" + clean_up + exit 1 + fi +} + +test_redis_connect() +{ + host="$1" + port="$2" + + redis-cli -h "$host" -p "$port" < /dev/null 1>&2 + if test $? -ne 0; then + echo "Cannot connect client to Redis server at $host:$port" 1>&2 + echo "FAILED" + clean_up + exit 1 + fi +} + +test_redis_cli() +{ + host="$1" + port="$2" + + redis-cli -h "$host" -p "$port" set a 1 1>&2 + redis-cli -h "$host" -p "$port" get a 1>&2 + if test $? -eq 1; then + echo "FAILED" + echo "Cannot talk to Redis server at $host:$port" 1>&2 + clean_up + exit 1 + fi +} + +end_with_success() +{ + echo "PASSED" + clean_up + exit 0 +} + +start_command="$1" + diff --git a/rs-hello/scripts/test/single.sh b/rs-hello/scripts/test/single.sh new file mode 100755 index 00000000..9476699c --- /dev/null +++ b/rs-hello/scripts/test/single.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +. ./scripts/test/common.sh + diff --git a/rs-hello/scripts/test/wrapper.sh b/rs-hello/scripts/test/wrapper.sh new file mode 100755 index 00000000..4ab9eb19 --- /dev/null +++ b/rs-hello/scripts/test/wrapper.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. ./scripts/test/common.sh + +if test $# -ne 1; then + echo "Unknown arguments." 1>&2 + echo "Usage: $0 " 1>&2 + exit 1 +fi + +# Clean up previous instances. +clean_up + +# Start instance. +start_instance 2>&1 | grep "Hello from Unikraft!" 1>&2 +if test $? -ne 0; then + echo "Wrong message printed." 1>&2 + echo "FAILED" + clean_up + exit 1 +fi + +# Stop instance. +end_with_success + diff --git a/rs-hello/setup.sh b/rs-hello/setup.sh new file mode 100755 index 00000000..1161330f --- /dev/null +++ b/rs-hello/setup.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +check_exists_and_create_symlink() +{ + path="$1" + + if ! test -d workdir/"$path"; then + if ! test -d ../repos/"$path"; then + echo "No directory ../repos/$path. Run the top-level setup.sh script first." 1>&2 + exit 1 + fi + depth=$(echo "$path" | awk -F / '{ print NF }') + if test "$depth" -eq 1; then + ln -sfn ../../repos/"$path" workdir/"$path" + elif test "$depth" -eq 2; then + ln -sfn ../../../repos/"$path" workdir/"$path" + else + echo "Unknown depth of path $path." 1>&2 + exit 1 + fi + fi +} + +if ! test -d workdir; then + mkdir workdir +fi + +if ! test -d workdir/libs; then + mkdir workdir/libs +fi + +check_exists_and_create_symlink "unikraft" diff --git a/rs-hello/src/main.rs b/rs-hello/src/main.rs new file mode 100644 index 00000000..4fa0349d --- /dev/null +++ b/rs-hello/src/main.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; + +extern "C" { + fn uk_console_out(s: *const u8, len: usize); +} + +#[no_mangle] +pub extern "C" fn main() -> () { + let msg = "Hello from Unikraft!\n"; + unsafe { + uk_console_out(msg.as_ptr(), msg.len()); + } +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + diff --git a/test.overall.sh b/test.overall.sh index 28b795e9..56b6cf9c 100755 --- a/test.overall.sh +++ b/test.overall.sh @@ -1,6 +1,6 @@ #!/bin/sh -to_test="c-fs c-hello c-http cpp-hello cpp-http elfloader-basic elfloader-net nginx python3-hello" +to_test="c-fs c-hello c-http cpp-hello cpp-http elfloader-basic elfloader-net nginx python3-hello rs-hello" if test $# -eq 1; then to_test="$1"