我正在尝试为x86实现一个boot-loader。初始版本只使用BIOS中断调用在屏幕上打印“Hello”。我正在使用QEmu和GDB按顺序浏览我的代码。 以下是代码片段:
mov ah, 0x0e
mov al, 'H'
int 0x10
mov al, 'e'
boot-loader从地址0x07c00开始。 根据我的理解,BIOS设置中断描述符表从地址0x0到0x3ff(1024字节)。 IDT有256个32位条目,每个条目指定16位段和16位偏移量,它是中断服务程序的地址。 因此,当我执行:
int 0x10
我应该跳到IDT第17条所指的地址。当我检查内存0x10的内容时,它包含以下数据“0xf000ff53”,因此程序应跳转到0xfff53位置,但我发现它在执行
后跳转到0xc4c71int 0x10
指令 为什么会发生这种情况?
答案 0 :(得分:3)
当我检查内存0x10的内容时
有你的问题。由于每个向量是4个字节,因此中断0x10
的条目位于地址0x40
。
(gdb) x/a 0x40
0x40: 0xc0004d65
(gdb) p/x $cs
$4 = 0xc000
(gdb) p/x $eip
$5 = 0x4d66
我的qemu + gdb组合似乎在中断后跳过一个字节,这可能是一个错误。
但我们确定跳过一个字节是一个错误
是。我们来测试一下:
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
,使用单字节指令(操作码ax
)递增0x40
,然后转到原始处理程序。如果你运行它,你会看到它打印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 $0x10
0x7c22: jmp 0x7c22
0x7c24: inc %ax
0x7c25: ljmp *0x7c29
(gdb) p/x $ax
$4 = 0xe48
(gdb) si
0x00007c25 in ?? ()
(gdb) p/x $ax
$5 = 0xe49
您可以看到它已转到0x7c25
而不是0x7c24
,但ax
已增加,因此inc ax
已执行。
用inc ax
(3字节)指令替换add ax, 1
的工作方式相同,因此gdb
实际上是跳过第一条指令而不是一个字节。