diff --git a/Makefile b/Makefile index f7efd43..2408a9d 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ CFLAGS := -std=c17 -ffreestanding -nostdlib -fno-builtin \ -fno-pic -fno-pie -fno-stack-protector \ -Wall -Wextra -Werror -O2 -Iinclude -OBJS := $(BUILD)/entry.o $(BUILD)/kmain.o $(BUILD)/console.o +OBJS := $(BUILD)/entry.o $(BUILD)/isr.o $(BUILD)/kmain.o $(BUILD)/console.o $(BUILD)/idt.o all: $(IMG) @@ -30,12 +30,18 @@ $(BUILD): $(BUILD)/entry.o: $(ARCH_DIR)/entry.asm | $(BUILD) $(NASM) -f elf64 $< -o $@ -$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h | $(BUILD) +$(BUILD)/isr.o: $(ARCH_DIR)/isr.asm | $(BUILD) + $(NASM) -f elf64 $< -o $@ + +$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h include/idt.h | $(BUILD) $(CC) $(CFLAGS) -c $< -o $@ $(BUILD)/console.o: $(ARCH_DIR)/console.c include/console.h | $(BUILD) $(CC) $(CFLAGS) -c $< -o $@ +$(BUILD)/idt.o: $(ARCH_DIR)/idt.c include/idt.h include/console.h | $(BUILD) + $(CC) $(CFLAGS) -c $< -o $@ + $(BUILD)/kernel.elf: $(OBJS) $(ARCH_DIR)/link.ld $(LD) -m elf_x86_64 -T $(ARCH_DIR)/link.ld -o $@ $(OBJS) diff --git a/include/idt.h b/include/idt.h new file mode 100644 index 0000000..5b31dd7 --- /dev/null +++ b/include/idt.h @@ -0,0 +1,66 @@ +/* Nadir interrupt IDT + CPU exception entry points. + * + * Runs in 64-bit long mode at Ring 0 (CPL 0) with the stage-2 GDT. + * Privileged primitive: `lidt` in idt_init(). Fault behavior: before + * idt_init() any exception triple-faults (no IDT); after, CPU vectors + * 0-31 land in the assembly stubs (isr.asm) and then in + * exception_handler(), which prints and halts. Vectors 32-255 are left + * zero (not present) on purpose because PIC remap + IRQ handling is a later step. + * Interrupts stay disabled throughout (IF=0); exceptions still fire. + */ + +#ifndef NADIR_IDT_H +#define NADIR_IDT_H + +#include + +/* Number of CPU exception vectors this base installs (0-31). */ +#define IDT_EXCEPTION_COUNT 32 + +/* Stack layout on entry to exception_handler(). + * + * Built by the CPU (RIP/CS/RFLAGS, plus an error code on vectors + * 8, 10-14, 17, 21) plus isr.asm: the stub normalizes the stack to + * [vector, error] and isr_common pushes the 15 general-purpose + * registers. RDI points at this struct (System V first argument). + * Field order matches the push order in isr_common — keep in sync. + */ +struct interrupt_frame { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rbp; + uint64_t rdi; + uint64_t rsi; + uint64_t rdx; + uint64_t rcx; + uint64_t rbx; + uint64_t rax; + /* Pushed by the stub: exception vector 0-31. */ + uint64_t vector; + /* CPU-pushed error code, or 0 inserted by the stub. */ + uint64_t error; + /* Pushed by the CPU. */ + uint64_t rip; + uint64_t cs; + uint64_t rflags; + /* Pushed by the CPU only on a privilege change; garbage for + * kernel-mode faults */ + uint64_t rsp; + uint64_t ss; +}; + +/* Build the IDT (vectors 0-31 -> isr stubs) and load it with `lidt`. + * Ring 0 only. Leaves IF cleared; returns with exceptions catchable. */ +void idt_init(void); + +/* C entry point for vectors 0-31, called from isr_common with the + * frame above. Prints vector/error/RIP and halts; never returns. */ +void exception_handler(struct interrupt_frame *frame); + +#endif /* NADIR_IDT_H */ diff --git a/kernel/arch/x86_64/idt.c b/kernel/arch/x86_64/idt.c new file mode 100644 index 0000000..e6bd4e3 --- /dev/null +++ b/kernel/arch/x86_64/idt.c @@ -0,0 +1,115 @@ +/* Nadir IDT base: 32 CPU-exception gates + fault printer. + * + * Runs in 64-bit long mode at Ring 0 (CPL 0) with the stage-2 GDT + * (kernel code selector 0x18). Privileged primitive: `lidt` in + * idt_init(). Fault behavior: before idt_init() any fault + * triple-faults; after, vectors 0-31 print diagnostics and halt. + * Vectors 32-255 stay not-present until the PIC is remapped. + * IF stays cleared; exceptions fire regardless of IF. + */ + +#include + +#include "console.h" +#include "idt.h" + +/* 64-bit interrupt gate descriptor (Intel SDM Vol. 3, IDT gate). */ +struct idt_entry { + uint16_t offset_low; + uint16_t selector; + uint8_t ist; + uint8_t type_attr; + uint16_t offset_mid; + uint32_t offset_high; + uint32_t reserved; +} __attribute__((packed)); + +/* IDTR image for `lidt`. */ +struct idtr { + uint16_t limit; + uint64_t base; +} __attribute__((packed)); + +/* Gate flags: present, DPL 0, 64-bit interrupt gate (0xE). */ +#define IDT_FLAG_PRESENT_INT 0x8EU +/* Kernel code segment from the stage-2 GDT (entry.asm). */ +#define KERNEL_CS 0x18U + +/* Full 256-entry table, 16-byte aligned as the CPU prefers. BSS. */ +static struct idt_entry idt[256] __attribute__((aligned(16))); +static struct idtr idtr; + +/* Stub addresses from isr.asm, one per exception vector 0-31. */ +extern uint64_t isr_stub_table[IDT_EXCEPTION_COUNT]; + +/* Install one gate: handler address + segment + IST index + flags. */ +static void idt_set_gate(uint8_t vector, uint64_t handler, uint16_t selector, + uint8_t ist, uint8_t flags) +{ + struct idt_entry *e = &idt[vector]; + + e->offset_low = (uint16_t)(handler & 0xFFFFU); + e->selector = selector; + e->ist = ist & 0x07U; + e->type_attr = flags; + e->offset_mid = (uint16_t)((handler >> 16) & 0xFFFFU); + e->offset_high = (uint32_t)((handler >> 32) & 0xFFFFFFFFU); + e->reserved = 0; +} + +/* Human-readable names for vectors 0-31 (Intel SDM Vol. 3). */ +static const char *const exception_names[IDT_EXCEPTION_COUNT] = { + "Divide Error (#DE)", "Debug Exception (#DB)", "NMI", + "Breakpoint (#BP)", "Overflow (#OF)", "Bound Range (#BR)", + "Invalid Opcode (#UD)", "Device Not Available (#NM)", + "Double Fault (#DF)", "Coprocessor Segment Overrun", + "Invalid TSS (#TS)", "Segment Not Present (#NP)", + "Stack-Segment Fault (#SS)", "General Protection (#GP)", + "Page Fault (#PF)", "Reserved (15)", + "x87 Floating-Point (#MF)", "Alignment Check (#AC)", + "Machine Check (#MC)", "SIMD Floating-Point (#XM)", + "Virtualization (#VE)", "Control Protection (#CP)", + "Reserved (22)", "Reserved (23)", "Reserved (24)", "Reserved (25)", + "Reserved (26)", "Reserved (27)", "Hypervisor Injection (#HV)", + "VMM Communication (#VC)", "Security Exception (#SX)", "Reserved (31)", +}; + +void idt_init(void) +{ + for (uint16_t i = 0; i < IDT_EXCEPTION_COUNT; i++) { + idt_set_gate((uint8_t)i, isr_stub_table[i], KERNEL_CS, 0, + IDT_FLAG_PRESENT_INT); + } + + idtr.limit = (uint16_t)(sizeof(idt) - 1); + idtr.base = (uint64_t)&idt[0]; + __asm__ volatile("lidt %0" ::"m"(idtr) : "memory"); +} + +void exception_handler(struct interrupt_frame *frame) +{ + uint64_t vector = frame->vector; + const char *name = "Unknown Exception"; + + if (vector < IDT_EXCEPTION_COUNT) { + name = exception_names[vector]; + } + + console_puts("\n*** EXCEPTION: "); + console_puts(name); + console_puts(" (vector "); + console_puthex64(vector); + console_puts(") ***\nerror: "); + console_puthex64(frame->error); + console_puts(" rip: "); + console_puthex64(frame->rip); + console_puts(" cs: "); + console_puthex64(frame->cs); + console_puts(" rflags: "); + console_puthex64(frame->rflags); + console_puts("\nSystem halted.\n"); + + for (;;) { + __asm__ volatile("cli; hlt"); + } +} diff --git a/kernel/arch/x86_64/isr.asm b/kernel/arch/x86_64/isr.asm new file mode 100644 index 0000000..6df5da1 --- /dev/null +++ b/kernel/arch/x86_64/isr.asm @@ -0,0 +1,107 @@ +; Nadir exception stubs: vectors 0-31 -> exception_handler. +; +; Runs in 64-bit long mode at Ring 0 (CPL 0). Entered by the CPU with +; IF cleared (interrupt gate); no privilege change, so the CPU pushed +; RIP/CS/RFLAGS plus an error code only on vectors 8, 10-14, 17, 21. +; Fault behavior: a fault here (before the IDT is loaded, or inside the +; handler) triple-faults. Never returns: the C handler halts. +; +; Contract with include/idt.h `struct interrupt_frame`: each stub leaves +; [vector, error] on the stack (pushing a dummy 0 where the CPU did not), +; then isr_common pushes the 15 GPRs and calls the C handler with RDI +; pointing at the frame. Field order must stay in sync. Interrupts stay +; disabled; -mno-red-zone makes the pushes safe. + +bits 64 +section .text + +global isr_stub_table +extern exception_handler + +; Vectors without a CPU error code: normalize with a dummy 0. +%macro ISR_NOERR 1 +global isr_stub_%1 +isr_stub_%1: + push 0 + push %1 + jmp isr_common +%endmacro + +; Vectors where the CPU already pushed an error code: only push the vector. +%macro ISR_ERR 1 +global isr_stub_%1 +isr_stub_%1: + push %1 + jmp isr_common +%endmacro + +ISR_NOERR 0 +ISR_NOERR 1 +ISR_NOERR 2 +ISR_NOERR 3 +ISR_NOERR 4 +ISR_NOERR 5 +ISR_NOERR 6 +ISR_NOERR 7 +ISR_ERR 8 +ISR_NOERR 9 +ISR_ERR 10 +ISR_ERR 11 +ISR_ERR 12 +ISR_ERR 13 +ISR_ERR 14 +ISR_NOERR 15 +ISR_NOERR 16 +ISR_ERR 17 +ISR_NOERR 18 +ISR_NOERR 19 +ISR_NOERR 20 +ISR_ERR 21 +ISR_NOERR 22 +ISR_NOERR 23 +ISR_NOERR 24 +ISR_NOERR 25 +ISR_NOERR 26 +ISR_NOERR 27 +ISR_NOERR 28 +ISR_NOERR 29 +ISR_NOERR 30 +ISR_NOERR 31 + +; Common entry: save GPRs, call void exception_handler(frame in RDI), halt +; if it ever returns (it must not). +isr_common: + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + mov rdi, rsp + call exception_handler + ; Unreachable in practice; park the CPU if the handler returns. + cli +.hang: + hlt + jmp .hang + +section .rodata +align 8 +isr_stub_table: + dq isr_stub_0, isr_stub_1, isr_stub_2, isr_stub_3 + dq isr_stub_4, isr_stub_5, isr_stub_6, isr_stub_7 + dq isr_stub_8, isr_stub_9, isr_stub_10, isr_stub_11 + dq isr_stub_12, isr_stub_13, isr_stub_14, isr_stub_15 + dq isr_stub_16, isr_stub_17, isr_stub_18, isr_stub_19 + dq isr_stub_20, isr_stub_21, isr_stub_22, isr_stub_23 + dq isr_stub_24, isr_stub_25, isr_stub_26, isr_stub_27 + dq isr_stub_28, isr_stub_29, isr_stub_30, isr_stub_31 diff --git a/kernel/core/kmain.c b/kernel/core/kmain.c index 090ae40..3152165 100644 --- a/kernel/core/kmain.c +++ b/kernel/core/kmain.c @@ -2,12 +2,13 @@ * * Called once from the stage-2 entry stub after the CPU is in 64-bit long * mode with a valid stack, zeroed BSS and identity-mapped memory. Runs at - * Ring 0 (CPL 0), interrupts disabled, no IDT yet: any fault triple-faults, - * so this function proves the whole boot chain simply by running. + * Ring 0 (CPL 0) interrupts disabled. Installs the exception IDT first + * so from then on a fault prints diagnostics instead of triple-faulting. * It never returns; on return the caller halts the CPU. */ #include "console.h" +#include "idt.h" /* A value that only comes out right if the CPU truly executes 64-bit * arithmetic: every nibble survives the shifts above 32 bits. */ @@ -18,6 +19,8 @@ void kmain(void) console_clear(); console_puts("Hello World from Nadir Kernel !\n"); console_puts("Running C code in 64-bit long mode (Ring 0).\n"); + idt_init(); + console_puts("IDT installed (vectors 0-31).\n"); console_puts("64-bit proof value: "); console_puthex64(PROOF_VALUE); console_puts("\nHalting.\n");