sarthak
sarthak

Reputation: 794

Interrupt Descriptor Table in x86

I am trying to implement a boot-loader for x86. The initial version just uses the BIOS interrupt call to print "Hello" on the screen. I am using QEmu along with GDB for going through my code sequentially. Here is a snippet of the code:

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

The boot-loader starts from address 0x07c00. From what I understood, the BIOS sets up the Interrupt Descriptor table from address 0x0 till 0x3ff (1024 bytes). The IDT has 256 32bit entries, each entry specifies 16bit segment and 16bit offset which is the address of the Interrupt service routine. Thus , when I execute:

int 0x10

I should jump to the address pointed by the 17th entry in the IDT. When I checked the contents of the memory 0x10, it contained the following data " 0xf000ff53", so the program should jump to the location 0xfff53 but I found that it instead jumps to 0xc4c71 after executing the

int 0x10

instruction Why is this happening??

Upvotes: 1

Views: 1639

Answers (1)

Jester
Jester

Reputation: 58762

When I checked the contents of the memory 0x10

There is your problem. Since each vector is 4 bytes, the entry for interrupt 0x10 is at address 0x40.

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

My qemu+gdb combination seems to skip a byte after the interrupt, that is probably a bug.

but are we sure this skipping a byte is a bug

Yes. Let's test:

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

This hooks int 0x10 to our handler, which increments ax using a single byte instruction (opcode 0x40) then goes to the original handler. If you run this, you will see it prints I instead of H, so the inc ax executed correctly. Also, you can put a breakpoint on the handler and see it stop there, then continue to the original handler:

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

Note that if you single step, gdb will again skip the first instruction:

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

You can see it went to 0x7c25 instead of 0x7c24, but ax has been incremented so the inc ax executed.

Replacing inc ax with add ax, 1 (which is a 3 byte) instruction works the same, so gdb is really skipping the first instruction not just a byte.

Upvotes: 4

Related Questions