mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-17 06:47:49 +00:00
Update CPU Exceptions post for new test system
This commit is contained in:
@@ -371,8 +371,21 @@ pub fn init_idt() {
|
|||||||
|
|
||||||
Note how this solution requires no `unsafe` blocks. The `lazy_static!` macro does use `unsafe` behind the scenes, but it is abstracted away in a safe interface.
|
Note how this solution requires no `unsafe` blocks. The `lazy_static!` macro does use `unsafe` behind the scenes, but it is abstracted away in a safe interface.
|
||||||
|
|
||||||
### Testing it
|
### Running it
|
||||||
Now we should be able to handle breakpoint exceptions! Let's try it in our `_start` function:
|
|
||||||
|
The last step for making exceptions work in our kernel it to call the `init_idt` function from our `main.rs`. Instead of calling it directly, we introduce a general `init` function in our `lib.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in src/lib.rs
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
interrupts::init_idt();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With this function we now have a central place for initialization routines that can be shared between the different `_start` functions in our `main.rs`, `lib.rs`, and integration tests.
|
||||||
|
|
||||||
|
Now we can update the `_start` function of our `main.rs` to call `init` and then trigger a breakpoint exception:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// in src/main.rs
|
// in src/main.rs
|
||||||
@@ -382,10 +395,10 @@ Now we should be able to handle breakpoint exceptions! Let's try it in our `_sta
|
|||||||
pub extern "C" fn _start() -> ! {
|
pub extern "C" fn _start() -> ! {
|
||||||
println!("Hello World{}", "!");
|
println!("Hello World{}", "!");
|
||||||
|
|
||||||
blog_os::interrupts::init_idt();
|
blog_os::init(); // new
|
||||||
|
|
||||||
// invoke a breakpoint exception
|
// invoke a breakpoint exception
|
||||||
x86_64::instructions::interrupts::int3();
|
x86_64::instructions::interrupts::int3(); // new
|
||||||
|
|
||||||
println!("It did not crash!");
|
println!("It did not crash!");
|
||||||
loop {}
|
loop {}
|
||||||
@@ -402,47 +415,46 @@ We see that the interrupt stack frame tells us the instruction and stack pointer
|
|||||||
|
|
||||||
### Adding a Test
|
### Adding a Test
|
||||||
|
|
||||||
Let's create an integration test that ensures that the above continues to work. For that we create a file named `test-exception-breakpoint.rs`:
|
Let's create a test that ensures that the above continues to work. First, we update the `_start` function to also call `init`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// in src/bin/test-exception-breakpoint.rs
|
// in src/lib.rs
|
||||||
|
|
||||||
#![no_std]
|
#[cfg(test)]
|
||||||
#![cfg_attr(not(test), no_main)]
|
|
||||||
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
|
|
||||||
|
|
||||||
use core::panic::PanicInfo;
|
|
||||||
use blog_os::{exit_qemu, serial_println};
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn _start() -> ! {
|
pub extern "C" fn _start() -> ! {
|
||||||
blog_os::interrupts::init_idt();
|
init(); // new
|
||||||
|
|
||||||
x86_64::instructions::interrupts::int3();
|
test_main();
|
||||||
|
|
||||||
serial_println!("ok");
|
|
||||||
|
|
||||||
unsafe { exit_qemu(); }
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
|
||||||
#[panic_handler]
|
|
||||||
fn panic(info: &PanicInfo) -> ! {
|
|
||||||
serial_println!("failed");
|
|
||||||
|
|
||||||
serial_println!("{}", info);
|
|
||||||
|
|
||||||
unsafe { exit_qemu(); }
|
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
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`.
|
Remember, this `_start` function is used when running `cargo xtest --lib`, since Rust's tests the `lib.rs` completely independent of the `main.rs`. We need to call `init` here to set up an IDT before running the tests.
|
||||||
|
|
||||||
You can try this new test by running `bootimage test`.
|
Now we can create a `test_breakpoint_exception` test:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in src/interrupts.rs
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::{serial_print, serial_println};
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn test_breakpoint_exception() {
|
||||||
|
serial_print!("test_breakpoint_exception...");
|
||||||
|
// invoke a breakpoint exception
|
||||||
|
x86_64::instructions::interrupts::int3();
|
||||||
|
serial_println!("[ok]");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Apart from printing status messages through the [serial port], the test invokes the `int3` function to trigger a breakpoint exception. By checking that the execution continues afterwards, we verify that our breakpoint handler is working correctly.
|
||||||
|
|
||||||
|
[serial port]: ./second-edition/posts/04-testing/index.md#serial-port
|
||||||
|
|
||||||
|
You can try this new test by running `cargo xtest --lib`. You should see `test_breakpoint_exception...[ok]` in the output.
|
||||||
|
|
||||||
### Fixing `cargo test` on Windows
|
### Fixing `cargo test` on Windows
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user