Search code examples
assemblyx86nasminterruptosdev

Kernel stops working when an interrupt occurs


I have created an operating system from scratch, which accepts interruptions from the keyboard. However, when the keyboard is pressed, the kernel stops working. Any advice is appreciated.

Here's the code:

BootLoader.asm

[BITS 16]
[ORG 0x7C00]

READSECTOR EQU 0x64
READMEMORY EQU 0x7E00

Init:
    CLI
    XOR AX, AX
    MOV DS, AX
    MOV ES, AX
    MOV SS, AX
    MOV SP, 0x7C00
    MOV BP, SP
    STI

Main:
    CALL LoadKernel
    CALL SetVBE
    CALL DisableInt
    CALL EnableA20
    CALL SetupGDT
    CALL EnterProtectedMode

LoadKernel:
    MOV BYTE[BootDrive], DL
    MOV BX, 0x02
    MOV CX, READSECTOR
    MOV DX, READMEMORY
    MOV AH, 0x02
    MOV AL, CL
    MOV CL, BL
    MOV BX, DX
    MOV CH, 0x00
    MOV DH, 0x00
    MOV DL, BYTE[BootDrive]
    INT 0x13
    JC LoadFailed
    RET

LoadFailed:
    MOV SI, MsgBootFailed
    CALL Print
    CLI
    HLT

Print:
    MOV AH, 0x0e
    MOV BL, 0x07
    MOV BH, 0x00

PrintLoop:
    LODSB
    TEST AL, AL
    JZ PrintEnd
    INT 0x10
    JMP PrintLoop

PrintEnd:
    RET

SetVBE:
    MOV AL, 0x13
    MOV AH, 0x00
    INT 0x10
    RET

DisableInt:
    MOV AL, 0xff
    OUT 0x21, AL
    NOP
    OUT 0xa1, AL
    CLI
    RET

EnableA20:
    IN AL, 0x92
    OR AL, 2
    OUT 0x92, AL
    RET

SetupGDT:
    CLI
    PUSHA
    LGDT [GDT_TOC]
    STI
    POPA
    RET

GDT_TOC:
    DW 8 * 3
    DW GDT, 0x0000

GDT:
    DW 0x0000, 0x0000, 0x0000, 0x0000
    DW 0xFFFF, 0x0000, 0x9A00, 0x00CF
    DW 0xFFFF, 0x0000, 0x9200, 0x00CF

EnterProtectedMode:
    MOV EAX, CR0
    AND EAX, 0x7fffffff
    OR EAX, 1
    MOV CR0, EAX
    JMP 08h:Main32

[BITS 32]
Main32:
    MOV     AX, 0x10
    MOV     DS, AX
    MOV     ES, AX
    MOV     FS, AX
    MOV     GS, AX
    MOV     SS, AX
    MOV     ESP, 0xffff
    CALL RelocKernel
    JMP ExecuteKernel

RelocKernel:
    MOV ESI, READMEMORY
    MOV EDI, 0x200000
    MOV ECX, 512 * READSECTOR
    CALL memcpy
    RET

memcpy:
    MOV EAX, [ESI]
    ADD ESI, 4
    MOV [EDI], EAX
    ADD EDI, 4
    SUB ECX, 1
    JNZ memcpy
    RET

ExecuteKernel:
    CLI
    MOV EBP, 0x200000
    ;ADD EBP, [EBP + 0x18]
    CALL EBP
    HLT

MsgBootFailed: DB "Kernel Load Failed.", 0x0D, 0x0A, 0x00
BootDrive: DB 0
TIMES 510 - ($ - $$) DB 0
DW 0xAA55

KernelMain.c

#include "KernelMain.h"

int main()
{
    InitGDTIDT();
    InitPIC();
    ASM_STI();
    IoWrite8(0x0021, 0xf9);
    IoWrite8(0x00a1, 0xef);
    for (;;)
    {
        ASM_HLT();
    }
    return 0;
}

KernelMain.h

typedef unsigned long size_t;
typedef signed int int32_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef signed short int16_t;
typedef unsigned char uint8_t;
typedef signed char int8_t;
typedef unsigned long long uint64_t;

#define ADR_IDT 0x0010f800
#define LIMIT_IDT 0x000007ff
#define ADR_GDT 0x00110000
#define LIMIT_GDT 0x0000ffff
#define ADR_KERNEL 0x00200000
#define LIMIT_KERNEL 0x0007ffff
#define AR_DATA32_RW 0x4092
#define AR_CODE32_ER 0x409a
#define AR_INTGATE32 0x008e

typedef struct
{
    uint16_t LimitLow;
    uint16_t BaseLow;
    uint8_t BaseMid;
    uint8_t AccessRight;
    uint8_t LimitHigh;
    uint8_t BaseHigh;
} SEGMENT_DESCRIPTOR;

typedef struct
{
    uint16_t OffsetLow;
    uint16_t Selector;
    uint8_t DwCount;
    uint8_t AccessRight;
    uint16_t OffsetHigh;
} GATE_DESCRIPTOR;
void InitGDTIDT();
void SetSegmentDescriptor(SEGMENT_DESCRIPTOR *sd, uint32_t limit, int base, int ar);
void SetGateDescriptor(GATE_DESCRIPTOR *gd, int offset, int selector, int ar);
void InitPIC();
void _inthandler21(int *esp);
void _inthandler27(int *esp);
void _inthandler2c(int *esp);
void _asm_inthandler21(void);
void _asm_inthandler27(void);
void _asm_inthandler2c(void);

Segment.c

#include "KernelMain.h"

void InitGDTIDT()
{
    SEGMENT_DESCRIPTOR *gdt = (SEGMENT_DESCRIPTOR *)ADR_GDT;
    GATE_DESCRIPTOR *idt = (GATE_DESCRIPTOR *)ADR_IDT;
    int i;

    for (i = 0; i <= LIMIT_GDT / 8; i++)
    {
        SetSegmentDescriptor(gdt + i, 0, 0, 0);
    }
    SetSegmentDescriptor(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW);
    SetSegmentDescriptor(gdt + 2, LIMIT_KERNEL, ADR_KERNEL, AR_CODE32_ER);
    LoadGDT(LIMIT_GDT, ADR_GDT);

    for (i = 0; i <= LIMIT_IDT / 8; i++)
    {
        SetGateDescriptor(idt + i, 0, 0, 0);
    }
    LoadIDT(LIMIT_IDT, ADR_IDT);

    SetGateDescriptor(idt + 0x21, (int)_asm_inthandler21, 2 * 8, AR_INTGATE32);
    SetGateDescriptor(idt + 0x27, (int)_asm_inthandler27, 2 * 8, AR_INTGATE32);
    SetGateDescriptor(idt + 0x2c, (int)_asm_inthandler2c, 2 * 8, AR_INTGATE32);
}

void SetSegmentDescriptor(SEGMENT_DESCRIPTOR *sd, uint32_t limit, int base, int ar)
{
    if (limit > 0xfffff)
    {
        ar |= 0x8000;
        limit /= 0x1000;
    }
    sd->LimitLow = limit & 0xffff;
    sd->BaseLow = base & 0xffff;
    sd->BaseMid = (base >> 16) & 0xff;
    sd->AccessRight = ar & 0xff;
    sd->LimitHigh = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
    sd->BaseHigh = (base >> 24) & 0xff;
}

void SetGateDescriptor(GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
    gd->OffsetLow = offset & 0xffff;
    gd->Selector = selector;
    gd->DwCount = (ar >> 8) & 0xff;
    gd->AccessRight = ar & 0xff;
    gd->OffsetHigh = (offset >> 16) & 0xffff;
}

Interrupts.asm

[BITS 32]
    GLOBAL  _asm_inthandler21, _asm_inthandler27, _asm_inthandler2c
    EXTERN  _inthandler21, _inthandler27, _inthandler2c

[SECTION  .text]
_asm_inthandler21:
        PUSH    ES
        PUSH    DS
        PUSHAD
        MOV     EAX,ESP
        PUSH    EAX
        MOV     AX,SS
        MOV     DS,AX
        MOV     ES,AX
        CALL    _inthandler21
        POP     EAX
        POPAD
        POP     DS
        POP     ES
        IRETD

_asm_inthandler27:
        PUSH    ES
        PUSH    DS
        PUSHAD
        MOV     EAX,ESP
        PUSH    EAX
        MOV     AX,SS
        MOV     DS,AX
        MOV     ES,AX
        CALL    _inthandler27
        POP     EAX
        POPAD
        POP     DS
        POP     ES
        IRETD

_asm_inthandler2c:
        PUSH    ES
        PUSH    DS
        PUSHAD
        MOV     EAX,ESP
        PUSH    EAX
        MOV     AX,SS
        MOV     DS,AX
        MOV     ES,AX
        CALL    _inthandler2c
        POP     EAX
        POPAD
        POP     DS
        POP     ES
        IRETD

Interrupts.c

#include "KernelMain.h"

#define PIC0_ICW1 0x0020
#define PIC0_OCW2 0x0020
#define PIC0_IMR 0x0021
#define PIC0_ICW2 0x0021
#define PIC0_ICW3 0x0021
#define PIC0_ICW4 0x0021
#define PIC1_ICW1 0x00a0
#define PIC1_OCW2 0x00a0
#define PIC1_IMR 0x00a1
#define PIC1_ICW2 0x00a1
#define PIC1_ICW3 0x00a1
#define PIC1_ICW4 0x00a1

void InitPIC()
{
    IoWrite8(PIC0_IMR, 0xff);
    IoWrite8(PIC1_IMR, 0xff);

    IoWrite8(PIC0_ICW1, 0x11);
    IoWrite8(PIC0_ICW2, 0x20);
    IoWrite8(PIC0_ICW3, 1 << 2);
    IoWrite8(PIC0_ICW4, 0x01);

    IoWrite8(PIC1_ICW1, 0x11);
    IoWrite8(PIC1_ICW2, 0x28);
    IoWrite8(PIC1_ICW3, 2);
    IoWrite8(PIC1_ICW4, 0x01);

    IoWrite8(PIC0_IMR, 0xfb);
    IoWrite8(PIC1_IMR, 0xff);

    return;
}

void _inthandler21(int *esp)
{
    for (int i = 0; i < 320 * 200; i++)
    {
        *((uint8_t *)i) = 1;
    }
    for (;;)
    {
        ASM_HLT();
    }
}

void _inthandler2c(int *esp)
{
    for (int i = 0; i < 320 * 200; i++)
    {
        *((uint8_t *)i) = 2;
    }
    for (;;)
    {
        ASM_HLT();
    }
}
void _inthandler27(int *esp)
{
    for (int i = 0; i < 320 * 200; i++)
    {
        *((uint8_t *)i) = 3;
    }
    IoWrite8(PIC0_OCW2, 0x67);
    return;
}

asm.c

#include "KernelMain.h"

void IoWrite8(uint32_t port, uint8_t data)
{
    asm volatile("outb %b0, %w1"
                 :
                 : "a"(data), "Nd"(port));
}

uint8_t IoRead8(uint32_t port)
{
    uint8_t data;
    asm volatile("inb %w1, %b0"
                 : "=a"(data)
                 : "Nd"(port));
    return data;
}

void LoadGDT(int Limit, int Addr)
{
    asm volatile("mov    0x4(%esp),%ax");
    asm volatile("mov    %ax,0x6(%esp)");
    asm volatile("lgdtl  0x6(%esp)");
    return;
}

void LoadIDT(int Limit, int Addr)
{
    asm volatile("mov    0x4(%esp),%ax");
    asm volatile("mov    %ax,0x6(%esp)");
    asm volatile("lidtl  0x6(%esp)");
    return;
}

void ASM_HLT()
{
    asm volatile("HLT");
}

void ASM_CLI()
{
    asm volatile("CLI");
}

void ASM_STI()
{
    asm volatile("STI");
}

After trying for a while, I found that _asm_inthandler21 is probably not called when the keyboard is pressed.


Solution

  • Whether the below answer solves 'the keyboard problem' is unsure, but the following issues are not inviting to keep searching for more...

    You destroy part of your kernel

    Your kernel stretches from 0x00007E00 to 0x00014600.
    Because of MOV ESP, 0xffff, the immediately following CALL RelocKernel overwrites part of the kernel (with the return address)!
    You could relocate the kernel with ESP pointing at 0x7C00 like it was initially, and then once relocation to 0x200000 finished, set ESP equal to 0x10000. Don't set ESP=0xFFFF, always pick an aligned address.

    You copy code that isn't there

    memcpy:
        MOV EAX, [ESI]
        ADD ESI, 4
        MOV [EDI], EAX
        ADD EDI, 4
        SUB ECX, 1
        JNZ memcpy
        RET
    

    The memcpy routine is fed a count expressed as bytes (512 * READSECTOR), yet the code transfers dwords and decrements the counter as if it were bytes! You need to use sub ecx, 4. Or else express the argument as dwords (512 / 4 * READSECTOR).

    Small imperfection

    GDT_TOC:
        DW 8 * 3
        DW GDT, 0x0000
    

    The first word is a limit (not a size) and should read 23 (not 24).


    You are asking BIOS to load (all at once) 51200 bytes at address 0x7E00. This crosses a 64KB boundary. Check out these answers: