526: Rewrite breakpoint test r=phil-opp a=phil-opp

The current test only tests the x86_64 crate, not any property of our kernel. This was reported in https://github.com/phil-opp/blog_os/issues/450#issuecomment-455771614.

529: Fixing typos in paging introduction r=phil-opp a=kolemannix

Love this series and thought I'd contribute in whatever way I could. Thanks for the great work.

Looking forward to the next post!

Co-authored-by: Philipp Oppermann <dev@phil-opp.com>
Co-authored-by: Koleman Nix <kolemannix@gmail.com>
This commit is contained in:
bors[bot]
2019-01-21 08:08:34 +00:00
3 changed files with 27 additions and 89 deletions

View File

@@ -404,65 +404,42 @@ Let's create an integration test that ensures that the above continues to work.
```rust
// in src/bin/test-exception-breakpoint.rs
[…]
use core::sync::atomic::{AtomicUsize, Ordering};
#![no_std]
#![cfg_attr(not(test), no_main)]
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
static BREAKPOINT_HANDLER_CALLED: AtomicUsize = AtomicUsize::new(0);
use core::panic::PanicInfo;
use blog_os::{exit_qemu, serial_println};
#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn _start() -> ! {
init_test_idt();
blog_os::interrupts::init_idt();
// invoke a breakpoint exception
x86_64::instructions::int3();
match BREAKPOINT_HANDLER_CALLED.load(Ordering::SeqCst) {
1 => serial_println!("ok"),
0 => {
serial_println!("failed");
serial_println!("Breakpoint handler was not called.");
}
other => {
serial_println!("failed");
serial_println!("Breakpoint handler was called {} times", other);
}
}
serial_println!("ok");
unsafe { exit_qemu(); }
loop {}
}
lazy_static! {
static ref TEST_IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt
};
}
#[cfg(not(test))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
serial_println!("failed");
pub fn init_test_idt() {
TEST_IDT.load();
}
serial_println!("{}", info);
extern "x86-interrupt" fn breakpoint_handler(
_stack_frame: &mut ExceptionStackFrame)
{
BREAKPOINT_HANDLER_CALLED.fetch_add(1, Ordering::SeqCst);
unsafe { exit_qemu(); }
loop {}
}
[…]
```
For space reasons we don't show the full content here. You can find the full file [on Github](https://github.com/phil-opp/blog_os/blob/master/src/bin/test-exception-breakpoint.rs).
It is similar to our `main.rs`, but instead of printing "It did not crash!" to the VGA buffer, it prints "ok" to the serial output and calls `exit_qemu`. This allows the `bootimage` tool to detect that our code successfully continued after invoking the `int3` instruction. If our `panic_handler` is called, we instead print `failed` to signalize the failure to `bootimage`.
It is similar to our `main.rs`, but uses a custom IDT called `TEST_IDT` and different `_start` and `breakpoint_handler` functions. The most interesting part is the `BREAKPOINT_HANDLER_CALLED` static. It is an [`AtomicUsize`], an integer type that can be safely concurrently modified because all of its operations are atomic. We increment it when the `breakpoint_handler` is called and verify in our `_start` function that the handler was called exactly once.
[`AtomicUsize`]: https://doc.rust-lang.org/core/sync/atomic/struct.AtomicUsize.html
The [`Ordering`] parameter specifies the desired guarantees of the atomic operations. The `SeqCst` ordering means “sequential consistent” and gives the strongest guarantees. It is a good default, because weaker orderings can have undesired effects.
[`Ordering`]: https://doc.rust-lang.org/core/sync/atomic/enum.Ordering.html
You can try this new test by running `bootimage test`.
### Fixing `cargo test` on Windows

View File

@@ -169,9 +169,9 @@ With these indices, we can now walk the page table hierarchy to determine the ma
![The same example 4-level page hierarchy with 5 additional arrows: "Step 0" from the CR3 register to the level 4 table, "Step 1" from the level 4 entry to the level 3 table, "Step 2" from the level 3 entry to the level 2 table, "Step 3" from the level 2 entry to the level 1 table, and "Step 4" from the level 1 table to the mapped frames.](x86_64-page-table-translation-steps.svg)
The permissions for the page in the level 1 table are `r`, which means read-only. The hardware enforces this permissions and would throw an exception if we tried to write to that page. Permissions in higher level pages restrict the possible permissions in lower level, so if we set the level 3 entry to read-only, no pages that use this entry can be writable, even if lower levels specify read/write permissions.
The permissions for the page in the level 1 table are `r`, which means read-only. The hardware enforces these permissions and would throw an exception if we tried to write to that page. Permissions in higher level pages restrict the possible permissions in lower level, so if we set the level 3 entry to read-only, no pages that use this entry can be writable, even if lower levels specify read/write permissions.
It's important to note that even through this example used only a single instance of each table, there are typically multiple instances of each level in each address space. At maximum, there are:
It's important to note that even though this example used only a single instance of each table, there are typically multiple instances of each level in each address space. At maximum, there are:
- one level 4 table,
- 512 level 3 tables (because the level 4 table has 512 entries),
@@ -213,7 +213,7 @@ We see that only bits 1251 are used to store the physical frame address, the
Let's take a closer look at the available flags:
- The `present` flag differentiates mapped pages from unmapped ones. It can be used to temporary swap out pages to disk when main memory becomes full. When the page is accessed subsequently, a special exception called _page fault_ occurs, to which the operating system can react by reloading the missing page from disk and then continuing the program.
- The `present` flag differentiates mapped pages from unmapped ones. It can be used to temporarily swap out pages to disk when main memory becomes full. When the page is accessed subsequently, a special exception called _page fault_ occurs, to which the operating system can react by reloading the missing page from disk and then continuing the program.
- The `writable` and `no execute` flags control whether the contents of the page are writable or contain executable instructions respectively.
- The `accessed` and `dirty` flags are automatically set by the CPU when a read or write to the page occurs. This information can be leveraged by the operating system e.g. to decide which pages to swap out or whether the page contents were modified since the last save to disk.
- The `write through caching` and `disable cache` flags allow to control the caches for every page individually.

View File

@@ -1,70 +1,31 @@
#![feature(abi_x86_interrupt)]
#![no_std]
#![cfg_attr(not(test), no_main)]
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
use blog_os::{exit_qemu, serial_println};
use core::panic::PanicInfo;
use core::sync::atomic::{AtomicUsize, Ordering};
use lazy_static::lazy_static;
static BREAKPOINT_HANDLER_CALLED: AtomicUsize = AtomicUsize::new(0);
use blog_os::{exit_qemu, serial_println};
#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn _start() -> ! {
init_test_idt();
blog_os::interrupts::init_idt();
// invoke a breakpoint exception
x86_64::instructions::int3();
match BREAKPOINT_HANDLER_CALLED.load(Ordering::SeqCst) {
1 => serial_println!("ok"),
0 => {
serial_println!("failed");
serial_println!("Breakpoint handler was not called.");
}
other => {
serial_println!("failed");
serial_println!("Breakpoint handler was called {} times", other);
}
}
unsafe {
exit_qemu();
}
serial_println!("ok");
unsafe { exit_qemu(); }
loop {}
}
/// This function is called on panic.
#[cfg(not(test))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
serial_println!("failed");
serial_println!("{}", info);
unsafe {
exit_qemu();
}
unsafe { exit_qemu(); }
loop {}
}
use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};
lazy_static! {
static ref TEST_IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt
};
}
pub fn init_test_idt() {
TEST_IDT.load();
}
extern "x86-interrupt" fn breakpoint_handler(_stack_frame: &mut ExceptionStackFrame) {
BREAKPOINT_HANDLER_CALLED.fetch_add(1, Ordering::SeqCst);
}