Demilade Sonuga's blog
All postsThe Interrupt Descriptor Table V
In this post, we're going to set up the IDT.
Setting Up The IDT
Setting up the IDT is similar to setting up the GDT:
- We create a new
IDT
structure. - Add whatever entries we want to add.
- Create a new
DescriptorTablePointer
pointing to the IDT. - Use a special instruction (
lidt
) to tell the CPU where the descriptor table pointer is.
In main.rs
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ...Others
boot_services.exit_boot_services(handle).unwrap();
setup_gdt();
setup_idt(); // NEW
// ...Others
}
// Creates a new IDT and sets it up
fn setup_idt() {
}
Just like the GDT and TSS, our IDT needs to last throughout the whole program so we place it in another static.
// ...Others
mod tss;
use tss::{TSS, load_tss};
mod interrupts;
use interrupts::{IDT, Entry}; // NEW
mod game;
static mut SCREEN: Option<&mut Screen> = None;
static mut GDT: Option<GDT> = None;
static mut TSS: Option<TSS> = None;
static mut IDT: Option<IDT> = None; // NEW
Initializing:
fn setup_idt() {
let idt: &mut IDT;
unsafe {
IDT = Some(IDT::new());
idt = IDT.as_mut().unwrap();
}
}
We don't have entries to add yet so we skip to step 3: creating a new DescriptorTablePointer
. We'll make
an associated function for this as we did for GDT
.
In interrupts.rs
// ...Others
use core::ops::{BitAnd, Shr};
use crate::gdt::DescriptorTablePointer; // NEW
impl IDT {
// Creates a descriptor table pointer that tells to tell
// the processor where the IDT is located
pub fn as_pointer(&self) -> DescriptorTablePointer {
DescriptorTablePointer {
limit: (core::mem::size_of::<Self>() - 1) as u16,
base: self
}
}
}
As for loading, we'll just make a regular function:
pub fn load_idt(pointer: &DescriptorTablePointer) {
unsafe {
asm!("lidt [{}]", in(reg) pointer);
}
}
In main.rs
fn setup_idt() {
let idt: &mut IDT;
unsafe {
IDT = Some(IDT::new());
idt = IDT.as_mut().unwrap();
}
// NEW:
let pointer = idt.as_pointer();
interrupts::disable_interrupts();
interrupts::load_idt(&pointer);
interrupts::enable_interrupts();
}
If you try compiling, you'll get an error about the DescriptorTablePointer
expecting a GDT
reference, instead
of IDT
. This is because of the &'static GDT
type specified as the type of the DescriptorTablePointer
base.
In gdt.rs
#[repr(C, packed)]
pub struct DescriptorTablePointer {
limit: u16,
// DELETED: base: &'static GDT
base: *const u8 // NEW
}
The base
field is now a pointer to bytes. To make this work:
impl GDT {
// ...Others
pub fn as_pointer(&'static self) -> DescriptorTablePointer {
DescriptorTablePointer {
// DELETED: base: self,
base: self as *const _ as *const u8, // NEW
limit: (mem::size_of::<Self>() - 1) as u16
}
}
// ...Others
}
In interrupts.rs
impl IDT {
// ...Others
pub fn as_pointer(&'static self) -> DescriptorTablePointer {
DescriptorTablePointer {
limit: (core::mem::size_of::<Self>() - 1) as u16,
// DELETED: base: self
base: self as *const _ as *const u8 // NEW
}
}
}
Another attempt at compilation throws visibility errors.
In gdt.rs
#[repr(C, packed)]
pub struct DescriptorTablePointer {
// DELETED: limit: u16,
pub limit: u16, // NEW
// DELETED: base: *const u8
pub base: *const u8 // NEW
}
All errors resolved.
A Bit of Testing
We know that whenever an interrupt occurs, the processor looks up its entry in the IDT and executes the service routine specified.
To see this in action, we'll use the breakpoint exception. The breakpoint exception is normally used by debuggers to stop programs while executing and inspecting their environment.
To trigger the breakpoint exception, we can generate the interrupt from the software using the int
instruction.
The breakpoint exception is the third entry, so int 3
is the instruction we need to invoke it.
First, we create the service routine itself:
In main.rs
extern "x86-interrupt" fn breakpoint_handler(frame: interrupts::InterruptStackFrame) {
let screen = get_screen().unwrap();
write!(screen, "In the breakpoint handler");
}
Notice that the function has a type that is compatible with the ServiceRoutine
struct. It doesn't
take an error code and it returns. If the function had a slightly different type, it would have resulted in
errors later and that's a good thing. It means that the compiler knows what type of functions should
be with which entries.
// ...Others
// DELETED: use interrupts::{IDT, Entry};
use interrupts::{IDT, Entry, ServiceRoutine}; // NEW
// ...Others
fn setup_idt() {
let idt: &mut IDT;
unsafe {
IDT = Some(IDT::new());
idt = IDT.as_mut().unwrap();
}
idt.breakpoint = Entry::exception(ServiceRoutine(breakpoint_handler), sel); // NEW
let pointer = idt.as_pointer();
interrupts::disable_interrupts();
interrupts::load_idt(&pointer);
interrupts::enable_interrupts();
}
To get the segment selector, we can return it from the setup_gdt
function and pass it as an argument here.
// ...Others
mod gdt;
// DELETED: use gdt::{Descriptor, GDT, SegmentRegister};
use gdt::{Descriptor, GDT, SegmentRegister, SegmentSelector}; // NEW
// ...Others
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ...Others
// DELETED: setup_gdt();
let cs = setup_gdt(); // NEW
// DELETED: setup_idt();
setup_idt(cs); // NEW
// ...Others
}
// DELETED: fn setup_gdt() {
fn setup_gdt() -> SegmentSelector { // NEW
// ...Others
gdt::CS.set(cs);
gdt::DS.set(ds);
gdt::SS.set(ds);
cs
}
// DELETED: fn setup_idt() {
fn setup_idt(sel: SegmentSelector) { // NEW
// ...Others
idt.breakpoint = Entry<ServiceRoutine>::exception(breakpoint_handler, sel);
let pointer = idt.as_pointer();
interrupts::disable_interrupts();
interrupts::load_idt(&pointer);
interrupts::enable_interrupts();
}
Finally, invoking the exception:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ...Others
let cs = setup_gdt();
setup_idt(cs);
// Invoking the breakpoint exception
unsafe { core::arch::asm!("int3"); }
loop {}
game::blasterball(screen);
// ...Others
}
If you run the code now, you'll get an "In the breakpoint handler" message on screen:
Take Away
For the full code, go to the repo
In The Next Post
We'll be taking a look at some important exceptions