Page 1 of 1

Issue with the IDT

Posted: Fri Feb 19, 2010 8:15 pm
by Synon
I'm reading JamesM's tutorial to get familiar with the concepts of a kernel, and I got to the end of chapter four and then tested interrupts. It does work, and my handler gets called and returns, but for some reason, I'm constantly getting int 0x0D. Now, I know you've had this question before, because I read the answers, as well as the Intel manual (vol. 3A chapter 6) and the relevant section in Ralph Brown's interrupt list, so I know what the problem is... roughly. From what I've read, I can gather that int 0xD means I've got a problem with my IDT or GDT. I only get this problem when I call an interupt (in the tutorial ints 0x3 and 0x4 are called, so I called them), so it's almost certainly not my GDT. I thought I'd mention all that to show I have done my homework, and I do try to solve my problems by myself :)

At first this was funny:
Image (that's printed again and again, until QEMU dies*)
but now it's just annoying, because I know it's probably some really small error somewhere in some obscure file. I haven't copied the tutorial code exactly (I don't know why, but I can't bare to do that; I always have to change something), but I haven't changed the relevant code very much... I've also checked mine against that in the tutorial, and I can't find what I've done wrong.


*QEMU produces this error:
qemu: fatal: Trying to execute code outside RAM or ROM at 0x20636e75

EAX=00000000 EBX=6574726f ECX=00000000 EDX=636e7566
ESI=000a2164 EDI=31746e69 EBP=46203a35 ESP=ffffd581
EIP=20636e75 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00104024 00000027
IDT= 0010404c 000007ff
CR0=60000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=0000003c CCD=00000000 CCO=LOGICL
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
Do you think it's to do with
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
?
In the ISR handler, all the segments get loaded to 10 except CS (which stays at 0008); but shouldn't they get changed back to whatever they were already? I don't know, but I don't think the stack segment and data segment should be in the same place, should they?

Could someone give me some advice as to what might be causing this? It's almost certainly the IDT causing the problem, but I can't figure out what that problem is. If I need to post any thing, e.g. code, etc. tell me.

Thank you :)

Re: Issue with the IDT

Posted: Fri Feb 19, 2010 11:11 pm
by Gigasoft
The error shown in QEmu indicates a stack buffer overflow. The text "int15: func " overwrites a return frame.

The GPF could be a result of returning to an address with an incorrect segment from the exception handler. The exception handler for 0x0D must have an add esp, 4 before the iret to get rid of the error code. You should also check that any functions that the exception handler calls have the correct parameter byte count in their ret instruction. What does your exception handler look like?

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 1:24 am
by Synon
Gigasoft wrote:The error shown in QEmu indicates a stack buffer overflow. The text "int15: func " overwrites a return frame.
Thanks. According to the Intel manuals, if bit 0 is set in the error code it means that the problem was with an external interrupt, if bit 1 is set it means that it was the IDT and if bit 2 is set, it means it was the GDT. If none of those are set, it's the LDT (I googled that, apparently it's a "Local Descriptor Table," but I don't know what that is, so I probably don't have one). I checked like this:

Code: Select all

#include <plx/isr.h>

struct {
    char*   message;
    boolean has_errcode;
}   exceptions[] = {
    {"Division by zero",                      False},
    {"Debug exception",                       False},
    {"Non-maskable interrupt",                False},
    {"Breakpoint exception",                  False},
    {"Into dected overflow",                  False},
    {"Out-of-bounds exception",               False},
    {"Illegal instruction (invalid opcode)",  False},
    {"No coprocessor exception",              False},
    {"Double fault",                          True },
    {"Coprocessor segment overrun exception", False},
    {"Bad TSS",                               True },
    {"Segment not present",                   True },
    {"Stack fault",                           True },
    {"General protection fault",              True },
    {"Page fault",                            True },
    {"Unknown interrupt exception",           False},
    {"Coprocessor fault",                     False},
    {"Alignment check exception",             False},
    {"Machine check exception",               False}
};

isr_t interrupt_handlers[256];

void register_interrupt_handler(uint8 n, isr_t handler)
{
    interrupt_handlers[n] = handler;
}

void irq_handler(struct reg regs)
{
    /* Send EOI to the PICs */
    if (regs.intno >= 40)
        outb(0xA0, 0x20); /* Slave */
    
    outb(0x20, 0x20); /* Master */
    
    if (interrupt_handlers[regs.intno] != 0) {
        isr_t handler = interrupt_handlers[regs.intno];
        handler(regs);
    }
}

void isr_handler(struct reg regs)
{
    if (regs.intno <= 18) {
        printk("CPU internal interrupt  0x%x:  %s",
               regs.intno, exceptions[regs.intno].message);

        if (exceptions[regs.intno].has_errcode == True && regs.errcode != 0)
            printk("  (errcode: 0x%x)", regs.errcode);

        /* According to the Intel manuals; in errcode:
         * +-----+-----+---------+
         * | bit + set + not set |
         * +-----+-----+---------+
         * |  0  + EXT +    -    |
         * +-----+-----+---------+
         * |  1  + IDT +    -    |
         * +-----+-----+---------+
         * |  2  + GDT +   LDT   |
         * +-----+-----+---------+
         * Where EXT is an external device, IDT is the interrupt descriptor
         * table, GDT is the global descriptor table and LDT is the local
         * descriptor table.
         */

         /* Here we check which bits are set with BIT_IS_SET (plx/common.h) */
         if (BIT_IS_SET(regs.errcode, 0))
             printk("  EXT\n");
         else if (BIT_IS_SET(regs.errcode, 1))
             printk("  IDT\n");
         else if (BIT_IS_SET(regs.errcode, 2))
             printk("  GDT\n");
         else
             printk("  LDT\n");
    }
}
but I'm getting "LDT," annoyingly enough. It has to be the one thing I can't fix on my own...
The GPF could be a result of returning to an address with an incorrect segment from the exception handler. The exception handler for 0x0D must have an add esp, 4 before the iret to get rid of the error code. You should also check that any functions that the exception handler calls have the correct parameter byte count in their ret instruction.
Well, as I say, I'm basing everything I've written on JamesM's tutorial. The handler does add esp, 8 before re-enabling interrupts and then doing iret. I assume it's 8 and not 4 because the interrupt number is also pushed, so that's two 32-bit integers and therefore 8 bytes:

Code: Select all

;==============================================================================;
extern  isr_handler ; C isr handler
extern  irq_handler ; C irq handler
%macro  ISR_NO_ERRCODE  1
    global  isr%1
isr%1:
    cli
    push    byte    0 ; Push dummy error code
    push    byte    %1
    jmp     isr_stub
%endmacro
;------------------------------------------------------------------------------;
%macro  ISR_ERRCODE     1
    global  isr%1
isr%1:
    cli
    push    byte    %1
    jmp     isr_stub
%endmacro
;------------------------------------------------------------------------------;
%macro  IRQ 2
    global  irq%1
irq%1:
    cli
    push    byte    0
    push    byte    %2
    jmp     irq_stub
%endmacro
;==============================================================================;

;==============================================================================;
ISR_NO_ERRCODE  0
ISR_NO_ERRCODE  1
ISR_NO_ERRCODE  2
ISR_NO_ERRCODE  3
ISR_NO_ERRCODE  4
ISR_NO_ERRCODE  5
ISR_NO_ERRCODE  6
ISR_NO_ERRCODE  7
ISR_ERRCODE     8
ISR_NO_ERRCODE  9
ISR_ERRCODE     10
ISR_ERRCODE     11
ISR_ERRCODE     12
ISR_ERRCODE     13
ISR_ERRCODE     14
ISR_NO_ERRCODE  15
ISR_NO_ERRCODE  16
ISR_NO_ERRCODE  17
ISR_ERRCODE     18
ISR_NO_ERRCODE  19
ISR_NO_ERRCODE  20
ISR_NO_ERRCODE  21
ISR_NO_ERRCODE  22
ISR_NO_ERRCODE  23
ISR_NO_ERRCODE  24
ISR_NO_ERRCODE  25
ISR_NO_ERRCODE  26
ISR_NO_ERRCODE  27
ISR_NO_ERRCODE  28
ISR_NO_ERRCODE  29
ISR_NO_ERRCODE  30
ISR_NO_ERRCODE  31
IRQ          0, 32
IRQ          1, 33
IRQ          2, 34
IRQ          3, 35
IRQ          4, 36
IRQ          5, 37
IRQ          6, 38
IRQ          7, 39
IRQ          8, 40
IRQ          9, 41
IRQ         10, 42
IRQ         11, 43
IRQ         12, 44
IRQ         13, 45
IRQ         14, 46
IRQ         15, 47
;------------------------------------------------------------------------------;
isr_stub:
    pusha
    
    mov     ax, ds
    push    eax
    
    mov     ax, 0x10
    mov     ds, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax
    
    call    isr_handler
    
    pop     eax
    mov     ds, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax
    
    popa
    add     esp,    8

    sti
    iret
;------------------------------------------------------------------------------;
irq_stub:
    pusha
    
    mov     ax, ds
    push    eax
    
    mov     ax, 0x10
    mov     ds, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax
    
    call    irq_handler
    
    pop     ebx
    mov     ds, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax
    
    popa
    add     esp,    8

    sti
    iret
;==============================================================================;
What does your exception handler look like?
At the moment, it's just the above. I will extend it obviously, but I don't have processes running, so I can't do anything except tell myself what happened. If

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 4:59 am
by Creature
Not sure if it's your problem, but you might want to be careful with this:

Code: Select all

    pop     ebx
    mov     ds, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax
in your irq_stub() function. You've pushed EAX with the contents of DS onto the stack earlier and now you pop it again and put it in EBX (which is good), after which you're restoring a segment which is equal to ax... (meaning whatever garbage might have gotten into EAX, will now be in your segment descriptors). In short: either replace pop ebx with pop eax or replace all the axes with bxes.

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 9:35 am
by Synon
Thanks, I'll have a gander at that.

Edit: it didn't solve the problem, but it... changed it:
Image
I don't know what all that stuff in the bottom right corner is... Could that indicate that I'm running off the end of the video memory buffer when I print too much text?

Also, QEMU doesn't crash any more.

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 11:45 am
by TheGuy
Are you setting the IDT gates for IRQs?

Code: Select all

/* We first remap the interrupt controllers, and then we install
*  the appropriate ISRs to the correct entries in the IDT. This
*  is just like installing the exception handlers */
void irq_install()
{
    irq_remap();

    idt_set_gate(32, (unsigned)irq0, 0x08, 0x8E);
    ...          /* You need to add the rest! */
    idt_set_gate(47, (unsigned)irq15, 0x08, 0x8E);
}
Source

Edit: Lucky number 13

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 12:05 pm
by Gigasoft
When bit 2 of the error code is clear, it indicates a GDT error, not a LDT error.

Is the default calling convention __cdecl? If not, the handlers could be trying to remove the registers from the stack.

I'd save and restore all the segment registers rather than just copy DS into all the others, just to be safe (remember to change the reg structure if you do this).

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 12:55 pm
by Synon
@TheGuy,
When you posted that, I actually hadn't set those up. I just did it, though; unfortunately I'm still getting int 0xD, but now the error code is 0x21f4; I don't know why it changed.
Lucky number 13
Lol, I didn't notice that.

@Gigasoft,
When bit 2 of the error code is clear, it indicates a GDT error, not a LDT error.
Oops.
Is the default calling convention __cdecl? If not, the handlers could be trying to remove the registers from the stack.
I'm using gcc, so I'm fairly sure it is __cdecl.
I'd save and restore all the segment registers rather than just copy DS into all the others, just to be safe (remember to change the reg structure if you do this).
Ok, I'll give that a try.


Like this:

Code: Select all

isr_stub:
    pusha
    
    ; Push the values of the segement selectors on the stack
    mov     ax, ds
    push    eax
    mov     ax, es
    push    eax
    mov     ax, fs
    push    eax
    mov     ax, gs
    push    eax

    mov     ax, 0x10
    mov     ds, ax
    mov     es, ax
    mov     fs, ax
    mov     gs, ax
    
    call    isr_handler
    
    ; Restore the segement selectors
    pop     eax
    mov     ds, ax
    pop     eax
    mov     es, ax
    pop     eax
    mov     fs, ax
    pop     eax
    mov     gs, ax
    
    popa
    add     esp,    8

    sti
    iret
and

Code: Select all

struct reg {
    uint32 ds,  es,  fs,  gs;
    uint32 edi, esi, ebp, esp, ebx, edx, ecx, eax;
    uint32 intno, errcode;
    uint32 eip, cs, eflags, useresp, ss;
};
?

===

I just found a detailed description of all of the exceptions in the Intel manual (vol. 3A, Chapter 6.15, p 268) that I hadn't seen when I looked there earlier. Hopefully this can shed some light on this particulary error code.

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 4:33 pm
by Gigasoft
Now it's reversing DS, ES, FS and GS. This should work:

isr_stub:
pushad
push gs
push fs
push es
push ds
mov ax,0x10
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
call isr_handler
pop ds
pop es
pop fs
pop gs
popad
add esp,8
iretd

The ISR and IRQ macros don't need to have a cli in them. The IDT entries for the IRQs could have a type of 0x8E, which automatically disables interrupts. Other exceptions should usually have a type of 0x8F, since they don't need to disable interrupts.

I'd also like to see a disassembly of isr_handler and the source code for printk.

Re: Issue with the IDT

Posted: Sat Feb 20, 2010 5:24 pm
by Synon
I've just noticed that it's only interrupts that push error messages that are causing this (so int 0xD, being an interrupt that pushes an error message, is probably causing itself again (isn't that a double fault?)). Other interrupts work fine, unless I call gcc with optimizations.
I'd also like to see a disassembly of isr_handler
Disassembly? Do you mean by that, that you want to see what code the compiler is generating for it?

Code: Select all

isr_handler:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	44(%ebp), %eax
	cmpl	$18, %eax
	ja	.L15
	movl	44(%ebp), %eax
	movl	exceptions(,%eax,8), %edx
	movl	44(%ebp), %eax
	movl	%edx, 8(%esp)
	movl	%eax, 4(%esp)
	movl	$.LC19, (%esp)
	call	printk
	movl	44(%ebp), %eax
	movl	exceptions+4(,%eax,8), %eax
	cmpl	$1, %eax
	jne	.L11
	movl	48(%ebp), %eax
	testl	%eax, %eax
	je	.L11
	movl	48(%ebp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC20, (%esp)
	call	printk
	movl	48(%ebp), %eax
	andl	$1, %eax
	testl	%eax, %eax
	je	.L12
	movl	$.LC21, (%esp)
	call	printk
	jmp	.L11
.L12:
	movl	48(%ebp), %eax
	andl	$2, %eax
	testl	%eax, %eax
	je	.L13
	movl	$.LC22, (%esp)
	call	printk
	jmp	.L11
.L13:
	movl	48(%ebp), %eax
	andl	$4, %eax
	testl	%eax, %eax
	je	.L14
	movl	$.LC23, (%esp)
	call	printk
	jmp	.L11
.L14:
	movl	$.LC23, (%esp)
	call	printk
.L11:
	movl	$.LC24, (%esp)
	call	printk
.L15:
	leave
	ret
source code for printk.

Code: Select all

#include <plx/common.h>
#include <plx/termio.h>
#include <lib/stdarg.h>

extern int vsnprintf(char*, size_t, const char*, va_list);

int printk(const char* fmt, ...)
{
    int i = 0;
    char buf[2048] = {0};
    va_list ap;
    
    va_start(ap, fmt);
    i = vsnprintf(buf, 2048 - 1, fmt, ap);
    va_end(ap);

    term_write(buf);

    return i;
}
I copied the Linux 0.01 implementation of stdarg.h, and then used that to implement vsnprintf. Do you need to see vsnprintf and term_out, too?
Gigasoft wrote:Now it's reversing DS, ES, FS and GS.
Thanks. I think I should go practice assembler some more; I'm clearly not as good at it as I need to be.
The ISR and IRQ macros don't need to have a cli in them. The IDT entries for the IRQs could have a type of 0x8E, which automatically disables interrupts. Other exceptions should usually have a type of 0x8F, since they don't need to disable interrupts.
I have all mine to 0x8E :S I'll go change those :)