Hello from Rust!

Okay, time for the big finale: printing our OKAY from Rust. First, let's change our Makefile to add the Rust code into our assembly code. We can build on the steps we did earlier. Here's a new rule to add to the Makefile:

cargo:
	xargo build --release --target x86_64-unknown-intermezzos-gnu

This uses xargo to automatically cross-compile (remember, we're trying to compile from our OS to intermezzOS) libcore for us. Easy! Let's give it a try:

$ make cargo
xargo build --release --target x86_64-unknown-intermezzos-gnu
 Downloading https://static.rust-lang.org/dist/2016-09-25/rustc-nightly-src.tar.gz
   Unpacking rustc-nightly-src.tar.gz
   Compiling sysroot for x86_64-unknown-intermezzos-gnu
   Compiling core v0.0.0 (file:///home/steve/.xargo/src/libcore)
   Compiling alloc v0.0.0 (file:///home/steve/.xargo/src/liballoc)
   Compiling rustc_unicode v0.0.0 (file:///home/steve/.xargo/src/librustc_unicode)
   Compiling rand v0.0.0 (file:///home/steve/.xargo/src/librand)
   Compiling collections v0.0.0 (file:///home/steve/.xargo/src/libcollections)
   Compiling intermezzos v0.1.0 (file:///home/steve/src/intermezzOS/kernel/chapter_05)
$

Success! It should all build properly. There's one more thing I'd like to note about this makefile: in a strict sense, it will try and rebuild too much. But watch what happens if we try to build a second time:

$ make cargo
xargo build --release --target x86_64-unknown-intermezzos-gnu
$

We issued some commands, but didn't actually compile anything. With this layout, we're letting Cargo worry if stuff needs to be rebuilt. This makes our Makefile a bit easier to write, and also a bit more reliable. Cargo knows what it needs to do, let's just trust it to do the right thing.

Now that we have it building, we need to modify the rule that builds the kernel to include libintermezzos.a:

target/kernel.bin: target/multiboot_header.o target/boot.o src/asm/linker.ld cargo
        ld -n -o target/kernel.bin -T src/asm/linker.ld target/multiboot_header.o target/boot.o target/x86_64-unknown-intermezzos-gnu/release/libintermezzos.a

And then we can build:

$ make
mkdir -p target
nasm -f elf64 src/asm/multiboot_header.asm -o target/multiboot_header.o
mkdir -p target
nasm -f elf64 src/asm/boot.asm -o target/boot.o
xargo build --release --target x86_64-unknown-intermezzos-gnu
   Compiling intermezzos v0.1.0 (file:///home/steve/src/intermezzOS/kernel/chapter_05)
ld -n -o target/kernel.bin -T src/asm/linker.ld target/multiboot_header.o target/boot.o target/x86_64-unknown-intermezzos-gnu/release/libintermezzos.a
$

Hooray! We are now successfully building our assembly code and our Rust code, and then putting them together.

Now, to write our Rust. Add this function to src/lib.rs:


# #![allow(unused_variables)]
#fn main() {
#[no_mangle]
pub extern fn kmain() -> ! {

    loop { }
}
#}

This is our main function, which is traditionally called kmain(), for 'kernel main.' We need to use the #[no_mangle] and pub extern annotations to indicate that we're going to call this function like we would call a C function. The -> ! indicates that this function never returns. And in fact, it does not return: the body is an infinite loop.

I'm going to pause here to mention that while I won't totally assume you're a Rust expert, this is more of an OS tutorial than a Rust tutorial. If anything about the Rust is confusing, I suggest you read over the official book to get an actual introduction to the language. It's tough enough explaining operating systems as it is without needing to fully explain a language too. But if you're an experienced programmer, you might be able to get away without it.

Anyway, our kmain() doesn't do anything. But let's try calling it anyway. Modify src/asm/boot.asm, removing all of the long_mode_start stuff, and changing the jmp line in start to look like this:

    ; jump to long mode!
    jmp gdt64.code:kmain

Finally, add this line to the top of the file:

extern kmain

This line says that we'll be defining kmain elsewhere: in this case, in Rust! And so we also change our jmp to jump to kmain.

If you type make run, everything should compile and run, but then not display anything. We didn't port over the message! Open src/lib.rs and change kmain() to look like this:


# #![allow(unused_variables)]
#fn main() {
#[no_mangle]
pub extern fn kmain() -> ! {
    unsafe {
        let vga = 0xb8000 as *mut u64;

        *vga = 0x2f592f412f4b2f4f;
    };

    loop { }
}
#}

The first thing you'll notice is the unsafe annotation. Yes, while one of Rust's defining features is safety, we'll certainly be making use of unsafe in our kernel. However, we'll be using less than you think. While this is just printing OKAY to the screen, our intermediate VGA driver will be using the exact same amount, with a lot more safe code on top.

In this case, the reason we need unsafe is the next two lines: we create a pointer to 0xb8000, and then write some numbers to it. Rust cannot know that this is safe; if it did, it would have to understand that we are a kernel, and understand the VGA specification. Having a programming language understand VGA at that level would be a bit too much. So instead, we have to use unsafe. Such is life.

However! We are now ready. We've worked really hard for this. Get pumped!!!

$ make run

If all goes well, this will print OKAY to your screen. But you'll have done it with Rust! It only took us five chapters to get here!

This is just the beginning, though. At the end of the next chapter, your main function will look like this, instead:


# #![allow(unused_variables)]
#fn main() {
# #[macro_export]
# macro_rules! kprintln {
#     ($ctx:ident, $fmt:expr) => ();
# }
#
#[no_mangle]
pub extern fn kmain() -> ! {
    kprintln!(CONTEXT, "Hello, world!");

    loop { }
}
#}

But for now, kick back and enjoy what you've done. Congratulations!