diff --git a/blog/content/edition-3/posts/02-booting/index.md b/blog/content/edition-3/posts/02-booting/index.md index 7748b942..cacfcc4a 100644 --- a/blog/content/edition-3/posts/02-booting/index.md +++ b/blog/content/edition-3/posts/02-booting/index.md @@ -47,8 +47,8 @@ Afterwards it looks for a bootable disk and starts booting the operating system [power-on self-test]: https://en.wikipedia.org/wiki/Power-on_self-test On x86, there are two firmware standards: the “Basic Input/Output System“ (**[BIOS]**) and the newer “Unified Extensible Firmware Interface” (**[UEFI]**). -The BIOS standard is old and outdated, but simple and well-supported on any x86 machine since the 1980s. -UEFI, in contrast, is more modern and has much more features, but also more complex. +The BIOS standard is outdated and not standardized, but relatively simple and supported on almost any x86 machine since the 1980s. +UEFI, in contrast, is more modern and has much more features, but also more complex and only runs on fairly recent hardware (built since ~2012). [BIOS]: https://en.wikipedia.org/wiki/BIOS [UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface @@ -116,8 +116,10 @@ Since it is not possible to do all that within the available 446 bytes, most boo Writing a BIOS bootloader is cumbersome as it requires assembly language and a lot of non insightful steps like _“write this magic value to this processor register”_. Therefore we don't cover bootloader creation in this post and instead use the existing [`bootloader`] crate to make our kernel bootable. -If you are interested in building your own BIOS bootloader: Stay tuned, a set of posts on this topic is already planned! + +(If you are interested in building your own BIOS bootloader, you can look through the [BIOS source code] of the `bootloader` crate on GitHub, which is mostly written in Rust and has only about 50 lines of assembly code.) + +[BIOS source code]: https://github.com/rust-osdev/bootloader/tree/main/bios #### The Future of BIOS @@ -152,9 +154,7 @@ Thus, malware should be prevented from compromising the early boot process. #### Issues & Criticism While most of the UEFI specification sounds like a good idea, there are also many issues with the standard. -The main issue for most people is the fear that the _secure boot_ mechanism can be used to [lock users into the Windows operating system][uefi-secure-boot-lock-in] and thus prevent the installation of alternative operating systems such as Linux. - -[uefi-secure-boot-lock-in]: https://arstechnica.com/information-technology/2015/03/windows-10-to-make-the-secure-boot-alt-os-lock-out-a-reality/ +The main issue for most people is the fear that the _secure boot_ mechanism could be used to lock users into a specific operating system (e.g. Windows) and thus prevent the installation of alternative operating systems. Another point of criticism is that the large number of features make the UEFI firmware very complex, which increases the chance that there are some bugs in the firmware implementation itself. This can lead to security problems because the firmware has complete control over the hardware. @@ -174,7 +174,7 @@ The UEFI boot process works in the following way: These partitions must be formatted with the [FAT file system] and assigned a special ID that indicates them as EFI system partition. The UEFI standard understands both the [MBR] and [GPT] partition table formats for this, at least theoretically. In practice, some UEFI implementations seem to [directly switch to BIOS-style booting when an MBR partition table is used][mbr-csm], so it is recommended to only use the GPT format with UEFI. -- If the firmware finds a EFI system partition, it looks for an executable file named `efi\boot\bootx64.efi` (on x86_64 systems) in it. +- If the firmware finds an EFI system partition, it looks for an executable file named `efi\boot\bootx64.efi` (on x86_64 systems). This executable must use the [Portable Executable (PE)] format, which is common in the Windows world. - It then loads the executable from disk to memory, sets up the execution environment (CPU state, page tables, etc.) in a standardized way, and finally jumps to the entry point of the loaded executable. @@ -218,13 +218,13 @@ The reference implementation is [GNU GRUB], which is the most popular bootloader To make a kernel Multiboot compliant, one just needs to insert a so-called [Multiboot header] at the beginning of the kernel file. This makes it very easy to boot an OS in GRUB. -However, GRUB and the Multiboot standard have some problems too: +However, GRUB and the Multiboot standard have some issues too: [Multiboot header]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format - The standard is designed to make the bootloader simple instead of the kernel. For example, the kernel needs to be linked with an [adjusted default page size], because GRUB can't find the Multiboot header otherwise. -Another example is that the [boot information], which is passed to the kernel, contains lots of architecture dependent structures instead of providing clean abstractions. +Another example is that the [boot information], which is passed to the kernel, contains lots of architecture-dependent structures instead of providing clean abstractions. - The standard supports only the 32-bit protected mode on BIOS systems. This means that you still have to do the CPU configuration to switch to the 64-bit long mode. - For UEFI systems, the standard provides very little added value as it simply exposes the normal UEFI interface to kernels. @@ -236,7 +236,7 @@ This makes development on Windows or Mac more difficult. [boot information]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format Because of these drawbacks we decided to not use GRUB or the Multiboot standard for this series. -However, we plan to add Multiboot support to our [`bootloader`] crate, so that it becomes possible to load your kernel on a GRUB system too. +However, we might add Multiboot support to our [`bootloader`] crate at some point, so that it becomes possible to load your kernel on a GRUB system too. If you're interested in writing a Multiboot compliant kernel, check out the [first edition] of this blog series. [first edition]: @/edition-1/_index.md @@ -246,100 +246,80 @@ If you're interested in writing a Multiboot compliant kernel, check out the [fir We now know that most operating system kernels are loaded by bootloaders, which are small programs that initialize the hardware to reasonable defaults, load the kernel from disk, and provide it with some fundamental information about the underlying system. In this section, we will learn how to combine the [minimal kernel] we created in the previous post with the `bootloader` crate in order to create a bootable disk image. -### The `bootloader` Crate - -Since bootloaders quite complex on their own, we won't create our own bootloader here (but we are planning a separate series of posts on this). -Instead, we will boot our kernel using the [`bootloader`] crate. -This crate supports both BIOS and UEFI booting and and creates a reasonable default execution environment for our kernel. +The [`bootloader`] crate supports both BIOS and UEFI booting on `x86_64` and creates a reasonable default execution environment for our kernel. This way, we can focus on the actual kernel design in the following posts instead of spending a lot of time on system initialization. -In order to use this crate in our kernel, we need to add a dependency on `bootloader_api` which provides all the necessary information we need to allow our kernel to function: +### The `bootloader_api` Crate -[`bootloader`]: https://crates.io/crates/bootloader +In order to make our kernel compatible with the `bootloader` crate, we first need to add a dependency on the [`bootloader_api`] crate: -```toml +[`bootloader`]: https://docs.rs/bootloader/latest/bootloader/ +[`bootloader_api`]: https://docs.rs/bootloader_api/latest/bootloader_api/ + +```toml,hl_lines=4 # in Cargo.toml [dependencies] -bootloader_api = "0.11.0" +bootloader_api = "0.11.2" ``` -For normal Rust crates, this step would be all that's needed for adding them as a dependency. -However, the `bootloader` crate is a bit special. -The problem is that it needs access to our kernel _after compilation_ in order to create a bootable disk image. -However, cargo has no support for automatically running code after a successful build, so we need some manual build code for this. -(There is a proposal for [post-build scripts] that would solve this issue, but it is not clear yet whether the Cargo team wants to add such a feature.) +Now we need to replace our custom `_start` entry point function with [`bootloader_api::entry_point`] macro. This macro instructs the compiler to create a special `.bootloader-config` section with encoded configuration options in the resulting executable, which is later read by the bootloader implementation. -[post-build scripts]: https://github.com/rust-lang/cargo/issues/545 +[`bootloader_api::entry_point`]: https://docs.rs/bootloader_api/latest/bootloader_api/macro.entry_point.html -#### Receiving the Boot Information +We will take a closer look at the `entry_point` macro and the different configuration options later. For now, we just use the default setup: -Before we look into the bootable disk image creation, we update need to update our `_start` entry point to be compatible with the `bootloader` crate. -As we already mentioned above, bootloaders commonly pass additional system information when invoking the kernel, such as the amount of available memory. -The `bootloader` crate also follows this convention, so we need to update our `_start` entry point to expect an additional argument. +```rust,hl_lines=3 6-8 +// in main.rs -The [`bootloader_api` documentation][`BootInfo`] specifies that a kernel entry point should have the following signature: +bootloader_api::entry_point!(kernel_main); -[`BootInfo`]: https://docs.rs/bootloader_api/0.11.0/bootloader_api/info/struct.BootInfo.html - -```rust -extern "C" fn(boot_info: &'static mut bootloader::BootInfo) -> ! { ... -} -``` - -The only difference to our `_start` entry point is the additional `boot_info` argument, which is passed by the `bootloader_api` crate. -This argument is a mutable reference to a [`bootloader::BootInfo`] type, which provides various information about the system. - -[`bootloader::BootInfo`]: https://docs.rs/bootloader/0.11.0/bootloader/boot_info/struct.BootInfo.html - -
extern "C" and !