Page 1 of 1

Triple fault on interrupts

Posted: Tue Nov 17, 2020 10:22 am
by poissonMyfish
Look, I know this is a really common question. Too common, in fact. Until it happened to me.
I am a beginner, so don't expect much. If I'm neglecting some important aspect, please let me know. Thanks.

For the past day I've been trying to figure this out. My x86 kernel boots with GRUB, I'm defining everything IDT and GDT related in Assembly (then call _load_gdt() and _load_idt() from C). I read the OSDev Wiki, James Molloy's guide, the Intel manuals (I'm still trying to fully understand them) and random blog posts. I mentioned the GDT because I suspect it might be problematic too (from what I've heard there is no real way to know if it's set up correctly).

Ok, here's the code (I apologize in advance for any stupid mistakes, I'm still learning):

Code: Select all

// main.c
#include <kernel/lime_tty.h>

extern void _load_gdt();  // From assembly
extern void _load_idt();

void lime_main()
{
    lime_tty_init(TtyTextMode);
    lime_tty_put_string("[ LIME ] Welcome to the Lime kernel!\n");
    
    _load_gdt();
    lime_tty_put_string("[ LIME ] Loaded GDT successfully!\n");

    _load_idt();
    lime_tty_put_string("[ LIME ] Loaded IDT successfully!\n");

    asm ("int $0x03");
}

Code: Select all

; interrupts.asm
%macro ISRNOERR 1
isr%1:
    cli
    push byte 0
    push byte %1
    jmp isr_common_stub
isr%1_end:      
%endmacro

%macro ISRERR 1
isr%1:
    cli
    push byte %1
    jmp isr_common_stub
isr%1_end:      
%endmacro

    ISRNOERR 0
    ISRNOERR 1
    ISRNOERR 2
    ISRNOERR 3
    ISRNOERR 4
    ISRNOERR 5
    ISRNOERR 6
    ISRNOERR 7
    ISRERR 8
    ISRNOERR 9
    ISRERR 10
    ISRERR 11
    ISRERR 12
    ISRERR 13
    ISRERR 14
    ISRNOERR 15
    ISRNOERR 16
    ISRNOERR 17
    ISRNOERR 18
    ISRNOERR 19
    ISRNOERR 20
    ISRNOERR 21
    ISRNOERR 22
    ISRNOERR 23
    ISRNOERR 24
    ISRNOERR 25
    ISRNOERR 26
    ISRNOERR 27
    ISRNOERR 28
    ISRNOERR 29
    ISRNOERR 30
    ISRNOERR 31
    ISRNOERR 32

isr_common_stub:
    pusha
    mov ax, ds
    push eax

    mov ax, 0x10 ; Data segment descriptor (gdt.asm)
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    extern handler
    call handler

    pop eax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    popa
    add esp, 8
    sti
    iret

Code: Select all

; idt.asm
section .text
global _load_idt
_load_idt:
    lidt [idt_info]
    ret

%macro IRQ 1
irq%1:
    dd isr%1
    dw 0x0008
    db 0x00
    db 10101110b
    dd isr%1_end
%endmacro

    %include "interrupts.asm"
    
section .rodata
idt:
    IRQ 0
    IRQ 1
    IRQ 2
    IRQ 3
    IRQ 4
    IRQ 5
    IRQ 6
    IRQ 7
    IRQ 8
    IRQ 9
    IRQ 10
    IRQ 11
    IRQ 12
    IRQ 13
    IRQ 14
    IRQ 15
    IRQ 16
    IRQ 17
    IRQ 18
    IRQ 19
    IRQ 20
    IRQ 21
    IRQ 22
    IRQ 23
    IRQ 24
    IRQ 25
    IRQ 26
    IRQ 27
    IRQ 28
    IRQ 29
    IRQ 30
    IRQ 31
    IRQ 32

idt_info:
    dw idt_info - idt - 1
    dd idt

Code: Select all

; gdt.asm
section .data
    ALIGN 4

section .text
global _load_gdt
_load_gdt:
    cli
    lgdt [gdt_desc]
    jmp 0x08:gdt_flush

gdt_flush:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    ret


section .rodata
gdt:
gdt_null:
    dd 0h
    dd 0h
    
gdt_code:
    dw 0FFFFh
    dw 00000h
    db 00h
    db 10011010b
    db 11001111b
    db 0
gdt_data:
    dw 0FFFFh
    dw 00000h
    db 00h
    db 10010010b
    db 11001111b
    db 0

gdt_desc:
    dw gdt_desc - gdt - 1
    dd gdt

Re: Triple fault on interrupts

Posted: Tue Nov 17, 2020 5:22 pm
by Octocontrabass

Code: Select all

%macro IRQ 1
irq%1:
    dd isr%1
    dw 0x0008
    db 0x00
    db 10101110b
    dd isr%1_end
%endmacro
This is 12 bytes, but IDT entries are only 8 bytes. The 32-bit ISR address should be split into two 16-bit halves.

Your assembler/linker probably can't handle relocations for half of an address, so you won't be able to define the IDT entries statically. You'll have to write code to generate a working IDT at runtime. (You don't have to generate a working IDT from scratch, though. You only need to put the bytes in the order the CPU expects. You can still use the assembler to do most of the work.)

Re: Triple fault on interrupts

Posted: Tue Nov 17, 2020 6:04 pm
by Schol-R-LEA
Octocontrabass wrote:The 32-bit ISR address should be split into two 16-bit halves.
To put this in context, this is (AFAIK) due to the way the original 16-bit protected mode IDT entries on the 80286 worked (as opposed to the real mode IVT entries, which were far simpler). The last 16-bit field was reserved for future use, and rather than have a completely new IDT format for 32-bit protected mode, Intel's engineers used that reserved field to make up the remainder of the 32-bit addresses.

This means that, as Octocontrabass said, the first and last fields should be words, not double-words. IIUC, the lower half of the address goes into the first field, and the upper half of the address into the final field.

As for the assembler, it is possible that this might work (I am assuming NASM here), but I haven't tested it out so I can make no promises.

Code: Select all

%macro IRQ 1
irq%1:
    dw (isr%1 & 0x000FFFF) ; extract the lower half of the address at assembly time
    dw 0x0008
    db 0x00
    db 10101110b
    dw (isr%1 >> 0x10)     ; extract the upper half of the address at assembly time
%endmacro
Either way, you will almost certainly have to reset most of the ISR addresses later in your kernel start-up procedure, since you will most likely need to reassign them dynamically when you set up the non-stub versions. Getting them 'right' at assembly time or link time isn't a high priority unless you are doing a strictly monolithic, single-binary-image kernel with no loadable drivers etc.

Comments and corrections welcome.

Re: Triple fault on interrupts

Posted: Tue Nov 17, 2020 7:10 pm
by Octocontrabass
Schol-R-LEA wrote:Comments and corrections welcome.
NASM doesn't support any object file formats that can relocate half of an address, so that won't work. I don't think there are any such formats for x86.

The funky IDT layout is specifically to allow mixing 16-bit and 32-bit code. No one does that anymore, but when the 386 was being designed, backwards compatibility was the number one goal. (It took a while for Intel's management to figure out that people wanted a new CPU that could run all of their old software.)

Re: Triple fault on interrupts

Posted: Tue Nov 17, 2020 9:33 pm
by nullplan
Nitpicks in your interrupt code: Both the CLI at the start and the STI at the end are useless and can be removed: You are loading the IDT entries as interrupt gates (which is good), so the ISRs will always be loaded with the interrupt flag deleted, and the STI before IRET doesn't do anything, because IRET pops flags as part of its operation. Also, ISR 17 (Alignment check exception) does have an error code, so it should be declared as such.

Re: Triple fault on interrupts

Posted: Wed Nov 18, 2020 9:56 am
by Schol-R-LEA
Octocontrabass wrote:
Schol-R-LEA wrote:Comments and corrections welcome.
NASM doesn't support any object file formats that can relocate half of an address, so that won't work. I don't think there are any such formats for x86.
Fair point, and I am disheartened at the fact that I didn't think of that earlier. I was thinking in terms of the addresses being resolvable at assembly time, which would never actually be the case for any format other than bare binaries. I'm not sure why it didn't occur to me.

Still, as I (and earlier, you) pointed out, this is all likely to be moot, as the OS would invariably have to replace those initial addresses with new values at run time.
Octocontrabass wrote:The funky IDT layout is specifically to allow mixing 16-bit and 32-bit code. No one does that anymore, but when the 386 was being designed, backwards compatibility was the number one goal.
Interesting, I wasn't aware of that. I do recall that mixed 16 and 32 bit code was possible, and presumably remains so for 32-bit protected mode, but I always assumed it was for library compatibility - though in truth, relatively few 286 libraries had been in use compared to those for real mode, in part because relatively few AT class PCs were in use even long after the 386 and 486 were on the markets. In hindsight mixed 16/32 bit compatibility seems like an odd aspect to focus on, but at the time it must have seemed necessary.

Note that, since MS-DOS still dominated the OS space, inexpensive XT class PCs were still the majority of new sales even going into 1990 (at least they were where I was working at the time, as I remember assembling a large number of them that year), and were still considered viable by some right up until the release of Windows 95 (again, based on the sales of them at the used PC store I was working at in 1995, so take that with a grain of salt). While 286 systems weren't exactly rare, they were never the dominant force that the 386 and later CPU models were, and when they were sold it was usually solely on them being a faster PC. There simply was never a lot of 286-specific code in use.
Octocontrabass wrote:(It took a while for Intel's management to figure out that people wanted a new CPU that could run all of their old software.)
I knew that Intel had been thinking in terms of source-level compatibility (or at least ease of porting) with the 8080A back when the 8086 was developed, but it sounds as if this assumption - that binary compatibility wasn't necessary so long as the source code could easily be modified to the new design - lingered longer than I thought.

Re: Triple fault on interrupts

Posted: Wed Nov 18, 2020 10:46 am
by reapersms
Yeah, they designed protected mode before they did 32 bit, and after their attempt at a Great Leap Forward into memory protection was soundly rejected by the entire software ecosystem at the time, they went for a bit more backwards compatability on the next attempt.

The reason for the keyboard controller reset junk, or the triple fault trick, was because there was no way to exit protected mode on the 286. They thought its benefits would be self-evident enough that the OS would switch over to it and never look back -- but I believe it also assumed the user applications were better behaved as well. I think there was some capability for running well behaved apps unmodified in protected mode, but those were about as common as a sparkle-encrusted unicorn.

And so we got the 386's protected mode, which carried most of the 286 structures along. At least protected mode everywhere caught on by around windows 95, even if 32 bit *everywhere* didn't kick in until ME died.

Re: Triple fault on interrupts

Posted: Wed Nov 18, 2020 6:42 pm
by eekee
Octocontrabass wrote:The funky IDT layout is specifically to allow mixing 16-bit and 32-bit code. No one does that anymore,
Small nitpick: There are still people writing DOS software which does exactly this. I remember chatting with a guy writing a new DOS extender within the last couple of years.

Re: Triple fault on interrupts

Posted: Thu Nov 19, 2020 12:23 am
by poissonMyfish
Sorry for not responding, I already fixed the issue, but the thread took ages to approve, so I kind of forgot about it.
Although, your replies gave me a better understanding about what was actually going on. I ended up filling it up dynamically in C.

Thanks!

Re: Triple fault on interrupts

Posted: Thu Nov 19, 2020 12:28 am
by poissonMyfish
nullplan wrote: Also, ISR 17 (Alignment check exception) does have an error code, so it should be declared as such.
I didn't know that, just fixed it