Skip to content

Instantly share code, notes, and snippets.

@lupyuen
Last active March 18, 2024 04:00
Show Gist options
  • Save lupyuen/7be4bedc6a109b2c3d1201aee6030428 to your computer and use it in GitHub Desktop.
Save lupyuen/7be4bedc6a109b2c3d1201aee6030428 to your computer and use it in GitHub Desktop.
Running a Rust App on Apache NuttX RTOS (QEMU RISC-V 32-bit). See https://github.com/apache/nuttx/issues/11907

Running a Rust App on Apache NuttX RTOS (QEMU RISC-V 32-bit)

Here are the steps to build NuttX for QEMU RISC-V (32-bit) and run the "Hello Rust" Demo App...

Build Apache NuttX RTOS for 64-bit RISC-V QEMU

Build NuttX for 32-bit RISC-V QEMU

  1. Install the Build Prerequisites, skip the RISC-V Toolchain...

    "Install Prerequisites"

  2. Download the RISC-V Toolchain for riscv64-unknown-elf...

    "Download Toolchain for 64-bit RISC-V"

  3. 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
  4. In menuconfig, browse to "Device Drivers > System Logging"

    Disable this option...

    Prepend Timestamp to Syslog Message
    
  5. 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
    
  6. Browse to "Application Configuration > Examples"

    Select "Hello Rust Example"

    Select it Twice so that "<M>" changes to "<*>"

    (Source Code for Hello Rust)

    (Reading from Console Input)

  7. Save and exit menuconfig.

    (See the NuttX Config)

  8. 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

    (See the Build Log)

    This produces the NuttX ELF Image nuttx that we may boot on QEMU RISC-V Emulator. (Next Section)

  9. 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

  10. 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)

Apache NuttX RTOS on RISC-V QEMU

Boot NuttX on 32-bit RISC-V QEMU

  1. Download and install QEMU Emulator.

    ## For macOS:
    brew install qemu
    
    ## For Debian and Ubuntu:
    sudo apt install qemu-system-riscv32
  2. 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
  3. 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>
    
  4. Enter "hello_rust" to run the Rust Demo App

    nsh> hello_rust
    Hello, Rust!!
    
  5. 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
    
  6. 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
    

    (See the Complete Log)

  7. To Exit QEMU: Press Ctrl-A then x

How NuttX Builds Rust Apps

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

(See the Detailed Build Log)

(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

(See the RISC-V Disassembly)

Undefined Reference to core::panicking::panic

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

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?

Rust Build for QEMU RISC-V 64-bit

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment