4 Commits

20 changed files with 297 additions and 44 deletions

5
.gitignore vendored
View File

@@ -6,4 +6,7 @@ iso_root
*.iso *.iso
*.gch *.gch
*/*.gch */*.gch
*/*/*.gch */*/*.gch
.gdb_history
symbols.map
symbols.S

24
DOOM.txt Normal file
View File

@@ -0,0 +1,24 @@
up to doom:
- Return from pedicel_main() normally (to idle)
** Checkpoint: ring0 process working
- VFS layer (open/read/write/...) with USTar filesystem (for initrd)
** Checkpoint: files not linked to but accessible by the kernel
- Ring3 memory mappings
- Ring3 privilege switch
** Checkpoint: welcome to userland
- Syscall interface
- Implement syscalls needed for doom
** Checkpoint: can run simple programs, ring 3, loaded from filesystem
- Properly handle the keyboard interrupt (keyboard buffer)
- Port DOOMgeneric (few functions with Framebuffer/ticks/etc.)
** Achievement: It runs doom!

View File

@@ -1,4 +1,4 @@
SOURCES = src/boot/boot.c src/sched/scheduler.c src/sched/process.c src/mem/heap/kheap.c src/mem/paging/vmm.c src/mem/paging/paging.c src/mem/paging/pmm.c src/string/string.c src/io/kbd/ps2.c src/io/serial/serial.c src/io/term/printf.c src/io/term/term.c src/idt/idt.c src/mem/gdt/gdt.c src/mem/misc/utils.c src/time/timer.c src/kmain.c SOURCES = src/debug/panic.c src/debug/stacktrace.c src/boot/boot.c src/sched/scheduler.c src/sched/process.c src/mem/heap/kheap.c src/mem/paging/vmm.c src/mem/paging/paging.c src/mem/paging/pmm.c src/string/string.c src/io/kbd/ps2.c src/io/serial/serial.c src/io/term/printf.c src/io/term/term.c src/idt/idt.c src/mem/gdt/gdt.c src/mem/misc/utils.c src/time/timer.c src/kmain.c
PROBLEMATIC_FLAGS=-Wno-unused-parameter -Wno-unused-variable PROBLEMATIC_FLAGS=-Wno-unused-parameter -Wno-unused-variable
@@ -7,6 +7,11 @@ build:
x86_64-elf-gcc -g -c -Isrc $(SOURCES) $(PROBLEMATIC_FLAGS) -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-omit-frame-pointer -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel x86_64-elf-gcc -g -c -Isrc $(SOURCES) $(PROBLEMATIC_FLAGS) -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-omit-frame-pointer -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel
objcopy -O elf64-x86-64 -B i386 -I binary zap-light16.psf zap-light16.o objcopy -O elf64-x86-64 -B i386 -I binary zap-light16.psf zap-light16.o
nasm -f elf64 src/idt/idt.S -o idt_stub.o nasm -f elf64 src/idt/idt.S -o idt_stub.o
nasm -f elf64 src/entry.S -o entry.o
x86_64-elf-ld -o pepperk -T linker.ld *.o
nm -n pepperk | awk '$$2 ~ /[TtDdBbRr]/ {print $$1, $$3}' > symbols.map
python3 symbols.py
nasm -f elf64 symbols.S -o symbols.o
x86_64-elf-ld -o pepperk -T linker.ld *.o x86_64-elf-ld -o pepperk -T linker.ld *.o
limine/limine: limine/limine:
@@ -32,11 +37,15 @@ build-iso: limine/limine build
./limine/limine bios-install pepper.iso ./limine/limine bios-install pepper.iso
debug: debug:
qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot -no-shutdown & /usr/bin/qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot -no-shutdown &
gdb pepperk --command=debug.gdb gdb pepperk --command=debug.gdb
debug2:
/usr/bin/qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot -no-shutdown &
pwndbg pepperk --command=debug.gdb
run: build-iso run: build-iso
qemu-system-x86_64 -cdrom pepper.iso -serial stdio /usr/bin/qemu-system-x86_64 -cdrom pepper.iso -serial stdio
clean: clean:
rm -rf *.o pepperk iso_root pepper.iso limine rm -rf *.o symbols.map symbols.S pepperk iso_root pepper.iso limine

View File

@@ -2,7 +2,7 @@
## Trying the kernel ## Trying the kernel
First install the dependencies: `sudo apt install xorriso make qemu-system` First install the dependencies: `sudo apt install python3 xorriso make qemu-system`
Then, to compile the kernel and make an ISO image file: `make build-iso` Then, to compile the kernel and make an ISO image file: `make build-iso`
To run it with QEMU, `make run` To run it with QEMU, `make run`

View File

@@ -1,6 +1,6 @@
OUTPUT_FORMAT(elf64-x86-64) OUTPUT_FORMAT(elf64-x86-64)
ENTRY(kmain) ENTRY(_start)
PHDRS PHDRS
{ {

View File

@@ -10,7 +10,7 @@
/* version */ /* version */
#define PEPPEROS_VERSION_MAJOR "0" #define PEPPEROS_VERSION_MAJOR "0"
#define PEPPEROS_VERSION_MINOR "0" #define PEPPEROS_VERSION_MINOR "0"
#define PEPPEROS_VERSION_PATCH "1" #define PEPPEROS_VERSION_PATCH "58"
#define PEPPEROS_SPLASH "pepperOS version "PEPPEROS_VERSION_MAJOR"."PEPPEROS_VERSION_MINOR"."PEPPEROS_VERSION_PATCH"\n" #define PEPPEROS_SPLASH "pepperOS version "PEPPEROS_VERSION_MAJOR"."PEPPEROS_VERSION_MINOR"."PEPPEROS_VERSION_PATCH"\n"
/* process */ /* process */
@@ -27,6 +27,7 @@
#define KERNEL_BASE 0xFFFFFFFF80000000ULL #define KERNEL_BASE 0xFFFFFFFF80000000ULL
// 2 MB should be enough (as of now, the whole kernel ELF is around 75kb) // 2 MB should be enough (as of now, the whole kernel ELF is around 75kb)
#define KERNEL_SIZE 0x200000 #define KERNEL_SIZE 0x200000
#define KERNEL_STACK_SIZE 65536
/* heap */ /* heap */
#define KHEAP_SIZE (16*1024*1024) #define KHEAP_SIZE (16*1024*1024)

23
src/debug/panic.c Normal file
View File

@@ -0,0 +1,23 @@
#include <stddef.h>
#include "idt/idt.h"
#include "io/serial/serial.h"
#include "kernel.h"
void panic(struct cpu_status_t* ctx, const char* str)
{
CLEAR_INTERRUPTS;
if (ctx == NULL)
{
DEBUG("\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[0m Something went horribly wrong! (no cpu ctx)");
DIE_DEBUG(str);
skputc('\n');
DEBUG("\x1b[38;5;231m\x1b[48;5;196mend Kernel panic - halting...\x1b[0m");
hcf();
}
DEBUG("\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[0m at rip=%p\nSomething went horribly wrong! vect=0x%.2x errcode=0x%x\n\rrax=%p rbx=%p rcx=%p rdx=%p\n\rrsi=%p rdi=%p r8=%p r9=%p\n\rr10=%p r11=%p r12=%p r13=%p\n\rr14=%p r15=%p\n\n\rflags=%p\n\rHalting...",
ctx->iret_rip,
ctx->vector_number, ctx->error_code, ctx->rax, ctx->rbx, ctx->rcx, ctx->rdx, ctx->rsi, ctx->rdi,
ctx->r8, ctx->r9, ctx->r10, ctx->r11, ctx->r12, ctx->r13, ctx->r14, ctx->r15, ctx->iret_flags);
debug_stack_trace(100);
hcf();
}

75
src/debug/stacktrace.c Normal file
View File

@@ -0,0 +1,75 @@
#include <stdint.h>
#include "kernel.h"
void debug_stack_trace(unsigned int max_frames)
{
DEBUG("*** begin stack trace ***");
// Thanks GCC :)
uintptr_t* rbp = (uintptr_t*)__builtin_frame_address(0);
for (unsigned int frame=0; frame<max_frames && rbp != NULL; frame++)
{
// Return address, 1 word above saved rbp
uintptr_t rip = rbp[1];
uintptr_t offset = 0;
const char* name = debug_find_symbol(rip, &offset);
DEBUG("[%u] <0x%p> (%s+0x%x)", frame, (void*)rip, name, offset);
uintptr_t* next_rbp = (uintptr_t*)rbp[0];
// invalid rbp or we're at the end
if (next_rbp <= rbp || next_rbp == NULL)
{
break;
}
rbp = next_rbp;
}
DEBUG("*** end stack trace ***");
}
typedef struct
{
uint64_t addr;
const char *name;
} __attribute__((packed)) kernel_symbol_t;
__attribute__((weak)) extern kernel_symbol_t symbol_table[];
__attribute__((weak)) extern uint64_t symbol_count;
// binary search
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset)
{
if (!symbol_table || symbol_count == 0)
{
if (offset) *offset = 0;
return "???";
}
int low = 0, high = (int)symbol_count - 1;
int best = -1;
while (low <= high)
{
int mid = (low + high) / 2;
if (symbol_table[mid].addr <= rip)
{
best = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
if (best != -1)
{
if (offset)
{
*offset = rip - symbol_table[best].addr;
}
return symbol_table[best].name;
}
if (offset) *offset = 0;
return "unknown";
}

23
src/entry.S Normal file
View File

@@ -0,0 +1,23 @@
bits 64
global _start
extern kmain
extern kernel_stack
KERNEL_STACK_SIZE equ 65536
section .text
_start:
cli
; load kernel stack
lea rsp, [kernel_stack+KERNEL_STACK_SIZE]
; rbp=0 so last frame in stack trace
xor rbp, rbp
; 16 byte align
and rsp, -16
call kmain

View File

@@ -13,6 +13,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "sched/scheduler.h" #include "sched/scheduler.h"
#include "config.h" #include "config.h"
#include "sched/process.h"
struct interrupt_descriptor idt[256]; struct interrupt_descriptor idt[256];
struct idtr idt_reg; struct idtr idt_reg;
@@ -118,6 +119,11 @@ static void gp_fault_handler(struct cpu_status_t* ctx)
struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context) struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
{ {
if (context == NULL)
{
panic(NULL, "Interrupt dispatch recieved NULL context!");
}
switch(context->vector_number) switch(context->vector_number)
{ {
case 0: case 0:
@@ -140,6 +146,7 @@ struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
break; break;
case 6: case 6:
DEBUG("Invalid Opcode!"); DEBUG("Invalid Opcode!");
panic(context, "Invalid Opcode!");
break; break;
case 7: case 7:
DEBUG("Device Not Available!"); DEBUG("Device Not Available!");
@@ -190,16 +197,17 @@ struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
case 32: // Timer Interrupt case 32: // Timer Interrupt
ticks++; ticks++;
// Send an EOI so that we can continue having interrupts
outb(0x20, 0x20);
if (ticks % SCHEDULER_QUANTUM == 0) if (ticks % SCHEDULER_QUANTUM == 0)
{ {
CLEAR_INTERRUPTS; return scheduler_schedule(context);
scheduler_schedule(); //struct cpu_status_t* current_ctx = scheduler_schedule(context);
SET_INTERRUPTS; //process_switch(current_ctx->iret_rsp, current_ctx->iret_rip);
//SET_INTERRUPTS;
} }
// Send an EOI so that we can continue having interrupts
outb(0x20, 0x20);
break; break;
case 33: case 33:

View File

@@ -30,6 +30,11 @@ enum ErrorCodes
void panic(struct cpu_status_t* ctx, const char* str); void panic(struct cpu_status_t* ctx, const char* str);
void hcf(); void hcf();
void idle();
void debug_stack_trace(unsigned int max_frames);
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset);
#define assert(check) do { if(!(check)) hcf(); } while(0) #define assert(check) do { if(!(check)) hcf(); } while(0)
struct boot_context struct boot_context

View File

@@ -35,26 +35,9 @@ void hcf()
} }
// Doing nothing (can be interrupted) // Doing nothing (can be interrupted)
void idle() {for(;;)asm("hlt");} void idle() {SET_INTERRUPTS; for(;;)asm("hlt");}
void panic(struct cpu_status_t* ctx, const char* str) uint8_t kernel_stack[KERNEL_STACK_SIZE] __attribute__((aligned(16)));
{
CLEAR_INTERRUPTS;
if (ctx == NULL)
{
DEBUG("\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[0m Something went horribly wrong! (no cpu ctx)");
DIE_DEBUG(str);
skputc('\n');
DEBUG("\x1b[38;5;231m\x1b[48;5;196mend Kernel panic - halting...\x1b[0m");
hcf();
}
DEBUG("\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[0m at rip=%p\nSomething went horribly wrong! vect=0x%.2x errcode=0x%x\n\rrax=%p rbx=%p rcx=%p rdx=%p\n\rrsi=%p rdi=%p r8=%p r9=%p\n\rr10=%p r11=%p r12=%p r13=%p\n\rr14=%p r15=%p\n\n\rflags=%p\n\rstack at rbp=%p\n\rHalting...",
ctx->iret_rip,
ctx->vector_number, ctx->error_code, ctx->rax, ctx->rbx, ctx->rcx, ctx->rdx, ctx->rsi, ctx->rdi,
ctx->r8, ctx->r9, ctx->r10, ctx->r11, ctx->r12, ctx->r13, ctx->r14, ctx->r15, ctx->iret_flags,
ctx->rbp);
hcf();
}
struct boot_context boot_ctx; struct boot_context boot_ctx;
@@ -68,17 +51,17 @@ extern struct process_t* current_process;
void pedicel_main(void* arg) void pedicel_main(void* arg)
{ {
panic(NULL, "we did it!"); printf("Hello, world from a KERNEL PROCESS!");
} }
void two_main(void* arg) void two_main(void* arg)
{ {
printf("...process 2 speaking!!!");
} }
void three_main(void* arg) void idle_main(void* arg)
{ {
for(;;)asm("hlt");
} }
// This is our entry point // This is our entry point
@@ -112,13 +95,17 @@ void kmain()
vmm_init(); vmm_init();
struct process_t* idle_proc = process_create("idle", (void*)idle_main, 0);
struct process_t* pedicel = process_create("pedicel", (void*)pedicel_main, 0); struct process_t* pedicel = process_create("pedicel", (void*)pedicel_main, 0);
//struct process_t* two = process_create("two", (void*)two_main, 0); struct process_t* two = process_create("two", (void*)two_main, 0);
//struct process_t* three = process_create("three", (void*)three_main, 0);
process_display_list(processes_list); process_display_list(processes_list);
scheduler_init(); scheduler_init();
current_process = idle_proc;
current_process->status = RUNNING;
SET_INTERRUPTS; SET_INTERRUPTS;
keyboard_init(FR); keyboard_init(FR);

View File

@@ -142,6 +142,6 @@ void kfree(void* ptr)
// Should return a pointer to top of the stack (as stack grows DOWNWARDS) // Should return a pointer to top of the stack (as stack grows DOWNWARDS)
void* kalloc_stack() void* kalloc_stack()
{ {
uint8_t* ptr = kmalloc(PROCESS_STACK_SIZE); uint8_t* ptr = kmalloc(PROCESS_STACK_SIZE); // As it's out of kmalloc, stack is already mapped into kernel space
return ptr ? ptr+PROCESS_STACK_SIZE : NULL; return ptr ? ptr+PROCESS_STACK_SIZE : NULL;
} }

View File

@@ -24,7 +24,7 @@ If we use 1GB huge pages: PML4 -> PDPT -> 1gb pages
4KB (regular size): PML4 -> PDPT -> PD -> PT -> 4kb pages 4KB (regular size): PML4 -> PDPT -> PD -> PT -> 4kb pages
*/ */
static inline void load_cr3(uint64_t value) { void load_cr3(uint64_t value) {
asm volatile ("mov %0, %%cr3" :: "r"(value) : "memory"); asm volatile ("mov %0, %%cr3" :: "r"(value) : "memory");
} }

View File

@@ -16,6 +16,9 @@
void paging_init(); void paging_init();
void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_t flags); void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_t flags);
// To swap root page tables
void load_cr3(uint64_t value);
extern uint64_t hhdm_off; extern uint64_t hhdm_off;
#define PHYS_TO_VIRT(x) ((void*)((uintptr_t)(x) + hhdm_off)) #define PHYS_TO_VIRT(x) ((void*)((uintptr_t)(x) + hhdm_off))

View File

@@ -11,10 +11,13 @@
#include "string/string.h" #include "string/string.h"
#include "mem/gdt/gdt.h" #include "mem/gdt/gdt.h"
#include "config.h" #include "config.h"
#include "io/serial/serial.h"
struct process_t* processes_list; struct process_t* processes_list;
struct process_t* current_process; struct process_t* current_process;
extern uint64_t *kernel_pml4;
size_t next_free_pid = 0; size_t next_free_pid = 0;
void process_init() void process_init()
@@ -52,14 +55,22 @@ struct process_t* process_create(char* name, void(*function)(void*), void* arg)
proc->pid = next_free_pid++; proc->pid = next_free_pid++;
proc->status = READY; proc->status = READY;
uint64_t* stack_top = (uint64_t*)kalloc_stack();
// push return address to the stack so when "ret" hits we jmp to exit instead of idk what
// stack grows DOWNWARDS!!
*(--stack_top) = (uint64_t)process_exit;
proc->context = ctx; proc->context = ctx;
proc->context->iret_ss = KERNEL_DATA_SEGMENT; // process will live in kernel mode proc->context->iret_ss = KERNEL_DATA_SEGMENT; // process will live in kernel mode
proc->context->iret_rsp = (uint64_t)kalloc_stack(); proc->context->iret_rsp = (uint64_t)stack_top;
proc->context->iret_flags = 0x202; //bit 2 and 9 set (Interrupt Flag) proc->context->iret_flags = 0x202; //bit 2 and 9 set (Interrupt Flag)
proc->context->iret_cs = KERNEL_CODE_SEGMENT; proc->context->iret_cs = KERNEL_CODE_SEGMENT;
proc->context->iret_rip = (uint64_t)function; // beginning of executable code proc->context->iret_rip = (uint64_t)function; // beginning of executable code
proc->context->rdi = (uint64_t)arg; // 1st arg is in rdi (as per x64 calling convention) proc->context->rdi = (uint64_t)arg; // 1st arg is in rdi (as per x64 calling convention)
proc->context->rbp = 0; proc->context->rbp = 0;
// Kernel PML4 as it already maps code/stack (when switching to userland we'll have to change that)
proc->root_page_table = kernel_pml4;
proc->next = 0; proc->next = 0;
@@ -126,4 +137,40 @@ struct process_t* process_get_next(struct process_t* process)
{ {
if (!process) return NULL; if (!process) return NULL;
return process->next; return process->next;
}
// (from gdt) This will switch tasks ONLY in ring 0
// KERNEL CS = 0x08
// KERNEL SS = 0x10
__attribute__((naked, noreturn))
void process_switch(uint64_t stack_addr, uint64_t code_addr)
{
asm volatile(" \
push $0x10 \n\
push %0 \n\
push $0x202 \n\
push $0x08 \n\
push %1 \n\
iretq \n\
" :: "r"(stack_addr), "r"(code_addr));
}
// Will be used to clean up resources (if any, when we implement it)
// Just mark as DEAD then idle. Scheduler will delete process at next timer interrupt % quantum.
void process_exit()
{
DEBUG("Exiting from process '%s'", current_process->name);
CLEAR_INTERRUPTS;
if (current_process)
{
current_process->status = DEAD;
}
SET_INTERRUPTS;
outb(0x20, 0x20);
for (;;)
{
asm("hlt");
}
} }

View File

@@ -9,6 +9,7 @@
#include <stddef.h> #include <stddef.h>
#include "config.h" #include "config.h"
#include <stdint.h>
typedef enum typedef enum
{ {
@@ -33,6 +34,8 @@ struct process_t* process_create(char* name, void(*function)(void*), void* arg);
void process_add(struct process_t** processes_list, struct process_t* process); void process_add(struct process_t** processes_list, struct process_t* process);
void process_delete(struct process_t** processes_list, struct process_t* process); void process_delete(struct process_t** processes_list, struct process_t* process);
struct process_t* process_get_next(struct process_t* process); struct process_t* process_get_next(struct process_t* process);
void process_switch(uint64_t stack_addr, uint64_t code_addr);
void process_exit();
void process_display_list(struct process_t* processes_list); void process_display_list(struct process_t* processes_list);

View File

@@ -6,6 +6,9 @@
#include "kernel.h" #include "kernel.h"
#include "process.h" #include "process.h"
#include "mem/paging/paging.h"
#include <stdint.h>
#include "io/serial/serial.h"
extern struct process_t* processes_list; extern struct process_t* processes_list;
extern struct process_t* current_process; extern struct process_t* current_process;
@@ -41,6 +44,12 @@ struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context)
} }
} }
DEBUG("current_process={pid=%u name='%s'}", current_process->pid, current_process->name);
// Current_process gets wrong context?? (iret_rip points to other stuff than process function; like putpixel() for example)
DEBUG("current_process={pid=%u, name='%s', root_page_table[virt]=%p}", current_process->pid, current_process->name, current_process->root_page_table);
load_cr3(VIRT_TO_PHYS((uint64_t)current_process->root_page_table));
DEBUG("loaded process pml4 into cr3");
return current_process->context; return current_process->context;
} }

View File

@@ -7,7 +7,7 @@
#ifndef SCHEDULER_H #ifndef SCHEDULER_H
#define SCHEDULER_H #define SCHEDULER_H
void scheduler_schedule(); struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context);
void scheduler_init(); void scheduler_init();
#endif #endif

33
symbols.py Normal file
View File

@@ -0,0 +1,33 @@
# Make assembly file from ELF symbols map
# Then link it to kernel so it's aware of symbol names
# then we can use that for the stack trace.
print("Extracting symbols from map file to assembly...")
with open("symbols.map", "r") as f:
lines = f.readlines()
symbols = []
for line in lines:
parts = line.split()
# output is formed like "address name"
symbols.append((parts[0], parts[1]))
with open("symbols.S", "w") as f:
f.write("section .rodata\n")
f.write("global symbol_table\n")
f.write("global symbol_count\n")
f.write("symbol_table:\n")
for i, (addr, name) in enumerate(symbols):
f.write(f" dq 0x{addr}\n")
f.write(f" dq sym_name_{i}\n")
f.write("\nsymbol_count: dq " + str(len(symbols)) + "\n\n")
for i, (addr, name) in enumerate(symbols):
# escaping quotes
safe_name = name.replace('"', '\\"')
f.write(f'sym_name_{i}: db "{safe_name}", 0\n')
print("Done!")