diff --git a/conf/i386.rmk b/conf/i386.rmk index c3f036d0f..7031adee7 100644 --- a/conf/i386.rmk +++ b/conf/i386.rmk @@ -25,3 +25,9 @@ pkglib_MODULES += ata.mod ata_mod_SOURCES = disk/ata.c ata_mod_CFLAGS = $(COMMON_CFLAGS) ata_mod_LDFLAGS = $(COMMON_LDFLAGS) + +pkglib_MODULES += backtrace.mod +backtrace_mod_SOURCES = lib/i386/backtrace.c lib/i386/backtrace_int.S +backtrace_mod_CFLAGS = $(COMMON_CFLAGS) +backtrace_mod_ASFLAGS = $(COMMON_ASFLAGS) +backtrace_mod_LDFLAGS = $(COMMON_LDFLAGS) diff --git a/include/grub/backtrace.h b/include/grub/backtrace.h new file mode 100644 index 000000000..a0503b8b7 --- /dev/null +++ b/include/grub/backtrace.h @@ -0,0 +1,25 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_BACKTRACE_HEADER +#define GRUB_BACKTRACE_HEADER 1 + +void grub_backtrace (void); +void grub_backtrace_pointer (void *ptr); + +#endif diff --git a/kern/i386/realmode.S b/kern/i386/realmode.S index a74eb1217..2a371c581 100644 --- a/kern/i386/realmode.S +++ b/kern/i386/realmode.S @@ -108,6 +108,12 @@ gdt: gdtdesc: .word 0x27 /* limit */ .long gdt /* addr */ +realidt: + .word 0 + .long 0 +protidt: + .word 0 + .long 0 /* * These next two routines, "real_to_prot" and "prot_to_real" are structured @@ -159,6 +165,9 @@ protcseg: /* zero %eax */ xorl %eax, %eax + sidt realidt + lidt protidt + /* return on the old (or initialized) stack! */ ret @@ -166,6 +175,9 @@ prot_to_real: /* just in case, set GDT */ lgdt gdtdesc + sidt protidt + lidt realidt + /* save the protected mode stack */ movl %esp, %eax movl %eax, protstack diff --git a/lib/i386/backtrace.c b/lib/i386/backtrace.c new file mode 100644 index 000000000..06fb6c54c --- /dev/null +++ b/lib/i386/backtrace.c @@ -0,0 +1,194 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_STACK_FRAME 102400 + +struct idt_descriptor +{ + grub_uint16_t limit; + void *base; +} __attribute__ ((packed)); + +#define GRUB_IDT_ENTRY_TYPE_PRESENT 0x80 +#define GRUB_IDT_ENTRY_TYPE_NOT_PRESENT 0x00 +#define GRUB_IDT_ENTRY_TYPE_RING0 0x00 +#define GRUB_IDT_ENTRY_TYPE_TRAP32 0x0f + +struct idt_entry +{ + grub_uint16_t addr_low; + grub_uint16_t segment; + grub_uint8_t unused; + grub_uint8_t type; + grub_uint16_t addr_high; +} __attribute__ ((packed)); + +void +print_address (void *addr) +{ + const char *name; + int section; + grub_off_t off; + auto int hook (grub_dl_t mod); + int hook (grub_dl_t mod) + { + grub_dl_segment_t segment; + for (segment = mod->segment; segment; segment = segment->next) + if (segment->addr <= addr && (grub_uint8_t *) segment->addr + + segment->size > (grub_uint8_t *) addr) + { + name = mod->name; + section = segment->section; + off = (grub_uint8_t *) addr - (grub_uint8_t *) segment->addr; + return 1; + } + return 0; + } + + name = NULL; + grub_dl_iterate (hook); + if (name) + grub_printf ("%s.%x+%lx", name, section, (unsigned long) off); + else + grub_printf ("%p", addr); +} + +void +grub_backtrace_pointer (void *ebp) +{ + void *ptr, *nptr; + unsigned i; + + ptr = ebp; + while (1) + { + grub_printf ("%p: ", ptr); + print_address (*(void **) (ptr + sizeof (void *))); + grub_printf (" ("); + for (i = 0; i < 2; i++) + grub_printf ("%p,", *(void **) + (ptr + (i + 2) * sizeof (void *))); + grub_printf ("%p", *(void **) + (ptr + (i + 2) * sizeof (void *))); + grub_printf (")\n"); + nptr = *(void **)ptr; + if (nptr < ptr || (void **) nptr - (void **) ptr > MAX_STACK_FRAME + || nptr == ptr) + { + grub_printf ("Invalid stack frame at %p (%p)\n", ptr, nptr); + break; + } + ptr = nptr; + } +} + +void +grub_interrupt_handler_real (void *ret, void *ebp) +{ + grub_printf ("Unhandled exception at "); + print_address (ret); + grub_printf ("\n"); + grub_backtrace_pointer (ebp); + grub_abort (); +} + + +void +grub_backtrace (void) +{ +#ifdef __x86_64__ + asm volatile ("movq %rbp, %rdi\n" + "call grub_backtrace_pointer"); +#else + asm volatile ("movl %ebp, %eax\n" + "call grub_backtrace_pointer"); +#endif +} + +static grub_err_t +grub_cmd_backtrace (grub_command_t cmd __attribute__ ((unused)), + int argc __attribute__ ((unused)), + char **args __attribute__ ((unused))) +{ + grub_backtrace (); + return 0; +} + +#define NUM_INTERRUPTS 0x20 + +void grub_int_handler (void); + +struct idt_descriptor idt_desc; + +static grub_err_t +setup_interrupts (void) +{ + struct idt_entry *idt_table; + unsigned long i; + grub_uint16_t seg; + idt_table = grub_memalign (0x10, NUM_INTERRUPTS * sizeof (struct idt_entry)); + if (!idt_table) + return grub_errno; + idt_desc.base = idt_table; + idt_desc.limit = NUM_INTERRUPTS; + + asm volatile ("xorl %%eax, %%eax\n" + "mov %%cs, %%ax\n" :"=a" (seg)); + + for (i = 0; i < NUM_INTERRUPTS; i++) + { + idt_table[i].addr_low = ((grub_addr_t) grub_int_handler) & 0xffff; + idt_table[i].addr_high = ((grub_addr_t) grub_int_handler) >> 16; + idt_table[i].unused = 0; + idt_table[i].segment = seg; + idt_table[i].type = GRUB_IDT_ENTRY_TYPE_PRESENT + | GRUB_IDT_ENTRY_TYPE_RING0 | GRUB_IDT_ENTRY_TYPE_TRAP32; + } + + asm volatile ("lidt %0" : : "m" (idt_desc)); + + return GRUB_ERR_NONE; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(backtrace) +{ + grub_err_t err; + err = setup_interrupts(); + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + cmd = grub_register_command ("backtrace", grub_cmd_backtrace, + 0, "Print backtrace."); +} + +GRUB_MOD_FINI(backtrace) +{ + grub_unregister_command (cmd); +} diff --git a/lib/i386/backtrace_int.S b/lib/i386/backtrace_int.S new file mode 100644 index 000000000..a51fc5ee4 --- /dev/null +++ b/lib/i386/backtrace_int.S @@ -0,0 +1,12 @@ +#include + + .text + .p2align 4 + +FUNCTION(grub_int_handler) + /* FIXME: push this only when CPU pushes no error on stack. */ + push $0 + pusha + movl 36(%esp), %eax + movl %ebp, %edx + call EXT_C(grub_interrupt_handler_real)