mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
fix: check writing of 01
This commit is contained in:
@@ -44,7 +44,7 @@ In order to create an OS kernel in Rust, we need to create an executable that ca
|
|||||||
This post describes the necessary steps to create a freestanding Rust binary and explains why the steps are needed. If you're just interested in a minimal example, you can **[jump to the summary](#summary)**.
|
This post describes the necessary steps to create a freestanding Rust binary and explains why the steps are needed. If you're just interested in a minimal example, you can **[jump to the summary](#summary)**.
|
||||||
|
|
||||||
## Disabling the Standard Library
|
## Disabling the Standard Library
|
||||||
By default, all Rust crates link the [standard library], which depends on the operating system for features such as threads, files, or networking. It also depends on the C standard library `libc`, which closely interacts with OS services. Since our plan is to write an operating system, we can not use any OS-dependent libraries. So we have to disable the automatic inclusion of the standard library through the [`no_std` attribute].
|
By default, all Rust crates link the [standard library], which depends on the operating system for features such as threads, files, or networking. It also depends on the C standard library `libc`, which closely interacts with OS services. Since our plan is to write an operating system, we can't use any OS-dependent libraries. So we have to disable the automatic inclusion of the standard library through the [`no_std` attribute].
|
||||||
|
|
||||||
[standard library]: https://doc.rust-lang.org/std/
|
[standard library]: https://doc.rust-lang.org/std/
|
||||||
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
[`no_std` attribute]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
|
||||||
@@ -150,7 +150,7 @@ Language items are special functions and types that are required internally by t
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
|
[`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
|
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
|
||||||
@@ -219,7 +219,7 @@ 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. The attribute is required because we need to tell the name of the entry point function to the linker in the next step.
|
By using the `#[no_mangle]` attribute, we disable [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 a unique name. The attribute is required because we need to tell the name of the entry point function to the linker in the next step.
|
||||||
|
|
||||||
We also have to 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 reason for naming the function `_start` is that this is the default entry point name for most systems.
|
We also have to 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 reason for naming the function `_start` is that this is the default entry point name for most systems.
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ To solve the errors, we need to tell the linker that it should not include the C
|
|||||||
|
|
||||||
### Building for a Bare Metal Target
|
### Building for a 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 on `x86_64`, Rust tries to build a `.exe` Windows executable that uses `x86_64` instructions. This environment is called your "host" system.
|
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 on `x86_64`, Rust tries to build an `.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`:
|
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`:
|
||||||
|
|
||||||
@@ -260,9 +260,9 @@ The above output is from a `x86_64` Linux system. We see that the `host` triple
|
|||||||
|
|
||||||
[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
|
[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 use the C runtime by default, which causes the linker errors. So to avoid the linker errors, we can compile for a different environment with no underlying operating system.
|
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 causes the linker errors. So, to avoid the linker errors, we can compile for a different environment with no underlying operating system.
|
||||||
|
|
||||||
An example for such a bare metal environment is the `thumbv7em-none-eabihf` target triple, which describes an [embedded] [ARM] system. The details are not important, all that matters is that the target triple has no underlying operating system, which is indicated by the `none` in the target triple. To be able to compile for this target, we need to add it in rustup:
|
An example of such a bare metal environment is the `thumbv7em-none-eabihf` target triple, which describes an [embedded] [ARM] system. The details are not important, all that matters is that the target triple has no underlying operating system, which is indicated by the `none` in the target triple. To be able to compile for this target, we need to add it in rustup:
|
||||||
|
|
||||||
[embedded]: https://en.wikipedia.org/wiki/Embedded_system
|
[embedded]: https://en.wikipedia.org/wiki/Embedded_system
|
||||||
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
|
[ARM]: https://en.wikipedia.org/wiki/ARM_architecture
|
||||||
@@ -335,7 +335,7 @@ error: linking with `link.exe` failed: exit code: 1561
|
|||||||
= note: LINK : fatal error LNK1561: entry point must be defined
|
= note: LINK : fatal error LNK1561: entry point must be defined
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
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
|
[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
|
||||||
|
|
||||||
@@ -355,7 +355,7 @@ error: linking with `link.exe` failed: exit code: 1221
|
|||||||
defined
|
defined
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
||||||
@@ -377,7 +377,7 @@ error: linking with `cc` failed: exit code: 1
|
|||||||
clang: error: linker command failed with exit code 1 […]
|
clang: error: linker command failed with exit code 1 […]
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo rustc -- -C link-args="-e __start"
|
cargo rustc -- -C link-args="-e __start"
|
||||||
@@ -414,7 +414,7 @@ error: linking with `cc` failed: exit code: 1
|
|||||||
clang: error: linker command failed with exit code 1 […]
|
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:
|
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 also be solved by adding the `-nostartfiles` linker argument:
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||||
@@ -424,7 +424,7 @@ Now our program should build successfully on macOS.
|
|||||||
|
|
||||||
#### Unifying the Build Commands
|
#### Unifying the Build Commands
|
||||||
|
|
||||||
Right now we have different build commands depending on the host platform, which is not ideal. To avoid this, we can create a file named `.cargo/config.toml` that contains the platform specific arguments:
|
Right now we have different build commands depending on the host platform, which is not ideal. To avoid this, we can create a file named `.cargo/config.toml` that contains the platform-specific arguments:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# in .cargo/config.toml
|
# in .cargo/config.toml
|
||||||
@@ -439,7 +439,7 @@ rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
|
|||||||
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
|
||||||
```
|
```
|
||||||
|
|
||||||
The `rustflags` key contains arguments that are automatically added to every invocation of `rustc`. For more information on the `.cargo/config.toml` file check out the [official documentation](https://doc.rust-lang.org/cargo/reference/config.html).
|
The `rustflags` key contains arguments that are automatically added to every invocation of `rustc`. For more information on the `.cargo/config.toml` file, check out the [official documentation](https://doc.rust-lang.org/cargo/reference/config.html).
|
||||||
|
|
||||||
Now our program should be buildable on all three platforms with a simple `cargo build`.
|
Now our program should be buildable on all three platforms with a simple `cargo build`.
|
||||||
|
|
||||||
@@ -511,7 +511,7 @@ cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
|
|||||||
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
cargo rustc -- -C link-args="-e __start -static -nostartfiles"
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example that a stack is initialized when the `_start` function is called. **So for any real use of such a binary, more steps are required**.
|
Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example, that a stack is initialized when the `_start` function is called. **So for any real use of such a binary, more steps are required**.
|
||||||
|
|
||||||
## What's next?
|
## What's next?
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user