Interrupt Descriptor Table

From OSDev.wiki
Jump to navigation Jump to search
This page is a work in progress.
This page may thus be incomplete. Its content may be changed in the near future.

The Interrupt Descriptor Table (IDT) is specific to the I386 architecture. It is the Protected mode counterpart to the Real Mode Interrupt Vector Table (IVT) telling where the Interrupt Service Routines (ISR) are located. It is similar to the Global Descriptor Table in structure.

The IDT entries are called gates. It can contain Interrupt Gates, Task Gates and Trap Gates.

Location and Size

Location of IDT (address and size) is kept in the IDTR register of the CPU, which can be loaded/stored using LIDT, SIDT instructions.

This is similar to the GDT, except:

  • The first entry (at zero offset) is used in the IDT.
  • There are 256 interrupts (0..255), so IDT should have 256 entries, each entry corresponding to a specific interrupt.
  • It can contain more or less than 256 entries. More entries are ignored. However, when you specify less than 256 entries, make sure to set the Present-bit on the ommitted entries to 0. When an interrupt or exception is invoked whose entry is not present, a GPF is raised that tells the number of the missing IDT entry, and even whether it was hardware or software interrupt. There should therefore be at least enough entries so a GPF can be caught.

Structure

The table contains 8-byte entries. Each entry has a complex structure:

struct IDTDescr{
   uint16 offset_1; // offset bits 0..15
   uint16 selector; // a code segment selector in GDT or LDT
   uint8 zero;      // unused, set to 0
   uint8 type_attr; // type and attributes, see below
   uint16 offset_2; // offset bits 16..31
};

The offset is a 32 bit value, split in two parts. The selector is a 16 bit value and must point to a valid selector in your GDT.

type_attr is specified here:

  7                           0
+---+---+---+---+---+---+---+---+
| P | Priv  | S |    GateType   |
+---+---+---+---+---+---+---+---+

The bit fields mean:

Length Name Description
P 1 bit Present This must be set to 1 for all valid gate descriptors, and to 0 when for any unused descriptor slot.
Priv 2 bits Privilege Contains the ring level:
  • 0: highest (kernel);
  • 3: lowest (user applications).
S 1 bit System segment
GateType 4 bits Gate Type Determines the type of gate:
0b0101=0x5 Task gate
0b0110=0x6 16-bit interrupt gate
0b0111=0x7 16-bit trap gate
0b1110=0xE 32-bit interrupt gate
0b1111=0xF 32-bit trap gate

I386 Interrupt Gate

The Interrupt Gate is used to specify an interrupt service routine. When you do INT 50 in assembly, running in protected mode, the CPU looks up the 50th entry (located at 50 * 8) in the IDT. Then the Interrupt Gates selector and offset value is loaded. The selector and offset is used to call the interrupt service routine. When the IRET instruction is read, it returns. If running in 32 bit mode and the specified selector is a 16 bit selector, then the CPU will go in 16 bit protected mode after calling the interrupt service routine. To return you need to do O32 IRET, else the CPU doesn't know that it should do a 32 bit return (reading 32 bit offset of the stack instead of 16 bit).

type_attr Type
0b1110=0xE 32-bit interrupt gate
0b0110=0x6 16-bit interrupt gate

Here are some pre-cooked type_attr values people are likely to use (assuming DPL=0):

  • 32-bit Interrupt gate: 0x8E ( P=1, DPL=00b, S=0, type=1110b => type_attr=1000_1110b=0x8E)

I386 Trap Gate

When an interrupt/exception occurs that corresponds to a Trap or Interrupt Gate, the CPU places the return info on the stack (EFLAGS, CS, EIP), so the interrupt handler can resume the interrupted code by IRET.

Then, execution is transferred to the given selector:offset from the gate descriptor.

For some exceptions, an error code is also pushed on the stack, which must be POPped before doing IRET.


Trap and Interrupt gates are similar, and their descriptors are structurally the same, they differ only in the "type" field. The difference is that for interrupt gates, interrupts are automatically disabled upon entry and reenabled upon IRET which restores the saved EFLAGS.

Choosing type_attr values: (See Descriptors#type_attr)

type_attr Type
0b1111=0xf 32-bit trap gate
0b0110=0x7 16-bit trap gate


Here are some pre-cooked type_attr values people are likely to use (assuming DPL=0):

  • 32-bit Trap gate: 0x8F ( P=1, DPL=00b, S=0, type=1111b => type_attr=1000_1111b=0x8F)

Thus, Trap and Interrupt gate descriptors hold the following data (other than type_attr):

  • 16-bit selector of a code segment in GDT or LDT
  • 32-bit offset into that segment - address of the handler, where execution will be transferred

I386 Task Gate

In the Task Gate descriptor the offset values are not used. Set them to 0.

When an interrupt/exception occurs whose entry is a Task Gate, a task switch results.

"A task gate in the IDT references a TSS descriptor in the GDT. A switch to the handler task is handled in the same manner as an ordinary task switch. (..) The link back to the interrupted task is stored in the previous task link field of the handler task's TSS. If an exception caused an error code to be generated, this error code is copied to the stack of the new task."

—Intel manual (vol.3 p.5-19)

"*NOTE* Because IA-32 tasks are not re-entrant, an interrupt-handler task must disable interrupts between the time it completes handling the interrupt and the time it executes the IRET instruction. This action prevents another interrupt from occurring while the interrupt task's TSS is still marked busy, which would cause a general-protection (#GP) exception."

—Intel manual


Choosing type_attr values: (See

type_attr Type
0b0101=0x5 task gate

For DPL=0, type_attr=0x85=0b0101

Thus, a TSS selector is the only custom piece of data you need for a Task Gate descriptor.

Advantages over using trap/interrupt gates:

  • The entire context of the interrupted task is saved automatically (no need to worry about registers)
  • The handler can be isolated from other tasks in a separate address space in LDT.
  • "A new tss permits the handler to use a new privilege level 0 stack when handling the exception or interrupt. If an exception or interrupt occurs when the current privilege level 0 stack is corrupted, accessing the handler through a task gate can prevent a system crash by providing the handler with a new privilege level 0 stack" --Intel manual

Disadvantage:

  • Saving the entire task context into TSS is slower than using a trap/interrupt gate (where the handler can save only what it needs).
    • Is it that much faster if the handler does PUSHAD or pushes registers one by one?
    • Does it make a difference, considering a non-dummy, non-trivial handler?

Loading/Storing

The IDT is loaded using the LIDT assembly instruction. It expects the location of a IDT description structure:

Byte:
       +---------------+---------------+
   0   |              Size             |
       +---------------+---------------+

       +---------------+---------------+---------------+---------------+
   2   |                             Offset                            |
       +---------------+---------------+---------------+---------------+

The offset is the virtual address of the table itself. The size is the size of the table subtracted by 1. This structure can be stored to memory again with the SIDT instruction.

See also

External references