/*- * Copyright (c) 2016 The FreeBSD Foundation * * This software was developed by Konstantin Belousov under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include "bootstrap.h" #include "loader_efi.h" #define NUM_IST 8 #define NUM_EXC 32 /* * This code catches exceptions but forwards hardware interrupts to * handlers installed by firmware. It differentiates exceptions * vs. interrupts by presence of the error code on the stack, which * causes different stack pointer value on trap handler entry. * * Use kernel layout for the trapframe just to not be original. * * Use free IST slot in existing TSS, or create our own TSS if * firmware did not configured any, to have stack switched to * IST-specified one, e.g. to handle #SS. If hand-off cannot find * unused IST slot, or create a new descriptor in GDT, we bail out. */ static struct region_descriptor fw_idt; /* Descriptor for pristine fw IDT */ static struct region_descriptor loader_idt;/* Descriptor for loader shadow IDT */ static EFI_PHYSICAL_ADDRESS lidt_pa; /* Address of loader shadow IDT */ static EFI_PHYSICAL_ADDRESS tss_pa; /* Address of TSS */ static EFI_PHYSICAL_ADDRESS exc_stack_pa;/* Address of IST stack for loader */ EFI_PHYSICAL_ADDRESS exc_rsp; /* %rsp value on our IST stack when exception happens */ EFI_PHYSICAL_ADDRESS fw_intr_handlers[NUM_EXC]; /* fw handlers for < 32 IDT vectors */ static int intercepted[NUM_EXC]; static int ist; /* IST for exception handlers */ static uint32_t tss_fw_seg; /* Fw TSS segment */ static uint32_t loader_tss; /* Loader TSS segment */ static struct region_descriptor fw_gdt; /* Descriptor of pristine GDT */ static EFI_PHYSICAL_ADDRESS loader_gdt_pa; /* Address of loader shadow GDT */ struct frame { struct frame *fr_savfp; uintptr_t fr_savpc; }; void report_exc(struct trapframe *tf); void report_exc(struct trapframe *tf) { struct frame *fp; uintptr_t pc, base; char buf[80]; int ret; base = (uintptr_t)boot_img->ImageBase; /* * printf() depends on loader runtime and UEFI firmware health * to produce the console output, in case of exception, the * loader or firmware runtime may fail to support the printf(). */ printf("====================================================" "============================\n"); printf("Exception %u\n", tf->tf_trapno); printf("ss 0x%04hx cs 0x%04hx ds 0x%04hx es 0x%04hx fs 0x%04hx " "gs 0x%04hx\n", (uint16_t)tf->tf_ss, (uint16_t)tf->tf_cs, (uint16_t)tf->tf_ds, (uint16_t)tf->tf_es, (uint16_t)tf->tf_fs, (uint16_t)tf->tf_gs); printf("err 0x%08x rfl 0x%08x addr 0x%016lx\n" "rsp 0x%016lx rip 0x%016lx\n", (uint32_t)tf->tf_err, (uint32_t)tf->tf_rflags, tf->tf_addr, tf->tf_rsp, tf->tf_rip); printf( "rdi 0x%016lx rsi 0x%016lx rdx 0x%016lx\n" "rcx 0x%016lx r8 0x%016lx r9 0x%016lx\n" "rax 0x%016lx rbx 0x%016lx rbp 0x%016lx\n" "r10 0x%016lx r11 0x%016lx r12 0x%016lx\n" "r13 0x%016lx r14 0x%016lx r15 0x%016lx\n", tf->tf_rdi, tf->tf_rsi, tf->tf_rdx, tf->tf_rcx, tf->tf_r8, tf->tf_r9, tf->tf_rax, tf->tf_rbx, tf->tf_rbp, tf->tf_r10, tf->tf_r11, tf->tf_r12, tf->tf_r13, tf->tf_r14, tf->tf_r15); fp = (struct frame *)tf->tf_rbp; pc = tf->tf_rip; printf("Stack trace:\n"); pager_open(); while (fp != NULL || pc != 0) { char *source = "PC"; if (pc >= base && pc < base + boot_img->ImageSize) { pc -= base; source = "loader PC"; } (void) snprintf(buf, sizeof (buf), "FP %016lx: %s 0x%016lx\n", (uintptr_t)fp, source, pc); if (pager_output(buf)) break; if (fp != NULL) fp = fp->fr_savfp; if (fp != NULL) pc = fp->fr_savpc; else pc = 0; } pager_close(); printf("Machine stopped.\n"); } static void prepare_exception(unsigned idx, uint64_t my_handler, int ist_use_table[static NUM_IST]) { struct gate_descriptor *fw_idt_e, *loader_idt_e; fw_idt_e = &((struct gate_descriptor *)fw_idt.rd_base)[idx]; loader_idt_e = &((struct gate_descriptor *)loader_idt.rd_base)[idx]; fw_intr_handlers[idx] = fw_idt_e->gd_looffset + (fw_idt_e->gd_hioffset << 16); intercepted[idx] = 1; ist_use_table[fw_idt_e->gd_ist]++; loader_idt_e->gd_looffset = my_handler; loader_idt_e->gd_hioffset = my_handler >> 16; /* * We reuse uefi selector for the code segment for the exception * handler code, while the reason for the fault might be the * corruption of that gdt entry. On the other hand, allocating * our own descriptor might be not much better, if gdt is corrupted. */ loader_idt_e->gd_selector = fw_idt_e->gd_selector; loader_idt_e->gd_ist = 0; loader_idt_e->gd_type = SDT_SYSIGT; loader_idt_e->gd_dpl = 0; loader_idt_e->gd_p = 1; loader_idt_e->gd_xx = 0; loader_idt_e->sd_xx1 = 0; } #define PREPARE_EXCEPTION(N) \ extern char EXC##N##_handler[]; \ prepare_exception(N, (uintptr_t)EXC##N##_handler, ist_use_table); static void free_tables(void) { if (lidt_pa != 0) { BS->FreePages(lidt_pa, EFI_SIZE_TO_PAGES(fw_idt.rd_limit)); lidt_pa = 0; } if (exc_stack_pa != 0) { BS->FreePages(exc_stack_pa, 1); exc_stack_pa = 0; } if (tss_pa != 0 && tss_fw_seg == 0) { BS->FreePages(tss_pa, EFI_SIZE_TO_PAGES(sizeof(struct amd64tss))); tss_pa = 0; } if (loader_gdt_pa != 0) { BS->FreePages(tss_pa, 2); loader_gdt_pa = 0; } ist = 0; loader_tss = 0; } static int efi_setup_tss(struct region_descriptor *gdt, uint32_t loader_tss_idx, struct amd64tss **tss) { EFI_STATUS status; struct system_segment_descriptor *tss_desc; tss_desc = (struct system_segment_descriptor *)(gdt->rd_base + (loader_tss_idx << 3)); status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, EFI_SIZE_TO_PAGES(sizeof(struct amd64tss)), &tss_pa); if (EFI_ERROR(status)) { printf("efi_setup_tss: AllocatePages tss error %lu\n", EFI_ERROR_CODE(status)); return (0); } *tss = (struct amd64tss *)tss_pa; bzero(*tss, sizeof(**tss)); tss_desc->sd_lolimit = sizeof(struct amd64tss); tss_desc->sd_lobase = tss_pa; tss_desc->sd_type = SDT_SYSTSS; tss_desc->sd_dpl = 0; tss_desc->sd_p = 1; tss_desc->sd_hilimit = sizeof(struct amd64tss) >> 16; tss_desc->sd_gran = 0; tss_desc->sd_hibase = tss_pa >> 24; tss_desc->sd_xx0 = 0; tss_desc->sd_xx1 = 0; tss_desc->sd_mbz = 0; tss_desc->sd_xx2 = 0; return (1); } static int efi_redirect_exceptions(void) { int ist_use_table[NUM_IST]; struct gate_descriptor *loader_idt_e; struct system_segment_descriptor *tss_desc, *gdt_desc; struct amd64tss *tss; struct region_descriptor *gdt_rd, loader_gdt; uint32_t i; EFI_STATUS status; register_t rfl; sidt(&fw_idt); status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, EFI_SIZE_TO_PAGES(fw_idt.rd_limit), &lidt_pa); if (EFI_ERROR(status)) { printf("efi_redirect_exceptions: AllocatePages IDT error %lu\n", EFI_ERROR_CODE(status)); lidt_pa = 0; return (0); } status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1, &exc_stack_pa); if (EFI_ERROR(status)) { printf("efi_redirect_exceptions: AllocatePages stk error %lu\n", EFI_ERROR_CODE(status)); exc_stack_pa = 0; free_tables(); return (0); } loader_idt.rd_limit = fw_idt.rd_limit; bcopy((void *)fw_idt.rd_base, (void *)loader_idt.rd_base, loader_idt.rd_limit); bzero(ist_use_table, sizeof(ist_use_table)); bzero(fw_intr_handlers, sizeof(fw_intr_handlers)); bzero(intercepted, sizeof(intercepted)); sgdt(&fw_gdt); tss_fw_seg = read_tr(); gdt_rd = NULL; if (tss_fw_seg == 0) { for (i = 2; (i << 3) + sizeof(*gdt_desc) <= fw_gdt.rd_limit; i += 2) { gdt_desc = (struct system_segment_descriptor *)( fw_gdt.rd_base + (i << 3)); if (gdt_desc->sd_type == 0 && gdt_desc->sd_mbz == 0) { gdt_rd = &fw_gdt; break; } } if (gdt_rd == NULL) { if (i >= 8190) { printf("efi_redirect_exceptions: all slots " "in gdt are used\n"); free_tables(); return (0); } loader_gdt.rd_limit = roundup2(fw_gdt.rd_limit + sizeof(struct system_segment_descriptor), sizeof(struct system_segment_descriptor)) - 1; i = (loader_gdt.rd_limit + 1 - sizeof(struct system_segment_descriptor)) / sizeof(struct system_segment_descriptor) * 2; status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, EFI_SIZE_TO_PAGES(loader_gdt.rd_limit), &loader_gdt_pa); if (EFI_ERROR(status)) { printf("efi_setup_tss: AllocatePages gdt error " "%lu\n", EFI_ERROR_CODE(status)); loader_gdt_pa = 0; free_tables(); return (0); } loader_gdt.rd_base = loader_gdt_pa; bzero((void *)loader_gdt.rd_base, loader_gdt.rd_limit); bcopy((void *)fw_gdt.rd_base, (void *)loader_gdt.rd_base, fw_gdt.rd_limit); gdt_rd = &loader_gdt; } loader_tss = i << 3; if (!efi_setup_tss(gdt_rd, i, &tss)) { tss_pa = 0; free_tables(); return (0); } } else { tss_desc = (struct system_segment_descriptor *)((char *) fw_gdt.rd_base + tss_fw_seg); if (tss_desc->sd_type != SDT_SYSTSS && tss_desc->sd_type != SDT_SYSBSY) { printf("LTR points to non-TSS descriptor\n"); free_tables(); return (0); } tss_pa = tss_desc->sd_lobase + (tss_desc->sd_hibase << 16); tss = (struct amd64tss *)tss_pa; tss_desc->sd_type = SDT_SYSTSS; /* unbusy */ } PREPARE_EXCEPTION(0); PREPARE_EXCEPTION(1); PREPARE_EXCEPTION(2); PREPARE_EXCEPTION(3); PREPARE_EXCEPTION(4); PREPARE_EXCEPTION(5); PREPARE_EXCEPTION(6); PREPARE_EXCEPTION(7); PREPARE_EXCEPTION(8); PREPARE_EXCEPTION(9); PREPARE_EXCEPTION(10); PREPARE_EXCEPTION(11); PREPARE_EXCEPTION(12); PREPARE_EXCEPTION(13); PREPARE_EXCEPTION(14); PREPARE_EXCEPTION(16); PREPARE_EXCEPTION(17); PREPARE_EXCEPTION(18); PREPARE_EXCEPTION(19); PREPARE_EXCEPTION(20); exc_rsp = exc_stack_pa + PAGE_SIZE - (6 /* hw exception frame */ + 3 /* scratch regs */) * 8; /* Find free IST and use it */ for (ist = 1; ist < NUM_IST; ist++) { if (ist_use_table[ist] == 0) break; } if (ist == NUM_IST) { printf("efi_redirect_exceptions: all ISTs used\n"); free_tables(); lidt_pa = 0; return (0); } for (i = 0; i < NUM_EXC; i++) { loader_idt_e = &((struct gate_descriptor *)loader_idt. rd_base)[i]; if (intercepted[i]) loader_idt_e->gd_ist = ist; } (&(tss->tss_ist1))[ist - 1] = exc_stack_pa + PAGE_SIZE; /* Switch to new IDT */ rfl = intr_disable(); if (loader_gdt_pa != 0) bare_lgdt(&loader_gdt); if (loader_tss != 0) ltr(loader_tss); lidt(&loader_idt); intr_restore(rfl); return (1); } static void efi_unredirect_exceptions(void) { register_t rfl; if (lidt_pa == 0) return; rfl = intr_disable(); if (ist != 0) (&(((struct amd64tss *)tss_pa)->tss_ist1))[ist - 1] = 0; if (loader_gdt_pa != 0) bare_lgdt(&fw_gdt); if (loader_tss != 0) ltr(tss_fw_seg); lidt(&fw_idt); intr_restore(rfl); free_tables(); } static int command_grab_faults(int argc, char *argv[]) { int res; res = efi_redirect_exceptions(); if (!res) printf("failed\n"); return (CMD_OK); } COMMAND_SET(grap_faults, "grab_faults", "grab faults", command_grab_faults); static int command_ungrab_faults(int argc, char *argv[]) { efi_unredirect_exceptions(); return (CMD_OK); } COMMAND_SET(ungrab_faults, "ungrab_faults", "ungrab faults", command_ungrab_faults); static int command_fault(int argc, char *argv[]) { __asm("ud2"); return (CMD_OK); } COMMAND_SET(fault, "fault", "generate fault", command_fault);