Skip to content

Development⚓︎

Preliminary Information

We expect you to have some experience with Rust: you need not be an expert, but unCORE requires you to understand the basics of the programming language. If you are a complete beginner, we highly recommend you to read the official Rust book.

🚀 Getting Started⚓︎

After you have forked the repository, you can clone it. All Rust code resides in code/. The documentation lives in documentation/. misc/ contains miscellaneous files, e.g., GDB initialization files, shell aliases, etc. In the .github/ directory you can find CI/CD and GitHub-related configuration files. The code/ directory is a Cargo Workspace.

If you want to start working on unCORE, go ahead and install Rust and mold. When you later work on this project, you will be told if you're missing other dependencies (like qemu-system-riscv64 or jq).

🧰 Workflow⚓︎

About the Workspace⚓︎

The workspace that lives inside code/ has a "main" binary and "proper" workspace members. The main binary lives in code/src/. An example for a workspace member is code/uncore/; this is where the kernel code resides.

You now have two options:

  1. When using cargo run -- <COMMAND>, the main binary is invoked, which contains code to handle the other workspace members; and this is the trick to using only Rust and no build system (other than Cargo). The default binary, when invoked, invokes Cargo again with all the correct arguments and options to properly build the kernel. It also performs required checks (e.g., on dependencies) beforehand.
  2. When you do not want to install Rust, you can also use a build container. This is also used in the CI to build and test unCORE.

With such a workspace, unCORE does not require a build system or additional build configuration in files like .cargo/config.toml that are inflexible.

The actual kernel code lives in code/uncore/src/. In this directory, you fill find main.rs, lib.rs and library/. main.rs is recognized by Cargo automatically as a binary. It contains the kernel's entry point (called by a bootloader). lib.rs is automatically recognized by Cargo as a library target. This is useful because we can put all kernel code in the library, whose root is lib.rs, and just call it from binaries - such binaries are not only main.rs, but integration tests (in code/uncore/tests/) as well! library/ is the top-level directory that is used by lib.rs as a module (i.e., with mod library;); library/ exists in order to not have all top-level modules in the top-level directory.

Working with unCORE⚓︎

When working on unCORE, you use the workspace's main binary. You run it by executing cargo run, and you provide all arguments to it in the same command. To see which commands and options the binary supports, run the following commands:

$ cd code
$ cargo run -- help # (1)
Compiling uncore-helper v1.0.0-alpha1 (/path/to/uncore/code)
Finished dev [unoptimized + debuginfo] target(s) in 1.56s
Running `target/debug/uncore-helper help`

Workspace member that eases working with unCORE.
...
  1. The -- is used to separate arguments for Cargo from those that we want to pass to our binary. In this case, run is an argument to Cargo, help is an argument to our binary.

There are different commands available: The run command will run unCORE; when you use run --debug, you can attach GDB; when you use u-test, you run unit-tests. Using the help (or --help or -h) command will always show which commands you can run. These patterns are used ubiquitously; unCORE's CI also makes use of these commands.

To further ease the process, aliases are defined in code/.cargo/config.toml. Hence, to run the kernel, you may use cargo _run. Have a look at the file to see which other aliases are defined.

How Is the Kernel Actually Built?

As mentioned earlier, the kernel is actually built by the main workspace binary (residing in code/src/). The function that invokes Cargo is code/src/command.rs:build. Cargo then builds the kernel whose code resides in code/uncore/src/.

The "heavy lifting" is done by Cargo. The workspace main binary "only" takes care of checking dependencies and invoking Cargo correctly, i.e., with the correct target (architecture), environment variables used when building, linker script (and linker), etc.

Development Container⚓︎

We strongly recommend you to use the Development Container that ships with the repository. This way, all dependencies come with the container image and you do not need to install anything manually on your host. You will need to have an OCI-compatible container runtime (e.g., Docker with Containerd, Podman with crun, etc.) installed and an IDE that supports the Development Container standard (e.g., Visual Studio Code with the ms-vscode-remote.remote-containers ("Dev Containers") extension installed). Using Development Containers has the additional upside that common tasks (like building or running unCORE) can then easily be handled by the IDE as well, because the appropriate configurations will be placed in the correct locations.

The configuration for the Development Container is located in the .devcontainer/ directory.

🧭 Conventions⚓︎

Please stick to the style and naming conventions you encounter in this project. Clippy is used to check and lint the Rust code in this project. rustfmt is used to format (and check) the code. An appropriate code/.rustfmt.toml is already provided. Similarly, we use EditorConfig. Conventions are enforced by our CI.

🧯 Debugging⚓︎

The workspace main binary provides an easy way to debug unCORE. Debugging is supported for running the plain kernel binary, the unit-, and the integration-tests. All you need to do is add the --debug flag to these targets:

$ cargo run -q -- -vv run --debug
...
DEBUG Checking run-time dependencies
TRACE Checking run-time dependencies required for debugging
...
INFO  Debugging unCORE
DEBUG You may use 'gdb-multiarch -q -x code/misc/gdb/<FILE>' to attach now
TRACE Remember: 'Ctrl-A x' will exit QEMU
...

You can then attach to QEMU with GDB. An example initialization script for GDB can found at misc/gdb/init.gdb.

Note

The command also works for unit-tests: cargo run -q -- -vv u-test --debug. For integration tests, you will need to specify the test name in conjunction with the --debug flag: cargo run -q -- -vv i-test --debug --test <TEST NAME>.