RISC-V

OS in Rust

Homework

  • Homeworks are due on the Friday after the week they correspond to lecture.
  • So 9 days after the corresponding lab.

Requirements

Code

Don’t be like me

Loops are more stable than recursion I guess.

src/main.rs
#![no_main]
#![no_std]

pub extern "C" fn _start() -> ! {
    loop {}
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

Other stuff

git clone https://github.com/Alignof/helloworld_in_riscv_and_rust_baremetal.git
  • Just kidding don’t use that, use this instead.
git clone https://github.com/cd-rs/hwr5.git
  • I made a cool version.

The working parts

$ tree
.
├── Cargo.lock
├── Cargo.toml
├── link.x
├── memory.x
└── src
    └── main.rs

1 directory, 5 files
  • Some of this shouldn’t be new to you.
  • Some should:

The internals

  • Without dwelling on the details, these .x files are for the linker
    • Remember our good friend the linker.
    • link.x was written by someone that understands linkers.
    • memory.x was written by a script from a crate written by someone that understands linkers.
  • From our perspective, they “just work”
    • But how?

Starting Point

  • At first, probably nothing works.
    • Or at least, cargo b doesn’t work.
$ cargo b
   Compiling osirs v0.1.0 (/home/user/tmp/hwr5)
error: linking with `cc` failed: exit status: 1
  |
  = note:  "cc" "-m64" "/tmp/rustcwuxfha/symbols.o" "<1 object files omitted>" "-Wl,--as-needed" "-Wl,-Bstatic" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib/{librustc_std_workspace_core-*,libcore-*,libcompiler_builtins-*}.rlib" "-L" "/tmp/rustcwuxfha/raw-dylibs" "-Wl,-Bdynamic" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/user/tmp/hwr5/target/debug/deps/osirs-80f8eb3240ba748e" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: /usr/bin/ld: /home/user/tmp/hwr5/target/debug/deps/osirs-80f8eb3240ba748e.9chdw59nqscmfe0ef1hrxy2nb.rcgu.o: in function `_start':
          /home/user/tmp/hwr5/src/main.rs:7: multiple definition of `_start'; /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o:(.text+0x0): first defined here
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
          (.text+0x1b): undefined reference to `main'
          /usr/bin/ld: (.text+0x21): undefined reference to `__libc_start_main'
          collect2: error: ld returned 1 exit status

  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-link-lib)

error: could not compile `osirs` (bin "osirs") due to 1 previous error

rustc

  • I found it easier to get to work with rustc
    • Cargo hater Calvin
  • Recall a way we ran the executable in the lab.
cargo rustc -- -C link-arg=-nostartfiles
  • We can just go straight to rustc
rust -C link-arg=-nostartfiles
  • Only one catch.
    • Well, two.
    • We want to link files.
    • Specifically, the .x files.

Naively

  • You can give rustc a shot straight-away.
$ rustc src/main.rs
error: unwinding panics are not supported without std
  |
  = help: using nightly cargo, use -Zbuild-std with panic="abort" to avoid unwinding
  = note: since the core library is usually precompiled with panic="unwind", rebuilding your crate with panic="abort" may not be enough to fix the problem

error: aborting due to 1 previous error
  • We recall we “fixed” the problem with panics by modifying Cargo.toml.
  • rustc doesn’t care about your Cargo.toml!
  • We instead have to specify an argument to rustc.
  • We do so similarly to the link-arg, with the -C flag.
    • We furnish basically the same thing we placed in Cargo.toml.
rustc src/main.rs -C panic=abort
  • Get this working and see if you can get the following error message:
$ rustc src/main.rs -C panic=abort
error: linking with `cc` failed: exit status: 1
  |
  = note:  "cc" "-m64" "/tmp/rustceBlIYK/symbols.o" "<1 object files omitted>" "-Wl,--as-needed" "-Wl,-Bstatic" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib/{librustc_std_workspace_core-*,libcore-*,libcompiler_builtins-*}.rlib" "-L" "/tmp/rustceBlIYK/raw-dylibs" "-Wl,-Bdynamic" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "main" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: /usr/bin/ld: main.main.5f6bf0c8e9d0afce-cgu.0.rcgu.o: in function `_start':
          main.5f6bf0c8e9d0afce-cgu.0:(.text._start+0x0): multiple definition of `_start'; /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o:(.text+0x0): first defined here
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
          (.text+0x1b): undefined reference to `main'
          /usr/bin/ld: (.text+0x21): undefined reference to `__libc_start_main'
          collect2: error: ld returned 1 exit status

  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link

error: aborting due to 1 previous error
  • Ah hah! A linker error! Just as we hoped.

-T

  • The linker complains it’s missing something, and it’s not sure what.
  • We are sure, it’s the .x files.
  • To furnish a file by name to the linker, we provide it as a link-arg prefixed with -T
MacOS Users!

This will not work on MacOS, to our knowledge.

Here is a guide to follow that should work: MacOS

  • If you link both .x files, you will get a different error message.
$ rustc -C link-args=-Tmemory.x -C link-args=-Tlink.x -C panic=abort src/main.rs
error: linking with `cc` failed: exit status: 1
  |
  = note:  "cc" "-m64" "/tmp/rustczhisJl/symbols.o" "<1 object files omitted>" "-Wl,--as-needed" "-Wl,-Bstatic" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib/{librustc_std_workspace_core-*,libcore-*,libcompiler_builtins-*}.rlib" "-L" "/tmp/rustczhisJl/raw-dylibs" "-Wl,-Bdynamic" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "main" "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" "-Tmemory.x" "-Tlink.x"
  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: /usr/bin/ld: main.main.5f6bf0c8e9d0afce-cgu.0.rcgu.o: in function `_start':
          main.5f6bf0c8e9d0afce-cgu.0:(.text._start+0x0): multiple definition of `_start'; /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o:(.text+0x0): first defined here
          /usr/bin/ld:
          .got section detected in the input files. Dynamic relocations are not
          supported. If you are linking to C code compiled using the `gcc` crate
          then modify your build script to compile the C code _without_ the
          -fPIC flag. See the documentation of the `gcc::Config.fpic` method for
          details.
          /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crti.o: in function `_init':
          (.init+0xb): relocation truncated to fit: R_X86_64_REX_GOTPCRELX against undefined symbol `__gmon_start__'
          collect2: error: ld returned 1 exit status


error: aborting due to 1 previous error

RISC-V

  • RISC-V is what it would be like if hardware were good.
    • By which I mean open-source.
Good (Open Source) Bad (Not)
Windows x
Linux x
ICC (Intel C Compiler) x
GCC x
x86-64 x
ARM64 x
RISC-V x

RISC-V (pronounced “risk-five”): 1  is a free and open standard instruction set architecture (ISA) based on reduced instruction set computer (RISC) principles. Unlike proprietary ISAs such as x86 and ARM, RISC-V is described as “free and open” because its specifications are released under permissive open-source licenses and can be implemented without paying royalties.

The RISC-V instruction set architecture (ISA) offers a highly customizable open standard platform, enabling developers to build, port, and optimize software applications, extensions, and hardware. Its simplicity and modularity enables efficient design and optimization, fostering innovation and reducing development time and cost.

  • Also unlike x86 and ARM, it is easy to develop for RISC-V, and that is what we’ve done.
$ grep riscv link.x
  By default uses the riscv crates default trap handler
ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned");
ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned");
ERROR(riscv-rt): the start of the REGION_DATA must be 4-byte aligned");
ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned");
ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned");
ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned");
ERROR(riscv-rt): `_stext` must be 4-byte aligned");
BUG(riscv-rt): .data is not 4-byte aligned");
BUG(riscv-rt): the LMA of .data is not 4-byte aligned");
BUG(riscv-rt): .bss is not 4-byte aligned");
BUG(riscv-rt): start of .heap is not 4-byte aligned");
ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region.
ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts.
  • By golly, our .x files are for RISC-V!
    • And therefore good.

Target

  • We recall the notion of a target triple
  • We note:
    • There are references to x86 (in my case) in the error logs.
    • There are references to RISC-V in the linker files.
    • We have to pick one, and…
    • I didn’t provide appropriate linker files for x86
      • Because it’s bad!
  • You will additionlly need to specify a target.
  • riscv64imac-unknown-none-elf
  • You will figure it out.
    • And you will know when you have, because you will see no errors and a main will have appeared.

Main

  • Go ahead and run main, what could go wrong.
$ ./main
-bash: ./main: cannot execute binary file: Exec format error
  • If, like me, you foolish use an architecture other than RISC-V as your daily driver (that’s a metaphor, not a reference to device drivers), main won’t work on your system.

QEMU

  • Well, it will, you just have to bamboozle your system into pretending to be a good system, like RISC-V
sudo apt install qemu-system-misc
  • We don’t allow our feelings to be hurt by RISC-V being described as “miscellaneous”.
    • I didn’t properly sandbox my system before debugging so I think this is all you need but I may be wrong if I lost a dependency somewhere.

Run QEMU

  • You can simply launch qemu with no strings attached.
qemu-system-riscv64
  • For me this popped open a new window.
    • This is good, as otherwise you have to deal with processes and I don’t have time to teach that.
    • If you type “quit” after the prompt of (qemu) it should close.

Run main

  • You can specify that you totally wrote a working OS, honest, by providing qemu with a specified kernel, like our main
qemu-system-riscv64 -kernel main
Forward Pointer

We will properly introduce kernels in the next lecture.

  • This will probably work.
$ qemu-system-riscv64 -kernel main
qemu-system-riscv64: Some ROM regions are overlapping
These ROM regions might have been loaded by direct user request or by default.
They could be BIOS/firmware images, a guest kernel, initrd or some other file loaded into guest memory.
Check whether you intended to load all this guest code, and whether it has been built to load to the correct addresses.

The following two regions overlap (in the memory address space):
  /usr/share/qemu/opensbi-riscv64-generic-fw_dynamic.elf ELF program header segment 1 (addresses 0x0000000080000000 - 0x0000000080016ce8)
  main ELF program header segment 0 (addresses 0x0000000080000000 - 0x0000000080000004)
  • And by probably I mean with a probability of zero.

BIOS

Forward Pointer

We will properly introduce the BIOS in the next lecture.

  • The BIOS can be thought of as a tiny bit of read-only memory (ROM) that lives on a computer.
  • When the computer first turns on, it immediately begins reading instructions from the BIOS (presumably over the bus, or through some other means).
  • The BIOS will then instruct the device how to procede.
  • We don’t need any of that, we already wrote an infinite loop and can just start there!
qemu-system-riscv64 -kernel main -bios none

In computing, BIOS (Basic Input/Output System, also known as the System BIOS, ROM BIOS, BIOS ROM or PC BIOS) is a type of firmware used to provide runtime services for operating systems and programs and to perform hardware initialization during the booting process (power-on startup). On a computer using BIOS firmware, the firmware comes pre-installed on the computer’s motherboard.

Machine

  • You may get an error not unlike this one:
$ qemu-system-riscv64 -bios none -kernel main
qemu-system-riscv64: Invalid HTIF fromhost or tohost address
  • You also may not get this error.
  • It is caused by not telling QEMU which specific, physical machine to pretend to be.
  • I recommend sifive_u, which I believe is the most popular physical chip for developmental purposes.
qemu-system-riscv64 -machine sifive_u -bios none -kernel main

Fin

For me that worked, hopefully for you too! If not, you have officially entered the fun zone.

TODO

For this homework, add a README.md to the crate describing how to get it to work.

  • It should be like this document, but concise.
  • Use cool markdown checkboxes.
- [ ] Do a thing.
- [ ] Do another thing.