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 :)
User contributions licensed under CC BY-SA 3.0