mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03:00
ca16d9d98e
The current strategy is simply to nuke all physical pages and force reload them from disk. This is obviously not optimal and should eventually be optimized. It should be fairly straightforward.
506 lines
13 KiB
C++
506 lines
13 KiB
C++
#include "types.h"
|
|
#include "kmalloc.h"
|
|
#include "i386.h"
|
|
#include "Assertions.h"
|
|
#include "Process.h"
|
|
#include "MemoryManager.h"
|
|
#include "IRQHandler.h"
|
|
#include "PIC.h"
|
|
#include "Scheduler.h"
|
|
|
|
//#define PAGE_FAULT_DEBUG
|
|
|
|
struct DescriptorTablePointer {
|
|
word size;
|
|
void* address;
|
|
} PACKED;
|
|
|
|
static DescriptorTablePointer s_idtr;
|
|
static DescriptorTablePointer s_gdtr;
|
|
static Descriptor* s_idt;
|
|
static Descriptor* s_gdt;
|
|
|
|
static IRQHandler** s_irq_handler;
|
|
|
|
static Vector<word, KmallocEternalAllocator>* s_gdt_freelist;
|
|
|
|
static word s_gdtLength;
|
|
|
|
word gdt_alloc_entry()
|
|
{
|
|
ASSERT(s_gdt_freelist);
|
|
ASSERT(!s_gdt_freelist->is_empty());
|
|
return s_gdt_freelist->take_last();
|
|
}
|
|
|
|
void gdt_free_entry(word entry)
|
|
{
|
|
s_gdt_freelist->append(entry);
|
|
}
|
|
|
|
extern "C" void handle_irq();
|
|
extern "C" void asm_irq_entry();
|
|
|
|
asm(
|
|
".globl asm_irq_entry\n"
|
|
"asm_irq_entry: \n"
|
|
" pusha\n"
|
|
" pushw %ds\n"
|
|
" pushw %es\n"
|
|
" pushw %ss\n"
|
|
" pushw %ss\n"
|
|
" popw %ds\n"
|
|
" popw %es\n"
|
|
" call handle_irq\n"
|
|
" popw %es\n"
|
|
" popw %ds\n"
|
|
" popa\n"
|
|
" iret\n"
|
|
);
|
|
|
|
#define EH_ENTRY(ec) \
|
|
extern "C" void exception_ ## ec ## _handler(RegisterDumpWithExceptionCode&); \
|
|
extern "C" void exception_ ## ec ## _entry(); \
|
|
asm( \
|
|
".globl exception_" # ec "_entry\n" \
|
|
"exception_" # ec "_entry: \n" \
|
|
" pusha\n" \
|
|
" pushw %ds\n" \
|
|
" pushw %es\n" \
|
|
" pushw %fs\n" \
|
|
" pushw %gs\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" popw %ds\n" \
|
|
" popw %es\n" \
|
|
" popw %fs\n" \
|
|
" popw %gs\n" \
|
|
" mov %esp, %eax\n" \
|
|
" call exception_" # ec "_handler\n" \
|
|
" popw %gs\n" \
|
|
" popw %gs\n" \
|
|
" popw %fs\n" \
|
|
" popw %es\n" \
|
|
" popw %ds\n" \
|
|
" popa\n" \
|
|
" add $0x4, %esp\n" \
|
|
" iret\n" \
|
|
);
|
|
|
|
#define EH_ENTRY_NO_CODE(ec) \
|
|
extern "C" void exception_ ## ec ## _handler(RegisterDump&); \
|
|
extern "C" void exception_ ## ec ## _entry(); \
|
|
asm( \
|
|
".globl exception_" # ec "_entry\n" \
|
|
"exception_" # ec "_entry: \n" \
|
|
" pusha\n" \
|
|
" pushw %ds\n" \
|
|
" pushw %es\n" \
|
|
" pushw %fs\n" \
|
|
" pushw %gs\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" pushw %ss\n" \
|
|
" popw %ds\n" \
|
|
" popw %es\n" \
|
|
" popw %fs\n" \
|
|
" popw %gs\n" \
|
|
" mov %esp, %eax\n" \
|
|
" call exception_" # ec "_handler\n" \
|
|
" popw %gs\n" \
|
|
" popw %gs\n" \
|
|
" popw %fs\n" \
|
|
" popw %es\n" \
|
|
" popw %ds\n" \
|
|
" popa\n" \
|
|
" iret\n" \
|
|
);
|
|
|
|
// 6: Invalid Opcode
|
|
EH_ENTRY_NO_CODE(6);
|
|
void exception_6_handler(RegisterDump& regs)
|
|
{
|
|
kprintf("%s invalid opcode: %u(%s)\n", current->is_ring0() ? "Kernel" : "Process", current->pid(), current->name().characters());
|
|
|
|
word ss;
|
|
dword esp;
|
|
if (current->is_ring0()) {
|
|
ss = regs.ds;
|
|
esp = regs.esp;
|
|
} else {
|
|
ss = regs.ss_if_crossRing;
|
|
esp = regs.esp_if_crossRing;
|
|
}
|
|
|
|
kprintf("pc=%w:%x ds=%w es=%w fs=%w gs=%w\n", regs.cs, regs.eip, regs.ds, regs.es, regs.fs, regs.gs);
|
|
kprintf("stk=%w:%x\n", ss, esp);
|
|
kprintf("eax=%x ebx=%x ecx=%x edx=%x\n", regs.eax, regs.ebx, regs.ecx, regs.edx);
|
|
kprintf("ebp=%x esp=%x esi=%x edi=%x\n", regs.ebp, esp, regs.esi, regs.edi);
|
|
|
|
if (current->is_ring0()) {
|
|
kprintf("Oh shit, we've crashed in ring 0 :(\n");
|
|
HANG;
|
|
}
|
|
HANG;
|
|
|
|
current->crash();
|
|
}
|
|
|
|
// 7: FPU not available exception
|
|
EH_ENTRY_NO_CODE(7);
|
|
void exception_7_handler(RegisterDump& regs)
|
|
{
|
|
(void)regs;
|
|
|
|
asm volatile("clts");
|
|
if (g_last_fpu_process == current)
|
|
return;
|
|
if (g_last_fpu_process) {
|
|
asm volatile("fnsave %0":"=m"(g_last_fpu_process->fpu_state()));
|
|
} else {
|
|
asm volatile("fnclex");
|
|
}
|
|
g_last_fpu_process = current;
|
|
|
|
if (current->has_used_fpu()) {
|
|
asm volatile("frstor %0"::"m"(current->fpu_state()));
|
|
} else {
|
|
asm volatile("fninit");
|
|
current->set_has_used_fpu(true);
|
|
}
|
|
|
|
#ifdef FPU_EXCEPTION_DEBUG
|
|
kprintf("%s FPU not available exception: %u(%s)\n", current->is_ring0() ? "Kernel" : "Process", current->pid(), current->name().characters());
|
|
|
|
word ss;
|
|
dword esp;
|
|
if (current->is_ring0()) {
|
|
ss = regs.ds;
|
|
esp = regs.esp;
|
|
} else {
|
|
ss = regs.ss_if_crossRing;
|
|
esp = regs.esp_if_crossRing;
|
|
}
|
|
|
|
kprintf("pc=%w:%x ds=%w es=%w fs=%w gs=%w\n", regs.cs, regs.eip, regs.ds, regs.es, regs.fs, regs.gs);
|
|
kprintf("stk=%w:%x\n", ss, esp);
|
|
kprintf("eax=%x ebx=%x ecx=%x edx=%x\n", regs.eax, regs.ebx, regs.ecx, regs.edx);
|
|
kprintf("ebp=%x esp=%x esi=%x edi=%x\n", regs.ebp, esp, regs.esi, regs.edi);
|
|
#endif
|
|
}
|
|
|
|
|
|
// 13: General Protection Fault
|
|
EH_ENTRY(13);
|
|
void exception_13_handler(RegisterDumpWithExceptionCode& regs)
|
|
{
|
|
kprintf("%s GPF: %u(%s)\n", current->is_ring0() ? "Kernel" : "User", current->pid(), current->name().characters());
|
|
|
|
word ss;
|
|
dword esp;
|
|
if (current->is_ring0()) {
|
|
ss = regs.ds;
|
|
esp = regs.esp;
|
|
} else {
|
|
ss = regs.ss_if_crossRing;
|
|
esp = regs.esp_if_crossRing;
|
|
}
|
|
|
|
kprintf("exception code: %w\n", regs.exception_code);
|
|
kprintf("pc=%w:%x ds=%w es=%w fs=%w gs=%w\n", regs.cs, regs.eip, regs.ds, regs.es, regs.fs, regs.gs);
|
|
kprintf("stk=%w:%x\n", ss, esp);
|
|
kprintf("eax=%x ebx=%x ecx=%x edx=%x\n", regs.eax, regs.ebx, regs.ecx, regs.edx);
|
|
kprintf("ebp=%x esp=%x esi=%x edi=%x\n", regs.ebp, esp, regs.esi, regs.edi);
|
|
|
|
if (current->is_ring0()) {
|
|
kprintf("Oh shit, we've crashed in ring 0 :(\n");
|
|
HANG;
|
|
}
|
|
|
|
current->crash();
|
|
}
|
|
|
|
// 14: Page Fault
|
|
EH_ENTRY(14);
|
|
void exception_14_handler(RegisterDumpWithExceptionCode& regs)
|
|
{
|
|
ASSERT(current);
|
|
|
|
dword faultAddress;
|
|
asm ("movl %%cr2, %%eax":"=a"(faultAddress));
|
|
|
|
dword fault_page_directory;
|
|
asm ("movl %%cr3, %%eax":"=a"(fault_page_directory));
|
|
|
|
dbgprintf("%s(%u): ring%u %s page fault in PD=%x, %s L%x\n",
|
|
current->name().characters(),
|
|
current->pid(),
|
|
regs.cs & 3,
|
|
regs.exception_code & 1 ? "PV" : "NP",
|
|
fault_page_directory,
|
|
regs.exception_code & 2 ? "write" : "read",
|
|
faultAddress);
|
|
|
|
word ss;
|
|
dword esp;
|
|
if (current->is_ring0()) {
|
|
ss = regs.ds;
|
|
esp = regs.esp;
|
|
} else {
|
|
ss = regs.ss_if_crossRing;
|
|
esp = regs.esp_if_crossRing;
|
|
}
|
|
|
|
auto dump_registers_and_code = [&] {
|
|
dbgprintf("exception code: %w\n", regs.exception_code);
|
|
dbgprintf("pc=%w:%x ds=%w es=%w fs=%w gs=%w\n", regs.cs, regs.eip, regs.ds, regs.es, regs.fs, regs.gs);
|
|
dbgprintf("stk=%w:%x\n", ss, esp);
|
|
dbgprintf("eax=%x ebx=%x ecx=%x edx=%x\n", regs.eax, regs.ebx, regs.ecx, regs.edx);
|
|
dbgprintf("ebp=%x esp=%x esi=%x edi=%x\n", regs.ebp, esp, regs.esi, regs.edi);
|
|
|
|
if (current->validate_read((void*)regs.eip, 8)) {
|
|
byte* codeptr = (byte*)regs.eip;
|
|
dbgprintf("code: %b %b %b %b %b %b %b %b\n",
|
|
codeptr[0],
|
|
codeptr[1],
|
|
codeptr[2],
|
|
codeptr[3],
|
|
codeptr[4],
|
|
codeptr[5],
|
|
codeptr[6],
|
|
codeptr[7]
|
|
);
|
|
}
|
|
};
|
|
|
|
#ifdef PAGE_FAULT_DEBUG
|
|
dump_registers_and_code();
|
|
#endif
|
|
|
|
auto response = MM.handle_page_fault(PageFault(regs.exception_code, LinearAddress(faultAddress)));
|
|
|
|
if (response == PageFaultResponse::ShouldCrash) {
|
|
kprintf("%s(%u) unrecoverable page fault, %s laddr=%p\n",
|
|
current->name().characters(),
|
|
current->pid(),
|
|
regs.exception_code & 2 ? "write" : "read",
|
|
faultAddress);
|
|
dump_registers_and_code();
|
|
current->crash();
|
|
} else if (response == PageFaultResponse::Continue) {
|
|
#ifdef PAGE_FAULT_DEBUG
|
|
dbgprintf("Continuing after resolved page fault\n");
|
|
#endif
|
|
} else {
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
#define EH(i, msg) \
|
|
static void _exception ## i () \
|
|
{ \
|
|
kprintf(msg"\n"); \
|
|
dword cr0, cr2, cr3, cr4; \
|
|
asm ("movl %%cr0, %%eax":"=a"(cr0)); \
|
|
asm ("movl %%cr2, %%eax":"=a"(cr2)); \
|
|
asm ("movl %%cr3, %%eax":"=a"(cr3)); \
|
|
asm ("movl %%cr4, %%eax":"=a"(cr4)); \
|
|
kprintf("CR0=%x CR2=%x CR3=%x CR4=%x\n", cr0, cr2, cr3, cr4); \
|
|
HANG; \
|
|
}
|
|
|
|
EH(0, "Divide error")
|
|
EH(1, "Debug exception")
|
|
EH(2, "Unknown error")
|
|
EH(3, "Breakpoint")
|
|
EH(4, "Overflow")
|
|
EH(5, "Bounds check")
|
|
EH(8, "Double fault")
|
|
EH(9, "Coprocessor segment overrun")
|
|
EH(10, "Invalid TSS")
|
|
EH(11, "Segment not present")
|
|
EH(12, "Stack exception")
|
|
EH(15, "Unknown error")
|
|
EH(16, "Coprocessor error")
|
|
|
|
static void write_raw_gdt_entry(word selector, dword low, dword high)
|
|
{
|
|
word i = (selector & 0xfffc) >> 3;
|
|
s_gdt[i].low = low;
|
|
s_gdt[i].high = high;
|
|
|
|
if (i > s_gdtLength) {
|
|
s_gdtr.size = (s_gdtLength + 1) * 8;
|
|
}
|
|
}
|
|
|
|
void write_gdt_entry(word selector, Descriptor& descriptor)
|
|
{
|
|
write_raw_gdt_entry(selector, descriptor.low, descriptor.high);
|
|
}
|
|
|
|
Descriptor& get_gdt_entry(word selector)
|
|
{
|
|
word i = (selector & 0xfffc) >> 3;
|
|
return *(Descriptor*)(&s_gdt[i]);
|
|
}
|
|
|
|
void flush_gdt()
|
|
{
|
|
s_gdtr.address = s_gdt;
|
|
s_gdtr.size = (s_gdtLength * 8) - 1;
|
|
asm("lgdt %0"::"m"(s_gdtr));
|
|
}
|
|
|
|
void gdt_init()
|
|
{
|
|
s_gdt = static_cast<Descriptor*>(kmalloc_eternal(sizeof(Descriptor) * 256));
|
|
s_gdtLength = 5;
|
|
|
|
s_gdt_freelist = new Vector<word, KmallocEternalAllocator>();
|
|
s_gdt_freelist->ensure_capacity(256);
|
|
for (size_t i = s_gdtLength; i < 256; ++i)
|
|
s_gdt_freelist->append(i * 8);
|
|
|
|
s_gdtLength = 256;
|
|
s_gdtr.address = s_gdt;
|
|
s_gdtr.size = (s_gdtLength * 8) - 1;
|
|
|
|
write_raw_gdt_entry(0x0000, 0x00000000, 0x00000000);
|
|
write_raw_gdt_entry(0x0008, 0x0000ffff, 0x00cf9a00);
|
|
write_raw_gdt_entry(0x0010, 0x0000ffff, 0x00cf9200);
|
|
write_raw_gdt_entry(0x0018, 0x0000ffff, 0x00cffa00);
|
|
write_raw_gdt_entry(0x0020, 0x0000ffff, 0x00cff200);
|
|
|
|
flush_gdt();
|
|
}
|
|
|
|
static void unimp_trap()
|
|
{
|
|
kprintf("Unhandled IRQ.");
|
|
HANG;
|
|
}
|
|
|
|
void register_irq_handler(byte irq, IRQHandler& handler)
|
|
{
|
|
ASSERT(!s_irq_handler[irq]);
|
|
s_irq_handler[irq] = &handler;
|
|
register_interrupt_handler(IRQ_VECTOR_BASE + irq, asm_irq_entry);
|
|
}
|
|
|
|
void unregister_irq_handler(byte irq, IRQHandler& handler)
|
|
{
|
|
ASSERT(s_irq_handler[irq] == &handler);
|
|
s_irq_handler[irq] = nullptr;
|
|
}
|
|
|
|
void register_interrupt_handler(byte index, void (*f)())
|
|
{
|
|
s_idt[index].low = 0x00080000 | LSW((f));
|
|
s_idt[index].high = ((dword)(f) & 0xffff0000) | 0x8e00;
|
|
flush_idt();
|
|
}
|
|
|
|
void register_user_callable_interrupt_handler(byte index, void (*f)())
|
|
{
|
|
s_idt[index].low = 0x00080000 | LSW((f));
|
|
s_idt[index].high = ((dword)(f) & 0xffff0000) | 0xef00;
|
|
flush_idt();
|
|
}
|
|
|
|
void flush_idt()
|
|
{
|
|
asm("lidt %0"::"m"(s_idtr));
|
|
}
|
|
|
|
/* If an 8259 gets cranky, it'll generate a spurious IRQ7.
|
|
* ATM I don't have a clear grasp on when/why this happens,
|
|
* so I ignore them and assume it makes no difference.
|
|
*/
|
|
|
|
extern "C" void irq7_handler();
|
|
asm(
|
|
".globl irq7_handler \n"
|
|
"irq7_handler: \n"
|
|
" iret\n"
|
|
);
|
|
|
|
void idt_init()
|
|
{
|
|
s_idt = static_cast<Descriptor*>(kmalloc_eternal(sizeof(Descriptor) * 256));
|
|
|
|
s_idtr.address = s_idt;
|
|
s_idtr.size = 0x100 * 8;
|
|
|
|
for (byte i = 0xff; i > 0x10; --i)
|
|
register_interrupt_handler(i, unimp_trap);
|
|
|
|
register_interrupt_handler(0x00, _exception0);
|
|
register_interrupt_handler(0x01, _exception1);
|
|
register_interrupt_handler(0x02, _exception2);
|
|
register_interrupt_handler(0x03, _exception3);
|
|
register_interrupt_handler(0x04, _exception4);
|
|
register_interrupt_handler(0x05, _exception5);
|
|
register_interrupt_handler(0x06, exception_6_entry);
|
|
register_interrupt_handler(0x07, exception_7_entry);
|
|
register_interrupt_handler(0x08, _exception8);
|
|
register_interrupt_handler(0x09, _exception9);
|
|
register_interrupt_handler(0x0a, _exception10);
|
|
register_interrupt_handler(0x0b, _exception11);
|
|
register_interrupt_handler(0x0c, _exception12);
|
|
register_interrupt_handler(0x0d, exception_13_entry);
|
|
register_interrupt_handler(0x0e, exception_14_entry);
|
|
register_interrupt_handler(0x0f, _exception15);
|
|
register_interrupt_handler(0x10, _exception16);
|
|
|
|
register_interrupt_handler(0x57, irq7_handler);
|
|
|
|
s_irq_handler = static_cast<IRQHandler**>(kmalloc_eternal(sizeof(IRQHandler*) * 16));
|
|
for (byte i = 0; i < 16; ++i) {
|
|
s_irq_handler[i] = nullptr;
|
|
}
|
|
|
|
flush_idt();
|
|
}
|
|
|
|
void load_task_register(word selector)
|
|
{
|
|
asm("ltr %0"::"r"(selector));
|
|
}
|
|
|
|
void handle_irq()
|
|
{
|
|
word isr = PIC::get_isr();
|
|
if (!isr) {
|
|
kprintf("Spurious IRQ\n");
|
|
return;
|
|
}
|
|
|
|
byte irq = 0;
|
|
for (byte i = 0; i < 16; ++i) {
|
|
if (i == 2)
|
|
continue;
|
|
if (isr & (1 << i)) {
|
|
irq = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (s_irq_handler[irq])
|
|
s_irq_handler[irq]->handle_irq();
|
|
PIC::eoi(irq);
|
|
}
|
|
|
|
void __assertion_failed(const char* msg, const char* file, unsigned line, const char* func)
|
|
{
|
|
asm volatile("cli");
|
|
kprintf("ASSERTION FAILED: %s\n%s:%u in %s\n", msg, file, line, func);
|
|
asm volatile("hlt");
|
|
for (;;);
|
|
}
|