Bootloader (part 1)

3 minute read

I’ve decided it would be an interesting experience to write a 360 bootloader in Rust.

Rust has been my new favorite language lately, and it nicely solves some problems I’ve ran into previously when working on the C-based bootloader: it gives me an ease-of-mind that the resulting code is memory-safe at runtime, and, most importantly, I get the ability to include no-std third-party libraries to extend and supercharge the bootloader.

Existing work

There are a bunch of existing Rust-based bootloaders for x86 machines, but nothing written for PowerPC yet. There certainly isn’t anything for the Xbox 360.

Existing x86 bootloaders include chocolate milk, KRaBs, and more (apparently writing an x86 bootloader is a meme nowadays).

The existing bootloader for the Xbox 360 is XeLL, which is written in C and includes mostly custom implementations for all functionality it uses.

Before writing any code though, there are a few lessons we can learn from the existing x86 bootloaders:

Assembly

You will have to write assembly code to handle the earliest stages of booting, as well as for handling hardware linkage such as interrupt routines or exception vectors.

Having asm sources somewhat complicates the build process however. Cargo doesn’t really have a great way to compile non-Rust code. The “best” way to include full assembly files in your code is to use the global_asm! macro, but it isn’t fantastic.

You could also use the cc crate to build assembly code, but this is a harder feat with MSVC since Microsoft didn’t make run-time discovery of the compiler or the Windows SDK easy (see: vcvarsall.bat).

On x86, the first boot stages are even more complicated because the bootloaders have to transition from 16-bit, to 32-bit, to 64-bit. Different code is valid for different modes, and x86 will even interpret code bytes differently depending on the CPU mode, so the code has to be specifically compiled for the corresponding CPU mode. This makes it really difficult to use inline assembly in the earliest stages of x86’s startup, since you cannot change the assembler’s target per-block (as far as I know).

Fortunately, for PowerPC, the boot process is extremely simple. The system comes up in a state where it can almost immediately run C or Rust code.

So it’d be preferable for us to write the bootloader in pure-Rust so it plays nice with Cargo, and so we can avoid extending the build process with a custom build.rs script. Fortunately in Rust, we could just define a naked pure-assembly function to handle startup:

#[naked]
#[no_mangle]
unsafe extern "C" fn start_rom() -> ! {
    asm!(
      "<startup code here>",
      options(noreturn)
    );
}

And we compile it, aaand:

error[E0472]: asm! is unsupported on this target
  --> src\main.rs:20:5
   |
20 |     asm!("", options(noreturn));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Dang! The new asm!() isn’t available for PowerPC :(

Contributing a pull request to Rust

Fortunately, after scavenging around a bit on the Rust repository, I found a pull request that had an implementation of another architecture for the asm!() macro. It was pretty simple (+~300 lines), so I duplicated it for PowerPC.

It’s currently (as of writing this blog) undergoing code review, but looks like it will land soon.

Linker scripts

You will need to tell the compiler how to lay out your bootloader, because the exact position of certain portions of it matter. For example, on PowerPC, the reset vector is at 0x100 in physical memory. You will have to have code at that location.

For example, KRaBs uses linker scripts for their bootloader stages.

On my 360 console, the CD bootloader was overwritten with a custom bootloader that loads our bootloader into RAM at 0x80000000_1C000000 from flash and jumps into it. For reference, this is the linker script for XeLL’s first stage.

So we will need to have a branch statement at that exact location in our bootloader, and it will need to branch to the start_rom() function defined above.

That function will then have to initialize system registers, set up a stack, and then jump into Rust. The rest of the setup can be done in Rust, where it’s much easier to write code.

Conclusion

I’m pretty excited about being able to use such a new and high-level language for writing this low-level code. There are a lot of pitfalls with writing such code in C (lack of debuggability; lack of third-party libraries; build system issues) that Rust breezes straight over.

Unfortunately I won’t be getting too far in writing this bootloader until my pull request is available on nightly Rust. When that happens, maybe I’ll get around to posting more on this blog?

Tags:

Categories:

Updated: