x86 中的中断描述符 Table

Interrupt Descriptor Table in x86

我正在尝试为 x86 实现引导加载程序。初始版本只是使用 BIOS 中断调用在屏幕上打印 "Hello"。我正在使用 QEmu 和 GDB 来按顺序检查我的代码。 这是代码片段:

mov ah, 0x0e
mov al, 'H'
int 0x10
mov al, 'e'

引导加载程序从地址 0x07c00 开始。 据我了解,BIOS 从地址 0x0 到 0x3ff(1024 字节)设置中断描述符 table。 IDT有256个32位表项,每个表项指定16位段和16位偏移量,即中断服务程序的地址。 因此,当我执行时:

int 0x10

我应该跳转到IDT中第17项指向的地址。当我检查内存0x10的内容时,它包含以下数据“0xf000ff53”,所以程序应该跳转到位置0xfff53但我发现它在执行

后反而跳转到0xc4c71
int 0x10

说明 为什么会这样??

When I checked the contents of the memory 0x10

这是你的问题。由于每个向量为 4 个字节,因此中断 0x10 的条目位于地址 0x40.

(gdb) x/a 0x40
0x40:   0xc0004d65
(gdb) p/x $cs
 = 0xc000
(gdb) p/x $eip
 = 0x4d66

我的 qemu+gdb 组合似乎在中断后跳过一个字节,这可能是一个错误。

but are we sure this skipping a byte is a bug

是的。让我们测试一下:

xor ax, ax
mov ds, ax
mov ax, [0x40]
mov dx, [0x42]
mov [old], ax
mov [old+2], dx
mov word [0x40], handler
mov [0x42], cs
mov ah, 0x0e
mov al, 'H'
int 0x10
jmp $
handler:
inc ax
jmp far [old]
old: dd 0

这将 int 0x10 挂接到我们的 handler,它使用单字节指令(操作码 0x40)递增 ax,然后转到原始处理程序。如果你 运行 这个,你会看到它打印 I 而不是 H,所以 inc ax 正确执行。另外,你可以在处理程序上打一个断点,看到它停在那里,然后继续原来的处理程序:

Breakpoint 2, 0x00007c24 in ?? ()
(gdb) x/a 0x7c29
0x7c29: 0xc0004d65
(gdb) si
0x00007c25 in ?? ()
(gdb)
0x00004d65 in ?? ()

请注意,如果您单步执行,gdb 将再次跳过第一条指令:

0x00007c20 in ?? ()
(gdb) x/4i $eip
=> 0x7c20:      int    [=13=]x10
   0x7c22:      jmp    0x7c22
   0x7c24:      inc    %ax
   0x7c25:      ljmp   *0x7c29
(gdb) p/x $ax
 = 0xe48
(gdb) si
0x00007c25 in ?? ()
(gdb) p/x $ax
 = 0xe49

你可以看到它转到了 0x7c25 而不是 0x7c24,但是 ax 已经递增所以 inc ax 被执行了。

add ax, 1(3 字节)指令替换 inc ax 的效果相同,因此 gdb 确实跳过了第一条指令,而不仅仅是一个字节。