diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..654c2e4c --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,82 @@ +branches: + except: + # Used by bors + - staging.tmp + +# Appveyor configuration template for Rust using rustup for Rust installation +# https://github.com/starkat99/appveyor-rust + +## Operating System (VM environment) ## + +# Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. +os: Visual Studio 2015 + +## Build Matrix ## +environment: + matrix: + +### MSVC Toolchains ### + # Nightly 64-bit MSVC + - channel: nightly + target: x86_64-pc-windows-msvc + MSYS_BITS: 64 + # Nightly 32-bit MSVC + - channel: nightly + target: i686-pc-windows-msvc + MSYS_BITS: 32 + +### GNU Toolchains ### + # Nightly 64-bit GNU + - channel: nightly + target: x86_64-pc-windows-gnu + MSYS_BITS: 64 + # Nightly 32-bit GNU + - channel: nightly + target: i686-pc-windows-gnu + MSYS_BITS: 32 + +cache: + - '%USERPROFILE%\.cargo\bin' + - '%USERPROFILE%\.cargo\config' + - '%USERPROFILE%\.cargo\env' + - '%USERPROFILE%\.cargo\.crates.toml' + - '%USERPROFILE%\.xargo' + - target + +## Install Script ## + +# This is the most important part of the Appveyor configuration. This installs the version of Rust +# specified by the 'channel' and 'target' environment variables from the build matrix. This uses +# rustup to install Rust. +# +# For simple configurations, instead of using the build matrix, you can simply set the +# default-toolchain and default-host manually here. +install: + - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - rustup-init -yv --default-toolchain %channel% --default-host %target% + - set PATH=%PATH%;%USERPROFILE%\.cargo\bin + - rustc -vV + - cargo -vV + +## Build Script ## + +# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents +# the "directory does not contain a project or solution file" error. +build: false + +before_test: + - mkdir bin + - mklink "bin\ld.exe" "C:\Program Files\LLVM\bin\lld.exe" + - set PATH=%CD%\bin;C:\msys64\mingw%MSYS_BITS%\bin;C:\msys64\usr\bin;%PATH% + - rustup component add rust-src + - set RUST_BACKTRACE=1 + - if not exist %USERPROFILE%\.cargo\bin\cargo-install-update.exe cargo install cargo-update + - if not exist %USERPROFILE%\.cargo\bin\xargo.exe cargo install xargo + - if not exist %USERPROFILE%\.cargo\bin\bootimage.exe cargo install bootimage + - cargo install-update -a + +# Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs +# directly or perform other testing commands. Rust will automatically be placed in the PATH +# environment variable. +test_script: + - bootimage --target x86_64-blog_os diff --git a/.travis.yml b/.travis.yml index ac22ca8b..55bd2489 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,5 @@ language: rust -rust: - - nightly - -cache: - directories: - - $HOME/.cargo - - $HOME/.xargo - - $TRAVIS_BUILD_DIR/target - -before_script: - - rustup component add rust-src - - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - - (test -x $HOME/.cargo/bin/xargo || cargo install xargo) - - cargo install-update -a - sudo: false notifications: @@ -22,10 +7,47 @@ notifications: on_success: never on_failure: change +branches: + except: + # Used by bors + - staging.tmp + +rust: + - nightly + +os: + - linux + - osx + addons: apt: + sources: + - llvm-toolchain-trusty-5.0 packages: - - nasm + - lld-5.0 + +cache: + directories: + - $HOME/.cargo + - $HOME/.xargo + - $TRAVIS_BUILD_DIR/target + +before_install: + - | + if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + set -e + LLVM_URL="https://releases.llvm.org/5.0.1/clang+llvm-5.0.1-x86_64-apple-darwin.tar.xz" + travis_retry wget -O llvm.tar.xz -nv ${LLVM_URL} + tar -xJ -f llvm.tar.xz + export PATH="`pwd`/clang+llvm-5.0.1-final-x86_64-apple-darwin/bin:$PATH" + fi + +before_script: + - rustup component add rust-src + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/xargo || cargo install xargo) + - (test -x $HOME/.cargo/bin/bootimage || cargo install bootimage) + - cargo install-update -a script: - - make + - bootimage --target x86_64-blog_os diff --git a/Cargo.toml b/Cargo.toml index 74109d7e..68212ff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,14 @@ [package] authors = ["Philipp Oppermann "] name = "blog_os" -version = "0.1.0" +version = "0.2.0" [dependencies] -bit_field = "0.7.0" -bitflags = "0.9.1" -multiboot2 = "0.1.0" -once = "0.3.2" -rlibc = "1.0" -spin = "0.4.5" -volatile = "0.1.0" -x86_64 = "0.1.2" -linked_list_allocator = "0.4.2" -[dependencies.lazy_static] -features = ["spin_no_std"] -version = "0.2.1" +# the profile used for `cargo build` +[profile.dev] +panic = "abort" # disable stack unwinding on panic -[lib] -crate-type = ["staticlib"] +# the profile used for `cargo build --release` +[profile.release] +panic = "abort" # disable stack unwinding on panic diff --git a/Makefile b/Makefile deleted file mode 100644 index 31b5e12c..00000000 --- a/Makefile +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2016 Philipp Oppermann. See the README.md -# file at the top-level directory of this distribution. -# -# Licensed under the Apache License, Version 2.0 or the MIT license -# , at your -# option. This file may not be copied, modified, or distributed -# except according to those terms. - -arch ?= x86_64 -target ?= $(arch)-blog_os -kernel := build/kernel-$(arch).bin -iso := build/os-$(arch).iso - -rust_os := target/$(target)/debug/libblog_os.a -linker_script := src/arch/$(arch)/linker.ld -grub_cfg := src/arch/$(arch)/grub.cfg -assembly_source_files := $(wildcard src/arch/$(arch)/*.asm) -assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \ - build/arch/$(arch)/%.o, $(assembly_source_files)) - -# used by docker_* targets -docker_image ?= blog_os -tag ?= 0.1 -docker_cargo_volume ?= blogos-$(shell id -u)-$(shell id -g)-cargo -docker_rustup_volume ?= blogos-$(shell id -u)-$(shell id -g)-rustup -docker_args ?= -e LOCAL_UID=$(shell id -u) -e LOCAL_GID=$(shell id -g) -v $(docker_cargo_volume):/usr/local/cargo -v $(docker_rustup_volume):/usr/local/rustup -v $(shell pwd):$(shell pwd) -w $(shell pwd) -docker_clean_args ?= $(docker_cargo_volume) $(docker_rustup_volume) - -.PHONY: all clean run debug iso cargo gdb - -all: $(kernel) - -clean: - @cargo clean - @rm -rf build - -run: $(iso) - @qemu-system-x86_64 -cdrom $(iso) -s - -debug: $(iso) - @qemu-system-x86_64 -cdrom $(iso) -s -S - -# docker_* targets - -docker_build: - @docker build docker/ -t $(docker_image):$(tag) - -docker_iso: - @docker run --rm $(docker_args) $(docker_image):$(tag) make iso - -docker_run: docker_iso - @qemu-system-x86_64 -cdrom $(iso) -s - -docker_interactive: - @docker run -it --rm $(docker_args) $(docker_image):$(tag) - -docker_clean: - @docker volume rm $(docker_clean_args) - -gdb: - @rust-os-gdb/bin/rust-gdb "build/kernel-x86_64.bin" -ex "target remote :1234" - -iso: $(iso) - -$(iso): $(kernel) $(grub_cfg) - @mkdir -p build/isofiles/boot/grub - @cp $(kernel) build/isofiles/boot/kernel.bin - @cp $(grub_cfg) build/isofiles/boot/grub - @grub-mkrescue -o $(iso) build/isofiles 2> /dev/null - @rm -r build/isofiles - -$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script) - @ld -n --gc-sections -T $(linker_script) -o $(kernel) $(assembly_object_files) $(rust_os) - -cargo: - @RUST_TARGET_PATH="$(shell pwd)" xargo build --target $(target) - -# compile assembly files -build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm - @mkdir -p $(shell dirname $@) - @nasm -felf64 $< -o $@ diff --git a/Xargo.toml b/Xargo.toml deleted file mode 100644 index 73f2a1d2..00000000 --- a/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.x86_64-blog_os.dependencies] -alloc = {} diff --git a/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md b/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md index 818fa085..ab6456f9 100644 --- a/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md +++ b/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md @@ -2,7 +2,7 @@ title = "Catching Exceptions" order = 1 path = "catching-exceptions" -date = "2016-05-28" +date = 2016-05-28 [extra] updated = "2016-06-25" +++ diff --git a/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md b/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md index 286bb078..a221bb9a 100644 --- a/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md +++ b/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md @@ -2,7 +2,7 @@ title = "Better Exception Messages" order = 2 path = "better-exception-messages" -date = "2016-08-03" +date = 2016-08-03 [extra] updated = "2016-11-01" +++ diff --git a/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md b/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md index 36ef6d09..88e1470b 100644 --- a/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md +++ b/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md @@ -2,7 +2,7 @@ title = "Returning from Exceptions" order = 3 path = "returning-from-exceptions" -date = "2016-09-21" +date = 2016-09-21 [extra] updated = "2016-11-01" +++ diff --git a/blog/content/first-edition/_index.md b/blog/content/first-edition/_index.md new file mode 100644 index 00000000..1215a59a --- /dev/null +++ b/blog/content/first-edition/_index.md @@ -0,0 +1,4 @@ ++++ +title = "First Edition" +template = "index.html" ++++ diff --git a/blog/content/posts/01-multiboot-kernel/index.md b/blog/content/posts/01-multiboot-kernel/index.md index f0c43335..560350db 100644 --- a/blog/content/posts/01-multiboot-kernel/index.md +++ b/blog/content/posts/01-multiboot-kernel/index.md @@ -2,7 +2,7 @@ title = "A minimal x86 kernel" order = 1 path = "multiboot-kernel/" -date = "2015-08-18" +date = 2015-08-18 +++ This post explains how to create a minimal x86 operating system kernel. In fact, it will just boot and print `OK` to the screen. In subsequent blog posts we will extend it using the [Rust] programming language. diff --git a/blog/content/posts/02-entering-longmode/index.md b/blog/content/posts/02-entering-longmode/index.md index 5e6cedd9..01dcb1d9 100644 --- a/blog/content/posts/02-entering-longmode/index.md +++ b/blog/content/posts/02-entering-longmode/index.md @@ -2,7 +2,7 @@ title = "Entering Long Mode" order = 2 path = "entering-longmode" -date = "2015-08-25" +date = 2015-08-25 [extra] updated = "2015-10-29" +++ diff --git a/blog/content/posts/03-set-up-rust/index.md b/blog/content/posts/03-set-up-rust/index.md index f6320da7..d4f46cff 100644 --- a/blog/content/posts/03-set-up-rust/index.md +++ b/blog/content/posts/03-set-up-rust/index.md @@ -2,7 +2,7 @@ title = "Set Up Rust" order = 3 path = "set-up-rust" -date = "2015-09-02" +date = 2015-09-02 [extra] updated = "2017-04-12" +++ diff --git a/blog/content/posts/04-printing-to-screen/index.md b/blog/content/posts/04-printing-to-screen/index.md index b0aaa680..5aa973e8 100644 --- a/blog/content/posts/04-printing-to-screen/index.md +++ b/blog/content/posts/04-printing-to-screen/index.md @@ -2,7 +2,7 @@ title = "Printing to Screen" order = 4 path = "printing-to-screen" -date = "2015-10-23" +date = 2015-10-23 [extra] updated = "2016-10-31" +++ diff --git a/blog/content/posts/05-allocating-frames/index.md b/blog/content/posts/05-allocating-frames/index.md index 8a26f04c..da675ee8 100644 --- a/blog/content/posts/05-allocating-frames/index.md +++ b/blog/content/posts/05-allocating-frames/index.md @@ -2,7 +2,7 @@ title = "Allocating Frames" order = 5 path = "allocating-frames" -date = "2015-11-15" +date = 2015-11-15 +++ In this post we create an allocator that provides free physical frames for a future paging module. To get the required information about available and used memory we use the Multiboot information structure. Additionally, we improve the `panic` handler to print the corresponding message and source line. diff --git a/blog/content/posts/06-page-tables/index.md b/blog/content/posts/06-page-tables/index.md index 7fa21ec5..721717dc 100644 --- a/blog/content/posts/06-page-tables/index.md +++ b/blog/content/posts/06-page-tables/index.md @@ -2,7 +2,7 @@ title = "Page Tables" order = 6 path = "page-tables" -date = "2015-12-09" +date = 2015-12-09 +++ In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will explore recursive page table mapping and use some Rust features to make it safe. Finally we will create functions to translate virtual addresses and to map and unmap pages. diff --git a/blog/content/posts/07-remap-the-kernel/index.md b/blog/content/posts/07-remap-the-kernel/index.md index f61c8f6c..912e9d24 100644 --- a/blog/content/posts/07-remap-the-kernel/index.md +++ b/blog/content/posts/07-remap-the-kernel/index.md @@ -2,7 +2,7 @@ title = "Remap the Kernel" order = 7 path = "remap-the-kernel" -date = "2016-01-01" +date = 2016-01-01 [extra] updated = "2016-03-06" +++ diff --git a/blog/content/posts/08-kernel-heap/index.md b/blog/content/posts/08-kernel-heap/index.md index 2cdacc8e..05623e99 100644 --- a/blog/content/posts/08-kernel-heap/index.md +++ b/blog/content/posts/08-kernel-heap/index.md @@ -2,7 +2,7 @@ title = "Kernel Heap" order = 8 path = "kernel-heap" -date = "2016-04-11" +date = 2016-04-11 updated = "2017-11-19" +++ diff --git a/blog/content/posts/09-handling-exceptions/index.md b/blog/content/posts/09-handling-exceptions/index.md index ea77aff1..05d0b135 100644 --- a/blog/content/posts/09-handling-exceptions/index.md +++ b/blog/content/posts/09-handling-exceptions/index.md @@ -2,7 +2,7 @@ title = "Handling Exceptions" order = 9 path = "handling-exceptions" -date = "2017-03-26" +date = 2017-03-26 +++ In this post, we start exploring CPU exceptions. Exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To catch them, we have to set up an _interrupt descriptor table_ that provides handler functions. At the end of this post, our kernel will be able to catch [breakpoint exceptions] and to resume normal execution afterwards. diff --git a/blog/content/posts/10-double-faults/index.md b/blog/content/posts/10-double-faults/index.md index c357c680..48692ebd 100644 --- a/blog/content/posts/10-double-faults/index.md +++ b/blog/content/posts/10-double-faults/index.md @@ -2,7 +2,7 @@ title = "Double Faults" order = 10 path = "double-faults" -date = "2017-01-02" +date = 2017-01-02 +++ In this post we explore double faults in detail. We also set up an _Interrupt Stack Table_ to catch double faults on a separate kernel stack. This way, we can completely prevent triple faults, even on kernel stack overflow. diff --git a/blog/content/second-edition/_index.md b/blog/content/second-edition/_index.md new file mode 100644 index 00000000..5a157eb1 --- /dev/null +++ b/blog/content/second-edition/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Second Edition" +template = "second-edition/index.html" ++++ diff --git a/blog/content/second-edition/extra/_index.md b/blog/content/second-edition/extra/_index.md new file mode 100644 index 00000000..72c0bc40 --- /dev/null +++ b/blog/content/second-edition/extra/_index.md @@ -0,0 +1,6 @@ ++++ +title = "Extra Content" +insert_anchor_links = "left" +render = false +sort_by = "order" ++++ diff --git a/blog/content/second-edition/extra/disable-red-zone/index.md b/blog/content/second-edition/extra/disable-red-zone/index.md new file mode 100644 index 00000000..b677b625 --- /dev/null +++ b/blog/content/second-edition/extra/disable-red-zone/index.md @@ -0,0 +1,27 @@ ++++ +title = "Disable the Red Zone" +order = 1 +path = "red-zone" +template = "second-edition/extra.html" ++++ + +The [red zone] is an optimization of the [System V ABI] that allows functions to temporary use the 128 bytes below its stack frame without adjusting the stack pointer: + +[red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone +[System V ABI]: http://wiki.osdev.org/System_V_ABI + +![stack frame with red zone](red-zone.svg) + +The image shows the stack frame of a function with `n` local variables. On function entry, the stack pointer is adjusted to make room on the stack for the return address and the local variables. + +The red zone is defined as the 128 bytes below the adjusted stack pointer. The function can use this area for temporary data that's not needed across function calls. Thus, the two instructions for adjusting the stack pointer can be avoided in some cases (e.g. in small leaf functions). + +However, this optimization leads to huge problems with exceptions or hardware interrupts. Let's assume that an exception occurs while a function uses the red zone: + +![red zone overwritten by exception handler](red-zone-overwrite.svg) + +The CPU and the exception handler overwrite the data in red zone. But this data is still needed by the interrupted function. So the function won't work correctly anymore when we return from the exception handler. This might lead to strange bugs that [take weeks to debug]. + +[take weeks to debug]: http://forum.osdev.org/viewtopic.php?t=21720 + +To avoid such bugs when we implement exception handling in the future, we disable the red zone right from the beginning. This is achieved by adding the `"disable-redzone": true` line to our target configuration file. diff --git a/blog/content/second-edition/extra/disable-red-zone/red-zone-overwrite.svg b/blog/content/second-edition/extra/disable-red-zone/red-zone-overwrite.svg new file mode 100644 index 00000000..d3be0805 --- /dev/null +++ b/blog/content/second-edition/extra/disable-red-zone/red-zone-overwrite.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + Old Stack Pointer + + + + + + + + + + + + + + + + + + + Return Address + + + + + + + + + + Local Variable 1 + + + + + + + Local Variables 2..n + + + + + + + + + Red Zone + + + 128 bytes + + + + + + + Exception Stack Frame + + + + + + + Register Backup + + + + + + + Handler Function Stack Frame + + + + + + + + + New Stack Pointer + + diff --git a/blog/content/second-edition/extra/disable-red-zone/red-zone.svg b/blog/content/second-edition/extra/disable-red-zone/red-zone.svg new file mode 100644 index 00000000..c5194ca8 --- /dev/null +++ b/blog/content/second-edition/extra/disable-red-zone/red-zone.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + Stack Pointer + + + + + + + + + + + + + + + + + + + Return Address + + + + + + + + + + Local Variable 1 + + + + + + + Local Variables 2..n + + + + + + + + + Red Zone + + + 128 bytes + + diff --git a/blog/content/second-edition/extra/disable-simd/index.md b/blog/content/second-edition/extra/disable-simd/index.md new file mode 100644 index 00000000..785c8b2e --- /dev/null +++ b/blog/content/second-edition/extra/disable-simd/index.md @@ -0,0 +1,42 @@ ++++ +title = "Disable SIMD" +order = 2 +path = "disable-simd" +template = "second-edition/extra.html" ++++ + +[Single Instruction Multiple Data (SIMD)] instructions are able to perform an operation (e.g. addition) simultaneously on multiple data words, which can speed up programs significantly. The `x86_64` architecture supports various SIMD standards: + +[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD + +- [MMX]: The _Multi Media Extension_ instruction set was introduced in 1997 and defines eight 64 bit registers called `mm0` through `mm7`. These registers are just aliases for the registers of the [x87 floating point unit]. +- [SSE]: The _Streaming SIMD Extensions_ instruction set was introduced in 1999. Instead of re-using the floating point registers, it adds a completely new register set. The sixteen new registers are called `xmm0` through `xmm15` and are 128 bits each. +- [AVX]: The _Advanced Vector Extensions_ are extensions that further increase the size of the multimedia registers. The new registers are called `ymm0` through `ymm15` and are 256 bits each. They extend the `xmm` registers, so e.g. `xmm0` is the lower half of `ymm0`. + +[MMX]: https://en.wikipedia.org/wiki/MMX_(instruction_set) +[x87 floating point unit]: https://en.wikipedia.org/wiki/X87 +[SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions +[AVX]: https://en.wikipedia.org/wiki/Advanced_Vector_Extensions + +By using such SIMD standards, programs can often speed up significantly. Good compilers are able to transform normal loops into such SIMD code automatically through a process called [auto-vectorization]. + +[auto-vectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization + +However, the large SIMD registers lead to problems in OS kernels. The reason is that the kernel has to backup all registers that it uses in hardware interrupt, because they need to have their original values when the interrupted program continues. So if the kernel uses SIMD registers, it has to backup a lot more data, which noticably decreases performance. To avoid this performance loss, we want to disable the `sse` and `mmx` features (the `avx` feature is disabled by default). + +We can do that through the the `features` field in our target specification. To disable the `mmx` and `sse` features we add them prefixed with a minus: + +```json +"features": "-mmx,-sse" +``` + +## Floating Point +Unfortunately for us, the `x86_64` architecture uses SSE registers for floating point operations. Thus, every use of floating point with disabled SSE causes an error in LLVM. The problem is that Rust's core library already uses floats (e.g., it implements traits for `f32` and `f64`), so avoiding floats in our kernel does not suffice. + +Fortunately, LLVM has support for a `soft-float` feature, emulates all floating point operations through software functions based on normal integers. This makes it possible to use floats in our kernel without SSE, it will just be a bit slower. + +To turn on the `soft-float` feature for our kernel, we add it to the `features` line in our target specification, prefixed with a plus: + +```json +"features": "-mmx,-sse,+soft-float" +``` diff --git a/blog/content/second-edition/extra/installing-lld/index.md b/blog/content/second-edition/extra/installing-lld/index.md new file mode 100644 index 00000000..94237b41 --- /dev/null +++ b/blog/content/second-edition/extra/installing-lld/index.md @@ -0,0 +1,18 @@ ++++ +title = "Installing LLD" +order = 3 +path = "installing-lld" +template = "second-edition/extra.html" ++++ + +[LLD] is the linker by the LLVM project. It has the big advantage that it is a cross-linker by default. This means that you can link libraries and executables for all kinds of platforms with the same LLD installation. + +[LLD]: https://lld.llvm.org/ + +There are plans to distribute LLD together with the Rust compiler, but is not quite there yet. So you have to install it manually. On this page, we try to describe the installation procedure for as many platforms as possible, so if you have additional information for any listed or unlisted platform, please send a pull request on the [Github repo](https://github.com/phil-opp/blog_os)! + +## Linux +On most Linux distributions LLD can be installed through the package manager. For example, for Debian and Ubuntu there is are official apt sources at . + +## Other Platforms +For Windows and Mac you can download a pre-built LLVM release from , which contains LLD. If there are no pre-compiled versions for your platform (e.g. some other Linux distribution), you can download the source code and [build it yourself](https://lld.llvm.org/#build). diff --git a/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md b/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md new file mode 100644 index 00000000..be9a7b69 --- /dev/null +++ b/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md @@ -0,0 +1,377 @@ ++++ +title = "A Freestanding Rust Binary" +order = 3 +path = "freestanding-rust-binary" +date = 0000-01-01 +template = "second-edition/page.html" ++++ + +This post describes how to create a Rust executable that does not link the standard library. This makes it possible to run Rust code on the [bare metal] without an underlying operating system. + +[bare metal]: https://en.wikipedia.org/wiki/Bare_machine + + + +## 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. Which makes sense, since we're trying to write our own OS and our own drivers. + +This means that we can't use most of the [Rust standard library], but there are 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]. 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/ +[Rust standard library]: https://doc.rust-lang.org/std/ +[iterators]: https://doc.rust-lang.org/book/second-edition/ch13-02-iterators.html +[closures]: https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html +[pattern matching]: https://doc.rust-lang.org/book/second-edition/ch06-00-enums.html +[string formatting]: https://doc.rust-lang.org/core/macro.write.html +[ownership system]: https://doc.rust-lang.org/book/second-edition/ch04-00-understanding-ownership.html +[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs +[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention + +In order to create an OS kernel in Rust, we need to create an executable that can be run without an underlying operating system. Such an executable is often called a “freestanding” or “bare-metal” executable. + +This post describes the necessary steps to get 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 +By default, all Rust crates link the [standard library], which dependends 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]. + +[standard library]: https://doc.rust-lang.org/std/ +[`no_std` attribute]: https://doc.rust-lang.org/book/first-edition/using-rust-without-the-standard-library.html + +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 +``` + +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). When we run the command, cargo creates the following directory structure for us: + +``` +blog_os +├── 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 `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. + +[semantic version]: http://semver.org/ + +### The `no_std` Attribute + +Right now our crate implicitly links the standard library. Let's try to disable this by adding the [`no_std` attribute]: + +```rust +// main.rs + +#![no_std] + +fn main() { + println!("Hello, world!"); +} +``` + +When we try to build it now (by running `cargo build`), the following error occurs: + +``` +error: cannot find macro `println!` in this scope + --> src/main.rs:4:5 + | +4 | println!("Hello, world!"); + | ^^^^^^^ +``` + +The reason for this error 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. + +[`println` macro]: https://doc.rust-lang.org/std/macro.println.html +[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29 + +So let's remove the printing and try again with an empty main function: + +```rust +// main.rs + +#![no_std] + +fn main() {} +``` + +``` +> cargo build +error: language item required, but not found: `panic_fmt` +error: language item required, but not found: `eh_personality` +``` + +Now the compiler is missing some _language items_. Language items are special pluggable functions that the compiler invokes on certain conditions, for example when the application [panics]. Normally, these items are provided by the standard library, but we disabled it. So we need to provide our own implementations. + +[panics]: https://doc.rust-lang.org/stable/book/second-edition/ch09-01-unrecoverable-errors-with-panic.html + +### Enabling Unstable Features + +Implementing language items is unstable and protected by a so-called _feature gate_. A feature gate is a special attribute that you have to specify at the top of your `main.rs` in order to use the corresponding feature. By doing this you basically say: “I know that this feature is unstable and that it might stop working without warning. I want to use it anyway.” + +To limit the use of unstable features, the feature gates are not available in the stable or beta Rust compilers, only [nightly Rust] makes it possible to opt-in. This means that you have to use a nightly compiler for OS development for the near future (since we need to implement unstable language items). To install a nightly compiler using [rustup], you just need to run `rustup default nightly` (for more information check out [rustup's documentation]). + +[nightly Rust]: https://doc.rust-lang.org/book/first-edition/release-channels.html +[rustup]: https://rustup.rs/ +[rustup's documentation]: https://github.com/rust-lang-nursery/rustup.rs#rustup-the-rust-toolchain-installer + +After installing a nightly Rust compiler, you can enable the unstable `lang_items` feature by inserting `#![feature(lang_items)]` right at the top of `main.rs`. + +### Implementing the Language Items + +To create a `no_std` binary, we have to implement the `panic_fmt` and the `eh_personality` language items. The `panic_fmt` items specifies a function that should be invoked when a panic occurs. This function should format an error message (hence the `_fmt` suffix) and then invoke the panic routine. In our case, there is not much we can do, since we can neither print anything nor do we have a panic routine. So we just loop indefinitely: + +```rust +#![feature(lang_items)] +#![no_std] + +fn main() {} + +#[lang = "panic_fmt"] +#[no_mangle] +pub extern fn rust_begin_panic(_msg: core::fmt::Arguments, + _file: &'static str, _line: u32, _column: u32) -> ! +{ + loop {} +} +``` + +The function signature is taken from the [unstable Rust book]. The signature isn't verified by the compiler, so implement it carefully. + +[unstable Rust book]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib + +Note that there is already an accepted RFC for a stable panic mechnism, which is only waiting for an implementation. See the [tracking issue](https://github.com/rust-lang/rust/issues/44489) for more information. + +Instead of implementing the second language item, `eh_personality`, we remove the need for it by disabling unwinding. + +### Disabling Unwinding + +The `eh_personality` language item is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of 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. + +[stack unwinding]: http://www.bogotobogo.com/cplusplus/stackunwinding.php +[libunwind]: http://www.nongnu.org/libunwind/ +[structured exception handling]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx + +There are other use cases as well for which unwinding is undesireable, so Rust provides an option to [abort on panic] instead. This disables the generation of unwinding symbol and thus considerably reduces binary size. There are multiple places where we can disable unwinding. The easiest way is to add the following lines to our `Cargo.toml`: + +```toml +[profile.dev] +panic = "abort" + +[profile.release] +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 + +However, if we try to compile it now, another language item is required: + +``` +> cargo build +error: requires `start` lang_item +``` + +### The `start` attribute + +One might think that the `main` function is the first function called when you run a program. 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. + +[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. This includes creating a stack and placing the arguments in the right registers. The C runtime then invokes the [entry point of the Rust runtime][rt::lang_start], which is marked by the `start` language item. Rust only has a very minimal runtime, which takes care of some small things such as setting up stack overflow guards or printing a backtrace on panic. The runtime then finally calls the `main` function. + +[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73 + +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. + +### 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 +#![feature(lang_items)] +#![no_std] +#![no_main] + +#[lang = "panic_fmt"] +#[no_mangle] +pub extern fn rust_begin_panic(_msg: core::fmt::Arguments, + _file: &'static str, _line: u32, _column: u32) -> ! +{ + loop {} +} +``` + +You might notice that we removed the `main` function. The reason is that a `main` doesn't make sense without an underlying runtime that calls it. Instead, we now to overwrite the operating system entry point. + +The entry point convention depends on your operating system. I recommend you to read the Linux section even if you're on a different OS because it is the target we will derive to build our kernel in the next post. + +#### Linux +On Linux, the default entry point is called `_start`. The linker just looks for a function with that name and sets this function as entry point the executable. So to overwrite the entry point, we define our own `_start` function: + +```rust +#[no_mangle] +pub extern fn _start() -> ! { + loop {} +} +``` + +It's important that we disable the [name mangling][mangling] through the `no_mangle` attribute, otherwise the compiler would generate some cryptic `_ZN3blog_os4_start7hb173fedf945531caE` symbol that the linker wouldn't recognize. + +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 fulfil the requirement by looping endlessly. + +[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call) + +If we try to build it now, an ugly linker error occurs: + +``` +error: linking with `cc` failed: exit code: 1 + | + = 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" + "/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09.0.o" […] + "-o" "/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09" + "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" + "-L" "/…/blog_os/target/debug/deps" + "-L" "/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" + "-Wl,-Bstatic" + "/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-dd5bba80e2402629.rlib" + "-Wl,-Bdynamic" + = 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' + /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start': + (.text+0x19): undefined reference to `__libc_csu_init' + /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start': + (.text+0x25): undefined reference to `__libc_start_main' + collect2: error: ld returned 1 exit status + +``` + +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 (unstable) `-Z pre-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 +``` + +With this command, our crate builds again as a freestanding executable! + +#### Windows +On Windows, the linker requires two entry points: `WinMain` and `WinMainCRTStartup`, [depending on the used subsystem](https://msdn.microsoft.com/en-us/library/f9t8842e.aspx). Like on Linux, we overwrite the entry points by defining `no_mangle` functions: + +```rust +#[no_mangle] +pub extern fn WinMainCRTStartup() -> ! { + WinMain(); +} + +#[no_mangle] +pub extern fn WinMain() -> ! { + loop {} +} +``` + +We just call `WinMain` from `WinMainCRTStartup` to avoid any ambiguity which function is called. + +#### OS X +Mac OS X [does not support statically linked binaries], so we have to link the `libSystem` library. The entry point is the called `main`: + +[does not support statically linked binaries]: https://developer.apple.com/library/content/qa/qa1118/_index.html + +```rust +#[no_mangle] +pub extern fn main() -> ! { + loop {} +} +``` + +To build it and link `libSystem`, we execute: + +``` +> cargo rustc -- -Z pre-link-arg=-lSystem +``` + + +## Summary + +A minimal freestanding Rust binary looks like this: + +`src/main.rs`: + +```rust +#![feature(lang_items)] // required for defining the panic handler +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +#[lang = "panic_fmt"] // define a function that should be called on panic +#[no_mangle] +pub extern fn rust_begin_panic(_msg: core::fmt::Arguments, + _file: &'static str, _line: u32, _column: u32) -> ! +{ + loop {} +} + +// On Linux: +#[no_mangle] // don't mangle the name of this function +pub extern fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start_` by default + loop {} +} + +// On Windows: +#[no_mangle] +pub extern fn WinMainCRTStartup() -> ! { + WinMain(); +} + +#[no_mangle] +pub extern fn WinMain() -> ! { + loop {} +} + +// On Mac: + +#[no_mangle] +pub extern fn main() -> ! { + loop {} +} +``` + +`Cargo.toml`: + +```toml +[package] +name = "crate_name" +version = "0.1.0" +authors = ["Author Name "] + +# the profile used for `cargo build` +[profile.dev] +panic = "abort" # disable stack unwinding on panic + +# the profile used for `cargo build --release` +[profile.release] +panic = "abort" # disable stack unwinding on panic +``` + +It can be compiled with: + +```bash +# Linux +> cargo rustc -- -Z pre-link-arg=-nostartfiles +# Windows +> cargo build +# Mac +> cargo rustc -- -Z pre-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**. + +## What's next? + +The [next post] build upon our minimal freestanding binary by explaining the steps needed for creating a minimal operating system kernel. It explains how to configure the kernel for the target system, how to start it using a bootloader, and how to print something to the screen. + +[next post]: ./second-edition/posts/02-minimal-rust-kernel/index.md diff --git a/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md b/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md new file mode 100644 index 00000000..dd79bb26 --- /dev/null +++ b/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md @@ -0,0 +1,363 @@ ++++ +title = "A Minimal Rust Kernel" +order = 4 +path = "minimal-rust-kernel" +date = 0000-01-01 +template = "second-edition/page.html" ++++ + +In this post we create a minimal 64-bit Rust kernel. We built upon the [freestanding Rust binary] from the previous post to create a bootable disk image, that prints something to the screen. + +[freestanding Rust binary]: ./second-edition/posts/01-freestanding-rust-binary/index.md + + + +This blog is openly developed on [Github]. If you have any problems or questions, please open an issue there. You can also leave comments [at the bottom]. + +[Github]: https://github.com/phil-opp/blog_os +[at the bottom]: #comments + +## The Boot Process +When you turn on a computer, it begins executing firmware code that is stored in motherboard [ROM]. This code performs a [power-on self-test], detects available RAM, and pre-initializes the CPU and hardware. Afterwards it looks for a bootable disk and starts booting the operating system kernel. + +[ROM]: https://en.wikipedia.org/wiki/Read-only_memory +[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 is more complex to set up (at least in my opinion). + +[BIOS]: https://en.wikipedia.org/wiki/BIOS +[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface + +Currently, we only provide BIOS support, but support for UEFI is planned, too. If you'd like to help us with this, check out the [Github issue](https://github.com/phil-opp/blog_os/issues/349). + +### BIOS Boot +Almost all x86 systems have support for BIOS booting, even newer UEFI-based machines (they include an emulated BIOS). This is great, because you can use the same boot logic across all machines from the last centuries. But this wide compatibility is at the same time the biggest disadvantage of BIOS booting, because it means that the CPU is put into a 16-bit compability mode called [real mode] before booting so that that arcane bootloaders from the 1980s would still work. + +But let's start from the beginning: + +When you turn on a computer, it loads the BIOS from some special flash memory located on the motherboard. The BIOS runs self test and initialization routines of the hardware, then it looks for bootable disks. If it finds one, the control is transferred to its _bootloader_, which is a 512-byte portion of executable code stored at the disk's beginning. Most bootloaders are larger than 512 bytes, so bootloaders are commonly split into a small first stage, which fits into 512 bytes, and a second stage, which is subsequently loaded by the first stage. + +The bootloader has to determine the location of the kernel image on the disk and load it into memory. It also needs to switch the CPU from the 16-bit [real mode] first to the 32-bit [protected mode], and then to the 64-bit [long mode], where 64-bit registers and the complete main memory are available. Its third job is to query certain information (such as a memory map) from the BIOS and pass it to the OS kernel. + +[real mode]: https://en.wikipedia.org/wiki/Real_mode +[protected mode]: https://en.wikipedia.org/wiki/Protected_mode +[long mode]: https://en.wikipedia.org/wiki/Long_mode +[memory segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation + +Writing a bootloader is a bit 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 provide a tool named [bootimage] that automatically appends a bootloader to your kernel. + +[bootimage]: https://github.com/phil-opp/bootimage + +If you are interested in building your own bootloader: Stay tuned, a set of posts on this topic is already planned! + +#### The Multiboot Standard +To avoid that every operating system implements its own bootloader, which is only compatible with a single OS, the Free Software Foundation created an open bootloader standard called [Multiboot] in 1995. The standard defines an interface between the bootloader and operating system, so that any Multiboot compliant bootloader can load any Multiboot compliant operating system. The reference implementation is [GNU GRUB], which is the most popular bootloader for Linux systems. + +[Multiboot]: https://wiki.osdev.org/Multiboot +[GNU GRUB]: https://en.wikipedia.org/wiki/GNU_GRUB + +To make a kernel Multiboot compliant, one just need 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 the Multiboot standard have some problems too: + +[Multiboot header]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format + +- They support only the 32-bit protected mode. This means that you still have to do the CPU configuration to switch to the 64-bit long mode. +- The newest version of the standard, [Multiboot 2.0], supports kernels in the 64-bit ELF format, but requires a [adjusting the default page size]. +- The development of the standard and GRUB is highly interleaved. For example, I'm not aware of an other bootloader that is Multiboot compliant. As a result, one often has to look into the GRUB source code when the standard is not clear enough. +- The standard is written for the C programming language. That means lots of C-typical structures such as linked lists in the [boot information]. +- Both GRUB and the Multiboot standard are only sparsely documented. +- GRUB needs to be installed on the host system to create a bootable disk image from the kernel file. This makes development on Windows or Mac more difficult. + +[boot information]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format +[Multiboot 2.0]: https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html +[adjusting the default page size]: https://wiki.osdev.org/Multiboot#Multiboot_2 + +Because of these drawbacks we decided to not use GRUB or the Multiboot standard. However, we plan to add Multiboot support to our [bootimage] tool, so that it's 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]: ./first-edition/_index.md + +### UEFI + +(We don't provide UEFI support at the moment, but we would love to! If you'd like to help, please tell us in the [Github issue](https://github.com/phil-opp/blog_os/issues/349).) + +## A Minimal Kernel +Now that we roughly know how a computer boots, it's time to create our own minimal kernel. Our goal is to create a disk image that prints a green “Hello” to the screen when booted. For that we build upon the [freestanding Rust binary] from the previous post. + +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_. + +### Target Specification +Cargo supports different target systems through the `--target` parameter. The target is decribed 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` means 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/). + +[target triple]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple +[ABI]: https://stackoverflow.com/a/2456882 +[platform-support]: https://forge.rust-lang.org/platform-support.html + +For our target system, however, we require some special configuration parameters (e.g. no underlying OS), so none of the [existing target triples][platform-support] fits. Fortunately Rust allows us to define our own target through a JSON file. For example, a JSON file that describes the `x86_64-unknown-linux-gnu` target looks like this: + +```json +{ + "llvm-target": "x86_64-unknown-linux-gnu", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "linux", + "executables": true, + "linker-flavor": "gcc", + "pre-link-args": ["-m64"], + "morestack": false +} +``` + +Most fields are required by LLVM to generate code for that platform. For example, the `data-layout` field defines the size of various integer, floating point, and pointer types. Then there are fields that Rust uses for conditional compilation, such as `target-pointer-width`. The third kind of fields define how the crate should be built. For example, the `pre-link-args` field specifies arguments passed to the [linker]. + +[linker]: https://en.wikipedia.org/wiki/Linker_(computing) + +We also target `x86_64` systems with our kernel, so our target specification will look very similar to the above. Let's start by creating a `x86_64-blog_os.json` file (choose any name you like) with the common content: + +```json +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, +} +``` + +Note that we changed the OS in the `llvm-target` and the `os` field to `none`, because we will run on bare metal. + +We add the following build-related entries: + + +```json +"linker-flavor": "ld", +"linker": "ld.lld", +``` + +Instead of using the platform's default linker (which might not support Linux targets), we use the cross platform [LLD] linker for linking our kernel. + +[LLD]: https://lld.llvm.org/ + +```json +"panic": "abort", +``` + +This setting specifies that the target doesn't support [stack unwinding] on panic, so instead the program should abort directly. This has the same effect as the `panic = "abort"` option in our Cargo.toml, so we can remove it from there. + +[stack unwinding]: http://www.bogotobogo.com/cplusplus/stackunwinding.php + +```json +"disable-redzone": true, +``` + +We're writing a kernel, so we'll need to handle interrupts at some point. To do that safely, we have to disable a certain stack pointer optimization called the _“red zone”_, because it would cause stack corruptions otherwise. For more information, see our separate post about [disabling the red zone]. + +[disabling the red zone]: ./second-edition/extra/disable-red-zone/index.md + +```json +"features": "-mmx,-sse,+soft-float", +``` + +The `features` field enables/disables target features. We disable the `mmx` and `sse` features by prefixing them with a minus and enable the `soft-float` feature by prefixing it with a plus. + +The `mmx` and `sse` features determine support for [Single Instruction Multiple Data (SIMD)] instructions, which can often speed up programs significantly. However, the large SIMD registers lead to performance problems in OS kernels, because the kernel has to back them up on each hardware interrupt. To avoid this, we disable SIMD for our kernel (not for applications running on top!). + +[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD + +A problem with disabling SIMD is that floating point operations on `x86_64` require SIMD registers by default. To solve this problem, we add the `soft-float` feature, which emulates all floating point operations through software functions based on normal integers. + +For more information, see our post on [disabling SIMD](./second-edition/extra/disable-simd/index.md). + +#### Putting it Together +Our target specification file now looks like this: + +```json +{ + "llvm-target": "x86_64-unknown-linux-gnu", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "linker-flavor": "ld", + "linker": "ld.lld", + "executables": true, + "features": "-mmx,-sse,+soft-float", + "disable-redzone": true, + "panic": "abort" +} +``` + +### Building our Kernel +Compiling for our new target will use Linux conventions. I'm not quite sure why, but I assume that it's just LLVM's default. This means that we need an entry point named `_start` as described in the [previous post]: + +[previous post]: ./second-edition/posts/01-freestanding-rust-binary/index.md + +```rust +// src/main.rs + +#![feature(lang_items)] // required for defining the panic handler +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +#[lang = "panic_fmt"] // define a function that should be called on panic +#[no_mangle] +pub extern fn rust_begin_panic(_msg: core::fmt::Arguments, + _file: &'static str, _line: u32, _column: u32) -> ! +{ + loop {} +} + +#[no_mangle] // don't mangle the name of this function +pub fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start_` by default + loop {} +} +``` + +We can now build the kernel for our new target by passing the name of the JSON file (without the `.json` extension) as `--target`. There's is currently an [open bug][custom-target-bug] with custom target, so you also need to set the `RUST_TARGET_PATH` environment variable to the current directory, otherwise Rust might not be able to find your target. The full command is: + +[custom-target-bug]: https://github.com/rust-lang/cargo/issues/4905 + +``` +> RUST_TARGET_PATH=(pwd) cargo build --target x86_64-unknown-blog_os + +error[E0463]: can't find crate for `core` + | + = note: the `x86_64-blog_os` target may not be installed +``` + +It failed! The error tells us that the Rust compiler no longer finds the core library. The [core library] is implicitly linked to all `no_std` crates and contains things such as `Result`, `Option`, and iterators. + +[core library]: https://doc.rust-lang.org/nightly/core/index.html + +The problem is that the core library is distributed together with the Rust compiler as a _precompiled_ library. So it is only valid for the host triple (e.g., `x86_64-unknown-linux-gnu`) but not for our custom target. If we want to compile code for other targets, we need to recompile `core` for these targets first. + +#### Xargo +That's where [xargo] comes in. It is a wrapper for cargo that eases cross compilation. We can install it by executing: + +[xargo]: https://github.com/japaric/xargo + +``` +cargo install xargo +``` + +Xargo depends on the rust source code, which we can install with `rustup component add rust-src`. + +Xargo is “a drop-in replacement for cargo”, so every cargo command also works with `xargo`. You can do e.g. `xargo --help`, `xargo clean`, or `xargo doc`. The only difference is that the build command has additional functionality: `xargo build` will automatically cross compile the `core` library when compiling for custom targets. + +Let's try it: + +```bash +> RUST_TARGET_PATH=(pwd) xargo build --target x86_64-unknown-blog_os + Compiling core v0.0.0 (file:///…/rust/src/libcore) + Finished release [optimized] target(s) in 22.87 secs + Compiling blog_os v0.1.0 (file:///…/blog_os) + Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs +``` + +(If you're getting a linking error because LLD could not be found, see our “[Installing LLD]” guide.) + +[Installing LLD]: ./second-edition/extra/installing-lld/index.md + +It worked! We see that `xargo` cross-compiled the `core` library for our new custom target and then continued to compile our `blog_os` crate. + +Now we are able to build our kernel for a bare metal target. However, our `_start` entry point, which will be called by the boot loader, is still empty. So let's output something to screen from it. + +### Printing to Screen +The easiest way to print text to the screen at this stage is the [VGA text buffer]. It is a special memory area mapped to the VGA hardware that contains the contents displayed on screen. It normally consists of 50 lines that each contain 80 character cells. Each character cell displays an ASCII character with some foreground and background colors. The screen output looks like this: + +[VGA text buffer]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode + +![screen output for common ASCII characters](https://upload.wikimedia.org/wikipedia/commons/6/6d/Codepage-737.png) + +We will discuss the exact layout of the VGA buffer in the next post, where we write a first small driver for it. For printing “Hello”, we just need to know that the buffer is located at address `0xb8000` and that each character cell consists of an ASCII byte and a color byte. + +The implementation looks like this: + +```rust +static HELLO: &[u8] = b"Hello World!"; + +#[no_mangle] +pub fn _start(boot_info: &'static mut BootInfo) -> ! { + let vga_buffer = 0xb8000 as *const u8 as *mut u8; + + for (i, &byte) in HELLO.iter().enumerate() { + unsafe { + *vga_buffer.offset(i as isize * 2) = byte; + *vga_buffer.offset(i as isize * 2 + 1) = 0xb; + } + } + + loop {} +} +``` + +First, we cast the integer `0xb8000` into a [raw pointer]. Then we [iterate] over the bytes of the [static] `HELLO` [byte string]. We use the [`enumerate`] method to additionally get a running variable `i`. In the body of the for loop, we use the [`offset`] method to write the string byte and the corresponding color byte. + +[iterate]: https://doc.rust-lang.org/book/second-edition/ch13-02-iterators.html +[static]: https://doc.rust-lang.org/book/first-edition/const-and-static.html#static +[`enumerate`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.enumerate +[byte string]: https://doc.rust-lang.org/reference/tokens.html#byte-string-literals +[raw pointer]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer +[`offset`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset + +Note that there's an [`unsafe`] block around all memory writes. The reason is that the Rust compiler can't prove that the raw pointers we create are valid. They could point anywhere and lead to data corruption. By putting them into an `unsafe` block we're basically telling the compiler that we are absolutely sure that the operations are valid. Note that an `unsafe` block does not turn off Rust's safety checks. It only allows you to do [four additional things]. + +[`unsafe`]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html +[four additional things]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers + +I want to emphasize that **this is not the way we want to do things in Rust!** It's very easy to mess up when working with raw pointers inside unsafe blocks, for example, we could easily write behind the buffer's end if we're not careful. + +So we want to minimize the use of `unsafe` as much as possible. Rust gives us the ability to do this by creating safe abstractions. For example, we could create a VGA buffer type that encapsulates all unsafety and ensures that it is _impossible_ to do anything wrong from the outside. This way, we would only need minimal amounts of `unsafe` and can be sure that we don't violate [memory safety]. We will create such a safe VGA buffer abstraction in the next post. + +[memory safety]: https://en.wikipedia.org/wiki/Memory_safety + +We now have a simple “Hello World!” kernel. It should be noted though that a more advanced kernel might still produce linker faults because the compiler tries to use some function normally provided by `libc`. For this case, there are two crates you should keep in mind: [`rlibc`] and [`compiler_builtins`]. The former provides implementations for `memcpy`, `memclear`, etc. and the latter provides various other builtin functions. + +[`rlibc`]: https://docs.rs/crate/rlibc +[`compiler_builtins`]: https://docs.rs/crate/compiler-builtins-snapshot + +### Creating a Bootimage +Now that we have an executable that does something perceptible, it is time to turn it into a bootable disk image. As we learned in the [section about booting], we need a bootloader for that, which initializes the CPU and loads our kernel. + +[section about booting]: #the-boot-process + +To make things easy, we created a tool named `bootimage` that automatically downloads a bootloader and combines it with the kernel executable to create a bootable disk image. To install it, execute `cargo install bootimage` in your terminal. After installing, creating a bootimage is as easy as executing `bootimage --target x86_64-unknown-blog_os`. The tool also recompiles your kernel using `xargo`, so it will automatically pick up any changes you make. + +You should now see a file named `bootimage.bin` in your crate root directory. This file is a bootable disk image, so can boot it in a virtual machine or copy it to an USB drive to boot it on real hardware. (Note that this is not a CD image, which have a different format, so burning it to a CD doesn't work). + +## Booting it! +We can now boot our kernel in a virtual machine. To boot it in [QEMU], execute the following command: + +[QEMU]: https://www.qemu.org/ + +``` +> qemu-system-x86_64 -drive format=raw,file=bootimage.bin +warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5] +``` + +![QEMU showing "Hello World!"](qemu.png) + +You can also [convert the raw disk image to a VDI] to load it in [VirtualBox]. + +[convert the raw disk image to a VDI]: https://blog.sleeplessbeastie.eu/2012/04/29/virtualbox-convert-raw-image-to-vdi-and-otherwise/ +[VirtualBox]: https://www.virtualbox.org/ + +It is also possible to write it to an USB stick and boot it on a real machine: + +``` +> dd if=bootimage.bin of=/dev/sdX && sync +``` + +Where `sdX` is the device name of your USB stick. It overwrites everything on that device, so be careful to choose the correct device name. + +## What's next? +In the next post, we will explore the VGA text buffer in more detail and write a safe interface for it. We will also add support for the `println` macro. +TODO diff --git a/blog/content/second-edition/posts/02-minimal-rust-kernel/qemu.png b/blog/content/second-edition/posts/02-minimal-rust-kernel/qemu.png new file mode 100644 index 00000000..909b531e Binary files /dev/null and b/blog/content/second-edition/posts/02-minimal-rust-kernel/qemu.png differ diff --git a/blog/content/second-edition/posts/_index.md b/blog/content/second-edition/posts/_index.md new file mode 100644 index 00000000..bd698bdf --- /dev/null +++ b/blog/content/second-edition/posts/_index.md @@ -0,0 +1,6 @@ ++++ +title = "Posts" +sort_by = "order" +insert_anchor_links = "left" +render = false ++++ diff --git a/blog/static/css/main.css b/blog/static/css/main.css index b04e43d8..e928c375 100644 --- a/blog/static/css/main.css +++ b/blog/static/css/main.css @@ -1,7 +1,15 @@ +.masthead-title { + font-size: 1.25rem; + display: inline; +} -.masthead-title small { +.masthead p { + font-size: 1.25rem; + display: inline; + margin: 0; margin-left: 1rem; - white-space: nowrap; + padding: 0; + line-height: 1; } .front-page-introduction { @@ -220,3 +228,11 @@ a.gutenberg-anchor { a.gutenberg-anchor:hover { text-decoration: none; } + +div.warning { + padding: .7rem 1rem; + margin: 1rem .2rem; + border: 2px solid orange; + border-radius: 5px; + background-color: #ffa50022; +} diff --git a/blog/static/css/poole.css b/blog/static/css/poole.css index e8572304..2efff964 100644 --- a/blog/static/css/poole.css +++ b/blog/static/css/poole.css @@ -285,7 +285,7 @@ tbody tr:nth-child(odd) th { .masthead-title a { color: #505050; } -.masthead-title small { +.masthead small { font-size: 75%; font-weight: 400; color: #c0c0c0; diff --git a/blog/static/favicon.ico b/blog/static/favicon.ico index 34b42063..0eb5eb16 100644 Binary files a/blog/static/favicon.ico and b/blog/static/favicon.ico differ diff --git a/blog/templates/base.html b/blog/templates/base.html index 155069b2..957e2643 100644 --- a/blog/templates/base.html +++ b/blog/templates/base.html @@ -1,6 +1,6 @@ - + @@ -20,10 +20,10 @@
-

+

{{ config.title }} - {{ config.extra.subtitle }} -

+ +

{{ config.extra.subtitle }}

{% block main %}{% endblock main %}
diff --git a/blog/templates/index.html b/blog/templates/index.html index 547de502..b041e8a8 100644 --- a/blog/templates/index.html +++ b/blog/templates/index.html @@ -16,7 +16,7 @@

Latest post: {% set latest_post = posts|last %} - {{ latest_post.title }} + {{ latest_post.title }}

@@ -47,10 +47,10 @@

{{ extra.title }}

diff --git a/blog/templates/macros.html b/blog/templates/macros.html index aa62e7fe..a1a8b05a 100644 --- a/blog/templates/macros.html +++ b/blog/templates/macros.html @@ -1,6 +1,6 @@ {% macro post_link(page) %} {% endmacro post_link %} diff --git a/blog/templates/page.html b/blog/templates/page.html index 463e937d..8a65cfe7 100644 --- a/blog/templates/page.html +++ b/blog/templates/page.html @@ -12,10 +12,10 @@

Table of Contents

    {% for h2 in page.toc %}
  1. - {{ h2.title | safe }} + {{ h2.title | safe }} {% if h2.children %}
      {% for h3 in h2.children %}
    1. - {{ h3.title | safe }} + {{ h3.title | safe }}
    2. {% endfor %}
    {% endif %}
  2. {% endfor %} @@ -28,10 +28,10 @@
    diff --git a/blog/templates/second-edition/base.html b/blog/templates/second-edition/base.html new file mode 100644 index 00000000..941fcf80 --- /dev/null +++ b/blog/templates/second-edition/base.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + {% block title %}{% endblock title %} + + + +
    +
    +

    + {{ config.title }} (Second Edition) +

    +

    {{ config.extra.subtitle }}

    +
    + +
    {% block main %}{% endblock main %}
    + +
    {% block after_main %}{% endblock after_main %}
    + +
    +
    + + © . All rights reserved. + Contact + +
    +
    + + + + + + + diff --git a/blog/templates/second-edition/extra.html b/blog/templates/second-edition/extra.html new file mode 100644 index 00000000..bc66e273 --- /dev/null +++ b/blog/templates/second-edition/extra.html @@ -0,0 +1,24 @@ +{% extends "second-edition/base.html" %} + +{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} + +{% block main %} +

    {{ page.title }}

    +
    + Note: This post is part of the upcoming second edition of “Writing an OS in Rust”, which is still under construction. To read the first edition, go here. +
    + {{ page.content | safe }} +{% endblock main %} + +{% block after_main %} +
    +
    +

    Comments

    + +
    +{% endblock after_main %} diff --git a/blog/templates/second-edition/index.html b/blog/templates/second-edition/index.html new file mode 100644 index 00000000..797a1114 --- /dev/null +++ b/blog/templates/second-edition/index.html @@ -0,0 +1,37 @@ +{% extends "second-edition/base.html" %} + +{% import "macros.html" as macros %} + +{% block title %}{{ config.title }}{% endblock title %} + +{% block main %} +{% set posts_section = get_section(path = "second-edition/posts/_index.md") %} +{% set posts = posts_section.pages | reverse %} +
    + Note: You are viewing the upcoming second edition of “Writing an OS in Rust”, which is still under construction. To read the first edition, go here. +
    +
    +

    + This blog series creates a small operating system in the + Rust programming language. Each post is a small tutorial and includes all + needed code, so you can follow along if you like. The source code is also available in the corresponding + Github repository. +

    +

    Latest post: + {% set latest_post = posts|last %} + {{ latest_post.title }} +

    +
    + + +
    + {{ macros::post_link(page=posts.0) }} + {{ macros::post_link(page=posts.1) }} +
    + + + +{% endblock main %} diff --git a/blog/templates/second-edition/page.html b/blog/templates/second-edition/page.html new file mode 100644 index 00000000..e501df44 --- /dev/null +++ b/blog/templates/second-edition/page.html @@ -0,0 +1,54 @@ +{% extends "second-edition/base.html" %} + +{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} + +{% block main %} +

    {{ page.title }}

    + + +
    + Note: This post is part of the upcoming second edition of “Writing an OS in Rust”, which is still under construction. To read the first edition, go here. +
    + {{ page.content | safe }} +{% endblock main %} + +{% block after_main %} +
    + + +
    +
    +

    Comments

    + +
    + +{% endblock after_main %} + + diff --git a/bors.toml b/bors.toml new file mode 100644 index 00000000..4e6e85f4 --- /dev/null +++ b/bors.toml @@ -0,0 +1,4 @@ +status = [ + "continuous-integration/travis-ci/push", + "continuous-integration/appveyor/branch" +] diff --git a/libs/bump_allocator/.gitignore b/libs/bump_allocator/.gitignore deleted file mode 100644 index 49d4fed9..00000000 --- a/libs/bump_allocator/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Generated by Cargo -/target/ diff --git a/src/arch/x86_64/boot.asm b/src/arch/x86_64/boot.asm deleted file mode 100644 index 0fcffc79..00000000 --- a/src/arch/x86_64/boot.asm +++ /dev/null @@ -1,202 +0,0 @@ -; Copyright 2016 Philipp Oppermann. See the README.md -; file at the top-level directory of this distribution. -; -; Licensed under the Apache License, Version 2.0 or the MIT license -; , at your -; option. This file may not be copied, modified, or distributed -; except according to those terms. - -global start -extern long_mode_start - -section .text -bits 32 -start: - mov esp, stack_top - ; Move Multiboot info pointer to edi to pass it to the kernel. We must not - ; modify the `edi` register until the kernel it called. - mov edi, ebx - - call check_multiboot - call check_cpuid - call check_long_mode - - call set_up_page_tables - call enable_paging - call set_up_SSE - - ; load the 64-bit GDT - lgdt [gdt64.pointer] - - jmp gdt64.code:long_mode_start - -set_up_page_tables: - ; recursive map P4 - mov eax, p4_table - or eax, 0b11 ; present + writable - mov [p4_table + 511 * 8], eax - - ; map first P4 entry to P3 table - mov eax, p3_table - or eax, 0b11 ; present + writable - mov [p4_table], eax - - ; map first P3 entry to P2 table - mov eax, p2_table - or eax, 0b11 ; present + writable - mov [p3_table], eax - - ; map each P2 entry to a huge 2MiB page - mov ecx, 0 ; counter variable -.map_p2_table: - ; map ecx-th P2 entry to a huge page that starts at address (2MiB * ecx) - mov eax, 0x200000 ; 2MiB - mul ecx ; start address of ecx-th page - or eax, 0b10000011 ; present + writable + huge - mov [p2_table + ecx * 8], eax ; map ecx-th entry - - inc ecx ; increase counter - cmp ecx, 512 ; if counter == 512, the whole P2 table is mapped - jne .map_p2_table ; else map the next entry - - ret - -enable_paging: - ; load P4 to cr3 register (cpu uses this to access the P4 table) - mov eax, p4_table - mov cr3, eax - - ; enable PAE-flag in cr4 (Physical Address Extension) - mov eax, cr4 - or eax, 1 << 5 - mov cr4, eax - - ; set the long mode bit in the EFER MSR (model specific register) - mov ecx, 0xC0000080 - rdmsr - or eax, 1 << 8 - wrmsr - - ; enable paging in the cr0 register - mov eax, cr0 - or eax, 1 << 31 - mov cr0, eax - - ret - -; Prints `ERR: ` and the given error code to screen and hangs. -; parameter: error code (in ascii) in al -error: - mov dword [0xb8000], 0x4f524f45 - mov dword [0xb8004], 0x4f3a4f52 - mov dword [0xb8008], 0x4f204f20 - mov byte [0xb800a], al - hlt - -; Throw error 0 if eax doesn't contain the Multiboot 2 magic value (0x36d76289). -check_multiboot: - cmp eax, 0x36d76289 - jne .no_multiboot - ret -.no_multiboot: - mov al, "0" - jmp error - -; Throw error 1 if the CPU doesn't support the CPUID command. -check_cpuid: - ; Check if CPUID is supported by attempting to flip the ID bit (bit 21) in - ; the FLAGS register. If we can flip it, CPUID is available. - - ; Copy FLAGS in to EAX via stack - pushfd - pop eax - - ; Copy to ECX as well for comparing later on - mov ecx, eax - - ; Flip the ID bit - xor eax, 1 << 21 - - ; Copy EAX to FLAGS via the stack - push eax - popfd - - ; Copy FLAGS back to EAX (with the flipped bit if CPUID is supported) - pushfd - pop eax - - ; Restore FLAGS from the old version stored in ECX (i.e. flipping the ID bit - ; back if it was ever flipped). - push ecx - popfd - - ; Compare EAX and ECX. If they are equal then that means the bit wasn't - ; flipped, and CPUID isn't supported. - cmp eax, ecx - je .no_cpuid - ret -.no_cpuid: - mov al, "1" - jmp error - -; Throw error 2 if the CPU doesn't support Long Mode. -check_long_mode: - ; test if extended processor info in available - mov eax, 0x80000000 ; implicit argument for cpuid - cpuid ; get highest supported argument - cmp eax, 0x80000001 ; it needs to be at least 0x80000001 - jb .no_long_mode ; if it's less, the CPU is too old for long mode - - ; use extended info to test if long mode is available - mov eax, 0x80000001 ; argument for extended processor info - cpuid ; returns various feature bits in ecx and edx - test edx, 1 << 29 ; test if the LM-bit is set in the D-register - jz .no_long_mode ; If it's not set, there is no long mode - ret -.no_long_mode: - mov al, "2" - jmp error - -; Check for SSE and enable it. If it's not supported throw error "a". -set_up_SSE: - ; check for SSE - mov eax, 0x1 - cpuid - test edx, 1<<25 - jz .no_SSE - - ; enable SSE - mov eax, cr0 - and ax, 0xFFFB ; clear coprocessor emulation CR0.EM - or ax, 0x2 ; set coprocessor monitoring CR0.MP - mov cr0, eax - mov eax, cr4 - or ax, 3 << 9 ; set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time - mov cr4, eax - - ret -.no_SSE: - mov al, "a" - jmp error - -section .bss -align 4096 -p4_table: - resb 4096 -p3_table: - resb 4096 -p2_table: - resb 4096 -stack_bottom: - resb 4096 * 4 -stack_top: - -section .rodata -gdt64: - dq 0 ; zero entry -.code: equ $ - gdt64 ; new - dq (1<<44) | (1<<47) | (1<<43) | (1<<53) ; code segment -.pointer: - dw $ - gdt64 - 1 - dq gdt64 diff --git a/src/arch/x86_64/grub.cfg b/src/arch/x86_64/grub.cfg deleted file mode 100644 index dbe53c10..00000000 --- a/src/arch/x86_64/grub.cfg +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2016 Philipp Oppermann. See the README.md -# file at the top-level directory of this distribution. -# -# Licensed under the Apache License, Version 2.0 or the MIT license -# , at your -# option. This file may not be copied, modified, or distributed -# except according to those terms. - -set timeout=0 -set default=0 - -menuentry "my os" { - multiboot2 /boot/kernel.bin - boot -} diff --git a/src/arch/x86_64/linker.ld b/src/arch/x86_64/linker.ld deleted file mode 100644 index 62f8f266..00000000 --- a/src/arch/x86_64/linker.ld +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2016 Philipp Oppermann. See the README.md -file at the top-level directory of this distribution. - -Licensed under the Apache License, Version 2.0 or the MIT license -, at your -option. This file may not be copied, modified, or distributed -except according to those terms. -*/ - -ENTRY(start) - -SECTIONS { - . = 1M; - - .rodata : - { - /* ensure that the multiboot header is at the beginning */ - KEEP(*(.multiboot_header)) - *(.rodata .rodata.*) - . = ALIGN(4K); - } - - .text : - { - *(.text .text.*) - . = ALIGN(4K); - } - - .data : - { - *(.data .data.*) - . = ALIGN(4K); - } - - .bss : - { - *(.bss .bss.*) - . = ALIGN(4K); - } - - .got : - { - *(.got) - . = ALIGN(4K); - } - - .got.plt : - { - *(.got.plt) - . = ALIGN(4K); - } - - .data.rel.ro : ALIGN(4K) { - *(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*) - . = ALIGN(4K); - } - - .gcc_except_table : ALIGN(4K) { - *(.gcc_except_table) - . = ALIGN(4K); - } -} diff --git a/src/arch/x86_64/long_mode_init.asm b/src/arch/x86_64/long_mode_init.asm deleted file mode 100644 index 79b4c025..00000000 --- a/src/arch/x86_64/long_mode_init.asm +++ /dev/null @@ -1,34 +0,0 @@ -; Copyright 2016 Philipp Oppermann. See the README.md -; file at the top-level directory of this distribution. -; -; Licensed under the Apache License, Version 2.0 or the MIT license -; , at your -; option. This file may not be copied, modified, or distributed -; except according to those terms. - -global long_mode_start -extern rust_main - -section .text -bits 64 -long_mode_start: - ; load 0 into all data segment registers - mov ax, 0 - mov ss, ax - mov ds, ax - mov es, ax - mov fs, ax - mov gs, ax - - ; call rust main (with multiboot pointer in rdi) - call rust_main -.os_returned: - ; rust main returned, print `OS returned!` - mov rax, 0x4f724f204f534f4f - mov [0xb8000], rax - mov rax, 0x4f724f754f744f65 - mov [0xb8008], rax - mov rax, 0x4f214f644f654f6e - mov [0xb8010], rax - hlt diff --git a/src/arch/x86_64/multiboot_header.asm b/src/arch/x86_64/multiboot_header.asm deleted file mode 100644 index 0b3f83bb..00000000 --- a/src/arch/x86_64/multiboot_header.asm +++ /dev/null @@ -1,24 +0,0 @@ -; Copyright 2016 Philipp Oppermann. See the README.md -; file at the top-level directory of this distribution. -; -; Licensed under the Apache License, Version 2.0 or the MIT license -; , at your -; option. This file may not be copied, modified, or distributed -; except according to those terms. - -section .multiboot_header -header_start: - dd 0xe85250d6 ; magic number (multiboot 2) - dd 0 ; architecture 0 (protected mode i386) - dd header_end - header_start ; header length - ; checksum - dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start)) - - ; insert optional multiboot tags here - - ; required end tag - dw 0 ; type - dw 0 ; flags - dd 8 ; size -header_end: diff --git a/src/interrupts/gdt.rs b/src/interrupts/gdt.rs deleted file mode 100644 index 5ea28e31..00000000 --- a/src/interrupts/gdt.rs +++ /dev/null @@ -1,95 +0,0 @@ -use x86_64::structures::tss::TaskStateSegment; -use x86_64::structures::gdt::SegmentSelector; -use x86_64::PrivilegeLevel; - -pub struct Gdt { - table: [u64; 8], - next_free: usize, -} - -impl Gdt { - pub fn new() -> Gdt { - Gdt { - table: [0; 8], - next_free: 1, - } - } - - pub fn add_entry(&mut self, entry: Descriptor) -> SegmentSelector { - let index = match entry { - Descriptor::UserSegment(value) => self.push(value), - Descriptor::SystemSegment(value_low, value_high) => { - let index = self.push(value_low); - self.push(value_high); - index - } - }; - SegmentSelector::new(index as u16, PrivilegeLevel::Ring0) - } - - fn push(&mut self, value: u64) -> usize { - if self.next_free < self.table.len() { - let index = self.next_free; - self.table[index] = value; - self.next_free += 1; - index - } else { - panic!("GDT full"); - } - } - - pub fn load(&'static self) { - use x86_64::instructions::tables::{DescriptorTablePointer, lgdt}; - use core::mem::size_of; - - let ptr = DescriptorTablePointer { - base: self.table.as_ptr() as u64, - limit: (self.table.len() * size_of::() - 1) as u16, - }; - - unsafe { lgdt(&ptr) }; - } -} - -pub enum Descriptor { - UserSegment(u64), - SystemSegment(u64, u64), -} - -impl Descriptor { - pub fn kernel_code_segment() -> Descriptor { - let flags = USER_SEGMENT | PRESENT | EXECUTABLE | LONG_MODE; - Descriptor::UserSegment(flags.bits()) - } - - pub fn tss_segment(tss: &'static TaskStateSegment) -> Descriptor { - use core::mem::size_of; - use bit_field::BitField; - - let ptr = tss as *const _ as u64; - - let mut low = PRESENT.bits(); - // base - low.set_bits(16..40, ptr.get_bits(0..24)); - low.set_bits(56..64, ptr.get_bits(24..32)); - // limit (the `-1` in needed since the bound is inclusive) - low.set_bits(0..16, (size_of::() - 1) as u64); - // type (0b1001 = available 64-bit tss) - low.set_bits(40..44, 0b1001); - - let mut high = 0; - high.set_bits(0..32, ptr.get_bits(32..64)); - - Descriptor::SystemSegment(low, high) - } -} - -bitflags! { - struct DescriptorFlags: u64 { - const CONFORMING = 1 << 42; - const EXECUTABLE = 1 << 43; - const USER_SEGMENT = 1 << 44; - const PRESENT = 1 << 47; - const LONG_MODE = 1 << 53; - } -} diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs deleted file mode 100644 index c4da624a..00000000 --- a/src/interrupts/mod.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use memory::MemoryController; -use x86_64::structures::tss::TaskStateSegment; -use x86_64::structures::idt::{Idt, ExceptionStackFrame, PageFaultErrorCode}; -use spin::Once; - -mod gdt; - -const DOUBLE_FAULT_IST_INDEX: usize = 0; - -lazy_static! { - static ref IDT: Idt = { - let mut idt = Idt::new(); - - idt.divide_by_zero.set_handler_fn(divide_by_zero_handler); - idt.breakpoint.set_handler_fn(breakpoint_handler); - idt.invalid_opcode.set_handler_fn(invalid_opcode_handler); - idt.page_fault.set_handler_fn(page_fault_handler); - - unsafe { - idt.double_fault.set_handler_fn(double_fault_handler) - .set_stack_index(DOUBLE_FAULT_IST_INDEX as u16); - } - - idt - }; -} - -static TSS: Once = Once::new(); -static GDT: Once = Once::new(); - -pub fn init(memory_controller: &mut MemoryController) { - use x86_64::structures::gdt::SegmentSelector; - use x86_64::instructions::segmentation::set_cs; - use x86_64::instructions::tables::load_tss; - use x86_64::VirtualAddress; - - let double_fault_stack = memory_controller - .alloc_stack(1) - .expect("could not allocate double fault stack"); - - let tss = TSS.call_once(|| { - let mut tss = TaskStateSegment::new(); - tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX] = - VirtualAddress(double_fault_stack.top()); - tss - }); - - let mut code_selector = SegmentSelector(0); - let mut tss_selector = SegmentSelector(0); - let gdt = GDT.call_once(|| { - let mut gdt = gdt::Gdt::new(); - code_selector = gdt.add_entry(gdt::Descriptor::kernel_code_segment()); - tss_selector = gdt.add_entry(gdt::Descriptor::tss_segment(&tss)); - gdt - }); - gdt.load(); - - unsafe { - // reload code segment register - set_cs(code_selector); - // load TSS - load_tss(tss_selector); - } - - IDT.load(); -} - -extern "x86-interrupt" fn divide_by_zero_handler(stack_frame: &mut ExceptionStackFrame) { - println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame); - loop {} -} - -extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) { - println!( - "\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}", - stack_frame.instruction_pointer, - stack_frame - ); -} - -extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: &mut ExceptionStackFrame) { - println!( - "\nEXCEPTION: INVALID OPCODE at {:#x}\n{:#?}", - stack_frame.instruction_pointer, - stack_frame - ); - loop {} -} - -extern "x86-interrupt" fn page_fault_handler( - stack_frame: &mut ExceptionStackFrame, - error_code: PageFaultErrorCode, -) { - use x86_64::registers::control_regs; - println!( - "\nEXCEPTION: PAGE FAULT while accessing {:#x}\nerror code: \ - {:?}\n{:#?}", - control_regs::cr2(), - error_code, - stack_frame - ); - loop {} -} - -extern "x86-interrupt" fn double_fault_handler( - stack_frame: &mut ExceptionStackFrame, - _error_code: u64, -) { - println!("\nEXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); - loop {} -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index ef14939f..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![feature(lang_items)] -#![feature(const_fn, unique)] -#![feature(alloc)] -#![feature(asm)] -#![feature(naked_functions)] -#![feature(abi_x86_interrupt)] -#![feature(const_unique_new, const_atomic_usize_new)] -#![feature(allocator_api)] -#![feature(global_allocator)] -#![no_std] - -#[macro_use] -extern crate alloc; -extern crate rlibc; -extern crate volatile; -extern crate spin; -extern crate multiboot2; -#[macro_use] -extern crate bitflags; -extern crate x86_64; -#[macro_use] -extern crate once; -extern crate linked_list_allocator; -#[macro_use] -extern crate lazy_static; -extern crate bit_field; -#[macro_use] -mod vga_buffer; -mod memory; -mod interrupts; - -#[no_mangle] -pub extern "C" fn rust_main(multiboot_information_address: usize) { - // ATTENTION: we have a very small stack and no guard page - vga_buffer::clear_screen(); - println!("Hello World{}", "!"); - - let boot_info = unsafe { multiboot2::load(multiboot_information_address) }; - enable_nxe_bit(); - enable_write_protect_bit(); - - // set up guard page and map the heap pages - let mut memory_controller = memory::init(boot_info); - - unsafe { - HEAP_ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE); - } - - // initialize our IDT - interrupts::init(&mut memory_controller); - - fn stack_overflow() { - stack_overflow(); // for each recursion, the return address is pushed - } - - // trigger a stack overflow - stack_overflow(); - - - println!("It did not crash!"); - loop {} -} - -fn enable_nxe_bit() { - use x86_64::registers::msr::{IA32_EFER, rdmsr, wrmsr}; - - let nxe_bit = 1 << 11; - unsafe { - let efer = rdmsr(IA32_EFER); - wrmsr(IA32_EFER, efer | nxe_bit); - } -} - -fn enable_write_protect_bit() { - use x86_64::registers::control_regs::{cr0, cr0_write, Cr0}; - - unsafe { cr0_write(cr0() | Cr0::WRITE_PROTECT) }; -} - -#[cfg(not(test))] -#[lang = "eh_personality"] -#[no_mangle] -pub extern "C" fn eh_personality() {} - -#[cfg(not(test))] -#[lang = "panic_fmt"] -#[no_mangle] -pub extern "C" fn panic_fmt(fmt: core::fmt::Arguments, file: &'static str, line: u32) -> ! { - println!("\n\nPANIC in {} at line {}:", file, line); - println!(" {}", fmt); - loop {} -} - -#[no_mangle] -pub extern "C" fn _Unwind_Resume() -> ! { loop {} } - -use linked_list_allocator::LockedHeap; - -pub const HEAP_START: usize = 0o_000_001_000_000_0000; -pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB - -#[global_allocator] -static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty(); diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..1ea314b7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,32 @@ +#![feature(lang_items)] // required for defining the panic handler +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +//extern crate rlibc; + +static HELLO: &[u8] = b"Hello World!"; + +#[no_mangle] // don't mangle the name of this function +pub fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start_` by default + let vga_buffer = 0xb8000 as *const u8 as *mut u8; + + for (i, &byte) in HELLO.iter().enumerate() { + unsafe { + *vga_buffer.offset(i as isize * 2) = byte; + *vga_buffer.offset(i as isize * 2 + 1) = 0xb; + } + } + + loop {} +} + +#[lang = "panic_fmt"] // define a function that should be called on panic +#[no_mangle] // TODO required? +pub extern fn rust_begin_panic(_msg: core::fmt::Arguments, + _file: &'static str, + _line: u32, + _column: u32) -> ! { + loop {} +} diff --git a/src/memory/area_frame_allocator.rs b/src/memory/area_frame_allocator.rs deleted file mode 100644 index 2afa3130..00000000 --- a/src/memory/area_frame_allocator.rs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use memory::{Frame, FrameAllocator}; -use multiboot2::{MemoryAreaIter, MemoryArea}; - -/// A frame allocator that uses the memory areas from the multiboot information structure as -/// source. The {kernel, multiboot}_{start, end} fields are used to avoid returning memory that is -/// already in use. -/// -/// `kernel_end` and `multiboot_end` are _inclusive_ bounds. -pub struct AreaFrameAllocator { - next_free_frame: Frame, - current_area: Option<&'static MemoryArea>, - areas: MemoryAreaIter, - kernel_start: Frame, - kernel_end: Frame, - multiboot_start: Frame, - multiboot_end: Frame, -} - -impl AreaFrameAllocator { - pub fn new( - kernel_start: usize, - kernel_end: usize, - multiboot_start: usize, - multiboot_end: usize, - memory_areas: MemoryAreaIter, - ) -> AreaFrameAllocator { - let mut allocator = AreaFrameAllocator { - next_free_frame: Frame::containing_address(0), - current_area: None, - areas: memory_areas, - kernel_start: Frame::containing_address(kernel_start), - kernel_end: Frame::containing_address(kernel_end), - multiboot_start: Frame::containing_address(multiboot_start), - multiboot_end: Frame::containing_address(multiboot_end), - }; - allocator.choose_next_area(); - allocator - } - - fn choose_next_area(&mut self) { - self.current_area = self.areas - .clone() - .filter(|area| { - let address = area.base_addr + area.length - 1; - Frame::containing_address(address as usize) >= self.next_free_frame - }) - .min_by_key(|area| area.base_addr); - - if let Some(area) = self.current_area { - let start_frame = Frame::containing_address(area.base_addr as usize); - if self.next_free_frame < start_frame { - self.next_free_frame = start_frame; - } - } - } -} - -impl FrameAllocator for AreaFrameAllocator { - fn allocate_frame(&mut self) -> Option { - if let Some(area) = self.current_area { - // "clone" the frame to return it if it's free. Frame doesn't - // implement Clone, but we can construct an identical frame. - let frame = Frame { number: self.next_free_frame.number }; - - // the last frame of the current area - let current_area_last_frame = { - let address = area.base_addr + area.length - 1; - Frame::containing_address(address as usize) - }; - - if frame > current_area_last_frame { - // all frames of current area are used, switch to next area - self.choose_next_area(); - } else if frame >= self.kernel_start && frame <= self.kernel_end { - // `frame` is used by the kernel - self.next_free_frame = Frame { number: self.kernel_end.number + 1 }; - } else if frame >= self.multiboot_start && frame <= self.multiboot_end { - // `frame` is used by the multiboot information structure - self.next_free_frame = Frame { number: self.multiboot_end.number + 1 }; - } else { - // frame is unused, increment `next_free_frame` and return it - self.next_free_frame.number += 1; - return Some(frame); - } - // `frame` was not valid, try it again with the updated `next_free_frame` - self.allocate_frame() - } else { - None // no free frames left - } - } - - fn deallocate_frame(&mut self, _frame: Frame) { - unimplemented!() - } -} diff --git a/src/memory/mod.rs b/src/memory/mod.rs deleted file mode 100644 index fc1a7dce..00000000 --- a/src/memory/mod.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -pub use self::area_frame_allocator::AreaFrameAllocator; -pub use self::paging::remap_the_kernel; -pub use self::stack_allocator::Stack; -use self::paging::PhysicalAddress; -use multiboot2::BootInformation; - -mod area_frame_allocator; -pub mod heap_allocator; -mod paging; -mod stack_allocator; - -pub const PAGE_SIZE: usize = 4096; - -pub fn init(boot_info: &BootInformation) -> MemoryController { - assert_has_not_been_called!("memory::init must be called only once"); - - let memory_map_tag = boot_info.memory_map_tag().expect("Memory map tag required"); - let elf_sections_tag = boot_info - .elf_sections_tag() - .expect("Elf sections tag required"); - - let kernel_start = elf_sections_tag - .sections() - .filter(|s| s.is_allocated()) - .map(|s| s.addr) - .min() - .unwrap(); - let kernel_end = elf_sections_tag - .sections() - .filter(|s| s.is_allocated()) - .map(|s| s.addr + s.size) - .max() - .unwrap(); - - println!( - "kernel start: {:#x}, kernel end: {:#x}", - kernel_start, - kernel_end - ); - println!( - "multiboot start: {:#x}, multiboot end: {:#x}", - boot_info.start_address(), - boot_info.end_address() - ); - - let mut frame_allocator = AreaFrameAllocator::new( - kernel_start as usize, - kernel_end as usize, - boot_info.start_address(), - boot_info.end_address(), - memory_map_tag.memory_areas(), - ); - - let mut active_table = paging::remap_the_kernel(&mut frame_allocator, boot_info); - - use self::paging::Page; - use super::{HEAP_START, HEAP_SIZE}; - - let heap_start_page = Page::containing_address(HEAP_START); - let heap_end_page = Page::containing_address(HEAP_START + HEAP_SIZE - 1); - - for page in Page::range_inclusive(heap_start_page, heap_end_page) { - active_table.map(page, paging::WRITABLE, &mut frame_allocator); - } - - let stack_allocator = { - let stack_alloc_start = heap_end_page + 1; - let stack_alloc_end = stack_alloc_start + 100; - let stack_alloc_range = Page::range_inclusive(stack_alloc_start, stack_alloc_end); - stack_allocator::StackAllocator::new(stack_alloc_range) - }; - - MemoryController { - active_table: active_table, - frame_allocator: frame_allocator, - stack_allocator: stack_allocator, - } -} - -pub struct MemoryController { - active_table: paging::ActivePageTable, - frame_allocator: AreaFrameAllocator, - stack_allocator: stack_allocator::StackAllocator, -} - -impl MemoryController { - pub fn alloc_stack(&mut self, size_in_pages: usize) -> Option { - let &mut MemoryController { - ref mut active_table, - ref mut frame_allocator, - ref mut stack_allocator, - } = self; - stack_allocator.alloc_stack(active_table, frame_allocator, size_in_pages) - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Frame { - number: usize, -} - -impl Frame { - fn containing_address(address: usize) -> Frame { - Frame { number: address / PAGE_SIZE } - } - - fn start_address(&self) -> PhysicalAddress { - self.number * PAGE_SIZE - } - - fn clone(&self) -> Frame { - Frame { number: self.number } - } - - fn range_inclusive(start: Frame, end: Frame) -> FrameIter { - FrameIter { - start: start, - end: end, - } - } -} - -struct FrameIter { - start: Frame, - end: Frame, -} - -impl Iterator for FrameIter { - type Item = Frame; - - fn next(&mut self) -> Option { - if self.start <= self.end { - let frame = self.start.clone(); - self.start.number += 1; - Some(frame) - } else { - None - } - } -} - -pub trait FrameAllocator { - fn allocate_frame(&mut self) -> Option; - fn deallocate_frame(&mut self, frame: Frame); -} diff --git a/src/memory/paging/entry.rs b/src/memory/paging/entry.rs deleted file mode 100644 index 5afbcd75..00000000 --- a/src/memory/paging/entry.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use memory::Frame; -use multiboot2::ElfSection; - -pub struct Entry(u64); - -impl Entry { - pub fn is_unused(&self) -> bool { - self.0 == 0 - } - - pub fn set_unused(&mut self) { - self.0 = 0; - } - - pub fn flags(&self) -> EntryFlags { - EntryFlags::from_bits_truncate(self.0) - } - - pub fn pointed_frame(&self) -> Option { - if self.flags().contains(PRESENT) { - Some(Frame::containing_address( - self.0 as usize & 0x000fffff_fffff000, - )) - } else { - None - } - } - - pub fn set(&mut self, frame: Frame, flags: EntryFlags) { - assert!(frame.start_address() & !0x000fffff_fffff000 == 0); - self.0 = (frame.start_address() as u64) | flags.bits(); - } -} - -bitflags! { - pub struct EntryFlags: u64 { - const PRESENT = 1 << 0; - const WRITABLE = 1 << 1; - const USER_ACCESSIBLE = 1 << 2; - const WRITE_THROUGH = 1 << 3; - const NO_CACHE = 1 << 4; - const ACCESSED = 1 << 5; - const DIRTY = 1 << 6; - const HUGE_PAGE = 1 << 7; - const GLOBAL = 1 << 8; - const NO_EXECUTE = 1 << 63; - } -} - -impl EntryFlags { - pub fn from_elf_section_flags(section: &ElfSection) -> EntryFlags { - use multiboot2::{ELF_SECTION_ALLOCATED, ELF_SECTION_WRITABLE, ELF_SECTION_EXECUTABLE}; - - let mut flags = EntryFlags::empty(); - - if section.flags().contains(ELF_SECTION_ALLOCATED) { - // section is loaded to memory - flags = flags | PRESENT; - } - if section.flags().contains(ELF_SECTION_WRITABLE) { - flags = flags | WRITABLE; - } - if !section.flags().contains(ELF_SECTION_EXECUTABLE) { - flags = flags | NO_EXECUTE; - } - - flags - } -} diff --git a/src/memory/paging/mapper.rs b/src/memory/paging/mapper.rs deleted file mode 100644 index c46a87cd..00000000 --- a/src/memory/paging/mapper.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::{VirtualAddress, PhysicalAddress, Page, ENTRY_COUNT}; -use super::entry::*; -use super::table::{self, Table, Level4}; -use memory::{PAGE_SIZE, Frame, FrameAllocator}; -use core::ptr::Unique; - -pub struct Mapper { - p4: Unique>, -} - -impl Mapper { - pub unsafe fn new() -> Mapper { - Mapper { p4: Unique::new_unchecked(table::P4) } - } - - pub fn p4(&self) -> &Table { - unsafe { self.p4.as_ref() } - } - - pub fn p4_mut(&mut self) -> &mut Table { - unsafe { self.p4.as_mut() } - } - - pub fn translate(&self, virtual_address: VirtualAddress) -> Option { - let offset = virtual_address % PAGE_SIZE; - self.translate_page(Page::containing_address(virtual_address)) - .map(|frame| frame.number * PAGE_SIZE + offset) - } - - pub fn translate_page(&self, page: Page) -> Option { - let p3 = self.p4().next_table(page.p4_index()); - - let huge_page = || { - p3.and_then(|p3| { - let p3_entry = &p3[page.p3_index()]; - // 1GiB page? - if let Some(start_frame) = p3_entry.pointed_frame() { - if p3_entry.flags().contains(HUGE_PAGE) { - // address must be 1GiB aligned - assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0); - return Some(Frame { - number: start_frame.number + page.p2_index() * ENTRY_COUNT + - page.p1_index(), - }); - } - } - if let Some(p2) = p3.next_table(page.p3_index()) { - let p2_entry = &p2[page.p2_index()]; - // 2MiB page? - if let Some(start_frame) = p2_entry.pointed_frame() { - if p2_entry.flags().contains(HUGE_PAGE) { - // address must be 2MiB aligned - assert!(start_frame.number % ENTRY_COUNT == 0); - return Some(Frame { number: start_frame.number + page.p1_index() }); - } - } - } - None - }) - }; - - p3.and_then(|p3| p3.next_table(page.p3_index())) - .and_then(|p2| p2.next_table(page.p2_index())) - .and_then(|p1| p1[page.p1_index()].pointed_frame()) - .or_else(huge_page) - } - - pub fn map_to(&mut self, page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A) - where - A: FrameAllocator, - { - let mut p3 = self.p4_mut().next_table_create(page.p4_index(), allocator); - let mut p2 = p3.next_table_create(page.p3_index(), allocator); - let mut p1 = p2.next_table_create(page.p2_index(), allocator); - - assert!(p1[page.p1_index()].is_unused()); - p1[page.p1_index()].set(frame, flags | PRESENT); - } - - pub fn map(&mut self, page: Page, flags: EntryFlags, allocator: &mut A) - where - A: FrameAllocator, - { - let frame = allocator.allocate_frame().expect("out of memory"); - self.map_to(page, frame, flags, allocator) - } - - pub fn identity_map(&mut self, frame: Frame, flags: EntryFlags, allocator: &mut A) - where - A: FrameAllocator, - { - let page = Page::containing_address(frame.start_address()); - self.map_to(page, frame, flags, allocator) - } - - pub fn unmap(&mut self, page: Page, allocator: &mut A) - where - A: FrameAllocator, - { - use x86_64::VirtualAddress; - use x86_64::instructions::tlb; - - assert!(self.translate(page.start_address()).is_some()); - - let p1 = self.p4_mut() - .next_table_mut(page.p4_index()) - .and_then(|p3| p3.next_table_mut(page.p3_index())) - .and_then(|p2| p2.next_table_mut(page.p2_index())) - .expect("mapping code does not support huge pages"); - let frame = p1[page.p1_index()].pointed_frame().unwrap(); - p1[page.p1_index()].set_unused(); - tlb::flush(VirtualAddress(page.start_address())); - // TODO free p(1,2,3) table if empty - // allocator.deallocate_frame(frame); - } -} diff --git a/src/memory/paging/mod.rs b/src/memory/paging/mod.rs deleted file mode 100644 index 91358528..00000000 --- a/src/memory/paging/mod.rs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -pub use self::entry::*; -use memory::{PAGE_SIZE, Frame, FrameAllocator}; -use self::temporary_page::TemporaryPage; -pub use self::mapper::Mapper; -use core::ops::{Add, Deref, DerefMut}; -use multiboot2::BootInformation; - -mod entry; -mod table; -mod temporary_page; -mod mapper; - -const ENTRY_COUNT: usize = 512; - -pub type PhysicalAddress = usize; -pub type VirtualAddress = usize; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Page { - number: usize, -} - -impl Page { - pub fn containing_address(address: VirtualAddress) -> Page { - assert!( - address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000, - "invalid address: 0x{:x}", - address - ); - Page { number: address / PAGE_SIZE } - } - - pub fn start_address(&self) -> usize { - self.number * PAGE_SIZE - } - - fn p4_index(&self) -> usize { - (self.number >> 27) & 0o777 - } - fn p3_index(&self) -> usize { - (self.number >> 18) & 0o777 - } - fn p2_index(&self) -> usize { - (self.number >> 9) & 0o777 - } - fn p1_index(&self) -> usize { - (self.number >> 0) & 0o777 - } - - pub fn range_inclusive(start: Page, end: Page) -> PageIter { - PageIter { - start: start, - end: end, - } - } -} - -impl Add for Page { - type Output = Page; - - fn add(self, rhs: usize) -> Page { - Page { number: self.number + rhs } - } -} - -#[derive(Clone)] -pub struct PageIter { - start: Page, - end: Page, -} - -impl Iterator for PageIter { - type Item = Page; - - fn next(&mut self) -> Option { - if self.start <= self.end { - let page = self.start; - self.start.number += 1; - Some(page) - } else { - None - } - } -} - -pub struct ActivePageTable { - mapper: Mapper, -} - -impl Deref for ActivePageTable { - type Target = Mapper; - - fn deref(&self) -> &Mapper { - &self.mapper - } -} - -impl DerefMut for ActivePageTable { - fn deref_mut(&mut self) -> &mut Mapper { - &mut self.mapper - } -} - -impl ActivePageTable { - unsafe fn new() -> ActivePageTable { - ActivePageTable { mapper: Mapper::new() } - } - - pub fn with( - &mut self, - table: &mut InactivePageTable, - temporary_page: &mut temporary_page::TemporaryPage, // new - f: F, - ) where - F: FnOnce(&mut Mapper), - { - use x86_64::registers::control_regs; - use x86_64::instructions::tlb; - - { - let backup = Frame::containing_address(control_regs::cr3().0 as usize); - - // map temporary_page to current p4 table - let p4_table = temporary_page.map_table_frame(backup.clone(), self); - - // overwrite recursive mapping - self.p4_mut()[511].set(table.p4_frame.clone(), PRESENT | WRITABLE); - tlb::flush_all(); - - // execute f in the new context - f(self); - - // restore recursive mapping to original p4 table - p4_table[511].set(backup, PRESENT | WRITABLE); - tlb::flush_all(); - } - - temporary_page.unmap(self); - } - - pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable { - use x86_64::PhysicalAddress; - use x86_64::registers::control_regs; - - let old_table = InactivePageTable { - p4_frame: Frame::containing_address(control_regs::cr3().0 as usize), - }; - unsafe { - control_regs::cr3_write(PhysicalAddress(new_table.p4_frame.start_address() as u64)); - } - old_table - } -} - -pub struct InactivePageTable { - p4_frame: Frame, -} - -impl InactivePageTable { - pub fn new( - frame: Frame, - active_table: &mut ActivePageTable, - temporary_page: &mut TemporaryPage, - ) -> InactivePageTable { - { - let table = temporary_page.map_table_frame(frame.clone(), active_table); - table.zero(); - table[511].set(frame.clone(), PRESENT | WRITABLE); - } - temporary_page.unmap(active_table); - - InactivePageTable { p4_frame: frame } - } -} - -pub fn remap_the_kernel(allocator: &mut A, boot_info: &BootInformation) -> ActivePageTable -where - A: FrameAllocator, -{ - let mut temporary_page = TemporaryPage::new(Page { number: 0xcafebabe }, allocator); - - let mut active_table = unsafe { ActivePageTable::new() }; - let mut new_table = { - let frame = allocator.allocate_frame().expect("no more frames"); - InactivePageTable::new(frame, &mut active_table, &mut temporary_page) - }; - - active_table.with(&mut new_table, &mut temporary_page, |mapper| { - let elf_sections_tag = boot_info - .elf_sections_tag() - .expect("Memory map tag required"); - - // identity map the allocated kernel sections - for section in elf_sections_tag.sections() { - if !section.is_allocated() { - // section is not loaded to memory - continue; - } - - assert!( - section.addr as usize % PAGE_SIZE == 0, - "sections need to be page aligned" - ); - println!( - "mapping section at addr: {:#x}, size: {:#x}", - section.addr, - section.size - ); - - let flags = EntryFlags::from_elf_section_flags(section); - - let start_frame = Frame::containing_address(section.start_address()); - let end_frame = Frame::containing_address(section.end_address() - 1); - for frame in Frame::range_inclusive(start_frame, end_frame) { - mapper.identity_map(frame, flags, allocator); - } - } - - // identity map the VGA text buffer - let vga_buffer_frame = Frame::containing_address(0xb8000); - mapper.identity_map(vga_buffer_frame, WRITABLE, allocator); - - // identity map the multiboot info structure - let multiboot_start = Frame::containing_address(boot_info.start_address()); - let multiboot_end = Frame::containing_address(boot_info.end_address() - 1); - for frame in Frame::range_inclusive(multiboot_start, multiboot_end) { - mapper.identity_map(frame, PRESENT, allocator); - } - }); - - let old_table = active_table.switch(new_table); - println!("NEW TABLE!!!"); - - let old_p4_page = Page::containing_address(old_table.p4_frame.start_address()); - active_table.unmap(old_p4_page, allocator); - println!("guard page at {:#x}", old_p4_page.start_address()); - - active_table -} diff --git a/src/memory/paging/table.rs b/src/memory/paging/table.rs deleted file mode 100644 index f94afc42..00000000 --- a/src/memory/paging/table.rs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use memory::paging::entry::*; -use memory::paging::ENTRY_COUNT; -use memory::FrameAllocator; -use core::ops::{Index, IndexMut}; -use core::marker::PhantomData; - -pub const P4: *mut Table = 0xffffffff_fffff000 as *mut _; - -pub struct Table { - entries: [Entry; ENTRY_COUNT], - level: PhantomData, -} - -impl Table -where - L: TableLevel, -{ - pub fn zero(&mut self) { - for entry in self.entries.iter_mut() { - entry.set_unused(); - } - } -} - -/* - * Addresses are expected to be canonical (bits 48-63 must be the same as bit 47), otherwise the - * CPU will #GP when we ask it to translate it. - */ -fn make_address_canonical(address : usize) -> usize { - let sign_extension = 0o177777_000_000_000_000_0000 * ((address >> 47) & 0b1); - (address & ((1 << 48) - 1)) | sign_extension -} - -impl Table -where - L: HierarchicalLevel, -{ - fn next_table_address(&self, index: usize) -> Option { - let entry_flags = self[index].flags(); - if entry_flags.contains(PRESENT) && !entry_flags.contains(HUGE_PAGE) { - let table_address = self as *const _ as usize; - Some(make_address_canonical((table_address << 9) | (index << 12))) - } else { - None - } - } - - pub fn next_table(&self, index: usize) -> Option<&Table> { - self.next_table_address(index) - .map(|address| unsafe { &*(address as *const _) }) - } - - pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table> { - self.next_table_address(index) - .map(|address| unsafe { &mut *(address as *mut _) }) - } - - pub fn next_table_create( - &mut self, - index: usize, - allocator: &mut A, - ) -> &mut Table - where - A: FrameAllocator, - { - if self.next_table(index).is_none() { - assert!( - !self.entries[index].flags().contains(HUGE_PAGE), - "mapping code does not support huge pages" - ); - let frame = allocator.allocate_frame().expect("no frames available"); - self.entries[index].set(frame, PRESENT | WRITABLE); - self.next_table_mut(index).unwrap().zero(); - } - self.next_table_mut(index).unwrap() - } -} - -impl Index for Table -where - L: TableLevel, -{ - type Output = Entry; - - fn index(&self, index: usize) -> &Entry { - &self.entries[index] - } -} - -impl IndexMut for Table -where - L: TableLevel, -{ - fn index_mut(&mut self, index: usize) -> &mut Entry { - &mut self.entries[index] - } -} - -pub trait TableLevel {} - -pub enum Level4 {} -#[allow(dead_code)] -pub enum Level3 {} -#[allow(dead_code)] -pub enum Level2 {} -pub enum Level1 {} - -impl TableLevel for Level4 {} -impl TableLevel for Level3 {} -impl TableLevel for Level2 {} -impl TableLevel for Level1 {} - -pub trait HierarchicalLevel: TableLevel { - type NextLevel: TableLevel; -} - -impl HierarchicalLevel for Level4 { - type NextLevel = Level3; -} - -impl HierarchicalLevel for Level3 { - type NextLevel = Level2; -} - -impl HierarchicalLevel for Level2 { - type NextLevel = Level1; -} diff --git a/src/memory/paging/temporary_page.rs b/src/memory/paging/temporary_page.rs deleted file mode 100644 index e404b183..00000000 --- a/src/memory/paging/temporary_page.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::{Page, ActivePageTable, VirtualAddress}; -use super::table::{Table, Level1}; -use memory::{Frame, FrameAllocator}; - -pub struct TemporaryPage { - page: Page, - allocator: TinyAllocator, -} - -impl TemporaryPage { - pub fn new(page: Page, allocator: &mut A) -> TemporaryPage - where - A: FrameAllocator, - { - TemporaryPage { - page: page, - allocator: TinyAllocator::new(allocator), - } - } - - /// Maps the temporary page to the given frame in the active table. - /// Returns the start address of the temporary page. - pub fn map(&mut self, frame: Frame, active_table: &mut ActivePageTable) -> VirtualAddress { - use super::entry::WRITABLE; - - assert!( - active_table.translate_page(self.page).is_none(), - "temporary page is already mapped" - ); - active_table.map_to(self.page, frame, WRITABLE, &mut self.allocator); - self.page.start_address() - } - - /// Maps the temporary page to the given page table frame in the active table. - /// Returns a reference to the now mapped table. - pub fn map_table_frame( - &mut self, - frame: Frame, - active_table: &mut ActivePageTable, - ) -> &mut Table { - unsafe { &mut *(self.map(frame, active_table) as *mut Table) } - } - - /// Unmaps the temporary page in the active table. - pub fn unmap(&mut self, active_table: &mut ActivePageTable) { - active_table.unmap(self.page, &mut self.allocator) - } -} - -struct TinyAllocator([Option; 3]); - -impl TinyAllocator { - fn new(allocator: &mut A) -> TinyAllocator - where - A: FrameAllocator, - { - let mut f = || allocator.allocate_frame(); - let frames = [f(), f(), f()]; - TinyAllocator(frames) - } -} - -impl FrameAllocator for TinyAllocator { - fn allocate_frame(&mut self) -> Option { - for frame_option in &mut self.0 { - if frame_option.is_some() { - return frame_option.take(); - } - } - None - } - - fn deallocate_frame(&mut self, frame: Frame) { - for frame_option in &mut self.0 { - if frame_option.is_none() { - *frame_option = Some(frame); - return; - } - } - panic!("Tiny allocator can hold only 3 frames."); - } -} diff --git a/src/memory/stack_allocator.rs b/src/memory/stack_allocator.rs deleted file mode 100644 index e69fcabf..00000000 --- a/src/memory/stack_allocator.rs +++ /dev/null @@ -1,81 +0,0 @@ -use memory::paging::{self, Page, PageIter, ActivePageTable}; -use memory::{PAGE_SIZE, FrameAllocator}; - -pub struct StackAllocator { - range: PageIter, -} - -impl StackAllocator { - pub fn new(page_range: PageIter) -> StackAllocator { - StackAllocator { range: page_range } - } -} - -impl StackAllocator { - pub fn alloc_stack( - &mut self, - active_table: &mut ActivePageTable, - frame_allocator: &mut FA, - size_in_pages: usize, - ) -> Option { - if size_in_pages == 0 { - return None; /* a zero sized stack makes no sense */ - } - - // clone the range, since we only want to change it on success - let mut range = self.range.clone(); - - // try to allocate the stack pages and a guard page - let guard_page = range.next(); - let stack_start = range.next(); - let stack_end = if size_in_pages == 1 { - stack_start - } else { - // choose the (size_in_pages-2)th element, since index - // starts at 0 and we already allocated the start page - range.nth(size_in_pages - 2) - }; - - match (guard_page, stack_start, stack_end) { - (Some(_), Some(start), Some(end)) => { - // success! write back updated range - self.range = range; - - // map stack pages to physical frames - for page in Page::range_inclusive(start, end) { - active_table.map(page, paging::WRITABLE, frame_allocator); - } - - // create a new stack - let top_of_stack = end.start_address() + PAGE_SIZE; - Some(Stack::new(top_of_stack, start.start_address())) - } - _ => None, /* not enough pages */ - } - } -} - -#[derive(Debug)] -pub struct Stack { - top: usize, - bottom: usize, -} - -impl Stack { - fn new(top: usize, bottom: usize) -> Stack { - assert!(top > bottom); - Stack { - top: top, - bottom: bottom, - } - } - - pub fn top(&self) -> usize { - self.top - } - - #[allow(dead_code)] - pub fn bottom(&self) -> usize { - self.bottom - } -} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs deleted file mode 100644 index f3374940..00000000 --- a/src/vga_buffer.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2016 Philipp Oppermann. See the README.md -// file at the top-level directory of this distribution. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use core::ptr::Unique; -use core::fmt; -use spin::Mutex; -use volatile::Volatile; - -const BUFFER_HEIGHT: usize = 25; -const BUFFER_WIDTH: usize = 80; - -pub static WRITER: Mutex = Mutex::new(Writer { - column_position: 0, - color_code: ColorCode::new(Color::LightGreen, Color::Black), - buffer: unsafe { Unique::new_unchecked(0xb8000 as *mut _) }, -}); - -macro_rules! println { - ($fmt:expr) => (print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); -} - -macro_rules! print { - ($($arg:tt)*) => ({ - $crate::vga_buffer::print(format_args!($($arg)*)); - }); -} - -pub fn print(args: fmt::Arguments) { - use core::fmt::Write; - WRITER.lock().write_fmt(args).unwrap(); -} - -pub fn clear_screen() { - for _ in 0..BUFFER_HEIGHT { - println!(""); - } -} - -#[allow(dead_code)] -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum Color { - Black = 0, - Blue = 1, - Green = 2, - Cyan = 3, - Red = 4, - Magenta = 5, - Brown = 6, - LightGray = 7, - DarkGray = 8, - LightBlue = 9, - LightGreen = 10, - LightCyan = 11, - LightRed = 12, - Pink = 13, - Yellow = 14, - White = 15, -} - -pub struct Writer { - column_position: usize, - color_code: ColorCode, - buffer: Unique, -} - -impl Writer { - pub fn write_byte(&mut self, byte: u8) { - match byte { - b'\n' => self.new_line(), - byte => { - if self.column_position >= BUFFER_WIDTH { - self.new_line(); - } - let row = BUFFER_HEIGHT - 1; - let col = self.column_position; - - let color_code = self.color_code; - - self.buffer().chars[row][col].write(ScreenChar { - ascii_character: byte, - color_code: color_code, - }); - self.column_position += 1; - } - } - } - - fn buffer(&mut self) -> &mut Buffer { - unsafe { self.buffer.as_mut() } - } - - fn new_line(&mut self) { - for row in 1..BUFFER_HEIGHT { - for col in 0..BUFFER_WIDTH { - let buffer = self.buffer(); - let character = buffer.chars[row][col].read(); - buffer.chars[row - 1][col].write(character); - } - } - self.clear_row(BUFFER_HEIGHT - 1); - self.column_position = 0; - } - - fn clear_row(&mut self, row: usize) { - let blank = ScreenChar { - ascii_character: b' ', - color_code: self.color_code, - }; - for col in 0..BUFFER_WIDTH { - self.buffer().chars[row][col].write(blank); - } - } -} - -impl fmt::Write for Writer { - fn write_str(&mut self, s: &str) -> ::core::fmt::Result { - for byte in s.bytes() { - self.write_byte(byte) - } - Ok(()) - } -} - -#[derive(Debug, Clone, Copy)] -struct ColorCode(u8); - -impl ColorCode { - const fn new(foreground: Color, background: Color) -> ColorCode { - ColorCode((background as u8) << 4 | (foreground as u8)) - } -} - -#[derive(Debug, Clone, Copy)] -#[repr(C)] -struct ScreenChar { - ascii_character: u8, - color_code: ColorCode, -} - -struct Buffer { - chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], -} diff --git a/x86_64-blog_os.json b/x86_64-blog_os.json index 909d3fe5..26cc0642 100644 --- a/x86_64-blog_os.json +++ b/x86_64-blog_os.json @@ -1,12 +1,14 @@ { "llvm-target": "x86_64-unknown-linux-gnu", "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "linker-flavor": "gcc", + "arch": "x86_64", "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", - "arch": "x86_64", "os": "none", + "linker-flavor": "ld", + "linker": "ld.lld", + "executables": true, "features": "-mmx,-sse,+soft-float", "disable-redzone": true, "panic": "abort"