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
确实跳过了第一条指令,而不仅仅是一个字节。
我正在尝试为 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但我发现它在执行
后反而跳转到0xc4c71int 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
确实跳过了第一条指令,而不仅仅是一个字节。