mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
Improve indentation, line length, and syntax highlighting
This commit is contained in:
@@ -183,11 +183,11 @@ We can use `objdump` to print the sections of the generated executable and verif
|
|||||||
kernel.bin: file format elf64-x86-64
|
kernel.bin: file format elf64-x86-64
|
||||||
|
|
||||||
Sections:
|
Sections:
|
||||||
Idx Name Size VMA LMA File off Algn
|
Idx Name Size VMA LMA File off Algn
|
||||||
0 .boot 00000018 0000000000100000 0000000000100000 00000080 2**0
|
0 .boot 00000018 0000000000100000 0000000000100000 00000080 2**0
|
||||||
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
CONTENTS, ALLOC, LOAD, READONLY, DATA
|
||||||
1 .text 0000000b 0000000000100020 0000000000100020 000000a0 2**4
|
1 .text 0000000b 0000000000100020 0000000000100020 000000a0 2**4
|
||||||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||||||
```
|
```
|
||||||
_Note_: The `ld` and `objdump` commands are platform specific. If you're _not_ working on x86_64 architecture, you will need to [cross compile binutils]. Then use `x86_64‑elf‑ld` and `x86_64‑elf‑objdump` instead of `ld` and `objdump`.
|
_Note_: The `ld` and `objdump` commands are platform specific. If you're _not_ working on x86_64 architecture, you will need to [cross compile binutils]. Then use `x86_64‑elf‑ld` and `x86_64‑elf‑objdump` instead of `ld` and `objdump`.
|
||||||
[cross compile binutils]: {{% relref "cross-compile-binutils.md" %}}
|
[cross compile binutils]: {{% relref "cross-compile-binutils.md" %}}
|
||||||
@@ -265,7 +265,7 @@ Right now we need to execute 4 commands in the right order everytime we change a
|
|||||||
├── linker.ld
|
├── linker.ld
|
||||||
└── grub.cfg
|
└── grub.cfg
|
||||||
```
|
```
|
||||||
The Makefile looks like this (but indented with tabs instead of spaces):
|
The Makefile looks like this (indented with tabs instead of spaces):
|
||||||
|
|
||||||
```Makefile
|
```Makefile
|
||||||
arch ?= x86_64
|
arch ?= x86_64
|
||||||
@@ -276,34 +276,34 @@ linker_script := src/arch/$(arch)/linker.ld
|
|||||||
grub_cfg := src/arch/$(arch)/grub.cfg
|
grub_cfg := src/arch/$(arch)/grub.cfg
|
||||||
assembly_source_files := $(wildcard src/arch/$(arch)/*.asm)
|
assembly_source_files := $(wildcard src/arch/$(arch)/*.asm)
|
||||||
assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \
|
assembly_object_files := $(patsubst src/arch/$(arch)/%.asm, \
|
||||||
build/arch/$(arch)/%.o, $(assembly_source_files))
|
build/arch/$(arch)/%.o, $(assembly_source_files))
|
||||||
|
|
||||||
.PHONY: all clean run iso
|
.PHONY: all clean run iso
|
||||||
|
|
||||||
all: $(kernel)
|
all: $(kernel)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm -r build
|
@rm -r build
|
||||||
|
|
||||||
run: $(iso)
|
run: $(iso)
|
||||||
@qemu-system-x86_64 -cdrom $(iso)
|
@qemu-system-x86_64 -cdrom $(iso)
|
||||||
|
|
||||||
iso: $(iso)
|
iso: $(iso)
|
||||||
|
|
||||||
$(iso): $(kernel) $(grub_cfg)
|
$(iso): $(kernel) $(grub_cfg)
|
||||||
@mkdir -p build/isofiles/boot/grub
|
@mkdir -p build/isofiles/boot/grub
|
||||||
@cp $(kernel) build/isofiles/boot/kernel.bin
|
@cp $(kernel) build/isofiles/boot/kernel.bin
|
||||||
@cp $(grub_cfg) build/isofiles/boot/grub
|
@cp $(grub_cfg) build/isofiles/boot/grub
|
||||||
@grub-mkrescue -o $(iso) build/isofiles 2> /dev/null
|
@grub-mkrescue -o $(iso) build/isofiles 2> /dev/null
|
||||||
@rm -r build/isofiles
|
@rm -r build/isofiles
|
||||||
|
|
||||||
$(kernel): $(assembly_object_files) $(linker_script)
|
$(kernel): $(assembly_object_files) $(linker_script)
|
||||||
@ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files)
|
@ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files)
|
||||||
|
|
||||||
# compile assembly files
|
# compile assembly files
|
||||||
build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm
|
build/arch/$(arch)/%.o: src/arch/$(arch)/%.asm
|
||||||
@mkdir -p $(shell dirname $@)
|
@mkdir -p $(shell dirname $@)
|
||||||
@nasm -felf64 $< -o $@
|
@nasm -felf64 $< -o $@
|
||||||
```
|
```
|
||||||
Some comments (see the [Makefile tutorial] if you don't know `make`):
|
Some comments (see the [Makefile tutorial] if you don't know `make`):
|
||||||
|
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ In `no_multiboot`, we use the `jmp` (“jump”) instruction to jump to our erro
|
|||||||
|
|
||||||
```nasm
|
```nasm
|
||||||
check_cpuid:
|
check_cpuid:
|
||||||
; Check if CPUID is supported by attempting to flip the ID bit (bit 21) in
|
; Check if CPUID is supported by attempting to flip the ID bit (bit 21)
|
||||||
; the FLAGS register. If we can flip it, CPUID is available.
|
; in the FLAGS register. If we can flip it, CPUID is available.
|
||||||
|
|
||||||
; Copy FLAGS in to EAX via stack
|
; Copy FLAGS in to EAX via stack
|
||||||
pushfd
|
pushfd
|
||||||
@@ -130,13 +130,13 @@ check_cpuid:
|
|||||||
pushfd
|
pushfd
|
||||||
pop eax
|
pop eax
|
||||||
|
|
||||||
; Restore FLAGS from the old version stored in ECX (i.e. flipping the ID bit
|
; Restore FLAGS from the old version stored in ECX (i.e. flipping the
|
||||||
; back if it was ever flipped).
|
; ID bit back if it was ever flipped).
|
||||||
push ecx
|
push ecx
|
||||||
popfd
|
popfd
|
||||||
|
|
||||||
; Compare EAX and ECX. If they are equal then that means the bit wasn't
|
; Compare EAX and ECX. If they are equal then that means the bit
|
||||||
; flipped, and CPUID isn't supported.
|
; wasn't flipped, and CPUID isn't supported.
|
||||||
cmp eax, ecx
|
cmp eax, ecx
|
||||||
je .no_cpuid
|
je .no_cpuid
|
||||||
ret
|
ret
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ target ?= $(arch)-unknown-linux-gnu
|
|||||||
rust_os := target/$(target)/debug/libblog_os.a
|
rust_os := target/$(target)/debug/libblog_os.a
|
||||||
# ...
|
# ...
|
||||||
$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script)
|
$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script)
|
||||||
@ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files) $(rust_os)
|
@ld -n -T $(linker_script) -o $(kernel) \
|
||||||
|
$(assembly_object_files) $(rust_os)
|
||||||
|
|
||||||
cargo:
|
cargo:
|
||||||
@cargo build --target $(target)
|
@cargo build --target $(target)
|
||||||
@@ -155,10 +156,14 @@ pub extern fn rust_main() {
|
|||||||
Now `make run` doesn't complain about `memcpy` anymore. Instead it will show a pile of new errors:
|
Now `make run` doesn't complain about `memcpy` anymore. Instead it will show a pile of new errors:
|
||||||
|
|
||||||
```
|
```
|
||||||
target/debug/libblog_os.a(core-35017696.0.o): In function `ops::f32.Rem::rem::hfcbbcbe5711a6e6emxm':
|
target/debug/libblog_os.a(core-35017696.0.o):
|
||||||
core.0.rs:(.text._ZN3ops7f32.Rem3rem20hfcbbcbe5711a6e6emxmE+0x1): undefined reference to `fmodf'
|
In function `ops::f32.Rem::rem::hfcbbcbe5711a6e6emxm':
|
||||||
target/debug/libblog_os.a(core-35017696.0.o): In function `ops::f64.Rem::rem::hbf225030671c7a35Txm':
|
core.0.rs:(.text._ZN3ops7f32.Rem3rem20hfcbbcbe5711a6e6emxmE+0x1):
|
||||||
core.0.rs:(.text._ZN3ops7f64.Rem3rem20hbf225030671c7a35TxmE+0x1): undefined reference to `fmod'
|
undefined reference to `fmodf'
|
||||||
|
target/debug/libblog_os.a(core-35017696.0.o):
|
||||||
|
In function `ops::f64.Rem::rem::hbf225030671c7a35Txm':
|
||||||
|
core.0.rs:(.text._ZN3ops7f64.Rem3rem20hbf225030671c7a35TxmE+0x1):
|
||||||
|
undefined reference to `fmod'
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -176,7 +181,8 @@ So how do we fix this problem? We don't use any floating point operations, so we
|
|||||||
|
|
||||||
```make
|
```make
|
||||||
$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script)
|
$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script)
|
||||||
@ld -n --gc-sections -T $(linker_script) -o $(kernel) $(assembly_object_files) $(rust_os)
|
@ld -n --gc-sections -T $(linker_script) -o $(kernel) \
|
||||||
|
$(assembly_object_files) $(rust_os)
|
||||||
```
|
```
|
||||||
Now we can do a `make run` again and… it doesn't boot anymore:
|
Now we can do a `make run` again and… it doesn't boot anymore:
|
||||||
|
|
||||||
@@ -205,8 +211,12 @@ The following snippet still fails:
|
|||||||
The error is a linker error again (hence the ugly error message):
|
The error is a linker error again (hence the ugly error message):
|
||||||
|
|
||||||
```
|
```
|
||||||
target/debug/libblog_os.a(blog_os.0.o): In function `blog_os::iter::Iterator::zip<core::iter::FlatMap<core::ops::Range<i32>, core::ops::Range<i32>, closure>,core::ops::RangeFrom<i32>>':
|
target/debug/libblog_os.a(blog_os.0.o):
|
||||||
/home/.../src/libcore/iter.rs:654: undefined reference to `_Unwind_Resume'
|
In function `blog_os::iter::Iterator::zip<core::iter::FlatMap<
|
||||||
|
core::ops::Range<i32>, core::ops::Range<i32>, closure>,
|
||||||
|
core::ops::RangeFrom<i32>>':
|
||||||
|
/home/.../src/libcore/iter.rs:654:
|
||||||
|
undefined reference to `_Unwind_Resume'
|
||||||
```
|
```
|
||||||
So the linker can't find a function named `_Unwind_Resume` that is referenced in `iter.rs:654` in libcore. This reference is not really there at [line 654 of libcore's `iter.rs`][iter.rs:654]. Instead, it is a compiler inserted _landing pad_, which is used for exception handling.
|
So the linker can't find a function named `_Unwind_Resume` that is referenced in `iter.rs:654` in libcore. This reference is not really there at [line 654 of libcore's `iter.rs`][iter.rs:654]. Instead, it is a compiler inserted _landing pad_, which is used for exception handling.
|
||||||
|
|
||||||
@@ -214,7 +224,7 @@ The easiest way of fixing this problem is to disable the landing pad creation si
|
|||||||
|
|
||||||
```make
|
```make
|
||||||
cargo:
|
cargo:
|
||||||
@cargo rustc --target $(target) -- -Z no-landing-pads
|
@cargo rustc --target $(target) -- -Z no-landing-pads
|
||||||
```
|
```
|
||||||
Now we fixed all linking issues.
|
Now we fixed all linking issues.
|
||||||
|
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ Rust's [macro syntax] is a bit strange, so we won't try to write a macro from sc
|
|||||||
[macro syntax]: https://doc.rust-lang.org/nightly/book/macros.html
|
[macro syntax]: https://doc.rust-lang.org/nightly/book/macros.html
|
||||||
[`println!` macro]: https://doc.rust-lang.org/nightly/std/macro.println!.html
|
[`println!` macro]: https://doc.rust-lang.org/nightly/std/macro.println!.html
|
||||||
|
|
||||||
```
|
```rust
|
||||||
macro_rules! println {
|
macro_rules! println {
|
||||||
($fmt:expr) => (print!(concat!($fmt, "\n")));
|
($fmt:expr) => (print!(concat!($fmt, "\n")));
|
||||||
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
|
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
|
||||||
@@ -394,7 +394,7 @@ It just adds a `\n` and then invokes the [`print!` macro], which is defined as:
|
|||||||
|
|
||||||
[`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html
|
[`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html
|
||||||
|
|
||||||
```
|
```rust
|
||||||
macro_rules! print {
|
macro_rules! print {
|
||||||
($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
|
($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
|
||||||
}
|
}
|
||||||
@@ -405,12 +405,13 @@ It calls the `_print` method in the `io` module of the current crate (`$crate`),
|
|||||||
|
|
||||||
To print to the VGA buffer, we just copy the `println!` macro and modify the `print!` macro to use our static `WRITER` instead of `_print`:
|
To print to the VGA buffer, we just copy the `println!` macro and modify the `print!` macro to use our static `WRITER` instead of `_print`:
|
||||||
|
|
||||||
```
|
```rust
|
||||||
// in src/vga_buffer.rs
|
// in src/vga_buffer.rs
|
||||||
macro_rules! print {
|
macro_rules! print {
|
||||||
($($arg:tt)*) => ({
|
($($arg:tt)*) => ({
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
$crate::vga_buffer::WRITER.lock().write_fmt(format_args!($($arg)*)).unwrap();
|
let writer = $crate::vga_buffer::WRITER.lock();
|
||||||
|
writer.write_fmt(format_args!($($arg)*)).unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -68,11 +68,13 @@ To print all available memory areas, we can use the `multiboot2` crate in our `r
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
let boot_info = unsafe{ multiboot2::load(multiboot_information_address) };
|
let boot_info = unsafe{ multiboot2::load(multiboot_information_address) };
|
||||||
let memory_map_tag = boot_info.memory_map_tag().expect("Memory map tag required");
|
let memory_map_tag = boot_info.memory_map_tag()
|
||||||
|
.expect("Memory map tag required");
|
||||||
|
|
||||||
println!("memory areas:");
|
println!("memory areas:");
|
||||||
for area in memory_map_tag.memory_areas() {
|
for area in memory_map_tag.memory_areas() {
|
||||||
println!(" start: 0x{:x}, length: 0x{:x}", area.base_addr, area.length);
|
println!(" start: 0x{:x}, length: 0x{:x}",
|
||||||
|
area.base_addr, area.length);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The `load` function is `unsafe` because it relies on a valid address. Since the memory tag is not required by the Multiboot specification, the `memory_map_tag()` function returns an `Option`. The `memory_areas()` function returns the desired memory area iterator.
|
The `load` function is `unsafe` because it relies on a valid address. Since the memory tag is not required by the Multiboot specification, the `memory_map_tag()` function returns an `Option`. The `memory_areas()` function returns the desired memory area iterator.
|
||||||
@@ -397,7 +399,7 @@ The [next post] will be about paging again. We will use the frame allocator to c
|
|||||||
[next post]: {{% relref "2015-12-09-modifying-page-tables.md" %}}
|
[next post]: {{% relref "2015-12-09-modifying-page-tables.md" %}}
|
||||||
|
|
||||||
## Recommended Posts
|
## Recommended Posts
|
||||||
Eric Kidd started the [Bare Metal Rust] series last week. Like this post, it builds upon the code from [Printing to Screen], but tries to support keyboard input instead of wrestling through memory management details ;).
|
Eric Kidd started the [Bare Metal Rust] series last week. Like this post, it builds upon the code from [Printing to Screen], but tries to support keyboard input instead of wrestling through memory management details.
|
||||||
|
|
||||||
[Bare Metal Rust]: http://www.randomhacks.net/bare-metal-rust/
|
[Bare Metal Rust]: http://www.randomhacks.net/bare-metal-rust/
|
||||||
[Printing to Screen]: {{% relref "2015-10-23-printing-to-screen.md" %}}
|
[Printing to Screen]: {{% relref "2015-10-23-printing-to-screen.md" %}}
|
||||||
|
|||||||
@@ -137,7 +137,9 @@ To extract the physical address, we add a `pointed_frame` method:
|
|||||||
```rust
|
```rust
|
||||||
pub fn pointed_frame(&self) -> Option<Frame> {
|
pub fn pointed_frame(&self) -> Option<Frame> {
|
||||||
if self.flags().contains(PRESENT) {
|
if self.flags().contains(PRESENT) {
|
||||||
Some(Frame::containing_address(self.0 as usize & 0x000fffff_fffff000))
|
Some(Frame::containing_address(
|
||||||
|
self.0 as usize & 0x000fffff_fffff000
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -319,7 +321,9 @@ Note that `self` stays borrowed as long as the returned reference is valid. This
|
|||||||
```rust
|
```rust
|
||||||
pub fn next_table<'a>(&'a self, index: usize) -> Option<&'a Table> {...}
|
pub fn next_table<'a>(&'a self, index: usize) -> Option<&'a Table> {...}
|
||||||
|
|
||||||
pub fn next_table_mut<'a>(&'a mut self, index: usize) -> Option<&'a mut Table> {...}
|
pub fn next_table_mut<'a>(&'a mut self, index: usize)
|
||||||
|
-> Option<&'a mut Table>
|
||||||
|
{...}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the additional lifetime parameters, which are identical for input and output references. That's exactly what we want. It ensures that we can't modify tables as long as we have references to lower tables. For example, it would be very bad if we could unmap a P3 table if we still write to one of its P2 tables.
|
Note the additional lifetime parameters, which are identical for input and output references. That's exactly what we want. It ensures that we can't modify tables as long as we have references to lower tables. For example, it would be very bad if we could unmap a P3 table if we still write to one of its P2 tables.
|
||||||
@@ -397,7 +401,8 @@ impl<L> Table<L> where L: HierarchicalLevel
|
|||||||
{
|
{
|
||||||
pub fn next_table(&self, index: usize) -> Option<&Table<???>> {...}
|
pub fn next_table(&self, index: usize) -> Option<&Table<???>> {...}
|
||||||
|
|
||||||
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<???>> {...}
|
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<???>>
|
||||||
|
{...}
|
||||||
|
|
||||||
fn next_table_address(&self, index: usize) -> Option<usize> {...}
|
fn next_table_address(&self, index: usize) -> Option<usize> {...}
|
||||||
}
|
}
|
||||||
@@ -454,7 +459,9 @@ Remember that this is bare metal kernel code. We just used type system magic to
|
|||||||
Now let's do something useful with our new module. We will create a function that translates a virtual address to the corresponding physical address. We add it to the `paging/mod.rs` module:
|
Now let's do something useful with our new module. We will create a function that translates a virtual address to the corresponding physical address. We add it to the `paging/mod.rs` module:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub fn translate(virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
|
pub fn translate(virtual_address: VirtualAddress)
|
||||||
|
-> Option<PhysicalAddress>
|
||||||
|
{
|
||||||
let offset = virtual_address % PAGE_SIZE;
|
let offset = virtual_address % PAGE_SIZE;
|
||||||
translate_page(Page::containing_address(virtual_address))
|
translate_page(Page::containing_address(virtual_address))
|
||||||
.map(|frame| frame.number * PAGE_SIZE + offset)
|
.map(|frame| frame.number * PAGE_SIZE + offset)
|
||||||
@@ -464,7 +471,8 @@ It uses two functions we haven't defined yet: `translate_page` and `Page::contai
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub fn containing_address(address: VirtualAddress) -> Page {
|
pub fn containing_address(address: VirtualAddress) -> Page {
|
||||||
assert!(address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000,
|
assert!(address < 0x0000_8000_0000_0000 ||
|
||||||
|
address >= 0xffff_8000_0000_0000,
|
||||||
"invalid address: 0x{:x}", address);
|
"invalid address: 0x{:x}", address);
|
||||||
Page { number: address / PAGE_SIZE }
|
Page { number: address / PAGE_SIZE }
|
||||||
}
|
}
|
||||||
@@ -543,8 +551,8 @@ p3.and_then(|p3| {
|
|||||||
// address must be 1GiB aligned
|
// address must be 1GiB aligned
|
||||||
assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0);
|
assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0);
|
||||||
return Some(Frame {
|
return Some(Frame {
|
||||||
number: start_frame.number + page.p2_index() * ENTRY_COUNT +
|
number: start_frame.number + page.p2_index() *
|
||||||
page.p1_index(),
|
ENTRY_COUNT + page.p1_index(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,7 +581,8 @@ Let's add a function that modifies the page tables to map a `Page` to a `Frame`:
|
|||||||
pub use self::entry::*;
|
pub use self::entry::*;
|
||||||
use memory::FrameAllocator;
|
use memory::FrameAllocator;
|
||||||
|
|
||||||
pub fn map_to<A>(page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A)
|
pub fn map_to<A>(page: Page, frame: Frame, flags: EntryFlags,
|
||||||
|
allocator: &mut A)
|
||||||
where A: FrameAllocator
|
where A: FrameAllocator
|
||||||
{
|
{
|
||||||
let p4 = unsafe { &mut *P4 };
|
let p4 = unsafe { &mut *P4 };
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ updated = "2016-03-06"
|
|||||||
|
|
||||||
In this post we will create a new page table to map the kernel sections correctly. Therefor we will extend the paging module to support modifications of _inactive_ page tables as well. Then we will switch to the new table and secure our kernel stack by creating a guard page.
|
In this post we will create a new page table to map the kernel sections correctly. Therefor we will extend the paging module to support modifications of _inactive_ page tables as well. Then we will switch to the new table and secure our kernel stack by creating a guard page.
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
As always, you can find the source code on [Github]. Don't hesitate to file issues there if you have any problems or improvement suggestions. There is also a comment section at the end of this page. Note that this post requires a current Rust nightly.
|
As always, you can find the source code on [Github]. Don't hesitate to file issues there if you have any problems or improvement suggestions. There is also a comment section at the end of this page. Note that this post requires a current Rust nightly.
|
||||||
|
|
||||||
[Github]: https://github.com/phil-opp/blog_os/tree/remap_the_kernel
|
[Github]: https://github.com/phil-opp/blog_os/tree/remap_the_kernel
|
||||||
@@ -27,16 +29,16 @@ _Updates_:
|
|||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
In the [previous post], we had a strange bug in the `unmap` function. Its reason was a silent stack overflow, which corrupted the page tables. Fortunately, our kernel stack is right above the page tables so that we noticed the overflow relatively quickly. This won't be the case when we add threads with new stacks in the future. Then a silent stack overflow could overwrite some data without us noticing. But eventually some completely unrelated function fails because a variable changed its value.
|
In the [previous post], we had a strange bug in the `unmap` function. Its reason was a silent stack overflow, which corrupted the page tables. Fortunately, our kernel stack is right above the page tables so that we noticed the overflow relatively quickly. This won't be the case when we add threads with new stacks in the future. Then a silent stack overflow could overwrite some data without us noticing. But eventually some completely unrelated function fails because a variable changed its value.
|
||||||
[previous post]: {{ page.previous.url }}
|
[previous post]: {{% relref "2015-12-09-modifying-page-tables.md" %}}
|
||||||
|
|
||||||
As you can imagine, these kinds of bugs are horrendous to debug. For that reason we will create a new hierarchical page table in this post, which has _guard page_ below the stack. A guard page is basically an unmapped page that causes a page fault when accessed. Thus we can catch stack overflows right when they happen.
|
As you can imagine, these kinds of bugs are horrendous to debug. For that reason we will create a new hierarchical page table in this post, which has _guard page_ below the stack. A guard page is basically an unmapped page that causes a page fault when accessed. Thus we can catch stack overflows right when they happen.
|
||||||
|
|
||||||
Also, we will use the [information about kernel sections] to map the various sections individually instead of blindly mapping the first gigabyte. To improve safety even further, we will set the correct page table flags for the various sections. Thus it won't be possible to modify the contents of `.text` or to execute code from `.data` anymore.
|
Also, we will use the [information about kernel sections] to map the various sections individually instead of blindly mapping the first gigabyte. To improve safety even further, we will set the correct page table flags for the various sections. Thus it won't be possible to modify the contents of `.text` or to execute code from `.data` anymore.
|
||||||
[information about kernel sections]: {% post_url 2015-11-15-allocating-frames %}#kernel-elf-sections
|
[information about kernel sections]: {{% relref "2015-11-15-allocating-frames.md#kernel-elf-sections" %}}
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
There are many things that can go wrong when we switch to a new table. Therefore it's a good idea to [set up a debugger][set up gdb]. You should not need it when you follow this post, but it's good to know how to debug a problem when it occurs[^fn-debug-notes].
|
There are many things that can go wrong when we switch to a new table. Therefore it's a good idea to [set up a debugger][set up gdb]. You should not need it when you follow this post, but it's good to know how to debug a problem when it occurs[^fn-debug-notes].
|
||||||
[set up gdb]: /set-up-gdb.html
|
[set up gdb]: {{% relref "set-up-gdb.md" %}}
|
||||||
[^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.
|
[^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.
|
||||||
|
|
||||||
We also update the `Page` and `Frame` types to make our lives easier. The `Page` struct gets some derived traits:
|
We also update the `Page` and `Frame` types to make our lives easier. The `Page` struct gets some derived traits:
|
||||||
@@ -275,8 +277,8 @@ Now our `TemporaryPage` type is nearly complete. We only add one more method for
|
|||||||
```rust
|
```rust
|
||||||
use super::table::{Table, Level1};
|
use super::table::{Table, Level1};
|
||||||
|
|
||||||
/// Maps the temporary page to the given page table frame in the active table.
|
/// Maps the temporary page to the given page table frame in the active
|
||||||
/// Returns a reference to the now mapped table.
|
/// table. Returns a reference to the now mapped table.
|
||||||
pub fn map_table_frame(&mut self,
|
pub fn map_table_frame(&mut self,
|
||||||
frame: Frame,
|
frame: Frame,
|
||||||
active_table: &mut ActivePageTable)
|
active_table: &mut ActivePageTable)
|
||||||
@@ -285,7 +287,7 @@ pub fn map_table_frame(&mut self,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
This function interprets the given frame as a page table frame and returns a `Table` reference. We return a table of level 1 because it [forbids calling the `next_table` methods][some clever solution]. Calling `next_table` must not be possible since it's not a page of the recursive mapping. To be able to return a `Table<Level1>`, we need to make the `Level1` enum in `memory/paging/table.rs` public.
|
This function interprets the given frame as a page table frame and returns a `Table` reference. We return a table of level 1 because it [forbids calling the `next_table` methods][some clever solution]. Calling `next_table` must not be possible since it's not a page of the recursive mapping. To be able to return a `Table<Level1>`, we need to make the `Level1` enum in `memory/paging/table.rs` public.
|
||||||
[some clever solution]: {% post_url 2015-12-09-modifying-page-tables %}#some-clever-solution
|
[some clever solution]: {{% relref "2015-12-09-modifying-page-tables.md#some-clever-solution" %}}
|
||||||
|
|
||||||
|
|
||||||
The `unsafe` block is safe since the `VirtualAddress` returned by the `map` function is always valid and the type cast just reinterprets the frame's content.
|
The `unsafe` block is safe since the `VirtualAddress` returned by the `map` function is always valid and the type cast just reinterprets the frame's content.
|
||||||
@@ -317,7 +319,8 @@ impl InactivePageTable {
|
|||||||
temporary_page: &mut TemporaryPage)
|
temporary_page: &mut TemporaryPage)
|
||||||
-> InactivePageTable {
|
-> InactivePageTable {
|
||||||
{
|
{
|
||||||
let table = temporary_page.map_table_frame(frame.clone(), active_table);
|
let table = temporary_page.map_table_frame(frame.clone(),
|
||||||
|
active_table);
|
||||||
// now we are able to zero the table
|
// now we are able to zero the table
|
||||||
table.zero();
|
table.zero();
|
||||||
// set up recursive mapping for the table
|
// set up recursive mapping for the table
|
||||||
@@ -461,7 +464,9 @@ To backup the physical P4 frame of the active table, we can either read it from
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use x86::controlregs;
|
use x86::controlregs;
|
||||||
let backup = Frame::containing_address(unsafe { controlregs::cr3() } as usize);
|
let backup = Frame::containing_address(
|
||||||
|
unsafe { controlregs::cr3() } as usize
|
||||||
|
);
|
||||||
```
|
```
|
||||||
Why is it unsafe? Because reading the CR3 register leads to a CPU exception if the processor is not running in kernel mode ([Ring 0]). But this code will always run in kernel mode, so the `unsafe` block is completely safe here.
|
Why is it unsafe? Because reading the CR3 register leads to a CPU exception if the processor is not running in kernel mode ([Ring 0]). But this code will always run in kernel mode, so the `unsafe` block is completely safe here.
|
||||||
|
|
||||||
@@ -552,7 +557,7 @@ pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
|
|||||||
First, we create a temporary page at page number `0xcafebabe`. We could use `0xdeadbeaf` or `0x123456789` as well, as long as the page is unused. The `active_table` and the `new_table` are created using their constructor functions.
|
First, we create a temporary page at page number `0xcafebabe`. We could use `0xdeadbeaf` or `0x123456789` as well, as long as the page is unused. The `active_table` and the `new_table` are created using their constructor functions.
|
||||||
|
|
||||||
Then we use the `with` function to temporary change the recursive mapping and execute the closure as if the `new_table` were active. This allows us to map the sections in the new table without changing the active mapping. To get the kernel sections, we use the [Multiboot information structure].
|
Then we use the `with` function to temporary change the recursive mapping and execute the closure as if the `new_table` were active. This allows us to map the sections in the new table without changing the active mapping. To get the kernel sections, we use the [Multiboot information structure].
|
||||||
[Multiboot information structure]: {% post_url 2015-11-15-allocating-frames %}#the-multiboot-information-structure
|
[Multiboot information structure]: {{% relref "2015-11-15-allocating-frames.md#the-multiboot-information-structure" %}}
|
||||||
|
|
||||||
Let's resolve the above `TODO` by identity mapping the sections:
|
Let's resolve the above `TODO` by identity mapping the sections:
|
||||||
|
|
||||||
@@ -693,7 +698,9 @@ Time to test it! We reexport the `remap_the_kernel` function from the memory mod
|
|||||||
```rust
|
```rust
|
||||||
// in src/memory/mod.rs
|
// in src/memory/mod.rs
|
||||||
pub use self::paging::remap_the_kernel;
|
pub use self::paging::remap_the_kernel;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
// in src/lib.rs
|
// in src/lib.rs
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
||||||
@@ -703,15 +710,18 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
|||||||
vga_buffer::clear_screen();
|
vga_buffer::clear_screen();
|
||||||
println!("Hello World{}", "!");
|
println!("Hello World{}", "!");
|
||||||
|
|
||||||
let boot_info = unsafe { multiboot2::load(multiboot_information_address) };
|
let boot_info = unsafe {
|
||||||
|
multiboot2::load(multiboot_information_address)
|
||||||
|
};
|
||||||
let memory_map_tag = boot_info.memory_map_tag()
|
let memory_map_tag = boot_info.memory_map_tag()
|
||||||
.expect("Memory map tag required");
|
.expect("Memory map tag required");
|
||||||
let elf_sections_tag = boot_info.elf_sections_tag()
|
let elf_sections_tag = boot_info.elf_sections_tag()
|
||||||
.expect("Elf sections tag required");
|
.expect("Elf sections tag required");
|
||||||
|
|
||||||
let kernel_start = elf_sections_tag.sections().map(|s| s.addr).min().unwrap();
|
let kernel_start = elf_sections_tag.sections().map(|s| s.addr)
|
||||||
let kernel_end = elf_sections_tag.sections().map(|s| s.addr + s.size).max()
|
.min().unwrap();
|
||||||
.unwrap();
|
let kernel_end = elf_sections_tag.sections().map(|s| s.addr + s.size)
|
||||||
|
.max().unwrap();
|
||||||
|
|
||||||
let multiboot_start = multiboot_information_address;
|
let multiboot_start = multiboot_information_address;
|
||||||
let multiboot_end = multiboot_start + (boot_info.total_size as usize);
|
let multiboot_end = multiboot_start + (boot_info.total_size as usize);
|
||||||
@@ -748,7 +758,9 @@ pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable {
|
|||||||
use x86::controlregs;
|
use x86::controlregs;
|
||||||
|
|
||||||
let old_table = InactivePageTable {
|
let old_table = InactivePageTable {
|
||||||
p4_frame: Frame::containing_address(unsafe { controlregs::cr3() } as usize),
|
p4_frame: Frame::containing_address(
|
||||||
|
unsafe { controlregs::cr3() } as usize
|
||||||
|
),
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
controlregs::cr3_write(new_table.p4_frame.start_address() as u64);
|
controlregs::cr3_write(new_table.p4_frame.start_address() as u64);
|
||||||
@@ -778,7 +790,7 @@ Let's cross our fingers and run it…
|
|||||||
|
|
||||||
### Debugging
|
### Debugging
|
||||||
A QEMU boot load indicates that some CPU exception occured. We can see all thrown CPU exception by starting QEMU with `-d int` (as described [here][qemu debugging]):
|
A QEMU boot load indicates that some CPU exception occured. We can see all thrown CPU exception by starting QEMU with `-d int` (as described [here][qemu debugging]):
|
||||||
[qemu debugging]: {% post_url 2015-09-02-set-up-rust %}#debugging
|
[qemu debugging]: {{% relref "2015-09-02-set-up-rust.md#debugging" %}}
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
> qemu-system-x86_64 -d int -no-reboot -cdrom build/os-x86_64.iso
|
> qemu-system-x86_64 -d int -no-reboot -cdrom build/os-x86_64.iso
|
||||||
@@ -798,12 +810,12 @@ These lines are the important ones. We can read many useful information from the
|
|||||||
[page fault error code]: http://wiki.osdev.org/Exceptions#Error_code
|
[page fault error code]: http://wiki.osdev.org/Exceptions#Error_code
|
||||||
|
|
||||||
- `IP=0008:000000000010ab97` or `pc=000000000010ab97`: The program counter register tells us that the exception occurred when the CPU tried to execute the instruction at `0x10ab97`. We can disassemble this address to see the corresponding function. The `0008:` prefix in `IP` indicates the code [GDT segment].
|
- `IP=0008:000000000010ab97` or `pc=000000000010ab97`: The program counter register tells us that the exception occurred when the CPU tried to execute the instruction at `0x10ab97`. We can disassemble this address to see the corresponding function. The `0008:` prefix in `IP` indicates the code [GDT segment].
|
||||||
[GDT segment]: {% post_url 2015-08-25-entering-longmode %}#loading-the-gdt
|
[GDT segment]: {{% relref "2015-08-25-entering-longmode.md#loading-the-gdt" %}}
|
||||||
|
|
||||||
- `SP=0010:00000000001182d0`: The stack pointer was `0x1182d0` (the `0010:` prefix indicates the data [GDT segment]). This tells us if it the stack overflowed.
|
- `SP=0010:00000000001182d0`: The stack pointer was `0x1182d0` (the `0010:` prefix indicates the data [GDT segment]). This tells us if it the stack overflowed.
|
||||||
|
|
||||||
- `CR2=00000000000b8f00`: Finally the most useful register. It tells us which virtual address caused the page fault. In our case it's `0xb8f00`, which is part of the [VGA text buffer].
|
- `CR2=00000000000b8f00`: Finally the most useful register. It tells us which virtual address caused the page fault. In our case it's `0xb8f00`, which is part of the [VGA text buffer].
|
||||||
[VGA text buffer]: {% post_url 2015-10-23-printing-to-screen %}#the-vga-text-buffer
|
[VGA text buffer]: {{% relref "2015-10-23-printing-to-screen.md#the-vga-text-buffer" %}}
|
||||||
|
|
||||||
So let's find out which function caused the exception:
|
So let's find out which function caused the exception:
|
||||||
|
|
||||||
@@ -998,7 +1010,7 @@ If we haven't forgotten to set the `WRITABLE` flag somewhere, it should still wo
|
|||||||
The final step is to create a guard page for our kernel stack.
|
The final step is to create a guard page for our kernel stack.
|
||||||
|
|
||||||
The decision to place the kernel stack right above the page tables was already useful to detect a silent stack overflow in the [previous post][silent stack overflow]. Now we profit from it again. Let's look at our assembly `.bss` section again to understand why:
|
The decision to place the kernel stack right above the page tables was already useful to detect a silent stack overflow in the [previous post][silent stack overflow]. Now we profit from it again. Let's look at our assembly `.bss` section again to understand why:
|
||||||
[silent stack overflow]: {% post_url 2015-12-09-modifying-page-tables %}#translate
|
[silent stack overflow]: {{% relref "2015-12-09-modifying-page-tables.md#translate" %}}
|
||||||
|
|
||||||
```nasm
|
```nasm
|
||||||
; in src/arch/x86_64/boot.asm
|
; in src/arch/x86_64/boot.asm
|
||||||
@@ -1032,7 +1044,9 @@ pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
|
|||||||
// below is the new part
|
// below is the new part
|
||||||
|
|
||||||
// turn the old p4 page into a guard page
|
// turn the old p4 page into a guard page
|
||||||
let old_p4_page = Page::containing_address(old_table.p4_frame.start_address());
|
let old_p4_page = Page::containing_address(
|
||||||
|
old_table.p4_frame.start_address()
|
||||||
|
);
|
||||||
active_table.unmap(old_p4_page, allocator);
|
active_table.unmap(old_p4_page, allocator);
|
||||||
println!("guard page at {:#x}", old_p4_page.start_address());
|
println!("guard page at {:#x}", old_p4_page.start_address());
|
||||||
}
|
}
|
||||||
@@ -1056,7 +1070,7 @@ Unfortunately stack probes require compiler support. They already work on Window
|
|||||||
|
|
||||||
## What's next?
|
## What's next?
|
||||||
Now that we have a (mostly) safe kernel stack and a working page table module, we can add a virtual memory allocator. The [next post] will explore Rust's allocator API and create a very basic allocator. At the end of that post, we will be able to use Rust's allocation and collections types such as [Box], [Vec], or even [BTreeMap].
|
Now that we have a (mostly) safe kernel stack and a working page table module, we can add a virtual memory allocator. The [next post] will explore Rust's allocator API and create a very basic allocator. At the end of that post, we will be able to use Rust's allocation and collections types such as [Box], [Vec], or even [BTreeMap].
|
||||||
[next post]: {{ page.next.url }}
|
[next post]: {{% relref "2016-04-11-kernel-heap.md" %}}
|
||||||
[Box]: https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html
|
[Box]: https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html
|
||||||
[Vec]: https://doc.rust-lang.org/nightly/collections/vec/struct.Vec.html
|
[Vec]: https://doc.rust-lang.org/nightly/collections/vec/struct.Vec.html
|
||||||
[BTreeMap]: https://doc.rust-lang.org/nightly/collections/struct.BTreeMap.html
|
[BTreeMap]: https://doc.rust-lang.org/nightly/collections/struct.BTreeMap.html
|
||||||
|
|||||||
Reference in New Issue
Block a user