Here are the steps to build NuttX for QEMU RISC-V (32-bit) and run the "Hello Rust" Demo App...
-
Install the Build Prerequisites, skip the RISC-V Toolchain...
-
Download the RISC-V Toolchain for riscv64-unknown-elf...
-
Download and configure NuttX...
mkdir nuttx cd nuttx git clone https://github.com/apache/nuttx nuttx git clone https://github.com/apache/nuttx-apps apps cd nuttx tools/configure.sh rv-virt:nsh make menuconfig
-
In menuconfig, browse to "Device Drivers > System Logging"
Disable this option...
Prepend Timestamp to Syslog Message
-
Browse to "Build Setup > Debug Options"
Select the following options...
Enable Debug Features Enable Error Output Enable Warnings Output Enable Informational Debug Output Enable Debug Assertions Enable Debug Assertions Show Expression Scheduler Debug Features Scheduler Error Output Scheduler Warnings Output Scheduler Informational Output
-
Browse to "Application Configuration > Examples"
Select "Hello Rust Example"
Select it Twice so that "
<M>
" changes to "<*>
" -
Save and exit menuconfig.
-
Build the NuttX Project and dump the RISC-V Disassembly to nuttx.S...
rustup target add riscv32i-unknown-none-elf make riscv64-unknown-elf-objdump \ -t -S --demangle --line-numbers --wide \ nuttx \ >nuttx.S \ 2>&1
This produces the NuttX ELF Image nuttx that we may boot on QEMU RISC-V Emulator. (Next Section)
-
If the GCC Linker fails with "Can't link soft-float modules with double-float modules"...
$ make LD: nuttx riscv64-unknown-elf-ld: nuttx/nuttx/staging/libapps.a (hello_rust_main.rs...nuttx.apps.examples.hello_rust_1.o): can't link soft-float modules with double-float modules riscv64-unknown-elf-ld: failed to merge target specific data of file nuttx/staging/libapps.a (hello_rust_main.rs...nuttx.apps.examples.hello_rust_1.o)
Then we patch the ELF Header like this and it should link correctly...
xxd -c 1 ../apps/examples/hello_rust/*hello_rust_1.o \ | sed 's/00000024: 00/00000024: 04/' \ | xxd -r -c 1 - /tmp/hello_rust_1.o cp /tmp/hello_rust_1.o ../apps/examples/hello_rust/*hello_rust_1.o make ## Ignore the warnings: ## riscv64-unknown-elf-ld: warning: nuttx/staging/libapps.a(hello_rust_main.rs...nuttx.apps.examples.hello_rust_1.o): ## mis-matched ISA version 2.1 for 'i' extension, the output version is 2.0
How did it work? We patched the ELF Header, changing it from Software Floating-Point to Double Precision Hardware Floating-Point...
## Before Patching: ELF Header says Software Floating-Point $ riscv64-unknown-elf-readelf -h -A ../apps/examples/hello_rust/*hello_rust_1.o Flags: 0x0 ## After Patching: ELF Header says Double-Precision Hardware Floating-Point $ riscv64-unknown-elf-readelf -h -A ../apps/examples/hello_rust/*hello_rust_1.o Flags: 0x4, double-float ABI
(Similar to this, except we're doing Double-Float instead of Single-Float)
TODO: Fix the Rust Build for NuttX
-
If the GCC Linker fails with the error "undefined reference to core::panicking::panic", please apply this patch...
Add -O to RUSTFLAGS in Makefile
Then rebuild:
make clean ; make
(If we still hit the same error, see the notes below)
-
Download and install QEMU Emulator.
## For macOS: brew install qemu ## For Debian and Ubuntu: sudo apt install qemu-system-riscv32
-
Start the QEMU RISC-V Emulator (32-bit) with the NuttX ELF Image nuttx from the previous section...
qemu-system-riscv32 \ -semihosting \ -M virt,aclint=on \ -cpu rv32 \ -smp 8 \ -bios none \ -kernel nuttx \ -nographic
-
NuttX is now running in the QEMU Emulator! (Pic above)
uart_register: Registering /dev/console uart_register: Registering /dev/ttyS0 nx_start_application: Starting init thread NuttShell (NSH) NuttX-12.1.0-RC0 nsh> nx_start: CPU0: Beginning Idle Loop nsh>
-
Enter "hello_rust" to run the Rust Demo App
nsh> hello_rust Hello, Rust!!
-
Enter "help" to see the available commands...
nsh> help help usage: help [-v] [<cmd>] . break dd exit ls ps source umount [ cat df false mkdir pwd test unset ? cd dmesg free mkrd rm time uptime alias cp echo help mount rmdir true usleep unalias cmp env hexdump mv set truncate xd basename dirname exec kill printf sleep uname Builtin Apps: nsh ostest sh
-
NuttX works like a tiny version of Linux, so the commands will look familiar...
nsh> uname -a NuttX 12.1.0-RC0 275db39 Jun 16 2023 20:22:08 risc-v rv-virt nsh> ls /dev /dev: console null ttyS0 zero nsh> ps PID GROUP PRI POLICY TYPE NPX STATE EVENT SIGMASK STACK USED FILLED COMMAND 0 0 0 FIFO Kthread N-- Ready 0000000000000000 002000 001224 61.2% Idle Task 1 1 100 RR Task --- Running 0000000000000000 002992 002024 67.6% nsh_main
-
To Exit QEMU: Press
Ctrl-A
thenx
Let's watch how NuttX builds Rust Apps by calling rustc
. (Instead of cargo build
)
Here's the NuttX Build Log...
$ make --trace
## Compile `hello_rust_main.rs` to `hello_rust.o`
rustc \
--edition 2021 \
--emit obj \
-g \
--target riscv32i-unknown-none-elf \
-C panic=abort \
-O \
hello_rust_main.rs \
-o hello_rust_main.rs...apps.examples.hello_rust.o
## Copy `hello_rust.o` to `hello_rust_1.o` (Why?)
cp \
hello_rust_main.rs...apps.examples.hello_rust.o \
hello_rust_main.rs...apps.examples.hello_rust_1.o
## Omitted: Bundle `hello_rust_1.o`
## into `staging/libapps.a`
## Link `staging/libapps.a` into `nuttx`
riscv64-unknown-elf-ld \
--entry=__start \
-melf32lriscv \
--gc-sections \
-nostdlib \
--cref \
-Map=nuttx/nuttx.map \
-Tboards/risc-v/qemu-rv/rv-virt/scripts/ld.script.tmp \
-L staging \
-L arch/risc-v/src/board \
-o nuttx \
qemu_rv_head.o \
--start-group \
-lsched \
-ldrivers \
-lboards \
-lc \
-lmm \
-larch \
-lm \
-lapps \
-lfs \
-lbinfmt \
-lboard riscv64-unknown-elf-toolchain-10.2.0-2020.12.8-x86_64-apple-darwin/bin/../lib/gcc/riscv64-unknown-elf/10.2.0/rv32imafdc/ilp32d/libgcc.a \
--end-group
(Rust Build with rustc
is defined here)
Here are the Rust Object Files produced by the NuttX Build...
$ ls -l ../apps/examples/hello_rust
total 112
-rw-r--r-- 1 650 Jul 20 2023 Kconfig
-rw-r--r-- 1 1071 Jul 20 2023 Make.defs
-rw-r--r-- 1 141 Mar 17 09:44 Make.dep
-rw-r--r-- 1 1492 Mar 16 20:41 Makefile
-rw-r--r-- 1 3982 Mar 17 00:06 hello_rust_main.rs
-rw-r--r-- 1 13168 Mar 17 09:44 hello_rust_main.rs.Users.Luppy.riscv.apps.examples.hello_rust.o
-rw-r--r-- 1 18240 Mar 17 09:54 hello_rust_main.rs.Users.Luppy.riscv.apps.examples.hello_rust_1.o
After adding RUSTFLAGS=-O
, we might still hit Undefined core::panicking::panic
. Here's our Test Code that has 2 Potential Panics: hello_rust_main.rs
-
Converting usize to c_int might panic (due to overflow)
-
Divide by 0 will panic
If we omit RUSTFLAGS=-O
: We see 2 Undefined core::panicking::panic
...
But when we add RUSTFLAGS=-O
: We still see 1 Undefined core::panicking::panic
for the divide-by-zero...
Somehow the divide-by-zero panic refuses to link correctly. Based on this discussion, it seems that the Rust Core Library is compiled with LTO (Link-Time Optimisation), so it might still cause problems with our code, which doesn't use LTO.
TODO: If we call cargo build
(instead of rustc
), will it fix this LTO issue? How different is the cargo build
Linker from GCC Linker?
TODO: Rust Build fails for QEMU RISC-V 64-bit...
$ tools/configure.sh rv-virt:nsh64
$ make menuconfig
## TODO: Enable "Hello Rust Example"
$ make
RUSTC: hello_rust_main.rs error: Error loading target specification:
Could not find specification for target "riscv64i-unknown-none-elf".
Run `rustc --print target-list` for a list of built-in targets
make[2]: *** [nuttx/apps/Application.mk:275: hello_rust_main.rs...nuttx.apps.examples.hello_rust.o] Error 1
make[1]: *** [Makefile:51: nuttx/apps/examples/hello_rust_all] Error 2
make: *** [tools/LibTargets.mk:232: nuttx/apps/libapps.a] Error 2
TODO: Test the Rust Build for QEMU Arm32 and Arm64