diff --git a/blog/content/edition-3/posts/01-minimal-kernel/index.md b/blog/content/edition-3/posts/01-minimal-kernel/index.md
index a51b9812..a5402944 100644
--- a/blog/content/edition-3/posts/01-minimal-kernel/index.md
+++ b/blog/content/edition-3/posts/01-minimal-kernel/index.md
@@ -3,7 +3,6 @@ title = "Minimal Kernel"
weight = 1
path = "minimal-kernel"
date = 0000-01-01
-draft = true
[extra]
chapter = "Bare Bones"
@@ -37,15 +36,15 @@ The complete source code for this post can be found in the [`post-01`][post bran
## Introduction
To write an operating system kernel, we need code that does not depend on any operating system features.
-This means that we can't use threads, files, heap memory, the network, random numbers, standard output, or any other features requiring OS abstractions or specific hardware.
+So we can't use threads, files, heap memory, the network, random numbers, standard output, or any other features requiring OS abstractions or specific hardware.
Which makes sense, since we're trying to write our own OS and our own drivers.
While this means that we can't use most of the [Rust standard library], there are still a lot of Rust features that we _can_ use.
-For example, we can use [iterators], [closures], [pattern matching], [option] and [result], [string formatting], and of course the [ownership system].
+For example, we can use [iterators], [closures], [pattern matching], [`Option`] and [`Result`], [string formatting], and of course the [ownership system].
These features make it possible to write a kernel in a very expressive, high level way without worrying about [undefined behavior] or [memory safety].
-[option]: https://doc.rust-lang.org/core/option/
-[result]:https://doc.rust-lang.org/core/result/
+[`Option`]: https://doc.rust-lang.org/core/option/
+[`Result`]: https://doc.rust-lang.org/core/result/
[Rust standard library]: https://doc.rust-lang.org/std/
[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html
[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
@@ -72,25 +71,25 @@ We start by creating a new cargo application project.
The easiest way to do this is through the command line:
```
-cargo new blog_os --bin --edition 2018
+cargo new kernel --bin --edition 2021
```
-I named the project `blog_os`, but of course you can choose your own name.
-The `--bin` flag specifies that we want to create an executable binary (in contrast to a library) and the `--edition 2018` flag specifies that we want to use the [2018 edition] of Rust for our crate.
+We name the project `kernel` here, but of course you can choose your own name.
+The `--bin` flag specifies that we want to create an executable binary (in contrast to a library) and the `--edition 2018` flag specifies that we want to use the [2021 edition] of Rust for our crate.
When we run the command, cargo creates the following directory structure for us:
-[2018 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
+[2021 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/index.html
```
-blog_os
+kernel
├── Cargo.toml
└── src
└── main.rs
```
-The `Cargo.toml` contains the crate configuration, for example the crate name, the author, the [semantic version] number, and dependencies.
+The `Cargo.toml` contains the crate configuration, for example the crate name, the [semantic version] number, and dependencies.
The `src/main.rs` file contains the root module of our crate and our `main` function.
-You can compile your crate through `cargo build` and then run the compiled `blog_os` binary in the `target/debug` subfolder.
+You can compile your crate through `cargo build` and then run the compiled `kernel` binary in the `target/debug` subfolder.
[semantic version]: https://semver.org/
@@ -117,9 +116,15 @@ error: cannot find macro `println!` in this scope
|
4 | println!("Hello, world!");
| ^^^^^^^
+
+error: `#[panic_handler]` function required, but not found
+
+error: language item required, but not found: `eh_personality`
+[...]
```
-The reason for this error is that the [`println` macro] is part of the standard library, which we no longer include.
+There are multiple errors.
+The reason for the first one is that the [`println` macro] is part of the standard library, which we no longer include.
So we can no longer print things.
This makes sense, since `println` writes to [standard output], which is a special file descriptor provided by the operating system.
@@ -137,12 +142,14 @@ fn main() {}
```
```
-> cargo build
+❯ cargo build
error: `#[panic_handler]` function required, but not found
+
error: language item required, but not found: `eh_personality`
+[...]
```
-Now the compiler is missing a `#[panic_handler]` function and a _language item_.
+The `println` error is gone, but the compiler is still missing a `#[panic_handler]` function and a _language item_.
### Panic Implementation
@@ -164,7 +171,7 @@ fn panic(_info: &PanicInfo) -> ! {
```
The [`PanicInfo` parameter][PanicInfo] contains the file and line where the panic happened and the optional panic message.
-The function should never return, so it is marked as a [diverging function] by returning the [“never” type] `!`.
+The handler function should never return, so it is marked as a [diverging function] by returning the [“never” type] `!`.
There is not much we can do in this function for now, so we just loop indefinitely.
[PanicInfo]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
@@ -174,40 +181,46 @@ There is not much we can do in this function for now, so we just loop indefinite
After defining a panic handler, only the `eh_personality` language item error remains:
```
-> cargo build
+❯ cargo build
error: language item required, but not found: `eh_personality`
+ |
+ = note: this can occur when a binary crate with `#![no_std]` is compiled for a
+ target where `eh_personality` is defined in the standard library
+ = help: you may be able to compile for a target that doesn't need `eh_personality`,
+ specify a target with `--target` or in `.cargo/config`
```
-### The `eh_personality` Language Item
+### Disabling Unwinding
Language items are special functions and types that are required internally by the compiler.
-For example, the [`Copy`] trait is a language item that tells the compiler which types have [_copy semantics_][`Copy`].
-When we look at the [implementation][copy code], we see it has the special `#[lang = "copy"]` attribute that defines it as a language item.
-
-[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
-[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
-
-While providing custom implementations of language items is possible, it should only be done as a last resort.
-The reason is that language items are highly unstable implementation details and not even type checked (so the compiler doesn't even check if a function has the right argument types).
-Fortunately, there is a more stable way to fix the above language item error.
+They are normally provided by the standard library, which we disabled using the `#![no_std]` attribute.
The [`eh_personality` language item] marks a function that is used for implementing [stack unwinding].
By default, Rust uses unwinding to run the destructors of all live stack variables in case of a [panic].
This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution.
-Unwinding, however, is a complicated process and requires some OS specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system.
+Unwinding, however, is a complex process and requires some OS-specific libraries, such as [libunwind] on Linux or [structured exception handling] on Windows.
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
[libunwind]: https://www.nongnu.org/libunwind/
[structured exception handling]: https://docs.microsoft.com/de-de/windows/win32/debug/structured-exception-handling
-#### Disabling Unwinding
+While unwinding is very useful, it also has some drawbacks.
+For example, it increases the size of the compiled executable because it requires additional context at runtime.
+Because of these drawbacks, Rust provides an option to [abort on panic] instead.
-There are other use cases as well for which unwinding is undesirable, so Rust provides an option to [abort on panic] instead.
-This disables the generation of unwinding symbol information and thus considerably reduces binary size.
-There are multiple ways to disable unwinding, the easiest is to add the following lines to our `Cargo.toml`:
+[abort on panic]: https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html#unwinding-the-stack-or-aborting-in-response-to-a-panic
+
+We already use a custom panic handler that never returns, so we don't need unwinding for our kernel.
+By disabling it, the `eh_personality` language item won't be required anymore.
+
+There are multiple ways to set the panic strategy, the easiest is to use [cargo profiles]:
+
+[cargo profiles]: https://doc.rust-lang.org/cargo/reference/profiles.html
```toml
+# in Cargo.toml
+
[profile.dev]
panic = "abort"
@@ -218,24 +231,25 @@ panic = "abort"
This sets the panic strategy to `abort` for both the `dev` profile (used for `cargo build`) and the `release` profile (used for `cargo build --release`).
Now the `eh_personality` language item should no longer be required.
-[abort on panic]: https://github.com/rust-lang/rust/pull/32900
Now we fixed both of the above errors.
However, if we try to compile it now, another error occurs:
```
-> cargo build
+❯ cargo build
error: requires `start` lang_item
```
-Our program is missing the `start` language item, which defines the entry point.
+Our kernel is missing the `start` language item, which defines the _entry point_ of the executable.
-### The `start` Language Item
-One might think that the `main` function is the first function called when a program is run.
-However, most languages have a [runtime system], which is responsible for things such as garbage collection (e.g. in Java) or software threads (e.g. goroutines in Go).
+## Setting the Entry Point
+
+The [entry point] of a program is the function that is called when the executable is started.
+One might think that the `main` function is the first function called, however, most languages have a [runtime system], which is responsible for things such as garbage collection (e.g. in Java) or software threads (e.g. goroutines in Go).
This runtime needs to be called before `main`, since it needs to initialize itself.
+[entry point]: https://en.wikipedia.org/wiki/Entry_point
[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
In a typical Rust binary that links the standard library, execution starts in a C runtime library called [`crt0`] (“C runtime zero”), which sets up the environment for a C application.
@@ -248,11 +262,77 @@ The runtime then finally calls the `main` function.
[call stack]: https://en.wikipedia.org/wiki/Call_stack
[rt::lang_start]: hhttps://github.com/rust-lang/rust/blob/0d97f7a96877a96015d70ece41ad08bb7af12377/library/std/src/rt.rs#L59-L70
-Our freestanding executable does not have access to the Rust runtime and `crt0`, so we need to define our own entry point.
-Implementing the `start` language item wouldn't help, since it would still require `crt0`.
-Instead, we need to overwrite the `crt0` entry point directly.
+Since we're building an operating system kernel that should run without any underlying operating system, we don't want our kernel to depend on any Rust or C runtime.
+To remove these dependencies, we need to do two things:
+
+1. Instruct the compiler that we want to build for a bare-metal target environment. This removes the dependency on the C library.
+2. Disable the Rust main function to remove the Rust runtime.
+
+### Bare-Metal Target
+
+By default Rust tries to build an executable that is able to run in your current system environment.
+For example, if you're using Windows and an `x86_64` CPU, Rust tries to build a `.exe` Windows executable that uses `x86_64` instructions.
+This environment is called your "host" system.
+
+To describe different environments, Rust uses a string called [_target triple_].
+You can see the target triple for your host system by running `rustc --version --verbose`:
+
+[_target triple_]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
+
+```
+rustc 1.66.0 (69f9c33d7 2022-12-12)
+binary: rustc
+commit-hash: 69f9c33d71c871fc16ac445211281c6e7a340943
+commit-date: 2022-12-12
+host: x86_64-unknown-linux-gnu
+release: 1.66.0
+LLVM version: 15.0.2
+```
+
+The above output is from a `x86_64` Linux system.
+We see that the `host` triple is `x86_64-unknown-linux-gnu`, which includes the CPU architecture (`x86_64`), the vendor (`unknown`), the operating system (`linux`), and the [ABI] (`gnu`).
+
+[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
+
+By compiling for our host triple, the Rust compiler and the linker assume that there is an underlying operating system such as Linux or Windows that uses the C runtime by default, which requires the `start` language item.
+To avoid the runtimes, we can compile for a different environment with no underlying operating system.
+
+#### The `x86_64-unknown-none` Target
+
+Rust supports a [variety of target systems][platform-support], including some bare-metal targets.
+For example, the `thumbv7em-none-eabihf` target triple can be used to compile for an [embedded] [ARM] system with a `Cortex M4F` CPU, as used in the [Rust Embedded Book].
+
+[platform-support]: https://doc.rust-lang.org/rustc/platform-support.html
+[embedded]: https://en.wikipedia.org/wiki/Embedded_system
+[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
+[Rust Embedded Book]: https://docs.rust-embedded.org/book/intro/index.html
+
+Our kernel should run on a bare-metal `x86_64` system, so the suitable target triple is [`x86_64-unknown-none`].
+The `-none` suffix indicates that there is no underlying operating system.
+To be able to compile for this target, we need to add it using [`rustup`]:
+
+[`x86_64-unknown-none`]: https://doc.rust-lang.org/rustc/platform-support/x86_64-unknown-none.html
+[`rustup`]: https://doc.rust-lang.org/rustc/platform-support/x86_64-unknown-none.html
+
+```
+rustup target add x86_64-unknown-none
+```
+
+This downloads a pre-compiled copy of the `core` library for the target.
+Afterwards, we can [cross compile] our executable for a bare metal environment by passing a `--target` argument:
+
+[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
+
+```
+❯ cargo build --target x86_64-unknown-none
+ Compiling kernel v0.1.0 (/<...>/kernel)
+error: requires `start` lang_item
+```
+
+We still get the error about a missing `start` language item because we're still depending on the Rust runtime. To remove that dependency, we can use the `#[no_main]` attribute.
+
+### The `#[no_main]` Attribute
-#### Overwriting the Entry Point
To tell the Rust compiler that we don't want to use the normal entry point chain, we add the `#![no_main]` attribute.
```rust
@@ -280,627 +360,211 @@ pub extern "C" fn _start() -> ! {
```
By using the `#[no_mangle]` attribute we disable the [name mangling] to ensure that the Rust compiler really outputs a function with the name `_start`.
-Without the attribute, the compiler would generate some cryptic `_ZN3blog_os4_start7hb173fedf945531caE` symbol to give every function an unique name.
+Without the attribute, the compiler would generate some cryptic `_ZN3kernel4_start7hb173fedf945531caE` symbol to give every function an unique name.
The reason for naming the function `_start` is that this is the default entry point name for most systems.
+[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
+
We mark the function as `extern "C"` to tell the compiler that it should use the [C calling convention] for this function (instead of the unspecified Rust calling convention).
-The `!` return type means that the function is diverging, i.e. not allowed to ever return.
+
+[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
+
+Like in our panic handler, the `!` return type means that the function is diverging, i.e. not allowed to ever return.
This is required because the entry point is not called by any function, but invoked directly by the operating system or bootloader.
So instead of returning, the entry point should e.g. invoke the [`exit` system call] of the operating system.
In our case, shutting down the machine could be a reasonable action, since there's nothing left to do if a freestanding binary returns.
For now, we fulfill the requirement by looping endlessly.
-[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
-[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
-When we run `cargo build` now, we get an ugly _linker_ error.
-
-## Linker Errors
-
-The [linker] is a program that combines the generated code into an executable.
-Since the executable format differs between Linux, Windows, and macOS, each system has its own linker that throws a different error.
-The fundamental cause of the errors is the same: the default configuration of the linker assumes that our program depends on the C runtime, which it does not.
-
-To solve the errors, we need to tell the linker that we want to build for a bare-metal target, where no underlying operating system or C runtime exist.
-As an alternative, it is also possible to disable the linking of the C runtime by passing a certain set of arguments to the linker.
-
-### Linker Arguments
-
-Linkers are very complex programs with a lot of configuration options.
-Each of the major operating systems (Linux, Windows, macOS) has its own linker implementation with different options, but all of them provide a way to disable the linking of the C runtime.
-By using these options, it is possible to create a freestanding executable that still runs on top of an existing operating system.
-
-_This is not what we want for our kernel, so this section is only provided for completeness.
-Feel free to skip this section if you like._
-
-In the subsections below, we explain the required linker arguments for each operating system.
-It's worth noting that creating a freestanding executable this way is probably not a good idea.
-The reason is that our executable still expects various things, for example that a stack is initialized when the `_start` function is called.
-Without the C runtime, some of these requirements might not be fulfilled, which might cause our program to fail, e.g. by causing a segmentation fault.
-
-If you want to create a minimal binary that runs on top of an existing operating system, including `libc` and setting the `#[start]` attribute as described [here](https://doc.rust-lang.org/1.16.0/book/no-stdlib.html) is probably a better idea.
-
-
-
-#### Linux
-
-
-On Linux the following linker error occurs (shortened):
+When we run `cargo build --target x86_64-unknown-none` now, it should finally compile without any errors:
```
-error: linking with `cc` failed: exit code: 1
- |
- = note: "cc" […]
- = note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
- (.text+0x12): undefined reference to `__libc_csu_fini'
- /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
- (.text+0x19): undefined reference to `__libc_csu_init'
- /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
- (.text+0x25): undefined reference to `__libc_start_main'
- collect2: error: ld returned 1 exit status
+❯ cargo build --target x86_64-unknown-none
+ Compiling kernel v0.1.0 (/<...>/kernel)
+ Finished dev [unoptimized + debuginfo] target(s) in 0.25s
```
-The problem is that the linker includes the startup routine of the C runtime by default, which is also called `_start`.
-It requires some symbols of the C standard library `libc` that we don't include due to the `no_std` attribute, therefore the linker can't resolve these references.
-To solve this, we can tell the linker that it should not link the C startup routine by passing the `-nostartfiles` flag.
+We successfully created a minimal bare-metal kernel executable! The compiled executable can be found at `target/x86_64-unknown-none/debug/kernel`.
+There is no `.exe` extension even if you're on Windows because the `x86_64-unknown-none` target uses UNIX standards.
-One way to pass linker attributes via cargo is the `cargo rustc` command.
-The command behaves exactly like `cargo build`, but allows to pass options to `rustc`, the underlying Rust compiler.
-`rustc` has the `-C link-arg` flag, which passes an argument to the linker.
-Combined, our new build command looks like this:
+To build the kernel with optimizations, we can run:
```
-cargo rustc -- -C link-arg=-nostartfiles
+cargo build --target x86_64-unknown-none --release
```
-Now our crate builds as a freestanding executable on Linux!
+The compiled executable is placed at `target/x86_64-unknown-none/release/kernel` in this case.
-We didn't need to specify the name of our entry point function explicitly since the linker looks for a function with the name `_start` by default.
-
+## Useful Tools
-#### Windows
-
+In this section, we will examine our kernel executable using the [`objdump`], [`nm`], and [`size`] tools.
-On Windows, the following linker error occurs (shortened):
+[`objdump`]: https://www.man7.org/linux/man-pages/man1/objdump.1.html
+[`nm`]: https://man7.org/linux/man-pages/man1/nm.1.html
+[`size`]: https://man7.org/linux/man-pages/man1/size.1.html
+
+If you're on a UNIX system, you might already have the `nm`, `objdump`, and `strip` tools installed.
+Otherwise, you can use the LLVM binutils shipped by `rustup` through the [`cargo-binutils`] crate.
+To install it, run **`cargo install cargo-binutils`** and **`rustup component add llvm-tools-preview`**.
+Afterwards, you can run the tools through `rust-nm`, `rust-objdump`, and `rust-strip`.
+
+[`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils
+
+### `nm`
+
+We defined a `_start` function as the entry point of our kernel.
+To verify that it is properly exposed in the executable, we can run `nm` to list all the symbols defined in the executable:
```
-error: linking with `link.exe` failed: exit code: 1561
- |
- = note: "C:\\Program Files (x86)\\…\\link.exe" […]
- = note: LINK : fatal error LNK1561: entry point must be defined
+❯ rust-nm target/x86_64-unknown-none/debug/kernel
+0000000000002218 d _DYNAMIC
+0000000000001210 T _start
```
-The "entry point must be defined" error means that the linker can't find the entry point.
-On Windows, the default entry point name [depends on the used subsystem][windows-subsystems].
-For the `CONSOLE` subsystem the linker looks for a function named `mainCRTStartup` and for the `WINDOWS` subsystem it looks for a function named `WinMainCRTStartup`.
-To override the default and tell the linker to look for our `_start` function instead, we can pass an `/ENTRY` argument to the linker:
-
-[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
+If we comment out the `_start` function or if we remove the `#[no_mangle]` attribute, the `_start` symbol is no longer there after recompiling:
```
-cargo rustc -- -C link-arg=/ENTRY:_start
+❯ rust-nm target/x86_64-unknown-none/debug/kernel
+0000000000002218 d _DYNAMIC
```
-From the different argument format we clearly see that the Windows linker is a completely different program than the Linux linker.
+### `objdump`
-Now a different linker error occurs:
+The `objdump` tool can inspect different parts of executables that use the [ELF file format].
+
+[ELF file format]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
+
+#### File Headers
+
+Upon other things, the ELF [file header] specifies the target architecture and the entry point address of the executable files.
+To print the file header, we can use `objdump -f`:
+
+[file header]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
```
-error: linking with `link.exe` failed: exit code: 1221
- |
- = note: "C:\\Program Files (x86)\\…\\link.exe" […]
- = note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
- defined
+❯ rust-objdump -f target/x86_64-unknown-none/debug/kernel
+
+target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
+architecture: x86_64
+start address: 0x0000000000001210
```
-This error occurs because Windows executables can use different [subsystems][windows-subsystems].
-For normal programs they are inferred depending on the entry point name: If the entry point is named `main`, the `CONSOLE` subsystem is used, and if the entry point is named `WinMain`, the `WINDOWS` subsystem is used.
-Since our `_start` function has a different name, we need to specify the subsystem explicitly:
+As expected, our kernel targets the `x86_64` CPU architecture.
+The start address specifies the memory address of our `_start` function.
+Here the function name `_start` becomes important.
+If we rename the function to something else (e.g., `_start_here`) and recompile, we see that no start address is set in the ELF file anymore:
```
-cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
+❯ rust-objdump -f target/x86_64-unknown-none/debug/kernel
+
+target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
+architecture: x86_64
+start address: 0x0000000000000000
```
-We use the `CONSOLE` subsystem here, but the `WINDOWS` subsystem would work too.
-Instead of passing `-C link-arg` multiple times, we use `-C link-args` which takes a space separated list of arguments.
+#### Sections
-With this command, our executable should build successfully on Windows.
-
-
-#### macOS
-
-
-On macOS, the following linker error occurs (shortened):
+Using `objdump -h`, we can print the various sections of our kernel executable:
```
-error: linking with `cc` failed: exit code: 1
- |
- = note: "cc" […]
- = note: ld: entry point (_main) undefined.
- for architecture x86_64
- clang: error: linker command failed with exit code 1 […]
-```
+❯ rust-objdump -h target/x86_64-unknown-none/debug/kernel
-This error message tells us that the linker can't find an entry point function with the default name `main` (for some reason all functions are prefixed with a `_` on macOS).
-To set the entry point to our `_start` function, we pass the `-e` linker argument:
+target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
+
+Sections:
+Idx Name Size VMA Type
+ 0 00000000 0000000000000000
+ 1 .dynsym 00000018 00000000000001c8
+ 2 .gnu.hash 0000001c 00000000000001e0
+ 3 .hash 00000010 00000000000001fc
+ 4 .dynstr 00000001 000000000000020c
+ 5 .text 00000004 0000000000001210 TEXT
+ 6 .dynamic 000000a0 0000000000002218
+ 7 .debug_abbrev 0000010c 0000000000000000 DEBUG
+ 8 .debug_info 000005ce 0000000000000000 DEBUG
+ 9 .debug_aranges 00000040 0000000000000000 DEBUG
+ 10 .debug_ranges 00000030 0000000000000000 DEBUG
+ 11 .debug_str 00000492 0000000000000000 DEBUG
+ 12 .debug_pubnames 000000bc 0000000000000000 DEBUG
+ 13 .debug_pubtypes 0000036c 0000000000000000 DEBUG
+ 14 .debug_frame 00000050 0000000000000000 DEBUG
+ 15 .debug_line 00000059 0000000000000000 DEBUG
+ 16 .comment 00000013 0000000000000000
+ 17 .symtab 00000060 0000000000000000
+ 18 .shstrtab 000000ce 0000000000000000
+ 19 .strtab 00000022 0000000000000000
+ ```
+
+The `.text` section contains the program code, the other sections are not important right now.
+The section dump is useful for debugging, for example for checking which section a pointer points to.
+
+Most of the sections only contain debug information and are not needed for execution.
+We can remove this debug information using `rust-strip`:
```
-cargo rustc -- -C link-args="-e __start"
+❯ rust-strip target/x86_64-unknown-none/debug/kernel
+❯ rust-objdump -h target/x86_64-unknown-none/debug/kernel
+
+target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
+
+Sections:
+Idx Name Size VMA Type
+ 0 00000000 0000000000000000
+ 1 .dynsym 00000018 00000000000001c8
+ 2 .gnu.hash 0000001c 00000000000001e0
+ 3 .hash 00000010 00000000000001fc
+ 4 .dynstr 00000001 000000000000020c
+ 5 .text 00000004 0000000000001210 TEXT
+ 6 .dynamic 000000a0 0000000000002218
+ 7 .shstrtab 00000034 0000000000000000
```
-The `-e` flag specifies the name of the entry point function.
-Since all functions have an additional `_` prefix on macOS, we need to set the entry point to `__start` instead of `_start`.
+#### Disassembling
-Now the following linker error occurs:
+Sometimes we need to check the [assembly code] that certain functions compile to.
+We can use the `objdump -d` command to print the `.text` section of an executable in assembly language:
+
+[assembly code]: https://en.wikipedia.org/wiki/X86_assembly_language
```
-error: linking with `cc` failed: exit code: 1
- |
- = note: "cc" […]
- = note: ld: dynamic main executables must link with libSystem.dylib
- for architecture x86_64
- clang: error: linker command failed with exit code 1 […]
+❯ rust-objdump -d target/x86_64-unknown-none/debug/kernel
+
+target/x86_64-unknown-none/debug/kernel: file format elf64-x86-64
+
+Disassembly of section .text:
+
+0000000000001210 <_start>:
+ 1210: eb 00 jmp 0x1212 <_start+0x2>
+ 1212: eb fe jmp 0x1212 <_start+0x2>
```
-macOS [does not officially support statically linked binaries] and requires programs to link the `libSystem` library by default.
-To override this and link a static binary, we pass the `-static` flag to the linker:
+We see that our `_start` function consists of just two [`jmp` instructions], which jump to the given address.
+The first `jmp` command jumps to the second `jmp` command at address `1212`.
+The second `jmp` command jumps to itself again, thereby representing the infinite loop that we've written in our `_start` function.
-[does not officially support statically linked binaries]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
+[`jmp` instructions]: https://www.felixcloutier.com/x86/jmp
+
+As you probably noticed, the first `jmp` command is not really needed.
+Such inefficiencies can happen in debug builds because the compiler does not optimize them.
+If we disassemble the optimized release build, we see that the compiler indeed removed the unneeded `jmp`:
```
-cargo rustc -- -C link-args="-e __start -static"
+❯ cargo build --target x86_64-unknown-none --release
+❯ rust-objdump -d target/x86_64-unknown-none/release/kernel
+
+target/x86_64-unknown-none/release/kernel: file format elf64-x86-64
+
+Disassembly of section .text:
+
+0000000000001210 <_start>:
+ 1210: eb fe jmp 0x1210 <_start>
```
-This still does not suffice, as a third linker error occurs:
-
-```
-error: linking with `cc` failed: exit code: 1
- |
- = note: "cc" […]
- = note: ld: library not found for -lcrt0.o
- clang: error: linker command failed with exit code 1 […]
-```
-
-This error occurs because programs on macOS link to `crt0` (“C runtime zero”) by default.
-This is similar to the error we had on Linux and can be also solved by adding the `-nostartfiles` linker argument:
-
-```
-cargo rustc -- -C link-args="-e __start -static -nostartfiles"
-```
-
-Now our program should build successfully on macOS.
-