mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
wip
This commit is contained in:
@@ -4,15 +4,12 @@ name = "blog_os"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
bit_field = "0.1.0"
|
||||
bitflags = "0.7.0"
|
||||
once = "0.2.1"
|
||||
rlibc = "0.1.4"
|
||||
spin = "0.3.4"
|
||||
|
||||
[dependencies.bit-field]
|
||||
git = "https://github.com/phil-opp/rust-bit-field.git"
|
||||
optional = false
|
||||
|
||||
[dependencies.hole_list_allocator]
|
||||
path = "libs/hole_list_allocator"
|
||||
|
||||
|
||||
@@ -1,19 +1,271 @@
|
||||
+++
|
||||
title = "CPU Exceptions"
|
||||
title = "Catching Exceptions"
|
||||
date = "2016-05-10"
|
||||
+++
|
||||
|
||||
TODO We will catch page faults,
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Interrupts
|
||||
Whenever a device (e.g. the keyboard contoller) needs
|
||||
Whenever a device (e.g. the keyboard contoller) needs
|
||||
|
||||
## Exceptions
|
||||
An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception when it should divide by 0. When an exception occurs, the CPU immediately calls a specific exception handler function, depending on the exception type.
|
||||
An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception if the current instruction tries to divide by 0. When an exception occurs, the CPU interrupts its current work and immediately calls a specific exception handler function, depending on the exception type.
|
||||
|
||||
We've already seen several types of exceptions in our kernel:
|
||||
|
||||
- **Illegal instruction**: TODO
|
||||
- **Page Fault**: The CPU tried to perform an illegal read or write.
|
||||
- **Double Fault**: TODO
|
||||
- **Triple Fault**:
|
||||
- **Invalid Opcode**: This exception occurs when the current instruction is invalid. For example, this exception occurred when we tried to use SSE instructions before enabling SSE. Without SSE, the CPU didn't know the `movups` and `movaps` instructions, so it throws an exception when it stumbles over them.
|
||||
- **Page Fault**: A page fault occurs on illegal memory accesses. For example, if the current instruction tries to read from an unmapped page or tries to write to a read-only page.
|
||||
- **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another exception exception occurs _while calling the exception handler_, the CPU raises a double fault exception. This exception also occurs when there is no handler function registered.
|
||||
- **Triple Fault**: If another exception occurs when the CPU tries to call the double fault handler function, it issues a fatal _triple fault_. We can't catch or handle a triple fault. Most processors react by resetting themselves and rebooting the operating system. This causes the bootloops we experienced in the previous posts.
|
||||
|
||||
The full list of
|
||||
For the full list of exceptions check out the [OSDev wiki][exceptions].
|
||||
|
||||
[exceptions]: http://wiki.osdev.org/Exceptions
|
||||
|
||||
### The Interrupt Descriptor Table
|
||||
In order to catch and handle exceptions, we have to set up a so-called _Interrupt Descriptor Table_ (IDT). In this table we can specify a handler function for each CPU exception. The hardware uses this table directly, so we need to follow a predefined format. Each entry must have the following 16-byte structure:
|
||||
|
||||
Bits | Name | Description
|
||||
--------|-----------------------------------|-----------------------------------
|
||||
0-15 | Function Pointer [0:15] | The lower bits of the pointer to the handler function.
|
||||
16-31 | GDT selector | Selector of a code segment in the GDT.
|
||||
32-34 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called.
|
||||
35-39 | Reserved (ignored) |
|
||||
40 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called.
|
||||
41-43 | must be one |
|
||||
44 | must be zero |
|
||||
45-46 | Descriptor Privilege Level (DPL) | The minimal required privilege level required for calling this handler.
|
||||
47 | Present |
|
||||
48-95 | Function Pointer [16:63] | The remaining bits of the pointer to the handler function.
|
||||
95-127 | Reserved (ignored) |
|
||||
|
||||
Each exception has a predefined IDT index. For example the invalid opcode exception has table index 6 and the page fault exception has table index 14. Thus, the hardware can automatically load the corresponding IDT entry for each exception. The [Exception Table][exceptions] in the OSDev wiki shows the IDT indexes of all exceptions in the “Vector nr.” column.
|
||||
|
||||
When an exception occurs, the CPU roughly does the following:
|
||||
|
||||
1. Read the corresponding entry from the Interrupt Descriptor Table (IDT). For example, the CPU reads the 14-th entry when a page fault occurs.
|
||||
2. Check if the entry is present. Raise a double fault if not.
|
||||
3. Push some registers on the stack, including the instruction pointer and the [EFLAGS] register. (We will use these values in a future post.)
|
||||
4. Disable interrupts if the entry is an interrupt gate (bit 40 not set).
|
||||
5. Load the specified GDT selector into the CS segment.
|
||||
6. Jump to the specified handler function.
|
||||
|
||||
[EFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register
|
||||
|
||||
## Handling Exceptions
|
||||
Let's try to catch and handle CPU exceptions. We start by creating a new `interrupts` module with an `idt` submodule:
|
||||
|
||||
``` rust
|
||||
// in src/lib.rs
|
||||
...
|
||||
mod interrupts;
|
||||
...
|
||||
```
|
||||
``` rust
|
||||
// src/interrupts/mod.rs
|
||||
|
||||
mod idt;
|
||||
```
|
||||
|
||||
Now we create types for the IDT and its entries:
|
||||
|
||||
```rust
|
||||
// src/interrupts/idt.rs
|
||||
|
||||
use x86::segmentation::SegmentSelector;
|
||||
|
||||
pub struct Idt([Entry; 16]);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Entry {
|
||||
pointer_low: u16,
|
||||
gdt_selector: SegmentSelector,
|
||||
options: EntryOptions,
|
||||
pointer_middle: u16,
|
||||
pointer_high: u32,
|
||||
reserved: u32,
|
||||
}
|
||||
```
|
||||
|
||||
The IDT is variable sized and can have up to 256 entries. We only need the first 16 entries in this post, so we define the table as `[Entry; 16]`. The remaining 240 handlers are treated as non-present by the CPU.
|
||||
|
||||
The `Entry` type is the translation of the above table to Rust. The `repr(C, packed)` attribute ensures that the compiler keeps the field ordering and does not add any padding between them. Instead of describing the `gdt_selector` as a plain `u16`, we use the `SegmentSelector` type of the `x86` crate. We also merge bits 32 to 47 into an `option` field, because Rust has no `u3` or `u1` type. The `EntryOptions` type is described below:
|
||||
|
||||
### Entry Options
|
||||
The `EntryOptions` type has the following skeleton:
|
||||
|
||||
``` rust
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EntryOptions(u16);
|
||||
|
||||
impl EntryOptions {
|
||||
fn new() -> Self {...}
|
||||
|
||||
fn set_present(&mut self, present: bool) {...}
|
||||
|
||||
fn disable_interrupts(&mut self, disable: bool) {...}
|
||||
|
||||
fn set_privilege_level(&mut self, dpl: u16) {...}
|
||||
|
||||
fn set_stack_index(&mut self, index: u16) {...}
|
||||
}
|
||||
```
|
||||
|
||||
The implementations of these methods need to modify the correct bits of the `u16` without touching the other bits. For example, we would need the following bit-fiddling to set the stack index:
|
||||
|
||||
``` rust
|
||||
self.0 = (self.0 & 0xfff8) | stack_index;
|
||||
```
|
||||
|
||||
Or alternatively:
|
||||
|
||||
``` rust
|
||||
self.0 = (self.0 & (!0b111)) | stack_index;
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
``` rust
|
||||
self.0 = ((self.0 >> 3) << 3) | stack_index;
|
||||
```
|
||||
|
||||
Well, none of these variants is really _readable_ and it's very easy to make mistakes somewhere. Therefore I created a `BitField` type with the following API:
|
||||
|
||||
``` rust
|
||||
self.0.set_range(0..3, stack_index);
|
||||
```
|
||||
|
||||
I think it is much more readable, since we abstracted away all bit-masking details. The `BitField` type is contained in the [bit_field] crate. (It's pretty new, so it might still contain bugs.) To add it as dependency, we run `cargo add bit_field` and add `extern crate bit_field;` to our `src/lib.rs`.
|
||||
|
||||
[bit_field]: TODO
|
||||
|
||||
Now we can use the crate to implement the methods of `EntryOptions`:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/idt.rs
|
||||
|
||||
use bit_field::BitField;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EntryOptions(BitField<u16>);
|
||||
|
||||
impl EntryOptions {
|
||||
fn minimal() -> Self {
|
||||
let mut options = BitField::new(0);
|
||||
options.set_range(9..12, 0b111); // required 'one' bits
|
||||
EntryOptions(options)
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut options = Self::minimal();
|
||||
options.set_present(true).disable_interrupts(true);
|
||||
options
|
||||
}
|
||||
|
||||
fn set_present(&mut self, present: bool) -> &mut Self {
|
||||
self.0.set_bit(15, present);
|
||||
self
|
||||
}
|
||||
|
||||
fn disable_interrupts(&mut self, disable: bool) -> &mut Self {
|
||||
self.0.set_bit(8, !disable);
|
||||
self
|
||||
}
|
||||
|
||||
fn set_privilege_level(&mut self, dpl: u16) -> &mut Self {
|
||||
self.0.set_range(13..15, dpl);
|
||||
self
|
||||
}
|
||||
|
||||
fn set_stack_index(&mut self, index: u16) -> &mut Self {
|
||||
self.0.set_range(0..3, index);
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
Note that the ranges are _exclusive_ the upper bound. The bit indexes are different from the values in the [above table], because the `option` field starts at bit 32. Thus the privilege level bits are bits 13 (`=45‑32`) and 14 (`=46‑32`).
|
||||
|
||||
The `minimal` function creates an `EntryOptions` type with only the “must-be-one” bits set. The `new` function, on the other hand, chooses reasonable defaults: It sets the present bit (why would you want to create a non-present entry?) and disables interrupts (normally we don't want that our exception handlers are interrupted). By returning the self pointer from the `set_*` methods, we allow easy method chaining such as `options.set_present(true).disable_interrupts(true)`.
|
||||
|
||||
[above table]: {{% relref "#the-interrupt-descriptor-table" %}}
|
||||
|
||||
### Creating IDT Entries
|
||||
Now we can add a function to create new IDT entries:
|
||||
|
||||
```rust
|
||||
type HandlerFunc = extern "C" fn() -> !;
|
||||
|
||||
impl Entry {
|
||||
pub fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self {
|
||||
let pointer = handler as u64;
|
||||
Entry {
|
||||
gdt_selector: gdt_selector,
|
||||
pointer_low: pointer as u16,
|
||||
pointer_middle: (pointer >> 16) as u16,
|
||||
pointer_high: (pointer >> 32) as u32,
|
||||
options: EntryOptions::new(),
|
||||
reserved: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
We chose `extern "C" fn() -> !` as handler function type, which is a function using the C [calling convention]. It takes no arguments and is [diverging] \(indicated by the `!` return type). TODO why?
|
||||
|
||||
[calling convention]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
[diverging]: https://doc.rust-lang.org/book/functions.html#diverging-functions
|
||||
|
||||
### IDT methods
|
||||
TODO
|
||||
|
||||
```rust
|
||||
impl Idt {
|
||||
pub fn new() -> Idt {
|
||||
Idt([Entry::missing(); 16])
|
||||
}
|
||||
|
||||
pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) {
|
||||
self.0[entry as usize] = Entry::new(segmentation::cs(), handler);
|
||||
}
|
||||
|
||||
pub fn options(&mut self, entry: u8) -> &mut EntryOptions {
|
||||
&mut self.0[entry as usize].options
|
||||
}
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn missing() -> Self {
|
||||
Entry {
|
||||
gdt_selector: 0,
|
||||
pointer_low: 0,
|
||||
pointer_middle: 0,
|
||||
pointer_high: 0,
|
||||
options: EntryOptions::minimal(),
|
||||
reserved: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### A static IDT
|
||||
TODO lazy_static etc
|
||||
|
||||
### Loading the IDT
|
||||
TODO
|
||||
|
||||
### Testing it
|
||||
TODO page fault, some other fault to trigger double fault, kernel stack overflow
|
||||
|
||||
## Switching stacks
|
||||
|
||||
### The Interrupt Stack Table
|
||||
|
||||
### The Task State Segment
|
||||
|
||||
### The Global Descriptor Table (again)
|
||||
|
||||
### Putting it together
|
||||
|
||||
## What's next?
|
||||
|
||||
@@ -6,12 +6,15 @@ impl Idt {
|
||||
Idt([Entry::missing(); 16])
|
||||
}
|
||||
|
||||
pub fn set_handler(&mut self, entry: usize, handler: extern "C" fn() -> !) {
|
||||
self.0[entry] = Entry::new(EntryType::InterruptGate, 0x8, handler);
|
||||
pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) {
|
||||
let segment: u16;
|
||||
unsafe { asm!("mov %cs, $0" : "=r" (segment) ) };
|
||||
let code_segment = SegmentSelector::from_raw(segment);
|
||||
self.0[entry as usize] = Entry::new(code_segment, handler);
|
||||
}
|
||||
|
||||
pub fn set_interrupt_stack(&mut self, entry: usize, stack_index: u16) {
|
||||
self.0[entry].options.set_stack_index(stack_index);
|
||||
pub fn options(&mut self, entry: u8) -> &mut EntryOptions {
|
||||
&mut self.0[entry as usize].options
|
||||
}
|
||||
|
||||
pub unsafe fn load(&'static self) {
|
||||
@@ -28,75 +31,77 @@ impl Idt {
|
||||
}
|
||||
|
||||
use bit_field::BitField;
|
||||
use x86::segmentation::SegmentSelector;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Entry {
|
||||
target_low: u16,
|
||||
gdt_selector: u16,
|
||||
pointer_low: u16,
|
||||
gdt_selector: SegmentSelector,
|
||||
options: EntryOptions,
|
||||
target_middle: u16,
|
||||
target_high: u32,
|
||||
pointer_middle: u16,
|
||||
pointer_high: u32,
|
||||
reserved: u32,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn missing() -> Entry {
|
||||
fn missing() -> Self {
|
||||
Entry {
|
||||
gdt_selector: 0,
|
||||
target_low: 0,
|
||||
target_middle: 0,
|
||||
target_high: 0,
|
||||
options: MISSING,
|
||||
gdt_selector: SegmentSelector::new(0),
|
||||
pointer_low: 0,
|
||||
pointer_middle: 0,
|
||||
pointer_high: 0,
|
||||
options: EntryOptions::minimal(),
|
||||
reserved: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(ty: EntryType, gdt_selector: u16, handler: extern "C" fn() -> !) -> Entry {
|
||||
let target = handler as u64;
|
||||
|
||||
pub fn new(gdt_selector: SegmentSelector, handler: extern "C" fn() -> !) -> Self {
|
||||
let pointer = handler as u64;
|
||||
Entry {
|
||||
gdt_selector: gdt_selector,
|
||||
target_low: target as u16,
|
||||
target_middle: (target >> 16) as u16,
|
||||
target_high: (target >> 32) as u32,
|
||||
options: EntryOptions::new(ty),
|
||||
pointer_low: pointer as u16,
|
||||
pointer_middle: (pointer >> 16) as u16,
|
||||
pointer_high: (pointer >> 32) as u32,
|
||||
options: EntryOptions::new(),
|
||||
reserved: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MISSING: EntryOptions = EntryOptions(BitField::new(0));
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EntryOptions(BitField<u16>);
|
||||
|
||||
impl EntryOptions {
|
||||
pub fn new(ty: EntryType) -> Self {
|
||||
let mut flags = BitField::new(0);
|
||||
match ty {
|
||||
EntryType::InterruptGate => flags.set_range(8..12, 0b1110),
|
||||
EntryType::TrapGate => flags.set_range(8..12, 0b1111),
|
||||
}
|
||||
// set present bit
|
||||
flags.set_bit(15);
|
||||
|
||||
EntryOptions(flags)
|
||||
pub fn minimal() -> Self {
|
||||
let mut options = BitField::new(0);
|
||||
options.set_range(9..12, 0b111); // required 'one' bits
|
||||
EntryOptions(options)
|
||||
}
|
||||
|
||||
pub fn set_privilege(&mut self, dpl: u16) {
|
||||
assert!(dpl < 4);
|
||||
pub fn new() -> Self {
|
||||
let mut options = Self::minimal();
|
||||
options.set_present(true);
|
||||
options
|
||||
}
|
||||
|
||||
pub fn set_present(&mut self, present: bool) -> &mut Self {
|
||||
self.0.set_bit(15, present);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disable_interrupts(&mut self, disable: bool) -> &mut Self {
|
||||
self.0.set_bit(8, !disable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_privilege_level(&mut self, dpl: u16) -> &mut Self {
|
||||
self.0.set_range(13..15, dpl);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_stack_index(&mut self, index: u16) {
|
||||
assert!(index < 8);
|
||||
pub fn set_stack_index(&mut self, index: u16) -> &mut Self {
|
||||
self.0.set_range(0..3, index);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum EntryType {
|
||||
InterruptGate,
|
||||
TrapGate,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use x86::task::{load_ltr, TaskStateSegment};
|
||||
use vga_buffer::print_error;
|
||||
|
||||
mod idt;
|
||||
mod gdt;
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: idt::Idt = {
|
||||
@@ -9,33 +13,79 @@ lazy_static! {
|
||||
idt.set_handler(13, general_protection_fault_handler);
|
||||
idt.set_handler(14, page_fault_handler);
|
||||
|
||||
idt.options(8).set_stack_index(1);
|
||||
|
||||
idt
|
||||
};
|
||||
|
||||
static ref TSS: TaskStateSegment = {
|
||||
let mut tss = TaskStateSegment::new();
|
||||
|
||||
let stack_size = 1024 * 3; // 3KiB
|
||||
let stack_bottom = unsafe {
|
||||
::alloc::heap::allocate(stack_size, 16) as usize // TODO
|
||||
};
|
||||
let stack_top = stack_bottom + stack_size;
|
||||
|
||||
tss.ist[0] = stack_top as u64;
|
||||
|
||||
tss
|
||||
};
|
||||
|
||||
static ref GDT: Gdt = {
|
||||
let mut table = gdt::Gdt::new();
|
||||
|
||||
let selectors = GdtSelectors {
|
||||
code: table.add_entry(gdt::Entry::code_segment()),
|
||||
data: table.add_entry(gdt::Entry::data_segment()),
|
||||
tss: table.add_entry(gdt::Entry::tss_segment(&TSS)),
|
||||
};
|
||||
|
||||
Gdt {
|
||||
table: table,
|
||||
selectors: selectors,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct Gdt {
|
||||
table: gdt::Gdt,
|
||||
selectors: GdtSelectors,
|
||||
}
|
||||
|
||||
struct GdtSelectors {
|
||||
code: gdt::Selector,
|
||||
data: gdt::Selector,
|
||||
tss: gdt::Selector,
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
assert_has_not_been_called!();
|
||||
|
||||
unsafe { IDT.load() }
|
||||
unsafe {
|
||||
GDT.table.load();
|
||||
gdt::reload_segment_registers(GDT.selectors.code, GDT.selectors.data);
|
||||
gdt::load_ltr(GDT.selectors.tss);
|
||||
IDT.load();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub extern fn divide_by_zero_handler() -> ! {
|
||||
println!("EXCEPTION: DIVIDE BY ZERO");
|
||||
pub extern "C" fn divide_by_zero_handler() -> ! {
|
||||
unsafe { print_error(format_args!("EXCEPTION: DIVIDE BY ZERO")) };
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub extern fn double_fault_handler() -> ! {
|
||||
println!("EXCEPTION: DOUBLE FAULT");
|
||||
pub extern "C" fn double_fault_handler() -> ! {
|
||||
unsafe { print_error(format_args!("EXCEPTION: DOUBLE FAULT")) };
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub extern fn general_protection_fault_handler() -> ! {
|
||||
println!("EXCEPTION: GENERAL PROTECTION FAULT");
|
||||
pub extern "C" fn general_protection_fault_handler() -> ! {
|
||||
unsafe { print_error(format_args!("EXCEPTION: GENERAL PROTECTION FAULT")) };
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub extern fn page_fault_handler() -> ! {
|
||||
println!("EXCEPTION: PAGE FAULT");
|
||||
pub extern "C" fn page_fault_handler() -> ! {
|
||||
unsafe { print_error(format_args!("EXCEPTION: PAGE FAULT")) };
|
||||
loop {}
|
||||
}
|
||||
|
||||
26
src/lib.rs
26
src/lib.rs
@@ -11,6 +11,8 @@
|
||||
#![feature(const_fn, unique)]
|
||||
#![feature(alloc, collections)]
|
||||
#![feature(asm)]
|
||||
#![feature(drop_types_in_const)]
|
||||
#![feature(heap_api)]
|
||||
#![no_std]
|
||||
|
||||
extern crate rlibc;
|
||||
@@ -47,16 +49,17 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
||||
// set up guard page and map the heap pages
|
||||
memory::init(boot_info);
|
||||
|
||||
use alloc::boxed::Box;
|
||||
let heap_test = Box::new(42);
|
||||
|
||||
for i in 0..10000 {
|
||||
format!("Some String");
|
||||
}
|
||||
|
||||
interrupts::init();
|
||||
|
||||
unsafe { *(0xdeadbeaf as *mut u32) = 42};
|
||||
//println!("{:?}", unsafe { *(0xdeadbeaf as *mut u32) });
|
||||
//unsafe { *(0xdeadbeaf as *mut u32) = 42 };
|
||||
|
||||
fn recursive() {
|
||||
recursive();
|
||||
}
|
||||
recursive();
|
||||
|
||||
unsafe { *(0xdeadbeaf as *mut u32) = 42 };
|
||||
|
||||
unsafe {
|
||||
asm!("xor eax, eax; idiv eax" :::: "intel");
|
||||
@@ -91,8 +94,11 @@ extern "C" fn eh_personality() {}
|
||||
#[cfg(not(test))]
|
||||
#[lang = "panic_fmt"]
|
||||
extern "C" fn panic_fmt(fmt: core::fmt::Arguments, file: &str, line: u32) -> ! {
|
||||
println!("\n\nPANIC in {} at line {}:", file, line);
|
||||
println!(" {}", fmt);
|
||||
use vga_buffer::print_error;
|
||||
unsafe {
|
||||
print_error(format_args!("\n\nPANIC in {} at line {}:", file, line));
|
||||
print_error(format_args!(" {}", fmt));
|
||||
}
|
||||
loop {}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,3 +135,15 @@ struct ScreenChar {
|
||||
struct Buffer {
|
||||
chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT],
|
||||
}
|
||||
|
||||
pub unsafe fn print_error(fmt: fmt::Arguments) {
|
||||
use core::fmt::Write;
|
||||
|
||||
let mut writer = Writer {
|
||||
column_position: 0,
|
||||
color_code: ColorCode::new(Color::Red, Color::Black),
|
||||
buffer: unsafe { Unique::new(0xb8000 as *mut _) },
|
||||
};
|
||||
writer.new_line();
|
||||
writer.write_fmt(fmt);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user