From bc5631d9a8c6aa600919ace475420a7240febcdc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 7 Jul 2019 10:33:12 +0200 Subject: [PATCH] Fix lot of dead links in both the 2nd and 1st edition --- .../01-catching-exceptions/index.md | 8 +-- .../02-better-exception-messages/index.md | 6 +- .../03-returning-from-exceptions/index.md | 4 +- .../posts/01-multiboot-kernel/index.md | 4 +- .../posts/02-entering-longmode/index.md | 4 +- .../posts/03-set-up-rust/index.md | 8 +-- .../posts/04-printing-to-screen/index.md | 4 +- .../posts/06-page-tables/index.md | 4 +- .../posts/07-remap-the-kernel/index.md | 10 ++-- .../posts/08-kernel-heap/index.md | 26 ++++----- .../posts/09-handling-exceptions/index.md | 4 +- .../posts/10-double-faults/index.md | 8 +-- .../posts/03-vga-text-buffer/index.md | 6 +- .../posts/05-cpu-exceptions/index.md | 2 +- .../posts/06-double-faults/index.md | 2 +- .../posts/08-paging-introduction/index.md | 2 +- .../posts/09-paging-implementation/index.md | 10 ++-- .../posts/10-heap-allocation/index.md | 14 ++--- blog/content/status-update/2019-05-01.md | 2 +- .../comments/allocating-frames.html | 16 +++--- .../comments/catching-exceptions.html | 4 +- .../first-edition/comments/double-faults.html | 17 +++--- .../comments/entering-longmode.html | 18 +++--- .../comments/handling-exceptions.html | 2 +- .../first-edition/comments/kernel-heap.html | 8 +-- .../comments/multiboot-kernel.html | 56 +++++++++---------- .../first-edition/comments/page-tables.html | 10 ++-- .../comments/printing-to-screen.html | 18 +++--- .../comments/remap-the-kernel.html | 14 ++--- .../first-edition/comments/set-up-rust.html | 8 +-- 30 files changed, 146 insertions(+), 153 deletions(-) diff --git a/blog/content/first-edition/extra/naked-exceptions/01-catching-exceptions/index.md b/blog/content/first-edition/extra/naked-exceptions/01-catching-exceptions/index.md index b45682b9..89b6995f 100644 --- a/blog/content/first-edition/extra/naked-exceptions/01-catching-exceptions/index.md +++ b/blog/content/first-edition/extra/naked-exceptions/01-catching-exceptions/index.md @@ -377,7 +377,7 @@ We can't know when the next IDT will be loaded. Maybe never. So in the worst cas This is exactly the definition of a [static lifetime]. So we can easily ensure that the IDT lives long enough by adding a `'static` requirement to the signature of the `load` function: -[static lifetime]: http://rustbyexample.com/scope/lifetime/static_lifetime.html +[static lifetime]: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html ```rust pub fn load(&'static self) {...} @@ -517,9 +517,9 @@ That's a not our exception handler. The reason is that Rust itself checks for a ### Inline Assembly In order to cause a divide-by-zero exception, we need to execute a [div] or [idiv] assembly instruction with operand 0. We could write a small assembly function and call it from our Rust code. An easier way is to use Rust's [inline assembly] macro. -[div]: http://x86.renejeschke.de/html/file_module_x86_id_72.html -[idiv]: http://x86.renejeschke.de/html/file_module_x86_id_137.html -[inline assembly]: https://doc.rust-lang.org/book/inline-assembly.html +[div]: https://www.felixcloutier.com/x86/div +[idiv]: https://www.felixcloutier.com/x86/idiv +[inline assembly]: https://doc.rust-lang.org/1.10.0/book/inline-assembly.html Inline assembly allows us to write raw x86 assembly within a Rust function. The feature is unstable, so we need to add `#![feature(asm)]` to our `src/lib.rs`. Then we're able to write a `divide_by_zero` function: diff --git a/blog/content/first-edition/extra/naked-exceptions/02-better-exception-messages/index.md b/blog/content/first-edition/extra/naked-exceptions/02-better-exception-messages/index.md index 08cb3d74..6c941a43 100644 --- a/blog/content/first-edition/extra/naked-exceptions/02-better-exception-messages/index.md +++ b/blog/content/first-edition/extra/naked-exceptions/02-better-exception-messages/index.md @@ -75,7 +75,7 @@ extern "C" fn divide_by_zero_handler() -> ! { ``` We're using [inline assembly] here to load the value from the `rsp` register into `stack_frame`. The syntax is a bit strange, so here's a quick explanation: -[inline assembly]: https://doc.rust-lang.org/nightly/book/inline-assembly.html +[inline assembly]: https://doc.rust-lang.org/1.10.0/book/inline-assembly.html - The `asm!` macro emits raw assembly instructions. This is the only way to read raw register values in Rust. - We insert a single assembly instruction: `mov $0, rsp`. It moves the value of `rsp` to some register (the `$0` is a placeholder for an arbitrary register, which gets filled by the compiler). @@ -339,7 +339,7 @@ objdump -d build/kernel-x86_64.bin | grep "10cf08:" ``` The [movaps] instruction is an [SSE] instruction that moves aligned 128bit values. It can fail for a number of reasons: -[movaps]: http://x86.renejeschke.de/html/file_module_x86_id_180.html +[movaps]: https://www.felixcloutier.com/x86/movaps [SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions 1. For an illegal memory operand effective address in the CS, DS, ES, FS or GS segments. @@ -497,7 +497,7 @@ Invalid opcode faults have the vector number 6, so we set the 6th IDT entry. Thi We can test our new handler with the special [ud2] instruction, which generates a invalid opcode: -[ud2]: http://x86.renejeschke.de/html/file_module_x86_id_318.html +[ud2]: https://www.felixcloutier.com/x86/ud ```rust // in src/lib.rs diff --git a/blog/content/first-edition/extra/naked-exceptions/03-returning-from-exceptions/index.md b/blog/content/first-edition/extra/naked-exceptions/03-returning-from-exceptions/index.md index cddd5e41..8bdf8566 100644 --- a/blog/content/first-edition/extra/naked-exceptions/03-returning-from-exceptions/index.md +++ b/blog/content/first-edition/extra/naked-exceptions/03-returning-from-exceptions/index.md @@ -459,7 +459,7 @@ In order to fix this problem, we need to backup all caller-saved multimedia regi The Rust compiler (and LLVM) assume that the `x86_64-unknown-linux-gnu` target supports only MMX and SSE, so we don't need to save the `ymm0` through `ymm15`. But we need to save `xmm0` through `xmm15` and also `mm0` through `mm7`. There is a special instruction to do this: [fxsave]. This instruction saves the floating point and multimedia state to a given address. It needs _512 bytes_ to store that state. -[fxsave]: http://x86.renejeschke.de/html/file_module_x86_id_128.html +[fxsave]: https://www.felixcloutier.com/x86/fxsave In order to save/restore the multimedia registers, we _could_ add new macros: @@ -482,7 +482,7 @@ macro_rules! restore_multimedia_registers { ``` First, we reserve the 512 bytes on the stack and then we use `fxsave` to backup the multimedia registers. In order to restore them later, we use the [fxrstor] instruction. Note that `fxsave` and `fxrstor` require a 16 byte aligned memory address. -[fxrstor]: http://x86.renejeschke.de/html/file_module_x86_id_127.html +[fxrstor]: https://www.felixcloutier.com/x86/fxrstor However, _we won't do it that way_. The problem is the large amount of memory required. We will reuse the same code when we handle hardware interrupts in a future post. So for each mouse click, pressed key, or arrived network package we need to write 512 bytes to memory. This would be a huge performance problem. diff --git a/blog/content/first-edition/posts/01-multiboot-kernel/index.md b/blog/content/first-edition/posts/01-multiboot-kernel/index.md index 87c4af59..f12a8be6 100644 --- a/blog/content/first-edition/posts/01-multiboot-kernel/index.md +++ b/blog/content/first-edition/posts/01-multiboot-kernel/index.md @@ -247,9 +247,7 @@ You can test it on real hardware, too. Just burn the ISO to a disk or USB stick ## Build Automation -Right now we need to execute 4 commands in the right order every time we change a file. That's bad. So let's automate the build using a [Makefile][Makefile tutorial]. But first we should create some clean directory structure for our source files to separate the architecture specific files: - -[Makefile tutorial]: http://mrbook.org/blog/tutorials/make/ +Right now we need to execute 4 commands in the right order every time we change a file. That's bad. So let's automate the build using a `Makefile`. But first we should create some clean directory structure for our source files to separate the architecture specific files: ``` … diff --git a/blog/content/first-edition/posts/02-entering-longmode/index.md b/blog/content/first-edition/posts/02-entering-longmode/index.md index c5c22f6d..7824b33b 100644 --- a/blog/content/first-edition/posts/02-entering-longmode/index.md +++ b/blog/content/first-edition/posts/02-entering-longmode/index.md @@ -410,9 +410,7 @@ gdt64: dq 0 ; zero entry dq (1<<43) | (1<<44) | (1<<47) | (1<<53) ; code segment ``` -We chose the `.rodata` section here because it's initialized read-only data. The `dq` command stands for `define quad` and outputs a 64-bit constant (similar to `dw` and `dd`). And the `(1<<43)` is a [bit shift] that sets bit 43. - -[bit shift]: http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/bitshift.html +We chose the `.rodata` section here because it's initialized read-only data. The `dq` command stands for `define quad` and outputs a 64-bit constant (similar to `dw` and `dd`). And the `(1<<43)` is a bit shift that sets bit 43. ### Loading the GDT To load our new 64-bit GDT, we have to tell the CPU its address and length. We do this by passing the memory location of a special pointer structure to the `lgdt` (load GDT) instruction. The pointer structure looks like this: diff --git a/blog/content/first-edition/posts/03-set-up-rust/index.md b/blog/content/first-edition/posts/03-set-up-rust/index.md index 01de1d01..53c4a7dd 100644 --- a/blog/content/first-edition/posts/03-set-up-rust/index.md +++ b/blog/content/first-edition/posts/03-set-up-rust/index.md @@ -78,7 +78,7 @@ Let's break it down: [attribute]: https://doc.rust-lang.org/book/attributes.html [name mangling]: https://en.wikipedia.org/wiki/Name_mangling [calling convention]: https://en.wikipedia.org/wiki/Calling_convention -[language item]: https://doc.rust-lang.org/book/lang-items.html +[language item]: https://doc.rust-lang.org/1.10.0/book/lang-items.html [unwinding]: https://doc.rust-lang.org/nomicon/unwinding.html ## Building Rust @@ -265,7 +265,7 @@ We add a new `kernel` target that just executes `xargo build` and modify the `$( But now `xargo build` is executed on every `make`, even if no source file was changed. And the ISO is recreated on every `make iso`/`make run`, too. We could try to avoid this by adding dependencies on all rust source and cargo configuration files to the `kernel` target, but the ISO creation takes only half a second on my machine and most of the time we will have changed a Rust file when we run `make`. So we keep it simple for now and let cargo do the bookkeeping of changed files (it does it anyway). -[github makefile]: https://github.com/phil-opp/blog_os/blob/post_3/Makefile +[github makefile]: https://github.com/phil-opp/blog_os/blob/first_edition_post_3/Makefile ### Calling Rust Now we can call the main method in `long_mode_start`: @@ -299,7 +299,7 @@ pub extern fn rust_main() { ``` When we test it using `make run`, it fails with `undefined reference to 'memcpy'`. The `memcpy` function is one of the basic functions of the C library (`libc`). Usually the `libc` crate is linked to every Rust program together with the standard library, but we opted out through `#![no_std]`. We could try to fix this by adding the [libc crate] as `extern crate`. But `libc` is just a wrapper for the system `libc`, for example `glibc` on Linux, so this won't work for us. Instead we need to recreate the basic `libc` functions such as `memcpy`, `memmove`, `memset`, and `memcmp` in Rust. -[libc crate]: https://doc.rust-lang.org/nightly/libc/index.html +[libc crate]: https://doc.rust-lang.org/1.10.0/libc/index.html #### rlibc Fortunately there already is a crate for that: [rlibc]. When we look at its [source code][rlibc source] we see that it contains no magic, just some [raw pointer] operations in a while loop. To add `rlibc` as a dependency we just need to add two lines to the `Cargo.toml`: @@ -343,7 +343,7 @@ target/x86_64-blog_os/debug/libblog_os.a(core-92335f822fa6c9a6.0.o): ``` [rlibc]: https://crates.io/crates/rlibc -[rlibc source]: https://github.com/rust-lang/rlibc/blob/master/src/lib.rs +[rlibc source]: https://github.com/alexcrichton/rlibc/blob/defb486e765846417a8e73329e8c5196f1dca49a/src/lib.rs [raw pointer]: https://doc.rust-lang.org/book/raw-pointers.html [crates.io]: https://crates.io diff --git a/blog/content/first-edition/posts/04-printing-to-screen/index.md b/blog/content/first-edition/posts/04-printing-to-screen/index.md index 09e78c04..94b83a2e 100644 --- a/blog/content/first-edition/posts/04-printing-to-screen/index.md +++ b/blog/content/first-edition/posts/04-printing-to-screen/index.md @@ -93,9 +93,7 @@ We use a [C-like enum] here to explicitly specify the number for each color. Bec Normally the compiler would issue a warning for each unused variant. By using the `#[allow(dead_code)]` attribute we disable these warnings for the `Color` enum. -To represent a full color code that specifies foreground and background color, we create a [newtype] on top of `u8`: - -[newtype]: https://aturon.github.io/features/types/newtype.html +To represent a full color code that specifies foreground and background color, we create a newtype on top of `u8`: ```rust struct ColorCode(u8); diff --git a/blog/content/first-edition/posts/06-page-tables/index.md b/blog/content/first-edition/posts/06-page-tables/index.md index 2757a4ac..e8e763ba 100644 --- a/blog/content/first-edition/posts/06-page-tables/index.md +++ b/blog/content/first-edition/posts/06-page-tables/index.md @@ -130,7 +130,7 @@ bitflags! { ``` To extract the flags from the entry we create an `Entry::flags` method that uses [from_bits_truncate]: -[from_bits_truncate]: https://doc.rust-lang.org/bitflags/bitflags/index.html#methods-1 +[from_bits_truncate]: https://docs.rs/bitflags/0.9.1/bitflags/example_generated/struct.Flags.html#method.from_bits_truncate ```rust pub fn flags(&self) -> EntryFlags { @@ -653,7 +653,7 @@ pub struct ActivePageTable { We can't store the `Table` directly because it needs to be at a special memory location (like the [VGA text buffer]). We could use a raw pointer or `&mut` instead of [Unique], but Unique indicates ownership better. [VGA text buffer]: ./first-edition/posts/04-printing-to-screen/index.md#the-text-buffer -[Unique]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html +[Unique]: https://doc.rust-lang.org/1.10.0/core/ptr/struct.Unique.html Because the `ActivePageTable` owns the unique recursive mapped P4 table, there must be only one `ActivePageTable` instance. Thus we make the constructor function unsafe: diff --git a/blog/content/first-edition/posts/07-remap-the-kernel/index.md b/blog/content/first-edition/posts/07-remap-the-kernel/index.md index 1968072c..b72c00a4 100644 --- a/blog/content/first-edition/posts/07-remap-the-kernel/index.md +++ b/blog/content/first-edition/posts/07-remap-the-kernel/index.md @@ -328,7 +328,7 @@ impl InactivePageTable { ``` We added two new arguments, `active_table` and `temporary_page`. We need an [inner scope] to ensure that the `table` variable is dropped before we try to unmap the temporary page again. This is required since the `table` variable exclusively borrows `temporary_page` as long as it's alive. -[inner scope]: http://rustbyexample.com/variable_bindings/scope.html +[inner scope]: https://doc.rust-lang.org/rust-by-example/variable_bindings/scope.html Now we are able to create valid inactive page tables, which are zeroed and recursively mapped. But we still can't modify them. To resolve this problem, we need to look at recursive mapping again. @@ -622,7 +622,7 @@ impl Iterator for FrameIter { Instead of creating a custom iterator, we could have used the [Range] struct of the standard library. But it requires that we implement the [One] and [Add] traits for `Frame`. Then every module could perform arithmetic operations on frames, for example `let frame3 = frame1 + frame2`. This would violate our safety invariants because `frame3` could be already in use. The `range_inclusive` function does not have these problems because it is only available inside the `memory` module. [Range]: https://doc.rust-lang.org/nightly/core/ops/struct.Range.html -[One]: https://doc.rust-lang.org/nightly/core/num/trait.One.html +[One]: https://doc.rust-lang.org/1.10.0/core/num/trait.One.html [Add]: https://doc.rust-lang.org/nightly/core/ops/trait.Add.html ### Page Align Sections @@ -784,7 +784,7 @@ pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable { ``` This function activates the given inactive table and returns the previous active table as a `InactivePageTable`. We don't need to flush the TLB here, as the CPU does it automatically when the P4 table is switched. In fact, the `tlb::flush_all` function, which we used above, does nothing more than [reloading the CR3 register]. -[reloading the CR3 register]: https://github.com/gz/rust-x86/blob/master/src/shared/tlb.rs#L19 +[reloading the CR3 register]: https://docs.rs/x86_64/0.1.2/src/x86_64/instructions/tlb.rs.html#11-14 Now we are finally able to switch to the new table. We do it by adding the following lines to our `remap_the_kernel` function: @@ -1093,8 +1093,8 @@ Now that we have a (mostly) safe kernel stack and a working page table module, w [next post]: ./first-edition/posts/08-kernel-heap/index.md [Box]: https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html -[Vec]: https://doc.rust-lang.org/nightly/collections/vec/struct.Vec.html -[BTreeMap]: https://doc.rust-lang.org/nightly/collections/btree_map/struct.BTreeMap.html +[Vec]: https://doc.rust-lang.org/1.10.0/collections/vec/struct.Vec.html +[BTreeMap]: https://doc.rust-lang.org/1.10.0/collections/btree_map/struct.BTreeMap.html ## Footnotes [^fn-debug-notes]: For this post the most useful GDB command is probably `p/x *((long int*)0xfffffffffffff000)@512`. It prints all entries of the recursively mapped P4 table by interpreting it as an array of 512 long ints (the `@512` is GDB's array syntax). Of course you can also print other tables by adjusting the address. diff --git a/blog/content/first-edition/posts/08-kernel-heap/index.md b/blog/content/first-edition/posts/08-kernel-heap/index.md index 62583415..a0b333ac 100644 --- a/blog/content/first-edition/posts/08-kernel-heap/index.md +++ b/blog/content/first-edition/posts/08-kernel-heap/index.md @@ -24,9 +24,9 @@ As always, you can find the complete source code on [GitHub]. Please file [issue ## Introduction The _heap_ is the memory area for long-lived allocations. The programmer can access it by using types like [Box][Box rustbyexample] or [Vec]. Behind the scenes, the compiler manages that memory by inserting calls to some memory allocator. By default, Rust links to the [jemalloc] allocator (for binaries) or the system allocator (for libraries). However, both rely on [system calls] such as [sbrk] and are thus unusable in our kernel. So we need to create and link our own allocator. -[Box rustbyexample]: http://rustbyexample.com/std/box.html +[Box rustbyexample]: https://doc.rust-lang.org/rust-by-example/std/box.html [Vec]: https://doc.rust-lang.org/book/vectors.html -[jemalloc]: http://www.canonware.com/jemalloc/ +[jemalloc]: http://jemalloc.net/ [system calls]: https://en.wikipedia.org/wiki/System_call [sbrk]: https://en.wikipedia.org/wiki/Sbrk @@ -42,7 +42,7 @@ These requirements make good allocators pretty complex. For example, [jemalloc] The allocator interface in Rust is defined through the [`Alloc` trait], which looks like this: -[`Alloc` trait]: https://doc.rust-lang.org/nightly/alloc/allocator/trait.Alloc.html +[`Alloc` trait]: https://doc.rust-lang.org/1.20.0/alloc/allocator/trait.Alloc.html ```rust pub unsafe trait Alloc { @@ -87,8 +87,8 @@ extern crate alloc; We don't need to add anything to our Cargo.toml, since the `alloc` crate is part of the standard library and shipped with the Rust compiler. The `alloc` crate provides the [format!] and [vec!] macros, so we use `#[macro_use]` to import them. -[format!]: //doc.rust-lang.org/nightly/collections/macro.format!.html -[vec!]: https://doc.rust-lang.org/nightly/collections/macro.vec!.html +[format!]: https://doc.rust-lang.org/1.10.0/collections/macro.format!.html +[vec!]: https://doc.rust-lang.org/1.10.0/collections/macro.vec!.html When we try to compile our crate now, the following error occurs: @@ -573,14 +573,14 @@ We can also use all other types of the `alloc` crate, including: - [BinaryHeap] - [BTreeMap] and [BTreeSet] -[Rc]: https://doc.rust-lang.org/nightly/alloc/rc/ -[Arc]: https://doc.rust-lang.org/nightly/alloc/arc/ -[String]: https://doc.rust-lang.org/nightly/collections/string/struct.String.html -[Linked List]: https://doc.rust-lang.org/nightly/collections/linked_list/struct.LinkedList.html -[VecDeque]: https://doc.rust-lang.org/nightly/collections/vec_deque/struct.VecDeque.html -[BinaryHeap]: https://doc.rust-lang.org/nightly/collections/binary_heap/struct.BinaryHeap.html -[BTreeMap]: https://doc.rust-lang.org/nightly/collections/btree_map/struct.BTreeMap.html -[BTreeSet]: https://doc.rust-lang.org/nightly/collections/btree_set/struct.BTreeSet.html +[Rc]: https://doc.rust-lang.org/1.10.0/alloc/rc/ +[Arc]: https://doc.rust-lang.org/1.10.0/alloc/arc/ +[String]: https://doc.rust-lang.org/1.10.0/collections/string/struct.String.html +[Linked List]: https://doc.rust-lang.org/1.10.0/collections/linked_list/struct.LinkedList.html +[VecDeque]: https://doc.rust-lang.org/1.10.0/collections/vec_deque/struct.VecDeque.html +[BinaryHeap]: https://doc.rust-lang.org/1.10.0/collections/binary_heap/struct.BinaryHeap.html +[BTreeMap]: https://doc.rust-lang.org/1.10.0/collections/btree_map/struct.BTreeMap.html +[BTreeSet]: https://doc.rust-lang.org/1.10.0/collections/btree_set/struct.BTreeSet.html ## A better Allocator Right now, we leak every freed memory block. Thus, we run out of memory quickly, for example, by creating a new `String` in each iteration of a loop: diff --git a/blog/content/first-edition/posts/09-handling-exceptions/index.md b/blog/content/first-edition/posts/09-handling-exceptions/index.md index 4cee4bf9..f22447ad 100644 --- a/blog/content/first-edition/posts/09-handling-exceptions/index.md +++ b/blog/content/first-edition/posts/09-handling-exceptions/index.md @@ -128,7 +128,7 @@ However, there is a major difference between exceptions and function calls: A fu [Calling conventions] specify the details of a function call. For example, they specify where function parameters are placed (e.g. in registers or on the stack) and how results are returned. On x86_64 Linux, the following rules apply for C functions (specified in the [System V ABI]): [Calling conventions]: https://en.wikipedia.org/wiki/Calling_convention -[System V ABI]: http://refspecs.linuxbase.org/elf/x86-64-abi-0.99.pdf +[System V ABI]: http://refspecs.linuxbase.org/elf/gabi41.pdf - the first six integer arguments are passed in registers `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9` - additional arguments are passed on the stack @@ -449,7 +449,7 @@ The reason for the diffent instruction pointer values is that the stored value i In some cases, the distinction between faults and traps is vague. For example, the [debug exception] behaves like a fault in some cases, but like a trap in others. So to find out the meaning of the saved instruction pointer, it is a good idea to read the official documentation for the exception, which can be found in the [AMD64 manual] in Section 8.2. For example, for the breakpoint exception it says: [debug exception]: http://wiki.osdev.org/Exceptions#Debug -[AMD64 manual]: http://developer.amd.com/wordpress/media/2012/10/24593_APM_v21.pdf +[AMD64 manual]: https://www.amd.com/system/files/TechDocs/24593.pdf > `#BP` is a trap-type exception. The saved instruction pointer points to the byte after the `INT3` instruction. diff --git a/blog/content/first-edition/posts/10-double-faults/index.md b/blog/content/first-edition/posts/10-double-faults/index.md index 793ab8f8..946b9579 100644 --- a/blog/content/first-edition/posts/10-double-faults/index.md +++ b/blog/content/first-edition/posts/10-double-faults/index.md @@ -356,7 +356,7 @@ impl MemoryController { ``` The `MemoryController` struct holds the three types that are required for `alloc_stack` and provides a simpler interface (only one argument). The `alloc_stack` wrapper just takes the tree types as `&mut` through [destructuring] and forwards them to the `stack_allocator`. The [ref mut]-s are needed to take the inner fields by mutable reference. Note that we're re-exporting the `Stack` type since it is returned by `alloc_stack`. -[destructuring]: http://rust-lang.github.io/book/ch18-00-patterns.html#Destructuring +[destructuring]: https://doc.rust-lang.org/1.10.0/book/patterns.html#destructuring [ref mut]: http://rust-lang.github.io/book/ch18-00-patterns.html#ref-and-ref-mut The last step is to create a `StackAllocator` and return a `MemoryController` from `memory::init`: @@ -554,7 +554,7 @@ pub enum Descriptor { The flag bits are common between all descriptor types, so we create a general `DescriptorFlags` type (using the [bitflags] macro): -[bitflags]: https://doc.rust-lang.org/bitflags/bitflags/macro.bitflags.html +[bitflags]: https://docs.rs/bitflags/0.9.1/bitflags/macro.bitflags.html ```rust // in src/interrupts/gdt.rs @@ -882,8 +882,8 @@ pub fn init(memory_controller: &mut MemoryController) { We first set the descriptors to `empty` and then update them from inside the closure (which implicitly borrows them as `&mut`). Now we're able to reload the code segment register using [`set_cs`] and to load the TSS using [`load_tss`]. -[`set_cs`]: https://docs.rs/x86/0.8.0/x86/shared/segmentation/fn.set_cs.html -[`load_tss`]: https://docs.rs/x86/0.8.0/x86/shared/task/fn.load_tss.html +[`set_cs`]: https://docs.rs/x86_64/0.1.2/x86_64/instructions/segmentation/fn.set_cs.html +[`load_tss`]: https://docs.rs/x86_64/0.1.2/x86_64/instructions/tables/fn.load_tss.html Now that we loaded a valid TSS and interrupt stack table, we can set the stack index for our double fault handler in the IDT: diff --git a/blog/content/second-edition/posts/03-vga-text-buffer/index.md b/blog/content/second-edition/posts/03-vga-text-buffer/index.md index 18625a21..0d894099 100644 --- a/blog/content/second-edition/posts/03-vga-text-buffer/index.md +++ b/blog/content/second-edition/posts/03-vga-text-buffer/index.md @@ -99,13 +99,13 @@ pub enum Color { ``` We use a [C-like enum] here to explicitly specify the number for each color. Because of the `repr(u8)` attribute each enum variant is stored as an `u8`. Actually 4 bits would be sufficient, but Rust doesn't have an `u4` type. -[C-like enum]: http://rustbyexample.com/custom_types/enum/c_like.html +[C-like enum]: https://doc.rust-lang.org/rust-by-example/custom_types/enum/c_like.html Normally the compiler would issue a warning for each unused variant. By using the `#[allow(dead_code)]` attribute we disable these warnings for the `Color` enum. By [deriving] the [`Copy`], [`Clone`], [`Debug`], [`PartialEq`], and [`Eq`] traits, we enable [copy semantics] for the type and make it printable and comparable. -[deriving]: http://rustbyexample.com/trait/derive.html +[deriving]: https://doc.rust-lang.org/rust-by-example/trait/derive.html [`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html [`Clone`]: https://doc.rust-lang.org/nightly/core/clone/trait.Clone.html [`Debug`]: https://doc.rust-lang.org/nightly/core/fmt/trait.Debug.html @@ -115,7 +115,7 @@ By [deriving] the [`Copy`], [`Clone`], [`Debug`], [`PartialEq`], and [`Eq`] trai To represent a full color code that specifies foreground and background color, we create a [newtype] on top of `u8`: -[newtype]: https://rustbyexample.com/generics/new_types.html +[newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html ```rust // in src/vga_buffer.rs diff --git a/blog/content/second-edition/posts/05-cpu-exceptions/index.md b/blog/content/second-edition/posts/05-cpu-exceptions/index.md index afaf522d..b4a5c124 100644 --- a/blog/content/second-edition/posts/05-cpu-exceptions/index.md +++ b/blog/content/second-edition/posts/05-cpu-exceptions/index.md @@ -278,7 +278,7 @@ This error occurs because the `x86-interrupt` calling convention is still unstab ### Loading the IDT In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it: -[`lidt`]: http://x86.renejeschke.de/html/file_module_x86_id_156.html +[`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt [InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.7.0/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load ```rust diff --git a/blog/content/second-edition/posts/06-double-faults/index.md b/blog/content/second-edition/posts/06-double-faults/index.md index ce515a0f..41534e84 100644 --- a/blog/content/second-edition/posts/06-double-faults/index.md +++ b/blog/content/second-edition/posts/06-double-faults/index.md @@ -268,7 +268,7 @@ Note that this double fault stack has no guard page that protects against stack Now that we created a new TSS, we need a way to tell the CPU that it should use it. Unfortunately this is a bit cumbersome, since the TSS uses the segmentation system (for historical reasons). Instead of loading the table directly, we need to add a new segment descriptor to the [Global Descriptor Table] \(GDT). Then we can load our TSS invoking the [`ltr` instruction] with the respective GDT index. (This is the reason why we named our module `gdt`.) [Global Descriptor Table]: http://www.flingos.co.uk/docs/reference/Global-Descriptor-Table/ -[`ltr` instruction]: http://x86.renejeschke.de/html/file_module_x86_id_163.html +[`ltr` instruction]: https://www.felixcloutier.com/x86/ltr ### The Global Descriptor Table The Global Descriptor Table (GDT) is a relict that was used for [memory segmentation] before paging became the de facto standard. It is still needed in 64-bit mode for various things such as kernel/user mode configuration or TSS loading. diff --git a/blog/content/second-edition/posts/08-paging-introduction/index.md b/blog/content/second-edition/posts/08-paging-introduction/index.md index 4e2b93df..5ee100e1 100644 --- a/blog/content/second-edition/posts/08-paging-introduction/index.md +++ b/blog/content/second-edition/posts/08-paging-introduction/index.md @@ -229,7 +229,7 @@ Let's take a closer look at the available flags: The `x86_64` crate provides types for [page tables] and their [entries], so we don't need to create these structures ourselves. -[page tables]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/struct.PageTable.html +[page tables]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/page_table/struct.PageTable.html [entries]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/page_table/struct.PageTableEntry.html ### The Translation Lookaside Buffer diff --git a/blog/content/second-edition/posts/09-paging-implementation/index.md b/blog/content/second-edition/posts/09-paging-implementation/index.md index d5531c25..3e5c1965 100644 --- a/blog/content/second-edition/posts/09-paging-implementation/index.md +++ b/blog/content/second-edition/posts/09-paging-implementation/index.md @@ -247,7 +247,7 @@ The above code assumes that the last level 4 entry with index `0o777` (511) is r Alternatively to performing the bitwise operations by hand, you can use the [`RecursivePageTable`] type of the `x86_64` crate, which provides safe abstractions for various page table operations. For example, the code below shows how to translate a virtual address to its mapped physical address: -[`RecursivePageTable`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/struct.RecursivePageTable.html +[`RecursivePageTable`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/mapper/struct.RecursivePageTable.html ```rust // in src/memory.rs @@ -633,8 +633,8 @@ The base of the abstraction are two traits that define various page table mappin The traits only define the interface, they don't provide any implementation. The `x86_64` crate currently provides two types that implement the traits: [`MappedPageTable`] and [`RecursivePageTable`]. The former type requires that each page table frame is mapped somewhere (e.g. at an offset). The latter type can be used when the level 4 table is [mapped recursively](#recursive-page-tables). -[`MappedPageTable`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/struct.MappedPageTable.html -[`RecursivePageTable`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/struct.RecursivePageTable.html +[`MappedPageTable`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/mapper/struct.MappedPageTable.html +[`RecursivePageTable`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/mapper/struct.RecursivePageTable.html We have the complete physical memory mapped at `physical_memory_offset`, so we can use the `MappedPageTable` type. To initialize it, we create a new `init` function in our `memory` module: @@ -749,7 +749,7 @@ pub fn create_example_mapping( In addition to the `page` that should be mapped, the function expects a `mapper` instance and a `frame_allocator`. The `mapper` is a type that implements the `Mapper` trait, which provides the `map_to` method. The generic `Size4KiB` parameter is needed because the [`Mapper`] trait is [generic] over the [`PageSize`] trait to work with both standard 4KiB pages and huge 2MiB/1GiB pages. We only want to create 4KiB pages, so we can use `Mapper` instead of requiring `MapperAllSizes`. [generic]: https://doc.rust-lang.org/book/ch10-00-generics.html -[`PageSize`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/trait.PageSize.html +[`PageSize`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/page/trait.PageSize.html For the mapping, we set the `PRESENT` flag because it is required for all valid entries and the `WRITABLE` flag to make the mapped page writable. Calling `map_to` is unsafe because it's possible to break memory safety with invalid arguments, so we need to use an `unsafe` block. For a list of all possible flags, see the [_Page Table Format_] section of the previous post. @@ -759,7 +759,7 @@ The `map_to` function can fail, so it returns a [`Result`]. Since this is just s [`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html [`expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect -[`MapperFlush`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/struct.MapperFlush.html +[`MapperFlush`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/mapper/struct.MapperFlush.html [`flush`]: https://docs.rs/x86_64/0.7.0/x86_64/structures/paging/struct.MapperFlush.html#method.flush [must_use]: https://doc.rust-lang.org/std/result/#results-must-be-used diff --git a/blog/content/second-edition/posts/10-heap-allocation/index.md b/blog/content/second-edition/posts/10-heap-allocation/index.md index 5cb3fa1d..ee3db14e 100644 --- a/blog/content/second-edition/posts/10-heap-allocation/index.md +++ b/blog/content/second-edition/posts/10-heap-allocation/index.md @@ -625,14 +625,14 @@ Of course there are many more allocation and collection types in the `alloc` cra - the [`BinaryHeap`] priority queue - [`BTreeMap`] and [`BTreeSet`] -[`Arc`]: https://doc.rust-lang.org/stable/alloc/sync/struct.Arc.html -[`String`]: https://doc.rust-lang.org/collections/string/struct.String.html +[`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html +[`String`]: https://doc.rust-lang.org/alloc/collections/string/struct.String.html [`format!`]: https://doc.rust-lang.org/alloc/macro.format.html -[`LinkedList`]: https://doc.rust-lang.org/collections/linked_list/struct.LinkedList.html -[`VecDeque`]: https://doc.rust-lang.org/collections/vec_deque/struct.VecDeque.html -[`BinaryHeap`]: https://doc.rust-lang.org/collections/binary_heap/struct.BinaryHeap.html -[`BTreeMap`]: https://doc.rust-lang.org/collections/btree_map/struct.BTreeMap.html -[`BTreeSet`]: https://doc.rust-lang.org/collections/btree_set/struct.BTreeSet.html +[`LinkedList`]: https://doc.rust-lang.org/alloc/collections/linked_list/struct.LinkedList.html +[`VecDeque`]: https://doc.rust-lang.org/alloc/collections/vec_deque/struct.VecDeque.html +[`BinaryHeap`]: https://doc.rust-lang.org/alloc/collections/binary_heap/struct.BinaryHeap.html +[`BTreeMap`]: https://doc.rust-lang.org/alloc/collections/btree_map/struct.BTreeMap.html +[`BTreeSet`]: https://doc.rust-lang.org/alloc/collections/btree_set/struct.BTreeSet.html These types will become very useful when we want to implement thread lists, scheduling queues, or support for async/await. diff --git a/blog/content/status-update/2019-05-01.md b/blog/content/status-update/2019-05-01.md index 5d1c2bd1..97fcaf0b 100644 --- a/blog/content/status-update/2019-05-01.md +++ b/blog/content/status-update/2019-05-01.md @@ -19,7 +19,7 @@ This post is an experiment inspired by [_This Week in Rust_] and similar series. - The [_Rewrite bootimage for new bootloader build system_](https://github.com/rust-osdev/bootimage/pull/34) pull request completely revamped the implementation of the crate. This was released as version `0.7.0`. See the [changelog](https://github.com/rust-osdev/bootimage/blob/master/Changelog.md#070) for a list of changes. - The rewrite had the unintended side-effect that `bootimage run` no longer ignored executables named `test-*`, so that an additional `--bin` argument was required for specifying which executable to run. To avoid breaking users of `bootimage test`, we yanked version `0.7.0`. After [fixing the issue](https://github.com/rust-osdev/bootimage/commit/8746c15bf326cf8438a4e64ffdda332fbe59e30d), version `0.7.1` was released ([changelog](https://github.com/rust-osdev/bootimage/blob/master/Changelog.md#071)). -- The [_New features for `bootimage runner`_](https://github.com/rust-osdev/bootimage/pull/36) pull request added support for additional arguments and various functionality for supporting `cargo xtest`. The changes were released as version `0.7.2` ([changelog](https://github.com/rust-osdev/bootimage/blob/master/Charelog.md#072)). +- The [_New features for `bootimage runner`_](https://github.com/rust-osdev/bootimage/pull/36) pull request added support for additional arguments and various functionality for supporting `cargo xtest`. The changes were released as version `0.7.2` ([changelog](https://github.com/rust-osdev/bootimage/blob/master/Changelog.md#072)). - An argument parsing bug that broke the new `cargo bootimage` subcommand on Windows was [fixed](https://github.com/rust-osdev/bootimage/commit/101eb43de403fd9f3cb3f044e2c263356d2c179a). The fix was released as version `0.7.3`. ## Blog OS diff --git a/blog/templates/first-edition/comments/allocating-frames.html b/blog/templates/first-edition/comments/allocating-frames.html index 08e4204e..500f3600 100644 --- a/blog/templates/first-edition/comments/allocating-frames.html +++ b/blog/templates/first-edition/comments/allocating-frames.html @@ -11,7 +11,7 @@ - Antworten
Tobias Schottdorf

> Note that we need to clone the iterator because the order of areas in the memory map isn't specified.

Could you elaborate on that? I'm probably getting something wrong, but I can't reproduce issues omitting the clone:

https://gist.github.com/tsc...

Tobias Schottdorf

> Note that we need to clone the iterator because the order of areas in the memory map isn't specified.

Could you elaborate on that? I'm probably getting something wrong, but I can't reproduce issues omitting the clone:

https://gist.github.com/tschottdorf/e1e0a4091136dd281ab3

Johan Montelius

Could we not use the start_address() and end_address() that are available in the BootInformation instead of doing the address calculation ourselves?

- +

let multiboot_start = boot_info.start_address();

- +

let multiboot_end = boot_info.end_address();

Madeleine Berner

Hi! Your link for "re-export" under the section "Testing it" is broken.

- +

Are you supposed to do something with the instruction "In order to test it in main, we need to re-export the AreaFrameAllocator in the memorymodule." ? I keep getting this error: "Could not find AreaFrameAllocator in memory "

- +

in src/lib.rs on this row:

- +

let mut frame_allocator = memory::AreaFrameAllocator::new(...

Philipp Oppermann

I'm a computer science student and I've taken some great OS courses. It's also a hobby of mine and I've experimented with a lot with toy x86 kernels and Rust. Most of the x86 information is from the OSDev wiki and the Intel/AMD manuals.

I also have a great research assistant job since November, where I try to bring Rust to an ARM Cortex-M7 board.

Philipp Oppermann

I'm a computer science student and I've taken some great OS courses. It's also a hobby of mine and I've experimented with a lot with toy x86 kernels and Rust. Most of the x86 information is from the OSDev wiki and the Intel/AMD manuals.

I also have a great research assistant job since November, where I try to bring Rust to an ARM Cortex-M7 board.

Andrew Nurse

I actually encountered the println deadlock earlier while debugging something and solved it in a slightly different way. The problem generally occurs when a second println is encountered while evaluating one of the arguments to an outer println. So, I changed println to call a helper function called print_fmt which took in a core::fmt::Arguments. I used the format_args macro (https://doc.rust-lang.org/n... to evaluate the arguments and produce the core::fmt::Arguments, which I pass to print_fmt. Only within print_fmt do I actually take the lock on the WRITER, which means that all the expressions in the println! have been fully evaluated.

The advantage being you can nest println's as far as you want and you won't deadlock :)

See my implementation here: https://github.com/anurse/O...

Great posts by the way, loving the series!

Andrew Nurse

I actually encountered the println deadlock earlier while debugging something and solved it in a slightly different way. The problem generally occurs when a second println is encountered while evaluating one of the arguments to an outer println. So, I changed println to call a helper function called print_fmt which took in a core::fmt::Arguments. I used the format_args macro (https://doc.rust-lang.org/n... to evaluate the arguments and produce the core::fmt::Arguments, which I pass to print_fmt. Only within print_fmt do I actually take the lock on the WRITER, which means that all the expressions in the println! have been fully evaluated.

The advantage being you can nest println's as far as you want and you won't deadlock :)

See my implementation here: https://github.com/anurse/Oxygen/blob/dfda170b3f3d45eca20d4a1366e5d62384d7b2e4/src/vga.rs

Great posts by the way, loving the series!

Philipp Oppermann

The biggest issue here is verifying that the Option is the correct size.

As far as I know, an Option<&X> has always the same size as a &X, since references implement the the NonZero trait. We could also use a struct StackPointer(usize) and implement NonZero for it. Then an Option<stackpointer> has the same size as an usize.

However, I don't think that it suffices to add a lifetime parameter to the index. For example, we could create two static TSSs A and B. Now we can load TSS A in the CPU but use an index from TSS B in our IDT.

Philipp Oppermann

The biggest issue here is verifying that the Option is the correct size.

As far as I know, an Option<&X> has always the same size as a &X, since references implement the the NonZero trait. We could also use a struct StackPointer(usize) and implement NonZero for it. Then an Option<stackpointer> has the same size as an usize.

However, I don't think that it suffices to add a lifetime parameter to the index. For example, we could create two static TSSs A and B. Now we can load TSS A in the CPU but use an index from TSS B in our IDT.

Philipp Oppermann

Thanks a lot! I'm currently working on a second edition of this blog, which reorders the posts (exceptions before page tables) and uses an own bootloader. So the plan is to rewrite the earlier posts, reuse the posts about exceptions, and then write some new posts about hardware interrupts and keyboard input.

- +

I created an issue to track this.

Are you running your own blog post ? i've reading it the first half and already want to point out, this is all i need from such a great programmer. Otherways i would have asked my boss for such a course, but i think this can bring me to the path i wanted, i am a webdeveloper and want to serve json files on the internet. But to be ISO 27001 compliant i needed this information...

- -

email: remco.pc@outlook.com - web: https://priya.software

Philipp Oppermann
Are you running your own blog post ?
- +

Sorry, I don't understand what you mean.

Philipp Oppermann

Looks like you created your own bootloader and already have some kind of filesystem. Really cool!

- +

We have just created the rust-osdev organization on Github, where we plan to host and maintain all kinds of libraries needed for OS development in Rust (e.g. the x86_64 crate, a bootloader, etc.). Let me know if you'd like to become a member, maybe we can join forces.

Sadly, this appears to no longer compile, as some of the dependencies are now rather different and some language features have changed. I know you're busy with the second edition effort, but is there any chance there are updates waiting in the wings to the first edition parts?

Dan Cross

Sadly, this appears to no longer compile, as some of the dependencies are now rather different and some language features have changed. I know you're busy with the second edition effort, but is there any chance there are updates waiting in the wings to the first edition parts?

I understand. It's a great service to the community that this exists at all; would you accept pull requests to fix code while the second edition is still being prepared?

Wink Saville

Philipp,

Just an FYI, In my baremetal-x86_64 repo I ported your boot.asm to boot.gas.S so I could use the code with gnu Assembler.

Wink Saville

Philipp,

Just an FYI, In my baremetal-x86_64 repo I ported your boot.asm to boot.gas.S so I could use the code with gnu Assembler.

Philipp Oppermann

That was an interesting debugging session :D

I tried every debugging trick I knew, read the manual entries for all involved instructions, and even tried to use GDB. But I could not find the bug.

Then I gave up and just looked at the source code in the repo and created a diff to your code. And the problem was surprisingly simple:

You swapped `stack_bottom` and `stack_top`.

But this small change causes big problems. Every `push` or `call` instruction overwrites some bits of the `.text` section below. The last function in the source file and thus the last function in the `.text` section is `check_long_mode`. If you add something behind it, e.g. another error function, it is no longer overwritten and works again.

I think the counter-intuitive thing is that stuff further down in the source file ends up further up in memory. And the stack grows downwards to make it even more confusing. Maybe we should add a small note in the text, why `stack_bottom` needs to be _above_ `stack_top` in the file?

Philipp Oppermann

That was an interesting debugging session :D

I tried every debugging trick I knew, read the manual entries for all involved instructions, and even tried to use GDB. But I could not find the bug.

Then I gave up and just looked at the source code in the repo and created a diff to your code. And the problem was surprisingly simple:

You swapped `stack_bottom` and `stack_top`.

But this small change causes big problems. Every `push` or `call` instruction overwrites some bits of the `.text` section below. The last function in the source file and thus the last function in the `.text` section is `check_long_mode`. If you add something behind it, e.g. another error function, it is no longer overwritten and works again.

I think the counter-intuitive thing is that stuff further down in the source file ends up further up in memory. And the stack grows downwards to make it even more confusing. Maybe we should add a small note in the text, why `stack_bottom` needs to be _above_ `stack_top` in the file?

Wink Saville

Agreed, and I see that in my more sophisticated program, the question is what is it that I'm doing wrong. I believe I've setup the Interrupt Descriptor Table to handle all interrupts, i.e. I have an array of 256 interrupt gates. That program is here (https://github.com/winksavi... but its too complicated to debug and I haven't yet checked in my non-working APIC timer code. But with that code I'm able to do software interrupts and also when my APIC timer code fires an interrupt fast enough it does work. So it would seem I've done most of the initialization "properly". Note, I'm also compiling my code with -mno-red-zone so that shouldn't be the problem.

So my debug strategy in situations such as this is to simplify. So the first thing was to just enable interrupts and doing nothing that should cause an interrupt to occur and then delay awhile in the code and see what happens. But, sure enough I'm still getting a double fault. Of course according to the documentation in the Intel SDM Volume 3 section 6.15 "Interrupt 8--Double Fault Exception (#DF)" the error code is 0 and CS EIP registers are undefined :(

Anyway, I then simplified to as simple as I can get. I modified your boot.asm program adding the code below the esp initialization that output's character to the VGA display.


start:
mov esp, stack_top

; Save registers
push edx
push ecx
push ebx
push eax

; Enable interrupts
;sti

; Initialize edx to vga buffer ah attribute, al ch
mov edx, 0xb8000
mov ax, 0x0f60

; ebx number of loops
mov ebx,10000

.loop:

; Output next character and attribute
mov word [edx], ax

; Increment to next character with wrap
inc al
cmp al, 0x7f
jne .nextloc
mov al,60

; Next location with wrap
.nextloc:
add edx, 2
and edx,0x7ff
or edx,0xb8000

; Delay
mov ecx,0x2000
.delay:
loop .delay

; Continue looping until ebx is 0
dec ebx
jnz .loop

; Disable interrupts
cli

; Restore registers
pop eax
pop ebx
pop ecx
pop edx

Here is a github repo: (https://github.com/winksavi.... If you add the above code to your boot.asm it will print 10,000 characters to the VGA display and then continue with the normal code paths. If the "sti" instruction is commented out, as it is above, then all is well. But if I uncomment the "sti" thus enabling interrupts then it fails.

I anticipated that enabling interrupts would succeed as I wouldn't expect any interrupts because the hardware is in a state where no interrupts should be generated. Or if grub or the BIOS is using interrupts then I'd expect things to also be OK.

Obviously I'm wrong and I'd hope you'd be able to suggest where my flaw is.

Wink Saville

Agreed, and I see that in my more sophisticated program, the question is what is it that I'm doing wrong. I believe I've setup the Interrupt Descriptor Table to handle all interrupts, i.e. I have an array of 256 interrupt gates. That program is here (https://github.com/winksaville/sadie but its too complicated to debug and I haven't yet checked in my non-working APIC timer code. But with that code I'm able to do software interrupts and also when my APIC timer code fires an interrupt fast enough it does work. So it would seem I've done most of the initialization "properly". Note, I'm also compiling my code with -mno-red-zone so that shouldn't be the problem.

So my debug strategy in situations such as this is to simplify. So the first thing was to just enable interrupts and doing nothing that should cause an interrupt to occur and then delay awhile in the code and see what happens. But, sure enough I'm still getting a double fault. Of course according to the documentation in the Intel SDM Volume 3 section 6.15 "Interrupt 8--Double Fault Exception (#DF)" the error code is 0 and CS EIP registers are undefined :(

Anyway, I then simplified to as simple as I can get. I modified your boot.asm program adding the code below the esp initialization that output's character to the VGA display.


start:
mov esp, stack_top

; Save registers
push edx
push ecx
push ebx
push eax

; Enable interrupts
;sti

; Initialize edx to vga buffer ah attribute, al ch
mov edx, 0xb8000
mov ax, 0x0f60

; ebx number of loops
mov ebx,10000

.loop:

; Output next character and attribute
mov word [edx], ax

; Increment to next character with wrap
inc al
cmp al, 0x7f
jne .nextloc
mov al,60

; Next location with wrap
.nextloc:
add edx, 2
and edx,0x7ff
or edx,0xb8000

; Delay
mov ecx,0x2000
.delay:
loop .delay

; Continue looping until ebx is 0
dec ebx
jnz .loop

; Disable interrupts
cli

; Restore registers
pop eax
pop ebx
pop ecx
pop edx

Here is a github repo: (https://github.com/winksaville/baremetal-po-x86_64/tree/test_enable_interrupts). If you add the above code to your boot.asm it will print 10,000 characters to the VGA display and then continue with the normal code paths. If the "sti" instruction is commented out, as it is above, then all is well. But if I uncomment the "sti" thus enabling interrupts then it fails.

I anticipated that enabling interrupts would succeed as I wouldn't expect any interrupts because the hardware is in a state where no interrupts should be generated. Or if grub or the BIOS is using interrupts then I'd expect things to also be OK.

Obviously I'm wrong and I'd hope you'd be able to suggest where my flaw is.

Philipp Oppermann

Hmm, do you have a link to the documentation? I can't find anything relevant on page 4-37 in this document: https://www.intel.com/Asset...

The AMD64 manual (http://developer.amd.com/wo... states on page 253:

Normally, an IRET that pops a null selector into the SS register causes a general-protection exception (#GP) to occur. However, in long mode, the null selector indicates the existence of nested interrupt handlers and/or privileged software in 64-bit mode. Long mode allows an IRET to pop a null selector into SS from the stack under the following conditions:
• The target mode is 64-bit mode.
• The target CPL<3.
In this case, the processor does not load an SS descriptor, and the null selector is loaded into SS without causing a #GP exception

Maybe I interpreted that wrong, though…

Philipp Oppermann

Hmm, do you have a link to the documentation? I can't find anything relevant on page 4-37 in this document: https://www.intel.com/Assets/en_US/PDF/manual/253667.pdf

The AMD64 manual states on page 253:

Normally, an IRET that pops a null selector into the SS register causes a general-protection exception (#GP) to occur. However, in long mode, the null selector indicates the existence of nested interrupt handlers and/or privileged software in 64-bit mode. Long mode allows an IRET to pop a null selector into SS from the stack under the following conditions:
• The target mode is 64-bit mode.
• The target CPL<3.
In this case, the processor does not load an SS descriptor, and the null selector is loaded into SS without causing a #GP exception

Maybe I interpreted that wrong, though…

You need to do a so-called far jump, which updates the code segment. I'm not sure right now if a far call is supported in long mode. Either way, returning to 32-bit code might not be a good idea anyway, since the opcodes might be interpreted differently.

Philipp Oppermann

You need to do a so-called far jump, which updates the code segment. I'm not sure right now if a far call is supported in long mode. Either way, returning to 32-bit code might not be a good idea anyway, since the opcodes might be interpreted differently.

Does the error occur when invoking nasm? Then you need to add extern long_mode_start somewhere inside the boot.asm (e.g. at the beginning). If it occurs while invoking ld, make sure that the long_mode_init.asm file is assembled and passed to ld (and it should of course define a global long_mode_start: label).

Philipp Oppermann

Does the error occur when invoking nasm? Then you need to add extern long_mode_start somewhere inside the boot.asm (e.g. at the beginning). If it occurs while invoking ld, make sure that the long_mode_init.asm file is assembled and passed to ld (and it should of course define a global long_mode_start: label).

Because the CR3 register can only be loaded from a register. So you have to load the p4_table address into a register first.

Philipp Oppermann

Because the CR3 register can only be loaded from a register. So you have to load the p4_table address into a register first.

David

I guess that what's unclear to me is why you say that each PTE entry -contains the 52-bit physical address of the next frame/entry but in the +Antworten

David

I guess that what's unclear to me is why you say that each PTE entry +contains the 52-bit physical address of the next frame/entry but in the table it looks like only bits 12-51 (40 bits) are used for that.

Thanks! Should be fixed now.

Philipp Oppermann

Thanks! Should be fixed now.

Philipp Oppermann

In the btree module of libcollections: https://github.com/rust-lan...

The rendered documentation is here.

Philipp Oppermann

In the btree module of libcollections: https://github.com/rust-lan...

The rendered documentation is here.

Ryan Breen

Love this series of articles! I'm very new to Rust and kernel development, and I've really enjoyed following along and trying to experiment a bit with alternative implementations. In that vein, I ported the inimitable gz's rust-slabmalloc (https://github.com/gz) to run in my implementation of these tutorials: https://github.com/ryanbree...

One potentially interesting approach I tried, taking a bit of a page from Linux which I know uses a dumbed down allocator for the early allocation during kernel boot, is to have my Rust allocator be tiered: during early kernel boot, it uses a bump allocator. The only allocations done by the bump allocator are to set up the memory to be used by the slab_allocator. This meant I could get the benefit of collections when porting slab_allocator, so I dropped its internal data structure in favor of a plain old vec.

Thanks for this series! You're doing awesome work and giving people a world of new educational opportunities.

Ryan Breen

Love this series of articles! I'm very new to Rust and kernel development, and I've really enjoyed following along and trying to experiment a bit with alternative implementations. In that vein, I ported the inimitable gz's rust-slabmalloc (https://github.com/gz) to run in my implementation of these tutorials: https://github.com/ryanbree...

One potentially interesting approach I tried, taking a bit of a page from Linux which I know uses a dumbed down allocator for the early allocation during kernel boot, is to have my Rust allocator be tiered: during early kernel boot, it uses a bump allocator. The only allocations done by the bump allocator are to set up the memory to be used by the slab_allocator. This meant I could get the benefit of collections when porting slab_allocator, so I dropped its internal data structure in favor of a plain old vec.

Thanks for this series! You're doing awesome work and giving people a world of new educational opportunities.

Johan M

Ahh, I see that the API to custom allocators changed :-0 I see that the code in git is updated but not for the bump_allocator. Even if one can work around it to conform to the new interface it is puzzling before you figure out what the problem is.

- +

A guide to the new allocator:

- +

https://github.com/rust-lang/rfcs/blob/master/text/1974-global-allocators.md

Lifepillar

Nice post! I am on OS X, but I find it easier to use Linux for this assembly stuff. Using VirtualBox, I have created a minimal Debian machine running an SSH server and with a folder shared between the OS X host and the Debian guest. So, I may install all the needed tools and cross-compile in Debian and have the final .iso accessible in OS X (to use it with QEMU), all of this while working in Terminal.app as usual.

As a side note, I had to set LDEMULATION="elf_x86_64" before linking, because I was getting this error: `ld: i386:x86-64 architecture of input file `multiboot_header.o' is incompatible with i386 output`. This may be because I have used Debian's 32-bit PC netinst iso instead of the 64-bit version.

Lifepillar

Nice post! I am on OS X, but I find it easier to use Linux for this assembly stuff. Using VirtualBox, I have created a minimal Debian machine running an SSH server and with a folder shared between the OS X host and the Debian guest. So, I may install all the needed tools and cross-compile in Debian and have the final .iso accessible in OS X (to use it with QEMU), all of this while working in Terminal.app as usual.

As a side note, I had to set LDEMULATION="elf_x86_64" before linking, because I was getting this error: `ld: i386:x86-64 architecture of input file `multiboot_header.o' is incompatible with i386 output`. This may be because I have used Debian's 32-bit PC netinst iso instead of the 64-bit version.

GW seo

When I run grub-mkrescue I got no output an just silence

after install xorriso I got error like this
-----
xorriso 1.3.2 : RockRidge filesystem manipulator, libburnia project.

Drive current: -outdev 'stdio:os.iso'

Media current: stdio file, overwriteable

Media status : is blank

Media summary: 0 sessions, 0 data blocks, 0 data, 861g free

Added to ISO image: directory '/'='/tmp/grub.pI5jyq'

xorriso : UPDATE : 276 files added in 1 seconds

Added to ISO image: directory '/'='/path/to/my/work/isofiles'

xorriso : FAILURE : Cannot find path '/efi.img' in loaded ISO image

xorriso : UPDATE : 280 files added in 1 seconds

xorriso : aborting : -abort_on 'FAILURE' encountered 'FAILURE'

-----

and I search for resolve this error, I arrive here[ https://bugs.archlinux.org/... ]

after isntall mtools, grub-mkrescue create os.iso

GW seo

When I run grub-mkrescue I got no output an just silence

after install xorriso I got error like this
-----
xorriso 1.3.2 : RockRidge filesystem manipulator, libburnia project.

Drive current: -outdev 'stdio:os.iso'

Media current: stdio file, overwriteable

Media status : is blank

Media summary: 0 sessions, 0 data blocks, 0 data, 861g free

Added to ISO image: directory '/'='/tmp/grub.pI5jyq'

xorriso : UPDATE : 276 files added in 1 seconds

Added to ISO image: directory '/'='/path/to/my/work/isofiles'

xorriso : FAILURE : Cannot find path '/efi.img' in loaded ISO image

xorriso : UPDATE : 280 files added in 1 seconds

xorriso : aborting : -abort_on 'FAILURE' encountered 'FAILURE'

-----

and I search for resolve this error, I arrive here[ https://bugs.archlinux.org/42334 ]

after isntall mtools, grub-mkrescue create os.iso

liveag

@phil_opp:disqus i created a GitHub repository where i work through your great guide step-by-step. It is located here: https://github.com/peacemem...
Please let me know if there are problems with the attribution. =)

liveag

@phil_opp:disqus i created a GitHub repository where i work through your great guide step-by-step. It is located here: https://github.com/peacememories/rust-kernel-experiments
Please let me know if there are problems with the attribution. =)

Thanks you for your great articles.
I have created my OS in Rust, and these are really useful for me.
I have been revising my OS based on your articles.
Also, I have been writing an article which is similar to your
http://mopp.github.io/artic...

I added link into my articles to this website.
If you feel unpleasant, please tell me and I will remove it.

Thanks

Thanks you for your great articles.
I have created my OS in Rust, and these are really useful for me.
I have been revising my OS based on your articles.
Also, I have been writing an article which is similar to your
http://mopp.github.io/articles/os/os00_intro

I added link into my articles to this website.
If you feel unpleasant, please tell me and I will remove it.

Thanks

Lonami

For anyone else struggling with "Boot failed: Could not read from CDROM (code 0009)", you need to install `grub-pc-bin` and then regenerate the .iso. Solution from here: http://intermezzos.github.i....

By the way, I'm loving the tutorial style. Very clear, thank you!

Lonami

For anyone else struggling with "Boot failed: Could not read from CDROM (code 0009)", you need to install `grub-pc-bin` and then regenerate the .iso. Solution from here: http://intermezzos.github.io/book/appendix/troubleshooting.html#could-not-read-from-cdrom-code-0009.

By the way, I'm loving the tutorial style. Very clear, thank you!

Philipp Oppermann

It seems like there is some problem with this lines:

- + Antworten
Philipp Oppermann

It seems like there is some problem with this lines:

+
build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm
   
- +

Do you have a file named build/arch/x86_64/boot.asm? For debugging, you could use explicit names instead of the wildcards (%):

- +
build/arch/$(arch)/boot.o: src/arch/$(arch)/boot.asm
   
- +

(Note that you need to copy this rule for every .asm file without wildcards.)

Dendyard

Lot of tutorials together. + Antworten

Dendyard

Lot of tutorials together. Thanks man (y)

Darryl Rees

This is incredible, just fantastic..

- +

I did have a couple of hiccups following along using Win10 WSL on a UEFI PC, maybe these details can be folded in to the tutorial?

- +

1) Couldn't boot QEMU with emulated video device

- +
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
-  
+
   Could not initialize SDL(No available video device) - exiting
   
- +

Solution: Use -curses option for qemu

- +

qemu-system-x86_64 -curses -cdrom os-x86_64.iso

- +

2) Could not boot from ISO (on a UEFI system)

- +
  Booting from DVD/CD...
-  
+
    Boot failed: Could not read from CDROM (code 0004)
-  
+
                          Booting from ROM...
   
- +

Solution: sudo -S apt-get install grub-pc-bin

I'm trying to do this, but I can't get the OK to actually display and I've kind of ran out of ideas. Trying to run with QEMU on Arch Linux.

- +

Things I've tried: Adding the multiboot tag that should tell grub I want a text mode, 80x25. Just gives me a black screen, instead of saying "Booting 'my os'"

- +

Switching grub to text mode with every possible switch I can find that looks related, with and without ^. Just gives me a black screen for all of them too. I can confirm my code actually seems to be executed - or, at least, hits the hlt instruction. Just that there's no output, which makes me think VGA problems, hence me trying all of the above. That seems to leave trying to parse the multiboot header or something, and that seems like... something I don't really want to try to do in assembly, including pushing it over assembly? I don't really want to move unless this works, though, because I see you still are using text mode extensively further on. :/

Anonym

For anyone trying to push themselves into using the GNU assembler (i.e. as), if you're getting "no multiboot header" errors with QEMU, put the line:

- +

.align 8

- +

before the end tags.

is it possible to manipulate the windows kernel. Which language is used in developing windows kernel?

windows technical support

is it possible to manipulate the windows kernel. Which language is used in developing windows kernel?

Philipp Oppermann

What are you doing in your main? Maybe there is something stack intensive before the `test_paging` call? My main function looks like this. I removed most of the example code from the previous posts, so maybe that's the reason..

Philipp Oppermann

What are you doing in your main? Maybe there is something stack intensive before the `test_paging` call? My main function looks like this. I removed most of the example code from the previous posts, so maybe that's the reason..

Stephen Checkoway

Neither choice seems like it works. Bits 62:MAXPHYADDR (where MAXPHYADDR is at most 52) are reserved and supposed to be set to 0. However, bits 11:9 appear to be free at every level of the page table hierarchy.

- +

Somewhat annoyingly, 10 bits are needed since 513 values need to be represented. Thus one could use three bits from each of the first four entries.

- +

x86-64 has a 64-byte cache line size so the four accesses do fit in a single cache line.

Hi Phil, when I try to add the test code to test the unmap with the lines of code below, looks like the system can't boot up, and qemu just keeps rebooting. But if I remove this line. the code works perfectly. Could you please help to have a check.

- +

println!("{:#x}", unsafe { *(Page::containing_address(addr).start_address() as *const u64) });

Jack Halford

hi phil, quick question

- +

It seems that as soon as I enable x86 paging the VGA buffer is not accessible anymore (because 0xb8000 is not identity mapped yet?). So essentially the test_paging routine doesnt print anything... so my thinking tells me the identity map is the first thing to do after enabling paging, yet its the subject of the next chapter, am I not getting something?

Chris Latham

hey, great articles so far!

i've been following along, and i've run into some issues with the ::core::fmt::Write implementation for our writer class.

if i add that code in, i get these linker errors:

core.0.rs:(.text._ZN4core3fmt5write17hdac96890aec66a9aE+0x324): undefined reference to `_Unwind_Resume'

core.0.rs:(.text._ZN4core3fmt5write17hdac96890aec66a9aE+0x3eb): undefined reference to `_Unwind_Resume'

core.0.rs:(.text._ZN4core3fmt5write17hdac96890aec66a9aE+0x3f3): undefined reference to `_Unwind_Resume'

i've gone back and checked that i set panic to "abort" for both dev and release profiles in my config.toml, the same way you did to fix the unwinding issues. everything seems to match up with what you have. what have i missed?

thanks in advance.

Chris Latham

hey, great articles so far!

i've been following along, and i've run into some issues with the ::core::fmt::Write implementation for our writer class.

if i add that code in, i get these linker errors:

core.0.rs:(.text._ZN4core3fmt5write17hdac96890aec66a9aE+0x324): undefined reference to `_Unwind_Resume'

core.0.rs:(.text._ZN4core3fmt5write17hdac96890aec66a9aE+0x3eb): undefined reference to `_Unwind_Resume'

core.0.rs:(.text._ZN4core3fmt5write17hdac96890aec66a9aE+0x3f3): undefined reference to `_Unwind_Resume'

i've gone back and checked that i set panic to "abort" for both dev and release profiles in my config.toml, the same way you did to fix the unwinding issues. everything seems to match up with what you have. what have i missed?

thanks in advance.

ocamlmycaml

So i'm trying to make `println!("{}: some number", 1);` work, but when I add that line to my rust_main function, the emulator does the whole triple exception thing starting with a 0xd error - which according to OSDev.org is a "General protection fault":

```check_exception old: 0xffffffff new 0xd
0: v=0d e=0000 i=0 cpl=0 IP=0008:ec834853e5894855 pc=ec834853e5894855 SP=0010:000000000012ec18 env->regs[R_EAX]=0000000000000a00```

`println!("Hello {}!", "world");` works just fine - it just doesn't seem to be able to interpolate non-string types. Would you have any idea on what's going wrong? I'm not sure where to even look. If you'd like to clone and run my code and take a look: https://github.com/ocamlmyc...

btw ++good tutorial, i'm learning a lot!

ocamlmycaml

So i'm trying to make `println!("{}: some number", 1);` work, but when I add that line to my rust_main function, the emulator does the whole triple exception thing starting with a 0xd error - which according to OSDev.org is a "General protection fault":

```check_exception old: 0xffffffff new 0xd
0: v=0d e=0000 i=0 cpl=0 IP=0008:ec834853e5894855 pc=ec834853e5894855 SP=0010:000000000012ec18 env->regs[R_EAX]=0000000000000a00```

`println!("Hello {}!", "world");` works just fine - it just doesn't seem to be able to interpolate non-string types. Would you have any idea on what's going wrong? I'm not sure where to even look. If you'd like to clone and run my code and take a look: https://github.com/ocamlmycaml/rust-moss/

btw ++good tutorial, i'm learning a lot!

Esdras

Solution for the problems of compilation:

- +

1: go to vga_buffer.rs 2: go to line buffer: unsafe { Unique::new(0xb8000 as *mut _) }, 3: change for buffer: unsafe { Unique::new_unchecked(0xb8000 as *mut _) }, @@ -390,11 +390,11 @@ Antworten

Hello :)

Great tutorials, just a quick question for learning purposes. Could the values in enum be defined implicitly like so?

- +
pub enum Color {
       Black,
       Blue,
-      ... 
+      ...
    }
   
Anonym

I followed everything in this tutorial to the letter, and had a question. If I were to try to print a string to the screen, how would I do it? I have been using

- +

print!("{}", string)

- +

with string containing what I want to print. I know this works in normal Rust, but would it work with the VGA buffer you made? Thanks!

Anonym

Question. How would I go about changing the color of the text on the fly? Like if I wanted to print Hello World

- +

and have "Hello" be green and "World" be white. How would I go about doing this?

Rhys Kenwell

Trying to get this to work, my code looks identical to yours, save for the occasional twist for aesthetics, or different variable name, but after enabling the nxe bit, when according to you it should boot successfully, it crashes for me.

- +

A bit of sleuthing on my part deduced the issue, I'm getting a double fault when I try to write to the cr3 register. A bit more debugging helped me find the culprit, when I write to cr3 in the switch method, something happens and the CPU double faults.

- +

The exact instruction that the pc points to in the register dump is "add $0x18, %rsp"

- +

Thanks in advance for helping me resolve this.

Hmm, sounds like your CPU somehow thinks that you set a reserved bit. If it works fine before setting the NXE bit, it could be caused by:

- + Antworten
Philipp Oppermann

Hmm, sounds like your CPU somehow thinks that you set a reserved bit. If it works fine before setting the NXE bit, it could be caused by:

+
  • a wrong register (should be IA32_EFER)
  • a wrong bit number (should be 1 << 11)
  • your CPU somehow doesn't support it (if you run it on real hardware) - +
    • does in work in QEMU?
    • The AMD manual says: “Before setting this bit, system software must verify the processor supports the NX feature by checking the CPUID NX feature flag (CPUID Fn8000_0001_EDX[NX]).”
- +

Hope this helps!

Alister Lee

Right, have learned a lot in the last month, following you on ARM. I expect I'll need rlibc, but I haven't yet.

What I have needed is `compiler-rt`, which you have avoided because you are building on a (tier 3) supported build target which is [not the case](http://stackoverflow.com/qu... for `arm-none-eabi`.

Alister Lee

Right, have learned a lot in the last month, following you on ARM. I expect I'll need rlibc, but I haven't yet.

What I have needed is `compiler-rt`, which you have avoided because you are building on a (tier 3) supported build target which is [not the case](http://stackoverflow.com/qu...) for `arm-none-eabi`.

Doesn't the linker problem still exist? Most of the options used by GNU 'ld' are not supported by macOS 10.11 'ld'. +Antworten

Doesn't the linker problem still exist? Most of the options used by GNU 'ld' are not supported by macOS 10.11 'ld'. May be you used cross compiled 'ld' ?

Don Rowe

Thanks again for sharing this! FYI, the link https://doc.rust-lang.org/s... in http://os.phil-opp.com/set-... is broken.

Don Rowe

Thanks again for sharing this! FYI, the link https://doc.rust-lang.org/std/rt/unwind/ in http://os.phil-opp.com/set-... is broken.

Aaron D

With the latest rust nightly I was getting linker errors after pulling in in the rlibc crate:

target/x86_64-unknown-linux-gnu/debug/libblog_os.a(core-93f19628b61beb76.0.o): In function `core::panicking::panic_fmt':
/buildslave/rust-buildbot/slave/nightly-dist-rustc-linux/build/src/libcore/panicking.rs:69: undefined reference to `rust_begin_unwind'
make: *** [build/kernel-x86_64.bin] Error 1

Apparently the later versions of the compiler are pretty strict about mangling almost anything they can for optimization. Usually the panic_fmt symbol becomes rust_begin_unwind (for some reason), but now it's getting mangled and so the linker can't find that symbol - it's a pretty cryptic error with discussion at https://github.com/rust-lan...

To fix it, you need to mark panic_fmt with no_mangle as well, so the line in lib.rs becomes:
#[lang = "panic_fmt"] #[no_mangle] extern fn panic_fmt() -> ! {loop{}}

This allows it to build properly.

Aaron D

With the latest rust nightly I was getting linker errors after pulling in in the rlibc crate:

target/x86_64-unknown-linux-gnu/debug/libblog_os.a(core-93f19628b61beb76.0.o): In function `core::panicking::panic_fmt':
/buildslave/rust-buildbot/slave/nightly-dist-rustc-linux/build/src/libcore/panicking.rs:69: undefined reference to `rust_begin_unwind'
make: *** [build/kernel-x86_64.bin] Error 1

Apparently the later versions of the compiler are pretty strict about mangling almost anything they can for optimization. Usually the panic_fmt symbol becomes rust_begin_unwind (for some reason), but now it's getting mangled and so the linker can't find that symbol - it's a pretty cryptic error with discussion at https://github.com/rust-lan...

To fix it, you need to mark panic_fmt with no_mangle as well, so the line in lib.rs becomes:
#[lang = "panic_fmt"] #[no_mangle] extern fn panic_fmt() -> ! {loop{}}

This allows it to build properly.