The Interrupt Descriptor Table (IDT) is an array of 8 byte interrupt descriptors in memory devoted to specifying (at most) 256 interrupt service routines. The first 32 entries are reserved for processor exceptions, and any 16 of the remaining entries can be used for hardware interrupts. The rest are available for software interrupts.
The address of the IDT is stored in a processor register called the IDTR. You can access this register with the following C functions:
void lidt(void *base, unsigned int limit) {
unsigned int i[2];
i[0] = limit << 16;
i[1] = (unsigned int) base;
asm ("lidt (%0)": :"p" (((char *) i)+2));
}
void *sidt(void) {
unsigned int ptr[2];
asm ("sidt (%0)": :"p" (((char *) ptr)+2));
return (void *) ptr[1];
}
Note that you can only get the base, and not the size of the current IDT, with the sidt() function. I don't think the limit is very important; I keep it set to its maximum value (0x7ff, or 256*8) to avoid problems.
The IDTR is not initialized by our boot loader. You must find space in memory for it and load it yourself.
Interrupt Descriptor Table Entry
An entry in the IDT takes up two words (64-bits), and is laid out like this:
The Target Segment Selector needs to be a code segment, probably 0x08 (see the predefined selectors in the boot loader).
Exception handlers should either be defined as an Interrupt Gate (type = 14) or a Trap Gate (15). The difference is simple: interrupts are automatically disabled by the processor when an interrupt gate is called, and they are left alone (whether enabled or disabled) for a trap gate. An entry in the IDT can also be a Task Gate (5), which will cause a context switch when encountered; however, exceptions are usually not handled in this manner.
Exception and interrupt handlers can be straight C functions, as follows:
void GeneralProtectionFault(void);
void setExceptionHandler(int excnum, void (*handler)(void));
void GeneralProtectionFault(void)
{
/* handle fault, print information, etc. */
...
/* DOES NOT RETURN */
}
setExceptionHandler(13, &GeneralProtectionFault);
assuming that setExceptionHandler()
installs a valid
entry in the IDT.
This works fine for handlers that do not need to return. However, handlers that must return (page faults, hardware interrupts, and more) need to use an assembly wrapper around their C code, for two reasons:
We can use either global inline assembly (inline assembly instructions in a .c file, but outside the scope of any C function), or make a seperate .S (assembler) file entirely. Each exception handler needs its own assembly wrapper, since the only way to determine which exception occurred is by which function has been called.
.globl asmExc13 ; make this symbol globally accessible
asmExc13:
pusha ; push all registers
call GeneralProtectionFault ; may need to tweak this symbol,
; depending on how the compiler mangles it
popa ; restore all registers
add $4, %esp ; remove error code, only necessary for #8, 10-14
iret ; and return from exception
You may need to do some tweaking with predeclaring symbols or adding leading underscores, depending on how the compiler mangles your function names.
The stack layout on entry to an C exception handler is different
from a normal C function call, as follows:
Location | Value on Exception | Value on Call |
---|---|---|
%esp+20 | eFlags | 4th parameter |
%esp+16 | CS | 3rd parameter |
%esp+12 | EIP | 2nd parameter |
%esp+8 | error code | 1st parameter |
%esp+4 | return value | return value |
%esp | old frame pointer | old frame pointer |
You can get these values on the stack from a C function by using the following function definition:
struct regs {
unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax;
};
/* assembly wrapper with pusha */
void SavedRegsExceptionHandler(struct regs r,
unsigned int errcode, unsigned int eip,
unsigned int cs, unsigned int eflags);
/* assembly wrapper without pusha, or direct call to C function */
void DirectExceptionHandler(unsigned int errcode, unsigned int eip,
unsigned int cs, unsigned int eflags);
These definitions work by knowing the C stack calling convention and then tricking the compiler into generating code that believes it is being passed parameters. In a way, it is; the processor (and assembly wrapper, if there is one) are passing them.
Exceptions that don't push an error code on the stack need to have the "unsigned int errcode" parameter removed from their definitions.
Exception Types:
Number | Description | Type |
---|---|---|
0 | Divide-by-zero | fault |
1 | Debug exception | trap or fault |
2 | Non-Maskable Interrupt (NMI) | trap |
3 | Breakpoint (INT 3) | trap |
4 | Overflow (INTO with EFlags[OF] set) | trap |
5 | Bound exception (BOUND on out-of-bounds access) | trap |
6 | Invalid Opcode | trap |
7 | FPU not available | trap |
8* | Double Fault | abort |
9 | Coprocessor Segment Overrun | abort |
10* | Invalid TSS | fault |
11* | Segment not present | fault |
12* | Stack exception | fault |
13* | General Protection | fault or trap |
14* | Page fault | fault |
15 | Reserved | |
16 | Floating-point error | fault |
17 | Alignment Check | fault |
18 | Machine Check | abort |
19-31 | Reserved By Intel | |
32-255 | Available for Software and Hardware Interrupts |