mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
The first post is now buildable on stable Rust (#551)
This commit is contained in:
committed by
GitHub
parent
1f27ca5352
commit
aa09d2f719
@@ -38,17 +38,6 @@ 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)**.
|
||||||
|
|
||||||
## Installing Rust Nightly
|
|
||||||
Rust has three release channels: _stable_, _beta_, and _nightly_. The Rust Book explains the difference between these channels really well, so take a minute and [check it out](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#choo-choo-release-channels-and-riding-the-trains). For building an operating system we will need some experimental features that are only available on the nightly channel, so we need to install a nightly version of Rust.
|
|
||||||
|
|
||||||
To manage Rust installations I highly recommend [rustup]. It allows you to install nightly, beta, and stable compilers side-by-side and makes it easy to update them. With rustup you can use a nightly compiler for the current directory by running `rustup override add nightly`. Alternatively, you can add a file called `rust-toolchain` with the content `nightly` to the project's root directory. You can check that you have a nightly version installed by running `rustc --version`: The version number should contain `-nightly` at the end.
|
|
||||||
|
|
||||||
[rustup]: https://www.rustup.rs/
|
|
||||||
|
|
||||||
The nightly compiler allows us to opt-in to various experimental features by using so-called _feature flags_ at the top of our file. For example, we could enable the experimental [`asm!` macro] for inline assembly by adding `#![feature(asm)]` to the top of our `main.rs`. Note that such experimental features are completely unstable, which means that future Rust versions might change or remove them without prior warning. For this reason we will only use them if absolutely necessary.
|
|
||||||
|
|
||||||
[`asm!` macro]: https://doc.rust-lang.org/nightly/unstable-book/language-features/asm.html
|
|
||||||
|
|
||||||
## 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 not use any OS-dependent libraries. So we have to disable the automatic inclusion of the standard library through the [`no_std` attribute].
|
||||||
|
|
||||||
@@ -242,14 +231,14 @@ If we try to build it now, an ugly linker error occurs:
|
|||||||
error: linking with `cc` failed: exit code: 1
|
error: linking with `cc` failed: exit code: 1
|
||||||
|
|
|
|
||||||
= note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L"
|
= note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L"
|
||||||
"/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib"
|
"/…/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib"
|
||||||
"/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09.0.o" […]
|
"/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09.0.o" […]
|
||||||
"-o" "/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09"
|
"-o" "/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09"
|
||||||
"-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
|
"-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs"
|
||||||
"-L" "/…/blog_os/target/debug/deps"
|
"-L" "/…/blog_os/target/debug/deps"
|
||||||
"-L" "/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib"
|
"-L" "/…/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib"
|
||||||
"-Wl,-Bstatic"
|
"-Wl,-Bstatic"
|
||||||
"/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-dd5bba80e2402629.rlib"
|
"/…/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-dd5bba80e2402629.rlib"
|
||||||
"-Wl,-Bdynamic"
|
"-Wl,-Bdynamic"
|
||||||
= note: /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
= note: /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start':
|
||||||
(.text+0x12): undefined reference to `__libc_csu_fini'
|
(.text+0x12): undefined reference to `__libc_csu_fini'
|
||||||
@@ -263,13 +252,13 @@ error: linking with `cc` failed: exit code: 1
|
|||||||
|
|
||||||
The problem is that we still link the startup routine of the C runtime, which requires some symbols of the C standard library `libc`, which we don't link due to the `no_std` attribute. So we need to get rid of the C startup routine. We can do that by passing the `-nostartfiles` flag to the linker.
|
The problem is that we still link the startup routine of the C runtime, which requires some symbols of the C standard library `libc`, which we don't link due to the `no_std` attribute. So we need to get rid of the C startup routine. We can do that by passing the `-nostartfiles` flag to the linker.
|
||||||
|
|
||||||
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 `-Z pre-link-arg` flag, which passes an argument to the linker. Combined, our new build command looks like this:
|
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:
|
||||||
|
|
||||||
```
|
```
|
||||||
> cargo rustc -- -Z pre-link-arg=-nostartfiles
|
> cargo rustc -- -C link-arg=-nostartfiles
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that all `-Z` flags are unstable, so the command only works with nightly Rust. Now our crate finally builds as a freestanding executable!
|
Now our crate finally builds as a freestanding executable!
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
On Windows, the linker requires two entry points [depending on the used subsystem]. For the `CONSOLE` subsystem, we need a function called `mainCRTStartup`, which calls a function called `main`. Like on Linux, we overwrite the entry points by defining `no_mangle` functions:
|
On Windows, the linker requires two entry points [depending on the used subsystem]. For the `CONSOLE` subsystem, we need a function called `mainCRTStartup`, which calls a function called `main`. Like on Linux, we overwrite the entry points by defining `no_mangle` functions:
|
||||||
@@ -303,7 +292,7 @@ pub extern "C" fn main() -> ! {
|
|||||||
To build it and link `libSystem`, we execute:
|
To build it and link `libSystem`, we execute:
|
||||||
|
|
||||||
```
|
```
|
||||||
> cargo rustc -- -Z pre-link-arg=-lSystem
|
> cargo rustc -- -C link-arg=-lSystem
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -381,11 +370,11 @@ The binary can be compiled with:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Linux
|
# Linux
|
||||||
> cargo rustc -- -Z pre-link-arg=-nostartfiles
|
> cargo rustc -- -C link-arg=-nostartfiles
|
||||||
# Windows
|
# Windows
|
||||||
> cargo build
|
> cargo build
|
||||||
# macOS
|
# macOS
|
||||||
> cargo rustc -- -Z pre-link-arg=-lSystem
|
> cargo rustc -- -C link-arg=-lSystem
|
||||||
```
|
```
|
||||||
|
|
||||||
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**.
|
||||||
|
|||||||
@@ -83,6 +83,17 @@ Now that we roughly know how a computer boots, it's time to create our own minim
|
|||||||
|
|
||||||
As you may remember, we built the freestanding binary through `cargo`, but depending on the operating system we needed different entry point names and compile flags. That's because `cargo` builds for the _host system_ by default, i.e. the system you're running on. This isn't something we want for our kernel, because a kernel that runs on top of e.g. Windows does not make much sense. Instead, we want to compile for a clearly defined _target system_.
|
As you may remember, we built the freestanding binary through `cargo`, but depending on the operating system we needed different entry point names and compile flags. That's because `cargo` builds for the _host system_ by default, i.e. the system you're running on. This isn't something we want for our kernel, because a kernel that runs on top of e.g. Windows does not make much sense. Instead, we want to compile for a clearly defined _target system_.
|
||||||
|
|
||||||
|
### Installing Rust Nightly
|
||||||
|
Rust has three release channels: _stable_, _beta_, and _nightly_. The Rust Book explains the difference between these channels really well, so take a minute and [check it out](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#choo-choo-release-channels-and-riding-the-trains). For building an operating system we will need some experimental features that are only available on the nightly channel, so we need to install a nightly version of Rust.
|
||||||
|
|
||||||
|
To manage Rust installations I highly recommend [rustup]. It allows you to install nightly, beta, and stable compilers side-by-side and makes it easy to update them. With rustup you can use a nightly compiler for the current directory by running `rustup override add nightly`. Alternatively, you can add a file called `rust-toolchain` with the content `nightly` to the project's root directory. You can check that you have a nightly version installed by running `rustc --version`: The version number should contain `-nightly` at the end.
|
||||||
|
|
||||||
|
[rustup]: https://www.rustup.rs/
|
||||||
|
|
||||||
|
The nightly compiler allows us to opt-in to various experimental features by using so-called _feature flags_ at the top of our file. For example, we could enable the experimental [`asm!` macro] for inline assembly by adding `#![feature(asm)]` to the top of our `main.rs`. Note that such experimental features are completely unstable, which means that future Rust versions might change or remove them without prior warning. For this reason we will only use them if absolutely necessary.
|
||||||
|
|
||||||
|
[`asm!` macro]: https://doc.rust-lang.org/nightly/unstable-book/language-features/asm.html
|
||||||
|
|
||||||
### Target Specification
|
### Target Specification
|
||||||
Cargo supports different target systems through the `--target` parameter. The target is described by a so-called _[target triple]_, which describes the CPU architecture, the vendor, the operating system, and the [ABI]. For example, the `x86_64-unknown-linux-gnu` target triple describes a system with a `x86_64` CPU, no clear vendor and a Linux operating system with the GNU ABI. Rust supports [many different target triples][platform-support], including `arm-linux-androideabi` for Android or [`wasm32-unknown-unknown` for WebAssembly](https://www.hellorust.com/setup/wasm-target/).
|
Cargo supports different target systems through the `--target` parameter. The target is described by a so-called _[target triple]_, which describes the CPU architecture, the vendor, the operating system, and the [ABI]. For example, the `x86_64-unknown-linux-gnu` target triple describes a system with a `x86_64` CPU, no clear vendor and a Linux operating system with the GNU ABI. Rust supports [many different target triples][platform-support], including `arm-linux-androideabi` for Android or [`wasm32-unknown-unknown` for WebAssembly](https://www.hellorust.com/setup/wasm-target/).
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user