Interrupt Descriptor Table: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
mNo edit summary
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(101 intermediate revisions by 46 users not shown)
Line 1: Line 1:
The '''Interrupt Descriptor Table''' ('''IDT''') is a binary data structure specific to the [[IA32_Architecture_Family|IA-32]] and [[X86-64|x86-64]] architectures. It is the [[Protected Mode]] and Long Mode counterpart to the [[Real Mode]] Interrupt Vector Table ([[IVT]]) telling the CPU where the [[Interrupt Service Routines]] (ISR) are located (one per interrupt vector). It is similar to the [[Global Descriptor Table]] in structure.
{{Convert}}
{{OSFaq|IDT}}

The '''Interrupt Descriptor Table''' ('''IDT''') is specific for the <tt>I386</tt> 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.
The IDT entries are called gates. It can contain Interrupt Gates, Task Gates and Trap Gates.


Before you implement the IDT, make sure you have a working GDT.
== Structure ==


== IDTR ==
The table contains 8-byte entries. Each entry have a complex structure:


The location of the '''IDT''' is kept in the '''IDTR''' ('''IDT''' register). This is loaded using the '''LIDT''' assembly instruction, whose argument is a pointer to an '''IDT Descriptor''' structure:
Byte:
+---------------+---------------+---------------+---------------+
0 | Offset 0:7 | Offset 8:15 | Selector 0:7 | Selector 7:15 |
+---------------+---------------+---------------+---------------+
+---------------+-----+---------+---------------+---------------+
4 |0 0 0 0 0 0 0 0|Flags|GateType | Offset 16:23 | Offset 24:31 |
+---------------+-----+---------+---------------+---------------+


{| class="wikitable"
What "Offset 0:7" means is that the byte contains bits 0-7 of the <tt>offset</tt> value. The <tt>offset</tt> is a 32 bit value. The <tt>selector</tt> is a 16 bit value and must point to a valid selector in your [[GDT]]. The Flags and GateType is specified here:
|+IDT Descriptor (IDTR):
!style="width: 66%; text-align: left;" |79 (64-bit Mode)<br>48 (32-bit Mode)&nbsp;&nbsp;&nbsp;<span style="float: right;">16</span>
!style="width: 34%; text-align: left; vertical-align: bottom;" |15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|-
|'''Offset'''<br>63 (64-bit Mode)<br>31 (32-bit Mode)&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|'''Size'''<br><br>15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|}


* '''Size''': One less than the size of the '''IDT''' in bytes.
Flags: GateType:
* '''Offset''': The linear address of the '''Interrupt Descriptor Table''' (not the physical address, paging applies).
(I386 Interrupt Gate) (I386 Task Gate) (I386 Trap Gate)
Bit: 7 . 5 4 . . . 0 4 . . . 0 4 . . . 0
+-------+ +-------------+ +-------------+ +-------------+
|Pr Priv| |'''0 1 1 1 0'''| |'''0 0 1 0 1'''| |'''0 1 1 1 1'''|
+-------+ +-------------+ +-------------+ +-------------+


Note that the amount of data loaded by '''LIDT''' differs in 32-bit and 64-bit modes, '''Offset''' is 4 bytes long in 32-bit mode and 8 bytes long in 64-bit mode.
The bit fields of Flags are:


This is similar to the '''[[GDT]]''', except:
* '''Pr:''' Present bit. This must be set to '''1''' for all valid gate descriptors.
* The first entry (at zero offset) is used in the '''IDT'''.
* '''Priv:''' Privilege, 2 bits. Contains the ring level, 0 = highest (kernel), 3 = lowest (user applications).
* There are 256 interrupt vectors (0..255), so the '''IDT''' should have 256 entries, each entry corresponding to a specific interrupt vector.
* Although the '''IDT''' can contain more than 256 entries, they are ignored.
* Although the '''IDT''' can contain less than 256 entries, any entries that are not present (due to this or other reasons) will generate a '''[[Exceptions#General Protection Fault|General Protection Fault]]''' when an attempt to access them is made. Ideally the '''IDT''' should contain enough entries so that this fault (which is itself an interrupt vector) can be handled.


For more information, see '''Section 2.4.3: IDTR Interrupt Descriptor Table Register''' and '''Figure 2-6: Memory Management Registers''' of the Intel Software Developer Manual, Volume 3-A.
=== I386 Interrupt Gate ===


== Structure on IA-32 ==
The Interrupt Gate is used to specify a [[Interrupt Service Routines|interrupt service routine]]. When you do <tt>[[INT]] 50</tt> 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 <tt>[[IRET]]</tt> 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 <tt>O32 IRET</tt>, 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).


=== I386 Task Gate ===
=== Table ===


On 32-bit processors, the entries in the '''IDT''' are 8 bytes long and form a table like this:
In the Task Gate descriptor the <tt>offset</tt> value is not used. Just set it to 0.


{|class="wikitable"
When an interrupt/exception occurs whose entry is a Task Gate, a task switch results.
|+Interrupt Descriptor Table (32-bit)
! Address !! Content
|-
| IDTR Offset + 0 || Entry 0
|-
| IDTR Offset + 8 || Entry 1
|-
| IDTR Offset + 16 || Entry 2
|- style="text-align: center;"
| '''...''' || '''...'''
|-
| IDTR Offset + 2040 || Entry 255
|}


The corresponding entry for a given '''Interrupt Vector''' is pointed to in memory by scaling the vector by 8 and adding it to the value in the '''Offset''' field of the '''IDTR'''.
:''"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)


=== Gate Descriptor ===
:''"*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


Each entry in the table has a complex structure:
Descriptor format:

<pre>
{| class="wikitable"
struct TaskGateDescr{
|+Gate Descriptor (32-bit):
uint16 reserved_1; // zero
!style="width: 50%; text-align: left;"|63&nbsp;&nbsp;&nbsp;<span style="float: right;">48</span>
uint16 selector; // refers to a TSS descriptor in the GDT
!style="width: 3.125%"|47
uint8 reserved_2; // zero
!style="width: 6.25%; text-align: left;"|46&nbsp;&nbsp;&nbsp;<span style="float: right;">45</span>
uint8 type_attr; // type and attributes, see below
!style="width: 3.125%"|44
uint16 reserved_3; // zero
!style="width: 12.5%; text-align: left;"|43&nbsp;&nbsp;&nbsp;<span style="float: right;">40</span>
!style="width: 25%; text-align: left;"|39&nbsp;&nbsp;&nbsp;<span style="float: right;">32</span>
|-
|'''Offset'''<br>31&nbsp;&nbsp;&nbsp;<span style="float: right;">16</span>
|style="text-align: center; vertical-align: top;"|'''P'''
|'''DPL'''<br>1&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|style="text-align: center; vertical-align: top;"|0
|'''Gate Type'''<br>3&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|style="vertical-align:top"|Reserved
|-
!style="text-align: left;" |31&nbsp;&nbsp;&nbsp;<span style="float: right;">16</span>
!style="text-align: left;" colspan="5" |15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|-
|'''Segment Selector'''<br>15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|colspan="5" |'''Offset'''<br>15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|}

* '''Offset:''' A 32-bit value, split in two parts. It represents the address of the entry point of the '''[[Interrupt Service Routines|Interrupt Service Routine]]'''.
* '''Selector:''' A '''[[Segment Selector]]''' with multiple fields which must point to a valid code segment in your '''[[GDT]]'''.
* '''Gate Type:''' A 4-bit value which defines the type of gate this '''Interrupt Descriptor''' represents. There are five valid type values:
** '''0b0101''' or '''0x5''': Task Gate, note that in this case, the '''Offset''' value is unused and should be set to zero.
** '''0b0110''' or '''0x6''': 16-bit Interrupt Gate
** '''0b0111''' or '''0x7''': 16-bit Trap Gate
** '''0b1110''' or '''0xE''': 32-bit Interrupt Gate
** '''0b1111''' or '''0xF''': 32-bit Trap Gate
* '''DPL:''' A 2-bit value which defines the '''[[Security#Rings|CPU Privilege Levels]]''' which are allowed to access this interrupt via the '''INT''' instruction. Hardware interrupts ignore this mechanism.
* '''P:''' Present bit. Must be set ('''1''') for the descriptor to be valid.

For more information, see '''Section 6.11: IDT Descriptors''' and '''Figure 6-2: IDT Gate Descriptors''' of the [https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html Intel Software Developer Manual, Volume 3-A].

=== Example Code ===

C Struct:

<syntaxhighlight lang="c">
struct InterruptDescriptor32 {
uint16_t offset_1; // offset bits 0..15
uint16_t selector; // a code segment selector in GDT or LDT
uint8_t zero; // unused, set to 0
uint8_t type_attributes; // gate type, dpl, and p fields
uint16_t offset_2; // offset bits 16..31
};
};
</syntaxhighlight>
</pre>


Example ''type_attributes'' values that people are likely to use (assuming DPL is 0):
Choosing type_attr values: ([detailed structure of type_attr|Descriptors#type_attr])
* 32-bit '''Interrupt Gate: 0x8E''' (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110='''0x8E''')
* P=1 (descriptor present, if you'll be using it)
* 32-bit '''Trap Gate: 0x8F''' (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b='''0x8F''')
* S=0 (system segment)
* '''Task Gate: 0x85''' (p=1, dpl=0b00, type=0b0101 => type_attributes=0b1000_0101='''0x85''')
* DPL (if you're not using protection, set to 0, otherwise FIXME)
* type=0101b=0x5 (task gate)


== Structure on x86-64 ==
For DPL=0, type_attr=0x85=1000_0101b


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


On 64-bit processors, the entries in the '''IDT''' are 16 bytes long and form a table like this:
Advantages over using trap/interrupt gates:


{|class="wikitable"
* The entire context of the interrupted task is saved automatically (no need to worry about registers)
|+Interrupt Descriptor Table (64-bit)
* The handler can be isolated from other tasks in a separate address space in LDT.
! Address !! Content
* ''"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
|-
| IDTR Offset + 0 || Entry 0
|-
| IDTR Offset + 16 || Entry 1
|-
| IDTR Offset + 32 || Entry 2
|- style="text-align: center;"
| '''...''' || '''...'''
|-
| IDTR Offset + 4080 || Entry 255
|}


The corresponding entry for a given '''Interrupt Vector''' is pointed to in memory by scaling the vector by 16 and adding it to the value in the '''Offset''' field of the '''IDTR'''.
Disadvantage:


=== Gate Descriptor ===
* 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?


Each entry in the table has a complex structure:
=== I386 Trap Gate ===


{| class="wikitable"
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.
|+Gate Descriptor (64-bit):
!colspan="7" style="text-align: left;"|127&nbsp;&nbsp;&nbsp;<span style="float: right;">96</span>
|-
|colspan="7" |Reserved
|-
!colspan="7" style="text-align: left;"|95&nbsp;&nbsp;&nbsp;<span style="float: right;">64</span>
|-
|colspan="7" |'''Offset'''<br>63&nbsp;&nbsp;&nbsp;<span style="float: right;">32</span>
|-
!style="width: 50%; text-align: left;"|63&nbsp;&nbsp;&nbsp;<span style="float: right;">48</span>
!style="width: 3.125%"|47
!style="width: 6.25%; text-align: left;"|46&nbsp;&nbsp;&nbsp;<span style="float: right;">45</span>
!style="width: 3.125%"|44
!style="width: 12.5%; text-align: left;"|43&nbsp;&nbsp;&nbsp;<span style="float: right;">40</span>
!style="width: 15.625%; text-align: left;"|39&nbsp;&nbsp;&nbsp;<span style="float: right;">35</span>
!style="width: 9.375%; text-align: left;"|34&nbsp;&nbsp;&nbsp;<span style="float: right;">32</span>
|-
|'''Offset'''<br>31&nbsp;&nbsp;&nbsp;<span style="float: right;">16</span>
|style="text-align: center; vertical-align: top;"|'''P'''
|'''DPL'''<br>1&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|style="text-align: center; vertical-align: top;"|0
|'''Gate Type'''<br>3&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|style="vertical-align:top"|Reserved
|'''IST'''<br>2&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|-
!style="text-align: left;" |31&nbsp;&nbsp;&nbsp;<span style="float: right;">16</span>
!style="text-align: left;" colspan="6" |15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|-
|'''Segment Selector'''<br>15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|colspan="6" |'''Offset'''<br>15&nbsp;&nbsp;&nbsp;<span style="float: right;">0</span>
|}


* '''Offset:''' A 64-bit value, split in three parts. It represents the address of the entry point of the '''[[Interrupt Service Routines|Interrupt Service Routine]]'''.
Then, execution is transferred to the given selector:offset from the gate descriptor.
* '''Selector:''' A '''[[Segment Selector]]''' with multiple fields which must point to a valid code segment in your '''[[GDT]]'''.
* '''IST:''' A 3-bit value which is an offset into the '''Interrupt Stack Table''', which is stored in the '''[[Task State Segment]]'''. If the bits are all set to zero, the '''Interrupt Stack Table''' is not used.
* '''Gate Type:''' A 4-bit value which defines the type of gate this '''Interrupt Descriptor''' represents. In long mode there are two valid type values:
** '''0b1110''' or '''0xE''': 64-bit Interrupt Gate
** '''0b1111''' or '''0xF''': 64-bit Trap Gate
* '''DPL:''' A 2-bit value which defines the '''[[Security#Rings|CPU Privilege Levels]]''' which are allowed to access this interrupt via the '''INT''' instruction. Hardware interrupts ignore this mechanism.
* '''P:''' Present bit. Must be set ('''1''') for the descriptor to be valid.


In your '''[[Interrupt Service Routines|Interrupt Service Routines]]''', remember to return from the interrupt using the '''IRETQ''' instruction instead of '''IRET''', as assemblers will not translate that for you. Many 64-bit IDT related problems on the forum are caused by that missing 'Q'. Don't let this happen to you.
For some exceptions, an error code is also pushed on the stack, which must be POPped before doing IRET.


For more information, see '''Section 6.14.1: 64-Bit Mode IDT''' and '''Figure 6-8: 64-Bit IDT Gate Descriptors''' of the Intel Software Developer Manual, Volume 3-A.
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.


=== Example Code ===
Here is the structure of Trap and Interrupt gate descriptors:

<pre>
C Struct:
struct TrapIntDescr{

uint16 offset_1; // offset bits 0..15
<syntaxhighlight lang="c">
uint16 selector; // a code segment selector in GDT or LDT
struct InterruptDescriptor64 {
uint8 zero; // unused, set to 0
uint16_t offset_1; // offset bits 0..15
uint8 type_attr; // type and attributes, see below
uint16_t selector; // a code segment selector in GDT or LDT
uint16 offset_2; // offset bits 16..31
uint8_t ist; // bits 0..2 holds Interrupt Stack Table offset, rest of bits zero.
uint8_t type_attributes; // gate type, dpl, and p fields
uint16_t offset_2; // offset bits 16..31
uint32_t offset_3; // offset bits 32..63
uint32_t zero; // reserved
};
};
</syntaxhighlight>
</pre>

Example ''type_attributes'' values that people are likely to use (assuming DPL is 0):
* 64-bit '''Interrupt Gate: 0x8E''' (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110='''0x8E''')
* 64-bit '''Trap Gate: 0x8F''' (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b='''0x8F''')

== Gate Types ==

There are basically two kinds of interrupts: ones that occur when code execution has encountered an '''[[Exceptions|Exception]]''' due to bad code, or ones that occur to handle events unrelated to currently executing code. In the first case it is pertinent to save the address of the ''currently'' executing instruction so that it can be retried, these are called '''Traps'''. In the second case it is pertinent to save the address of the ''next'' instruction so that execution can be resumed where it left off. These could be caused by an IRQ or other hardware event, or by use of the '''INT''' instruction. Another difference to note is that with '''Traps''', new interrupts might occur during the service routine, but when the CPU is serving an IRQ, further interrupts are masked until an '''End of Interrupt''' signal is sent. How a certain interrupt is served depends on which kind of gate you put in the IDT entry.

=== Interrupt Gate ===


An '''Interrupt Gate''' is used to specify an '''[[Interrupt Service Routines|Interrupt Service Routine]]'''. For example, when the assembly instruction '''INT 50''' is performed while running in protected mode, the CPU looks up the 50th entry (located at 50 * 8) in the '''IDT'''. Then the Interrupt Gate's '''Selector''' and '''Offset''' values are loaded. The '''Selector''' and '''Offset''' are used to call the '''Interrupt Service Routine'''. When the '''IRET''' instruction is performed, the CPU returns from the interrupt. If the CPU was running in 32-bit mode and the specified selector is a 16-bit gate, then the CPU will go in 16-bit Protected Mode after calling the '''ISR'''. To return in this case, the '''O32 IRET''' instruction should be used, or else the CPU will not know that it should do a 32-bit return (reading 32-bit values off the [[stack]] instead of 16 bit).
Choosing type_attr values: ([detailed structure of type_attr|Descriptors#type_attr])
* P=1 (descriptor present, if you'll be using it)
* S=0 (system segment)
* DPL (if you're not using protection, set to 0, otherwise FIXME)
* type (FIXME how do 16-bit ones work?):
** 1110b=0xE (32-bit interrupt gate)
** 1111b=0xF (32-bit trap gate)
** 0110b=0x6 (16-bit interrupt gate)
** 0111b=0x7 (16-bit trap gate)


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


A '''Trap Gate''' should be used to handle '''[[Exceptions|Exceptions]]'''. When such an exception occurs, there can sometimes be an error code placed on the stack, which should be popped before returning from the interrupt.
* 32-bit Interrupt gate: 0x8E ( P=1, DPL=00b, S=0, type=1110b => type_attr=1000_1110b=0x8E)
* 32-bit Trap gate: 0x8F ( P=1, DPL=00b, S=0, type=1111b => type_attr=1000_1111b=0x8F)


'''Trap Gates''' and '''Interrupt Gates''' are similar, and their descriptors are structurally the same, differing only in the '''Gate Type''' field. The difference is that for '''Interrupt Gates''', interrupts are automatically disabled upon entry and reenabled upon '''IRET''', whereas this does not occur for '''Trap Gates'''.
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


==Loading/Storing==
=== Task Gate ===


A '''Task Gate''' is a gate type specific to IA-32 that is used for hardware task switching. For a '''Task Gate''' the '''Selector''' value should refer to a position in the '''[[GDT]]''' which specifies a '''[[Task State Segment]]''' rather than a code segment, and the '''Offset''' value is unused and should be set to zero. Rather than jumping to a service routine, when the CPU processes this interrupt, it will perform a hardware task switch to the specified task. A pointer back to the task which was interrupted will be stored in the '''Task Link''' field in the '''TSS'''.
The IDT is loaded using the <tt>[[LIDT]]</tt> assembly instruction. It expects the location of a IDT description structure:


{{Quotation|"*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 Software Developer Manual}}
Byte:
+---------------+---------------+
0 | Size |
+---------------+---------------+
+---------------+---------------+---------------+---------------+
2 | Offset |
+---------------+---------------+---------------+---------------+


This type of gate is not often used as hardware task switching is slow and has little to no optimization on modern processors. As well, it has been entirely removed on [[X86-64|x86-64]].
The <tt>offset</tt> is the location of the table itself. The <tt>size</tt> is the size of the table subtracted by 1.


== See also ==
==See Also==
===Articles===


* [[GDT]]
* [[GDT]]
* [[IDT_problems|IDT problems]]


== External references ==
=== External References ===


* [http://www.logix.cz/michal/doc/i386/chp09-00.htm Michal Ludvig's Intel 80386 Programmer's Reference Manual chapter 9]
* [http://www.logix.cz/michal/doc/i386/chp09-00.htm Michal Ludvig's Intel 80386 Programmer's Reference Manual chapter 9]


[[Category:X86 CPU]]
[[Category:X86 CPU]]
[[Category:Interrupts]]
[[de:Interrupt_Descriptor_Table]]

Latest revision as of 04:30, 9 June 2024

The Interrupt Descriptor Table (IDT) is a binary data structure specific to the IA-32 and x86-64 architectures. It is the Protected Mode and Long Mode counterpart to the Real Mode Interrupt Vector Table (IVT) telling the CPU where the Interrupt Service Routines (ISR) are located (one per interrupt vector). 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.

Before you implement the IDT, make sure you have a working GDT.

IDTR

The location of the IDT is kept in the IDTR (IDT register). This is loaded using the LIDT assembly instruction, whose argument is a pointer to an IDT Descriptor structure:

IDT Descriptor (IDTR):
79 (64-bit Mode)
48 (32-bit Mode)   16
15   0
Offset
63 (64-bit Mode)
31 (32-bit Mode)   0
Size

15   0
  • Size: One less than the size of the IDT in bytes.
  • Offset: The linear address of the Interrupt Descriptor Table (not the physical address, paging applies).

Note that the amount of data loaded by LIDT differs in 32-bit and 64-bit modes, Offset is 4 bytes long in 32-bit mode and 8 bytes long in 64-bit mode.

This is similar to the GDT, except:

  • The first entry (at zero offset) is used in the IDT.
  • There are 256 interrupt vectors (0..255), so the IDT should have 256 entries, each entry corresponding to a specific interrupt vector.
  • Although the IDT can contain more than 256 entries, they are ignored.
  • Although the IDT can contain less than 256 entries, any entries that are not present (due to this or other reasons) will generate a General Protection Fault when an attempt to access them is made. Ideally the IDT should contain enough entries so that this fault (which is itself an interrupt vector) can be handled.

For more information, see Section 2.4.3: IDTR Interrupt Descriptor Table Register and Figure 2-6: Memory Management Registers of the Intel Software Developer Manual, Volume 3-A.

Structure on IA-32

Table

On 32-bit processors, the entries in the IDT are 8 bytes long and form a table like this:

Interrupt Descriptor Table (32-bit)
Address Content
IDTR Offset + 0 Entry 0
IDTR Offset + 8 Entry 1
IDTR Offset + 16 Entry 2
... ...
IDTR Offset + 2040 Entry 255

The corresponding entry for a given Interrupt Vector is pointed to in memory by scaling the vector by 8 and adding it to the value in the Offset field of the IDTR.

Gate Descriptor

Each entry in the table has a complex structure:

Gate Descriptor (32-bit):
63   48 47 46   45 44 43   40 39   32
Offset
31   16
P DPL
1   0
0 Gate Type
3   0
Reserved
31   16 15   0
Segment Selector
15   0
Offset
15   0
  • Offset: A 32-bit value, split in two parts. It represents the address of the entry point of the Interrupt Service Routine.
  • Selector: A Segment Selector with multiple fields which must point to a valid code segment in your GDT.
  • Gate Type: A 4-bit value which defines the type of gate this Interrupt Descriptor represents. There are five valid type values:
    • 0b0101 or 0x5: Task Gate, note that in this case, the Offset value is unused and should be set to zero.
    • 0b0110 or 0x6: 16-bit Interrupt Gate
    • 0b0111 or 0x7: 16-bit Trap Gate
    • 0b1110 or 0xE: 32-bit Interrupt Gate
    • 0b1111 or 0xF: 32-bit Trap Gate
  • DPL: A 2-bit value which defines the CPU Privilege Levels which are allowed to access this interrupt via the INT instruction. Hardware interrupts ignore this mechanism.
  • P: Present bit. Must be set (1) for the descriptor to be valid.

For more information, see Section 6.11: IDT Descriptors and Figure 6-2: IDT Gate Descriptors of the Intel Software Developer Manual, Volume 3-A.

Example Code

C Struct:

struct InterruptDescriptor32 {
   uint16_t offset_1;        // offset bits 0..15
   uint16_t selector;        // a code segment selector in GDT or LDT
   uint8_t  zero;            // unused, set to 0
   uint8_t  type_attributes; // gate type, dpl, and p fields
   uint16_t offset_2;        // offset bits 16..31
};

Example type_attributes values that people are likely to use (assuming DPL is 0):

  • 32-bit Interrupt Gate: 0x8E (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110=0x8E)
  • 32-bit Trap Gate: 0x8F (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b=0x8F)
  • Task Gate: 0x85 (p=1, dpl=0b00, type=0b0101 => type_attributes=0b1000_0101=0x85)

Structure on x86-64

Table

On 64-bit processors, the entries in the IDT are 16 bytes long and form a table like this:

Interrupt Descriptor Table (64-bit)
Address Content
IDTR Offset + 0 Entry 0
IDTR Offset + 16 Entry 1
IDTR Offset + 32 Entry 2
... ...
IDTR Offset + 4080 Entry 255

The corresponding entry for a given Interrupt Vector is pointed to in memory by scaling the vector by 16 and adding it to the value in the Offset field of the IDTR.

Gate Descriptor

Each entry in the table has a complex structure:

Gate Descriptor (64-bit):
127   96
Reserved
95   64
Offset
63   32
63   48 47 46   45 44 43   40 39   35 34   32
Offset
31   16
P DPL
1   0
0 Gate Type
3   0
Reserved IST
2   0
31   16 15   0
Segment Selector
15   0
Offset
15   0
  • Offset: A 64-bit value, split in three parts. It represents the address of the entry point of the Interrupt Service Routine.
  • Selector: A Segment Selector with multiple fields which must point to a valid code segment in your GDT.
  • IST: A 3-bit value which is an offset into the Interrupt Stack Table, which is stored in the Task State Segment. If the bits are all set to zero, the Interrupt Stack Table is not used.
  • Gate Type: A 4-bit value which defines the type of gate this Interrupt Descriptor represents. In long mode there are two valid type values:
    • 0b1110 or 0xE: 64-bit Interrupt Gate
    • 0b1111 or 0xF: 64-bit Trap Gate
  • DPL: A 2-bit value which defines the CPU Privilege Levels which are allowed to access this interrupt via the INT instruction. Hardware interrupts ignore this mechanism.
  • P: Present bit. Must be set (1) for the descriptor to be valid.

In your Interrupt Service Routines, remember to return from the interrupt using the IRETQ instruction instead of IRET, as assemblers will not translate that for you. Many 64-bit IDT related problems on the forum are caused by that missing 'Q'. Don't let this happen to you.

For more information, see Section 6.14.1: 64-Bit Mode IDT and Figure 6-8: 64-Bit IDT Gate Descriptors of the Intel Software Developer Manual, Volume 3-A.

Example Code

C Struct:

struct InterruptDescriptor64 {
   uint16_t offset_1;        // offset bits 0..15
   uint16_t selector;        // a code segment selector in GDT or LDT
   uint8_t  ist;             // bits 0..2 holds Interrupt Stack Table offset, rest of bits zero.
   uint8_t  type_attributes; // gate type, dpl, and p fields
   uint16_t offset_2;        // offset bits 16..31
   uint32_t offset_3;        // offset bits 32..63
   uint32_t zero;            // reserved
};

Example type_attributes values that people are likely to use (assuming DPL is 0):

  • 64-bit Interrupt Gate: 0x8E (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110=0x8E)
  • 64-bit Trap Gate: 0x8F (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b=0x8F)

Gate Types

There are basically two kinds of interrupts: ones that occur when code execution has encountered an Exception due to bad code, or ones that occur to handle events unrelated to currently executing code. In the first case it is pertinent to save the address of the currently executing instruction so that it can be retried, these are called Traps. In the second case it is pertinent to save the address of the next instruction so that execution can be resumed where it left off. These could be caused by an IRQ or other hardware event, or by use of the INT instruction. Another difference to note is that with Traps, new interrupts might occur during the service routine, but when the CPU is serving an IRQ, further interrupts are masked until an End of Interrupt signal is sent. How a certain interrupt is served depends on which kind of gate you put in the IDT entry.

Interrupt Gate

An Interrupt Gate is used to specify an Interrupt Service Routine. For example, when the assembly instruction INT 50 is performed while running in protected mode, the CPU looks up the 50th entry (located at 50 * 8) in the IDT. Then the Interrupt Gate's Selector and Offset values are loaded. The Selector and Offset are used to call the Interrupt Service Routine. When the IRET instruction is performed, the CPU returns from the interrupt. If the CPU was running in 32-bit mode and the specified selector is a 16-bit gate, then the CPU will go in 16-bit Protected Mode after calling the ISR. To return in this case, the O32 IRET instruction should be used, or else the CPU will not know that it should do a 32-bit return (reading 32-bit values off the stack instead of 16 bit).

Trap Gate

A Trap Gate should be used to handle Exceptions. When such an exception occurs, there can sometimes be an error code placed on the stack, which should be popped before returning from the interrupt.

Trap Gates and Interrupt Gates are similar, and their descriptors are structurally the same, differing only in the Gate Type field. The difference is that for Interrupt Gates, interrupts are automatically disabled upon entry and reenabled upon IRET, whereas this does not occur for Trap Gates.

Task Gate

A Task Gate is a gate type specific to IA-32 that is used for hardware task switching. For a Task Gate the Selector value should refer to a position in the GDT which specifies a Task State Segment rather than a code segment, and the Offset value is unused and should be set to zero. Rather than jumping to a service routine, when the CPU processes this interrupt, it will perform a hardware task switch to the specified task. A pointer back to the task which was interrupted will be stored in the Task Link field in the TSS.

"*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 Software Developer Manual

This type of gate is not often used as hardware task switching is slow and has little to no optimization on modern processors. As well, it has been entirely removed on x86-64.

See Also

Articles

External References