* The boot loader does now set up an IDT that allows it to catch processor

exceptions (page faults and the like). The handler dumps possibly interesting
  information (registers, a (numerical) stack trace) to the serial output, and,
  if possible, also to the screen. That should help debugging boot loader
  crashes.
* For the IDT the boot loader sets up for the kernel the descriptors are set up
  the same way, so until the kernel initializes the IDT itself (arch_init()) the
  facility is still in place and can thus catch very early kernel boot crashes.
  Unfortunately the on-screen output doesn't seem to work anymore at that point,
  so the output only goes to the serial port...
* ... and to the debug syslog -- dprintf() does now append it there after
  debug_cleanup() has been called. Seems to work fine in qemu, but when I tested
  it on real hardware the debug syslog was gone.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@42092 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold 2011-06-11 00:08:42 +00:00
parent 8c3c117201
commit 2e8aa19c63
12 changed files with 465 additions and 50 deletions

View File

@ -21,7 +21,8 @@ KernelMergeObject boot_platform_bios_ia32.o :
shell.S
start.cpp
debug.cpp
bios.S
bios.cpp
bios_asm.S
console.cpp
serial.cpp
devices.cpp
@ -37,6 +38,8 @@ KernelMergeObject boot_platform_bios_ia32.o :
video.cpp
apm.cpp
hpet.cpp
interrupts.cpp
interrupts_asm.S
# VESA/DDC EDID
decode_edid.c

View File

@ -0,0 +1,22 @@
/*
* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "bios.h"
#include "interrupts.h"
extern "C" void call_bios_internal(uint8 num, struct bios_regs* regs);
void
call_bios(uint8 num, struct bios_regs* regs)
{
restore_bios_idt();
call_bios_internal(num, regs);
set_debug_idt();
}

View File

@ -158,11 +158,11 @@ _real_code_segment:
//--------------------------------------------------------------
/*! void call_bios(uint8 num, struct bios_regs *regs)
/*! void call_bios_internal(uint8 num, struct bios_regs *regs)
Does a BIOS call by triggering a software interrupt in real
mode.
*/
FUNCTION(call_bios)
FUNCTION(call_bios_internal)
pushal
pushfl

View File

@ -6,7 +6,6 @@
#include "debug.h"
#include <stdarg.h>
#include <string.h>
#include <boot/platform.h>
@ -26,6 +25,33 @@ static char sBuffer[16384];
static uint32 sBufferPosition;
static ring_buffer* sDebugSyslogBuffer = NULL;
static bool sPostCleanup = false;
static void
dprintf_args(const char *format, va_list args)
{
char buffer[512];
int length = vsnprintf(buffer, sizeof(buffer), format, args);
if (length >= (int)sizeof(buffer))
length = sizeof(buffer) - 1;
if (sPostCleanup && sDebugSyslogBuffer != NULL) {
ring_buffer_write(sDebugSyslogBuffer, (const uint8*)buffer, length);
} else if (sBufferPosition + length < sizeof(sBuffer)) {
memcpy(sBuffer + sBufferPosition, buffer, length);
sBufferPosition += length;
}
serial_puts(buffer, length);
if (platform_boot_options() & BOOT_OPTION_DEBUG_OUTPUT)
fprintf(stderr, "%s", buffer);
}
// #pragma mark -
/*! This works only after console_init() was called.
@ -54,26 +80,29 @@ panic(const char *format, ...)
void
dprintf(const char *format, ...)
{
char buffer[512];
va_list list;
int length;
va_list args;
va_start(list, format);
length = vsnprintf(buffer, sizeof(buffer), format, list);
va_end(list);
va_start(args, format);
dprintf_args(format, args);
va_end(args);
}
if (length >= (int)sizeof(buffer))
length = sizeof(buffer) - 1;
if (sBufferPosition + length < sizeof(sBuffer)) {
memcpy(sBuffer + sBufferPosition, buffer, length);
sBufferPosition += length;
}
void
kprintf(const char *format, ...)
{
va_list args;
serial_puts(buffer, length);
va_start(args, format);
if (platform_boot_options() & BOOT_OPTION_DEBUG_OUTPUT)
fprintf(stderr, "%s", buffer);
// print to console, if available
if (stdout != NULL)
vfprintf(stdout, format, args);
// always print to serial line
dprintf_args(format, args);
va_end(args);
}
@ -120,7 +149,8 @@ debug_cleanup(void)
if (gKernelArgs.keep_debug_output_buffer) {
// copy the output gathered so far into the ring buffer
ring_buffer_clear(sDebugSyslogBuffer);
ring_buffer_write(sDebugSyslogBuffer, (uint8*)sBuffer, sBufferPosition);
ring_buffer_write(sDebugSyslogBuffer, (uint8*)sBuffer,
sBufferPosition);
memcpy(buffer, kDebugSyslogSignature, signatureLength);
} else {
@ -137,4 +167,6 @@ debug_cleanup(void)
gKernelArgs.debug_size = sBufferPosition;
}
}
sPostCleanup = true;
}

View File

@ -6,6 +6,8 @@
#define DEBUG_H
#include <stdarg.h>
#include <SupportDefs.h>

View File

@ -0,0 +1,249 @@
/*
* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "interrupts.h"
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <KernelExport.h>
#include <boot/platform.h>
#include <boot/platform/generic/text_console.h>
#include <arch_cpu.h>
#include <descriptors.h>
#include "debug.h"
#include "keyboard.h"
//#define TRACE_INTERRUPTS
#ifdef TRACE_INTERRUPTS
# define TRACE(x...) dprintf(x)
#else
# define TRACE(x...) ;
#endif
// interrupt function prototypes
#define INTERRUPT_FUNCTION(vector) \
extern "C" void interrupt_function##vector();
INTERRUPT_FUNCTIONS()
#undef INTERRUPT_FUNCTION
#define INTERRUPT_FUNCTION(vector) \
extern "C" void exception_interrupt_function##vector();
INTERRUPT_FUNCTIONS()
#undef INTERRUPT_FUNCTION
static const char *kInterruptNames[DEBUG_IDT_SLOT_COUNT] = {
/* 0 */ "Divide Error Exception",
/* 1 */ "Debug Exception",
/* 2 */ "NMI Interrupt",
/* 3 */ "Breakpoint Exception",
/* 4 */ "Overflow Exception",
/* 5 */ "BOUND Range Exceeded Exception",
/* 6 */ "Invalid Opcode Exception",
/* 7 */ "Device Not Available Exception",
/* 8 */ "Double Fault Exception",
/* 9 */ "Coprocessor Segment Overrun",
/* 10 */ "Invalid TSS Exception",
/* 11 */ "Segment Not Present",
/* 12 */ "Stack Fault Exception",
/* 13 */ "General Protection Exception",
/* 14 */ "Page-Fault Exception",
/* 15 */ "-",
/* 16 */ "x87 FPU Floating-Point Error",
/* 17 */ "Alignment Check Exception",
/* 18 */ "Machine-Check Exception",
/* 19 */ "SIMD Floating-Point Exception",
};
struct interrupt_frame {
uint32 vector;
uint32 edi;
uint32 esi;
uint32 ebp;
uint32 esp;
uint32 ebx;
uint32 edx;
uint32 ecx;
uint32 eax;
uint32 error_code;
uint32 eip;
uint32 cs;
uint32 eflags;
};
struct interrupt_descriptor {
uint32 a;
uint32 b;
};
static interrupt_descriptor sDebugIDT[DEBUG_IDT_SLOT_COUNT];
static gdt_idt_descr sBIOSIDTDescriptor;
static gdt_idt_descr sDebugIDTDescriptor = { sizeof(sDebugIDT) - 1, sDebugIDT };
static void
set_interrupt_gate(int n, void (*function)())
{
if (n >= DEBUG_IDT_SLOT_COUNT)
return;
sDebugIDT[n].a = (KERNEL_CODE_SEG << 16) | (0x0000ffff & (addr_t)function);
sDebugIDT[n].b = (0xffff0000 & (addr_t)function) | 0x8e00;
}
static void
set_idt(const gdt_idt_descr& idtDescriptor)
{
asm("lidt %0;" : : "m" (idtDescriptor));
}
extern "C" void
handle_exception_exception()
{
while (true);
}
extern "C" void
handle_exception(interrupt_frame frame)
{
// Before doing anything else, set the interrupt gates so that
// handle_exception_exception() is called. This way we may avoid
// triple-faulting, if e.g. the stack is messed up and the user has at
// least the already printed output.
#define INTERRUPT_FUNCTION(vector) \
set_interrupt_gate(vector, &exception_interrupt_function##vector);
INTERRUPT_FUNCTIONS()
#undef INTERRUPT_FUNCTION
// If the console is already/still initialized, clear the screen and prepare
// for output.
if (stdout != NULL) {
console_set_color(RED, BLACK);
console_clear_screen();
console_set_cursor(0, 0);
console_hide_cursor();
}
// print exception name
if (frame.vector < DEBUG_IDT_SLOT_COUNT)
kprintf("%s", kInterruptNames[frame.vector]);
else
kprintf("Unknown exception %" B_PRIu32, frame.vector);
// additional info for page fault
if (frame.vector == 14) {
uint32 cr2;
asm("movl %%cr2, %0" : "=r"(cr2));
kprintf(": %s fault at address: %#" B_PRIx32,
(frame.error_code & 0x2) != 0 ? "write" : "read", cr2);
}
kprintf("\n");
// print greeting and registers
kprintf("Welcome to Boot Loader Death Land!\n");
kprintf("\n");
#define REG(name) " " #name " %-#10" B_PRIx32
kprintf(REG(eax) " " REG(ebx) " " REG(ecx) " " REG(edx) "\n",
frame.eax, frame.ebx, frame.ecx, frame.edx);
kprintf(REG(esi) " " REG(edi) " " REG(ebp) " " REG(esp) "\n",
frame.esi, frame.edi, frame.ebp, frame.esp);
kprintf(REG(eip) REG(eflags) "\n", frame.eip, frame.eflags);
#undef REG
// print stack trace (max 10 frames)
kprintf("\n frame return address\n");
struct x86_stack_frame {
x86_stack_frame* next;
void* return_address;
};
x86_stack_frame* stackFrame = (x86_stack_frame*)frame.ebp;
void* instructionPointer = (void*)frame.eip;
for (int32 i = 0; i < 10 && stackFrame != NULL; i++) {
kprintf(" %p %p\n", stackFrame, instructionPointer);
instructionPointer = stackFrame->return_address;
stackFrame = stackFrame->next;
}
if (stdout != NULL) {
kprintf("\nPress a key to reboot.\n");
clear_key_buffer();
wait_for_key();
platform_exit();
}
while (true);
}
void
interrupts_init()
{
// get the IDT
asm("sidt %0;" : : "m" (sBIOSIDTDescriptor));
TRACE("IDT: base: %p, limit: %u\n", sBIOSIDTDescriptor.base,
sBIOSIDTDescriptor.limit);
// set interrupt gates
#define INTERRUPT_FUNCTION(vector) \
set_interrupt_gate(vector, &interrupt_function##vector);
INTERRUPT_FUNCTIONS()
#undef INTERRUPT_FUNCTION
// set the debug IDT
set_debug_idt();
}
void
restore_bios_idt()
{
set_idt(sBIOSIDTDescriptor);
}
void
set_debug_idt()
{
set_idt(sDebugIDTDescriptor);
}
void
interrupts_init_kernel_idt(void* idt, size_t idtSize)
{
// clear it but copy the descriptors we've set up for the exceptions
memset(idt, 0, idtSize);
memcpy(idt, sDebugIDT, sizeof(sDebugIDT));
// load the idt
gdt_idt_descr idtDescriptor;
idtDescriptor.limit = idtSize - 1;
idtDescriptor.base = idt;
set_idt(idtDescriptor);
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef INTERRUPTS_H
#define INTERRUPTS_H
#ifndef _ASSEMBLER
#include <sys/cdefs.h>
#include <SupportDefs.h>
struct gdt_idt_descr {
uint16 limit;
void* base;
} _PACKED;
__BEGIN_DECLS
void interrupts_init();
void set_debug_idt();
void restore_bios_idt();
void interrupts_init_kernel_idt(void* idt, size_t idtSize);
__END_DECLS
#endif // _ASSEMBLER
#define INTERRUPT_FUNCTION_ERROR(vector) INTERRUPT_FUNCTION(vector)
#define INTERRUPT_FUNCTIONS5(vector1, vector2, vector3, vector4, vector5) \
INTERRUPT_FUNCTION(vector1) \
INTERRUPT_FUNCTION(vector2) \
INTERRUPT_FUNCTION(vector3) \
INTERRUPT_FUNCTION(vector4) \
INTERRUPT_FUNCTION(vector5)
#define INTERRUPT_FUNCTIONS() \
INTERRUPT_FUNCTIONS5(0, 1, 2, 3, 4) \
INTERRUPT_FUNCTIONS5(5, 6, 7, 8, 9) \
INTERRUPT_FUNCTION_ERROR(10) \
INTERRUPT_FUNCTION_ERROR(11) \
INTERRUPT_FUNCTION_ERROR(12) \
INTERRUPT_FUNCTION_ERROR(13) \
INTERRUPT_FUNCTION_ERROR(14) \
INTERRUPT_FUNCTION(15) \
INTERRUPT_FUNCTION(16) \
INTERRUPT_FUNCTION_ERROR(17) \
INTERRUPT_FUNCTION(18) \
INTERRUPT_FUNCTION(19)
#define DEBUG_IDT_SLOT_COUNT 20
#endif // INTERRUPTS_H

View File

@ -0,0 +1,46 @@
/*
* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include <asm_defs.h>
#include "interrupts.h"
// #pragma mark - interrupt functions
#define INTERRUPT_FUNCTION(vector) \
.align 8; \
FUNCTION(interrupt_function##vector): \
pushl $0; \
pusha; \
pushl $vector; \
call handle_exception;
#undef INTERRUPT_FUNCTION_ERROR
#define INTERRUPT_FUNCTION_ERROR(vector) \
.align 8; \
FUNCTION(interrupt_function##vector): \
pusha; \
pushl $vector; \
call handle_exception;
INTERRUPT_FUNCTIONS()
// #pragma mark - interrupt functions during exception
#undef INTERRUPT_FUNCTION
#define INTERRUPT_FUNCTION(vector) \
.align 8; \
FUNCTION(exception_interrupt_function##vector): \
call handle_exception_exception;
#undef INTERRUPT_FUNCTION_ERROR
#define INTERRUPT_FUNCTION_ERROR(vector) INTERRUPT_FUNCTION(vector)
INTERRUPT_FUNCTIONS()

View File

@ -7,19 +7,21 @@
#include "mmu.h"
#include "bios.h"
#include <string.h>
#include <OS.h>
#include <arch/cpu.h>
#include <arch_kernel.h>
#include <boot/platform.h>
#include <boot/stdio.h>
#include <boot/kernel_args.h>
#include <boot/stage2.h>
#include <arch/cpu.h>
#include <arch_kernel.h>
#include <kernel.h>
#include <OS.h>
#include <string.h>
#include "bios.h"
#include "interrupts.h"
/*! The (physical) memory layout of the boot loader is currently as follows:
@ -58,11 +60,6 @@
// for output to work.
struct gdt_idt_descr {
uint16 limit;
uint32 *base;
} _PACKED;
// memory structure returned by int 0x15, ax 0xe820
struct extended_memory {
uint64 base_addr;
@ -491,7 +488,6 @@ mmu_init_for_kernel(void)
TRACE("mmu_init_for_kernel\n");
// set up a new idt
{
struct gdt_idt_descr idtDescriptor;
uint32 *idt;
// find a new idt
@ -504,18 +500,9 @@ mmu_init_for_kernel(void)
gKernelArgs.arch_args.vir_idt = (uint32)get_next_virtual_page();
map_page(gKernelArgs.arch_args.vir_idt, (uint32)idt, kDefaultPageFlags);
// clear it out
uint32* virtualIDT = (uint32*)gKernelArgs.arch_args.vir_idt;
for (int32 i = 0; i < IDT_LIMIT / 4; i++) {
virtualIDT[i] = 0;
}
// load the idt
idtDescriptor.limit = IDT_LIMIT - 1;
idtDescriptor.base = (uint32 *)gKernelArgs.arch_args.vir_idt;
asm("lidt %0;"
: : "m" (idtDescriptor));
// initialize it
interrupts_init_kernel_idt((void*)gKernelArgs.arch_args.vir_idt,
IDT_LIMIT);
TRACE("idt at virtual address 0x%lx\n", gKernelArgs.arch_args.vir_idt);
}
@ -561,7 +548,7 @@ mmu_init_for_kernel(void)
// load the GDT
gdtDescriptor.limit = GDT_LIMIT - 1;
gdtDescriptor.base = (uint32 *)gKernelArgs.arch_args.vir_gdt;
gdtDescriptor.base = (void*)gKernelArgs.arch_args.vir_gdt;
asm("lgdt %0;"
: : "m" (gdtDescriptor));

View File

@ -103,7 +103,7 @@ start_loader:
/** Loads %di sectors from floppy disk, starting at head %dh, sector %cx.
* The data is loaded to %es:%bx. On exit, %es:%bx will point immediately
* The data is loaded to %es:%bx. On exit, %es:%bx will point immediately
* behind the loaded data, so that you can continue to read in data.
* %ax, %cx, %dx, %bp, %di and %si will be clobbered.
*/
@ -127,7 +127,7 @@ load_sectors:
mov %cx, %si // and remember it
pop %cx
pop %bx
load_track:
mov %di, %ax // limit the sector count to track boundaries
add %cl, %al
@ -292,8 +292,10 @@ _protected_code_segment:
mov %ax, %gs
mov %ax, %ss
mov $0x10000, %ebp // setup new stack
mov %ebp, %esp
movl $0x10000, %esp // setup new stack
pushl $0 // terminate stack frame chain (next frame and
pushl $0 // return address)
mov %esp, %ebp
call _start

View File

@ -20,6 +20,7 @@
#include "cpu.h"
#include "debug.h"
#include "hpet.h"
#include "interrupts.h"
#include "keyboard.h"
#include "mmu.h"
#include "multiboot.h"
@ -84,6 +85,10 @@ platform_start_kernel(void)
smp_init_other_cpus();
debug_cleanup();
mmu_init_for_kernel();
// We're about to enter the kernel -- disable console output.
stdout = NULL;
smp_boot_other_cpus();
dprintf("kernel entry at %lx\n",
@ -128,6 +133,7 @@ _start(void)
serial_init();
serial_enable();
interrupts_init();
console_init();
cpu_init();
mmu_init();

View File

@ -20,7 +20,8 @@ UsePrivateHeaders [ FDirName storage ] ;
local bios_ia32_src =
start.cpp
debug.cpp
bios.S
bios.cpp
bios_asm.S
console.cpp
serial.cpp
keyboard.cpp
@ -34,6 +35,8 @@ local bios_ia32_src =
video.cpp
hpet.cpp
apm.cpp
interrupts.cpp
interrupts_asm.S
;
local bios_ia32_edid_src =