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 :)

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
David
  • 487
  • 1
  • 5
  • 18
  • There are too many questions in this supposedly one question. The print problem is completely separate from the IDT woes. You cannot return from `main` because this is not a hosted implementation. – Antti Haapala -- Слава Україні Oct 19 '19 at 06:59
  • right, i'll leave just the first one – David Oct 19 '19 at 07:03
  • And despite being so long the code in question is incomplete too - having it in the github is not sufficient! it doesn't have the – Antti Haapala -- Слава Україні Oct 19 '19 at 07:03
  • `jmp $` is an infinite loop, akin to `for (;;);` It cannot cause illegal opcode exception as such. – Antti Haapala -- Слава Україні Oct 19 '19 at 07:04
  • BTW I suggest that you start with a multiboot kernel that is loaded with Grub, a boot loader is the single most annoying thing to write. – Antti Haapala -- Слава Україні Oct 19 '19 at 07:06
  • Where’s the call to main? I can’t find it. – prl Oct 19 '19 at 08:24
  • Your bootloader probably works okay in most emulators, on real hardware but you didn't properly set the DS segment to 0 before saving the value in DL to memory. It just happened to work. I have general bootloader tips here: https://stackoverflow.com/a/32705076/3857942 – Michael Petch Oct 19 '19 at 10:46
  • I guess the infinite loop in the exception handlers is deliberate, so not really a bug. When you set up an entry in the IDT to be an interrupt gate (which you do) there is no need to use `cli` at the beginning of each handler as interrupt gates automatically have the CPU disable external interrupts (setting IF to 0) before your interrupt handler is called. – Michael Petch Oct 19 '19 at 10:52
  • 1
    I wasn't originally looking at the dev branch. Upon close inspection your IRQ handler does this `handler = irq_routines[r->int_no];` when it should be `handler = irq_routines[r->int_no-32];` (you need to subtract 32 to get the index into the irq table. When compiling a kernel with GCC add the `-ffreestanding` option to avoid using a hosted configuration. – Michael Petch Oct 19 '19 at 12:58
  • 1
    Lastly in your bootloader you are only reading 15 sectors (7680 bytes). Your kernel appears to be larger than that. You have to make sure you read in enough sectors to load the entire kernel or weird and wonderful things can happen. Bochs and QEMU support multitrack reads so you should be able to read the 35 sectors (17920 bytes) after the boot sector. (change `mov al, 15` to `mov al, 35`). Be aware your kernel is getting close to 17920 as it is (may vary between distro and compiler) – Michael Petch Oct 19 '19 at 13:12
  • @M I adjusted the code with the changes you suggested – David Oct 19 '19 at 14:30
  • Subtracting 32 when indexing the irq_routines stopped the "Invalid opcode faults" (makes a lot of sense now that I see it), loading 35 sectors instead of 15 fixed problems with accessing global variables. I also set ds, es and ss registers. Thanks – David Oct 19 '19 at 14:37
  • I originally had a typo in an earlier comment. It should have said: To avoid unnecessary copying of structures, I'd recommend your handlers be defined as `void (*handler) (struct registers *r);` instead of `void (*handler) (struct registers r);` . You'd also have to change `if (handler) handler(*r);` to `if (handler) handler(r);` in `irq_handler` – Michael Petch Oct 19 '19 at 17:10

0 Answers0