Bare bones kernel throws Invalid Opcode Exceptions and General Protection Fault Exceptions

1

I was following this guide in trying to build a small kernel. I used it to write a small bootloader in assembly which eventually loads and runs C code. I've tried to implement basic interrupt-handling and following this other tutorial but my interrupt handling doesn't seem to work and it might point to other problems with the kernel:

if I do not include an infinite loop at the end of the main (in kernel.c), main returns to the following instruction in assembly:

jmp $ # See kernel_entry.asm.

and this seems to cause a fault: "Invalid Opcode Exception." which I don't understand.

I have 3 entries in the GDT, the second is for my code segment and the third is for my data segment. They are basically the same (base address=0x0 and limit=0xfffff) except for a few flags (as you'll see in the code).

I suspect that the issue(s) might lie in the addresses with which the compiler/linker makes the final executable that is the kernel binary but I'm not even sure how to debug this.

Here are the files that I think are relevant: boot_sect.asm

; boot_sect.asm
; A boot sector program that enters 32-bit mode and loads the kernel
; into address 0x1000.
[org 0x7c00]

KERNEL_OFFSET equ 0x1000

mov [BOOT_DRIVE], dl

mov bp, 0x9000
mov sp, bp

mov bx, MSG_REAL_MODE
call print_string
call print_endl

call load_kernel

call switch_to_pm

jmp $

%include "boot/disk_load.asm"
%include "boot/gdt.asm"
%include "boot/print_string.asm"
%include "boot/switch_to_pm.asm"
%include "boot/print_string_pm.asm"

[bits 16]
load_kernel:
    mov bx, KERNEL_OFFSET
    mov dh, 15
    mov dl, [BOOT_DRIVE]

    call disk_load
    ret

[bits 32]
BEGIN_PM:

call KERNEL_OFFSET
jmp $

BOOT_DRIVE: db 0
MSG_REAL_MODE: db "Started in 16-bit real mode", 0
MSG_LOAD_KERNEL: db "Loading kernel into memory.", 0

; Pad like before :)
times 510 - ($ - $$) db 0
dw 0xaa55

gdt.asm:

;gdt_asm
gdt_start:

gdt_null: ; mandatory null descriptor as first entry in GDT
    dd 0x0
    dd 0x0

gdt_code:
    ; base = 0x0, limit = 0xfffff
    ; 1st flags: (present)1 (privilege)00 (descriptor type)1 -> 1001b
    ; type flags: (code)1 (conforming)0 (readable)1 (accessed)0 -> 1010b
    ; 2nd flags: (granularity)1 (32-bit default)1 (64-bit seg)0 (AVL)0 -> 1100b
    dw 0xffff   ; Limit bits 0-15
    dw 0x0      ; Base bits 0-15
    db 0x0      ; Base bits 16-23
    db 10011010b    ; 1st flags, type flags
    db 11001111b    ; 2nd flags, Limit bits 16-19
    db 0x0      ; Base bits 24-31

gdt_data:
    ; Same as code section except for the type flags
    ; type flags (code)0 (expand down)0 (writeable)1 (accessed)0 -> 0010b
    dw 0xffff   ; Limit (bits 0-15)
    dw 0x0      ; Base (bits 0-15_
    db 0x0      ;Base (bits 16-23)
    db 10010010b    ; 1st flags, type flags
    db 11001111b    ; 2nd flags, Limit (bits 16-19)
    db 0x0      ; Base (bits 24-31)

gdt_end:        ; This label helps us calculate the size of the GDT.

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

; Variables to mark offset (in bytes) to from the start if the GDT to the code and data segments.
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

disk_load.asm:

disk_load:
    push dx

    mov ah, 0x02
    mov al, dh
    mov ch, 0x00
    mov dh, 0x00
    mov cl, 0x02

    int 0x13
    jc call_disk_error

    pop dx

    cmp dh, al
    jne call_disk_error_mismatch

finish_disk_load:
    ret

call_disk_error:
    call display_disk_error
    ret

call_disk_error_mismatch:
    call display_disk_error_mismatch
    ret
;;;;;;;;;;;;;;;;
;;Helper stuff;;
;;;;;;;;;;;;;;;;
display_disk_error:
    push bx
    mov bx, DISK_ERROR_MSG
    call print_string
    pop bx
    ret

display_disk_error_mismatch:
    push bx
    mov bx, DISK_ERROR_MSG_MISMATCH
    call print_string
    pop bx
    ret

DISK_ERROR_MSG_MISMATCH:
    db "Error reading from disk MISMATCH!", 0

DISK_ERROR_MSG:
    db "Error reading from disk NO MISMATCH!", 0

DISK_LOAD_DONE_MSG:
    db "Done loading from disk. (No problems)", 0

switch_to_pm.asm:

; A simple program to switch from 16-bit real mode to 32-bit protected mode.

switch_to_pm:
    cli ; disable interrupts.
    lgdt [gdt_descriptor]

    mov eax, cr0    ; set right-most bit of CPU special control register.
    or al, 1
    mov cr0, eax

    jmp CODE_SEG:init_pm    ; cause the CPU to flush real-mode 16 bit 
                ; instructions in pipeline.

[bits 32]
init_pm:
    mov ax, DATA_SEG ;DATA_SEG is defined in gdt.asm
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000
    mov esp, ebp

    mov ebx, MSG_PROT_MODE
    call print_string_pm

    ;ret
    call BEGIN_PM

MSG_PROT_MODE: db "Successfully landed in 32-bit Protected Mode", 0

kernel.c:

// A simple kernel.
#include "system.h"

#include "../drivers/screen.h"

#include "idt.h"
#include "isrs.h"
#include "irq.h"

#define NUM_TEMPLATE "000000000000"

extern void enable_interrupts(void);
extern int main_start;
extern int loop2;
extern int loop_;
extern void initialize_idt(void);

void init(void) {
    init_idt();
    install_isrs();
    install_irqs();
    enable_interrupts();
}

int i = 0;
char* kernel_load_message = "msg\n";

int main(void) {
    init();
    char* kernel_init_message = "Kernel initialized.\n";
    char* long_kernel_story = 
    "============================================\n"
    "This is the story of a little kernel\n"
    "============================================\n";
    i = 78 * 90;
    print(kernel_load_message);
    print(kernel_init_message);
    print(long_kernel_story);

    // for (;;);
    return 0;
}

system.c:

#include "system.h"

void int_to_string(char* s, int val, int n) {
  const int size = n;
  char t;
  for (int i = 0; i < size && val; i++) {
    t = val % 10;
    s[size - i - 1] = t + 48;
    val /= 10;
  }
  return;
}

void strcopy(char* dest, const char* src) {
  short curr_index = 0;
  char curr_char = src[curr_index];

  while (curr_char && curr_index < STR_MESSAGE_LENGTH) {
    dest[curr_index] = curr_char;
    curr_index += 1;
    curr_char = src[curr_index];
  }
  dest[STR_MESSAGE_LENGTH - 1] = '\0';
  return;
}

idt.c:

#include "idt.h"

extern void initialize_idt(void);

struct idt_info idt_info_ptr;
struct idt_entry idt[256];

void init_idt(void) {
  idt_info_ptr.base = (u32) (&idt);
  idt_info_ptr.limit = sizeof(struct idt_entry) * 256;

  initialize_idt();
}

void set_idt_entry(unsigned char isr_index, unsigned int base, unsigned short sel, unsigned char flags) {
  idt[isr_index].offset0_15 = base & 0x0000ffff;
  idt[isr_index].select = sel;
  idt[isr_index].zero = 0;
  idt[isr_index].flags = flags;
  idt[isr_index].offset16_31 = (base & 0xffff0000) >> 16;
}

isrs.c:

#include "system.h"
#include "isrs.h"

#include "../drivers/screen.h"

void install_isrs() {
  set_idt_entry(0, (unsigned) isr0, 0x08, 0x8E);
  set_idt_entry(1, (unsigned) isr1, 0x08, 0x8E);
  set_idt_entry(2, (unsigned) isr2, 0x08, 0x8E);
  set_idt_entry(3, (unsigned) isr3, 0x08, 0x8E);
  set_idt_entry(4, (unsigned) isr4, 0x08, 0x8E);
  set_idt_entry(5, (unsigned) isr5, 0x08, 0x8E);
  set_idt_entry(6, (unsigned) isr6, 0x08, 0x8E);
  set_idt_entry(7, (unsigned) isr7, 0x08, 0x8E);
  set_idt_entry(8, (unsigned) isr8, 0x08, 0x8E);
  set_idt_entry(9, (unsigned) isr9, 0x08, 0x8E);
  set_idt_entry(10, (unsigned) isr10, 0x08, 0x8E);
  set_idt_entry(11, (unsigned) isr11, 0x08, 0x8E);
  set_idt_entry(12, (unsigned) isr12, 0x08, 0x8E);
  set_idt_entry(13, (unsigned) isr13, 0x08, 0x8E);
  set_idt_entry(14, (unsigned) isr14, 0x08, 0x8E);
  set_idt_entry(15, (unsigned) isr15, 0x08, 0x8E);
  set_idt_entry(16, (unsigned) isr16, 0x08, 0x8E);
  set_idt_entry(17, (unsigned) isr17, 0x08, 0x8E);
  set_idt_entry(18, (unsigned) isr18, 0x08, 0x8E);
  set_idt_entry(19, (unsigned) isr19, 0x08, 0x8E);
  set_idt_entry(20, (unsigned) isr20, 0x08, 0x8E);
  set_idt_entry(21, (unsigned) isr21, 0x08, 0x8E);
  set_idt_entry(22, (unsigned) isr22, 0x08, 0x8E);
  set_idt_entry(23, (unsigned) isr23, 0x08, 0x8E);
  set_idt_entry(24, (unsigned) isr24, 0x08, 0x8E);
  set_idt_entry(25, (unsigned) isr25, 0x08, 0x8E);
  set_idt_entry(26, (unsigned) isr26, 0x08, 0x8E);
  set_idt_entry(27, (unsigned) isr27, 0x08, 0x8E);
  set_idt_entry(28, (unsigned) isr28, 0x08, 0x8E);
  set_idt_entry(29, (unsigned) isr29, 0x08, 0x8E);
  set_idt_entry(30, (unsigned) isr30, 0x08, 0x8E);
  set_idt_entry(31, (unsigned) isr31, 0x08, 0x8E);
}

void fault_handler(struct registers* regs) {
  char* exception_messages[] = {
    /*0 */ "Division By Zero Exception",
    /*1 */ "Debug Exception",
    /*2 */ "Non Maskable Interrupt Exception",
    /*3 */ "Breakpoint Exception",
    /*4 */ "Into Detected Overflow Exception",
    /*5 */ "Out of Bounds Exception",
    /*6 */ "Invalid Opcode Exception",
    /*7 */ "No Coprocessor Exception",
    /*8 */ "Double Fault Exception ",
    /*9 */ "Coprocessor Segment Overrun Exception",
    /*10*/  "Bad TSS Exception ",
    /*11*/  "Segment Not Present Exception ",
    /*12*/  "Stack Fault Exception ",
    /*13*/  "General Protection Fault Exception ",
    /*14*/  "Page Fault Exception ",
    /*15*/  "Unknown Interrupt Exception",
    /*16*/  "Coprocessor Fault Exception",
    /*17*/  "Alignment Check Exception (486+)",
    /*18*/  "Machine Check Exception (Pentium/586+)"
    /*19*/  "Reserved",
    /*20*/  "Reserved",
    /*21*/  "Reserved",
    /*22*/  "Reserved",
    /*23*/  "Reserved",
    /*24*/  "Reserved",
    /*25*/  "Reserved",
    /*26*/  "Reserved",
    /*27*/  "Reserved",
    /*28*/  "Reserved",
    /*29*/  "Reserved",
    /*30*/  "Reserved"
  };
  print("Handling Fault.\n");
  print(exception_messages[regs->int_no]);
  print("\n");
  return;
}

irq.c:

#include "system.h"
#include "low_level.h"

#include "irq.h"
#include "idt.h"

#define PIC_INIT_MSG 0x11
#define PIC_MASTER_CMD_PORT 0x20
#define PIC_SLAVE_CMD_PORT 0xA0
#define PIC_MASTER_DATA_PORT 0x21
#define PIC_SLAVE_DATA_PORT 0xA1
#define PIC_MASTER_IDT_OFFSET 0x20
#define PIC_SLAVE_IDT_OFFSET 0x28
#define PIC_EOI

void* irq_routines[16] = {
  0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0
};

void install_irq(int irq, void (*handler) (struct registers* r)) {
  irq_routines[irq] = handler;
}

void uninstall_irq(int irq) {
  irq_routines[irq] = 0;
}

/**
 * Give the two PICs the initialise command (code 0x11).
 * This command makes the PIC wait for 3 extra "initialisation words" on the data port.
 * These bytes give the PIC:
 * (1) Its vector offset. (ICW2)
 * (2) Tell it how it is wired to master/slaves. (ICW3)
 * (3) Gives additional information about the environment. (ICW4) 
 */
void irq_remap(void) {
  // Send init message to PICs.
  port_byte_out(PIC_MASTER_CMD_PORT, PIC_INIT_MSG);
  port_byte_out(PIC_SLAVE_CMD_PORT, PIC_INIT_MSG);

  // Send IDT offset (index) to PICs.:
  // 32 (0x20) for  master and 40 (0x28) for slave.
  port_byte_out(PIC_MASTER_DATA_PORT, PIC_MASTER_IDT_OFFSET);
  port_byte_out(PIC_SLAVE_DATA_PORT, PIC_SLAVE_IDT_OFFSET);

  // Send/Set Master-Slave relationship
  // - Let master know slave is at IRQ2:             00000100b (0x4)
  // - Let slave know it's "cascade identity" is 2:  00000010b (0x2)
  port_byte_out(PIC_MASTER_DATA_PORT, 0x04);
  port_byte_out(PIC_SLAVE_DATA_PORT, 0x02);

  // Give the PICs additional information
  port_byte_out(PIC_MASTER_DATA_PORT, 0x01);
  port_byte_out(PIC_SLAVE_DATA_PORT, 0x01);

  // Unmask interrupts?
  port_byte_out(PIC_MASTER_DATA_PORT, 0x0);
  port_byte_out(PIC_SLAVE_DATA_PORT, 0x0);
}

void install_irqs(void) {
  irq_remap();

  set_idt_entry(32, (unsigned) irq0, 0x08, 0x8E);
  set_idt_entry(33, (unsigned) irq1, 0x08, 0x8E);
  set_idt_entry(34, (unsigned) irq2, 0x08, 0x8E);
  set_idt_entry(35, (unsigned) irq3, 0x08, 0x8E);
  set_idt_entry(36, (unsigned) irq4, 0x08, 0x8E);
  set_idt_entry(37, (unsigned) irq5, 0x08, 0x8E);
  set_idt_entry(38, (unsigned) irq6, 0x08, 0x8E);
  set_idt_entry(39, (unsigned) irq7, 0x08, 0x8E);
  set_idt_entry(40, (unsigned) irq8, 0x08, 0x8E);
  set_idt_entry(41, (unsigned) irq9, 0x08, 0x8E);
  set_idt_entry(42, (unsigned) irq10, 0x08, 0x8E);
  set_idt_entry(43, (unsigned) irq11, 0x08, 0x8E);
  set_idt_entry(44, (unsigned) irq12, 0x08, 0x8E);
  set_idt_entry(45, (unsigned) irq13, 0x08, 0x8E);
  set_idt_entry(46, (unsigned) irq14, 0x08, 0x8E);
  set_idt_entry(47, (unsigned) irq15, 0x08, 0x8E);
}

void irq_handler(struct registers* r) {

  void (*handler) (struct registers r);

  handler = irq_routines[r->int_no];

  if (handler)
    handler(*r);

  // If it's a slave interrupt, must send "complete" signal to slave too.
  if (r->int_no >= 40) {
    port_byte_out(PIC_SLAVE_CMD_PORT, 0x20);
  }

  port_byte_out(PIC_MASTER_CMD_PORT, 0x20);
}


**low_level.c**:

    unsigned char port_byte_in(unsigned short port) {
        // Reads a byte from the specified port.
        // "=a" (result) means: put AL register in variable RESULT when finished
        // "d" (port) means: load EDX with port
        unsigned char result;
        __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
        return result;
    }

    void port_byte_out(unsigned short port, unsigned char data) {
        // "a" (data) means: load EAX with data
        // "d: (port) means: load EDX with port
        __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
    }

    unsigned short port_word_in(unsigned short port) {
        unsigned short result;
        __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
        return result;
    }

    void port_word_out(unsigned short port, unsigned char data) {
        __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
    }

If more of the code is needed, I'm more than happy to share. See my github here (See "dev" branch!). Thanks :)

gcc
x86
kernel
nasm
osdev
asked on Stack Overflow Oct 19, 2019 by David • edited Oct 19, 2019 by Michael Petch

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0