Those functions are going to be used for netdumps too. --- Makefile | 7 +- defs.h | 8 + kaslr_helper.c | 463 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ sadump.c | 487 +++++---------------------------------------------------- symbols.c | 6 +- 5 files changed, 518 insertions(+), 453 deletions(-) create mode 100644 kaslr_helper.c diff --git a/Makefile b/Makefile index bdc8321..f8647b3 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ CFILES=main.c tools.c global_data.c memory.c filesys.c help.c task.c \ xen_hyper.c xen_hyper_command.c xen_hyper_global_data.c \ xen_hyper_dump_tables.c kvmdump.c qemu.c qemu-load.c sadump.c ipcs.c \ ramdump.c vmware_vmss.c \ - xen_dom0.c + xen_dom0.c kaslr_helper.c SOURCE_FILES=${CFILES} ${GENERIC_HFILES} ${MCORE_HFILES} \ ${REDHAT_CFILES} ${REDHAT_HFILES} ${UNWIND_HFILES} \ @@ -90,7 +90,7 @@ OBJECT_FILES=main.o tools.o global_data.o memory.o filesys.o help.o task.o \ xen_hyper.o xen_hyper_command.o xen_hyper_global_data.o \ xen_hyper_dump_tables.o kvmdump.o qemu.o qemu-load.o sadump.o ipcs.o \ ramdump.o vmware_vmss.o \ - xen_dom0.o + xen_dom0.o kaslr_helper.o MEMORY_DRIVER_FILES=memory_driver/Makefile memory_driver/crash.c memory_driver/README @@ -517,6 +517,9 @@ ramdump.o: ${GENERIC_HFILES} ${REDHAT_HFILES} ramdump.c vmware_vmss.o: ${GENERIC_HFILES} ${VMWARE_HFILES} vmware_vmss.c ${CC} -c ${CRASH_CFLAGS} vmware_vmss.c ${WARNING_OPTIONS} ${WARNING_ERROR} +kaslr_helper.o: ${GENERIC_HFILES} kaslr_helper.c + ${CC} -c ${CRASH_CFLAGS} kaslr_helper.c ${WARNING_OPTIONS} ${WARNING_ERROR} + ${PROGRAM}: force @make --no-print-directory all diff --git a/defs.h b/defs.h index 7998ebf..18b41d0 100644 --- a/defs.h +++ b/defs.h @@ -6334,6 +6334,7 @@ FILE *set_sadump_fp(FILE *); void get_sadump_regs(struct bt_info *bt, ulong *ipp, ulong *spp); void sadump_display_regs(int, FILE *); int sadump_phys_base(ulong *); +int sadump_set_phys_base(ulong); void sadump_show_diskset(void); int sadump_is_zero_excluded(void); void sadump_set_zero_excluded(void); @@ -6341,6 +6342,8 @@ void sadump_unset_zero_excluded(void); struct sadump_data; struct sadump_data *get_sadump_data(void); int sadump_calc_kaslr_offset(ulong *); +ulong sadump_get_cr3(void); +ulong sadump_get_idtr(void); /* * qemu.c @@ -6389,6 +6392,11 @@ uint vmware_vmss_page_size(void); int read_vmware_vmss(int, void *, int, ulong, physaddr_t); int write_vmware_vmss(int, void *, int, ulong, physaddr_t); +/* + * kaslr_helper.c + */ +int calc_kaslr_offset(ulong *, ulong *); + /* * gnu_binutils.c */ diff --git a/kaslr_helper.c b/kaslr_helper.c new file mode 100644 index 0000000..e2da81c --- /dev/null +++ b/kaslr_helper.c @@ -0,0 +1,463 @@ +/* + * kaslr_helper - helper for kaslr offset calculation + * + * Copyright (c) 2011 FUJITSU LIMITED + * Copyright (c) 2018 Red Hat Inc. + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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. + * + * Author: HATAYAMA Daisuke <d.hatayama@xxxxxxxxxxxxxx> + */ + +#include "defs.h" +#include <elf.h> +#include <inttypes.h> + +#ifdef X86_64 +/* + * Get address of vector0 interrupt handler (Devide Error) from Interrupt + * Descriptor Table. + */ +static ulong +get_vec0_addr(ulong idtr) +{ + struct gate_struct64 { + uint16_t offset_low; + uint16_t segment; + uint32_t ist : 3, zero0 : 5, type : 5, dpl : 2, p : 1; + uint16_t offset_middle; + uint32_t offset_high; + uint32_t zero1; + } __attribute__((packed)) gate; + + readmem(idtr, PHYSADDR, &gate, sizeof(gate), "idt_table", FAULT_ON_ERROR); + + return ((ulong)gate.offset_high << 32) + + ((ulong)gate.offset_middle << 16) + + gate.offset_low; +} + +/* + * Parse a string of [size[KMG] ]offset[KMG] + * Import from Linux kernel(lib/cmdline.c) + */ +static ulong memparse(char *ptr, char **retptr) +{ + char *endptr; + + unsigned long long ret = strtoull(ptr, &endptr, 0); + + switch (*endptr) { + case 'E': + case 'e': + ret <<= 10; + case 'P': + case 'p': + ret <<= 10; + case 'T': + case 't': + ret <<= 10; + case 'G': + case 'g': + ret <<= 10; + case 'M': + case 'm': + ret <<= 10; + case 'K': + case 'k': + ret <<= 10; + endptr++; + default: + break; + } + + if (retptr) + *retptr = endptr; + + return ret; +} + +/* + * Find "elfcorehdr=" in the boot parameter of kernel and return the address + * of elfcorehdr. + */ +static ulong +get_elfcorehdr(ulong kaslr_offset) +{ + char cmdline[BUFSIZE], *ptr; + ulong cmdline_vaddr; + ulong cmdline_paddr; + ulong buf_vaddr, buf_paddr; + char *end; + ulong elfcorehdr_addr = 0, elfcorehdr_size = 0; + int verbose = CRASHDEBUG(1)? 1: 0; + + cmdline_vaddr = st->saved_command_line_vmlinux + kaslr_offset; + if (!kvtop(NULL, cmdline_vaddr, &cmdline_paddr, verbose)) + return 0; + + if (CRASHDEBUG(1)) { + fprintf(fp, "cmdline vaddr=%lx\n", cmdline_vaddr); + fprintf(fp, "cmdline paddr=%lx\n", cmdline_paddr); + } + + if (!readmem(cmdline_paddr, PHYSADDR, &buf_vaddr, sizeof(ulong), + "saved_command_line", RETURN_ON_ERROR)) + return 0; + + if (!kvtop(NULL, buf_vaddr, &buf_paddr, verbose)) + return 0; + + if (CRASHDEBUG(1)) { + fprintf(fp, "cmdline buffer vaddr=%lx\n", buf_vaddr); + fprintf(fp, "cmdline buffer paddr=%lx\n", buf_paddr); + } + + memset(cmdline, 0, BUFSIZE); + if (!readmem(buf_paddr, PHYSADDR, cmdline, BUFSIZE, + "saved_command_line", RETURN_ON_ERROR)) + return 0; + + ptr = strstr(cmdline, "elfcorehdr="); + if (!ptr) + return 0; + + if (CRASHDEBUG(1)) + fprintf(fp, "2nd kernel detected\n"); + + ptr += strlen("elfcorehdr="); + elfcorehdr_addr = memparse(ptr, &end); + if (*end == '@') { + elfcorehdr_size = elfcorehdr_addr; + elfcorehdr_addr = memparse(end + 1, &end); + } + + if (CRASHDEBUG(1)) { + fprintf(fp, "elfcorehdr_addr=%lx\n", elfcorehdr_addr); + fprintf(fp, "elfcorehdr_size=%lx\n", elfcorehdr_size); + } + + return elfcorehdr_addr; +} + + /* + * Get vmcoreinfo from elfcorehdr. + * Some codes are imported from Linux kernel(fs/proc/vmcore.c) + */ +static int +get_vmcoreinfo(ulong elfcorehdr, ulong *addr, int *len) +{ + unsigned char e_ident[EI_NIDENT]; + Elf64_Ehdr ehdr; + Elf64_Phdr phdr; + Elf64_Nhdr nhdr; + ulong ptr; + ulong nhdr_offset = 0; + int i; + + if (!readmem(elfcorehdr, PHYSADDR, e_ident, EI_NIDENT, + "EI_NIDENT", RETURN_ON_ERROR)) + return FALSE; + + if (e_ident[EI_CLASS] != ELFCLASS64) { + error(INFO, "Only ELFCLASS64 is supportd\n"); + return FALSE; + } + + if (!readmem(elfcorehdr, PHYSADDR, &ehdr, sizeof(ehdr), + "Elf64_Ehdr", RETURN_ON_ERROR)) + return FALSE; + + /* Sanity Check */ + if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || + (ehdr.e_type != ET_CORE) || + ehdr.e_ident[EI_CLASS] != ELFCLASS64 || + ehdr.e_ident[EI_VERSION] != EV_CURRENT || + ehdr.e_version != EV_CURRENT || + ehdr.e_ehsize != sizeof(Elf64_Ehdr) || + ehdr.e_phentsize != sizeof(Elf64_Phdr) || + ehdr.e_phnum == 0) { + error(INFO, "Invalid elf header\n"); + return FALSE; + } + + ptr = elfcorehdr + ehdr.e_phoff; + for (i = 0; i < ehdr.e_phnum; i++) { + ulong offset; + char name[16]; + + if (!readmem(ptr, PHYSADDR, &phdr, sizeof(phdr), + "Elf64_Phdr", RETURN_ON_ERROR)) + return FALSE; + + ptr += sizeof(phdr); + if (phdr.p_type != PT_NOTE) + continue; + + offset = phdr.p_offset; + if (!readmem(offset, PHYSADDR, &nhdr, sizeof(nhdr), + "Elf64_Nhdr", RETURN_ON_ERROR)) + return FALSE; + + offset += DIV_ROUND_UP(sizeof(Elf64_Nhdr), sizeof(Elf64_Word))* + sizeof(Elf64_Word); + memset(name, 0, sizeof(name)); + if (!readmem(offset, PHYSADDR, name, sizeof(name), + "Elf64_Nhdr name", RETURN_ON_ERROR)) + return FALSE; + + if(!strcmp(name, "VMCOREINFO")) { + nhdr_offset = offset; + break; + } + } + + if (!nhdr_offset) + return FALSE; + + *addr = nhdr_offset + + DIV_ROUND_UP(nhdr.n_namesz, sizeof(Elf64_Word))* + sizeof(Elf64_Word); + *len = nhdr.n_descsz; + + if (CRASHDEBUG(1)) { + fprintf(fp, "vmcoreinfo addr=%lx\n", *addr); + fprintf(fp, "vmcoreinfo len=%d\n", *len); + } + + return TRUE; +} + +/* + * Check if current kaslr_offset/phys_base is for 1st kernel or 2nd kernel. + * If we are in 2nd kernel, get kaslr_offset/phys_base from vmcoreinfo. + * + * 1. Get command line and try to retrieve "elfcorehdr=" boot parameter + * 2. If "elfcorehdr=" is not found in command line, we are in 1st kernel. + * There is nothing to do. + * 3. If "elfcorehdr=" is found, we are in 2nd kernel. Find vmcoreinfo + * using "elfcorehdr=" and retrieve kaslr_offset/phys_base from vmcoreinfo. + */ +static int +get_kaslr_offset_from_vmcoreinfo(ulong orig_kaslr_offset, + ulong *kaslr_offset, ulong *phys_base) +{ + ulong elfcorehdr_addr = 0; + ulong vmcoreinfo_addr; + int vmcoreinfo_len; + char *buf, *pos; + int ret = FALSE; + + /* Find "elfcorehdr=" in the kernel boot parameter */ + elfcorehdr_addr = get_elfcorehdr(orig_kaslr_offset); + if (!elfcorehdr_addr) + return FALSE; + + /* Get vmcoreinfo from the address of "elfcorehdr=" */ + if (!get_vmcoreinfo(elfcorehdr_addr, &vmcoreinfo_addr, &vmcoreinfo_len)) + return FALSE; + + if (!vmcoreinfo_len) + return FALSE; + + if (CRASHDEBUG(1)) + fprintf(fp, "Find vmcoreinfo in kdump memory\n"); + + buf = GETBUF(vmcoreinfo_len); + if (!readmem(vmcoreinfo_addr, PHYSADDR, buf, vmcoreinfo_len, + "vmcoreinfo", RETURN_ON_ERROR)) + goto quit; + + /* Get phys_base form vmcoreinfo */ + pos = strstr(buf, "NUMBER(phys_base)="); + if (!pos) + goto quit; + *phys_base = strtoull(pos + strlen("NUMBER(phys_base)="), NULL, 0); + + /* Get kaslr_offset form vmcoreinfo */ + pos = strstr(buf, "KERNELOFFSET="); + if (!pos) + goto quit; + *kaslr_offset = strtoull(pos + strlen("KERNELOFFSET="), NULL, 16); + + ret = TRUE; + +quit: + FREEBUF(buf); + return ret; +} + +/* + * Calculate kaslr_offset and phys_base + * + * kaslr_offset: + * The difference between original address in System.map or vmlinux and + * actual address placed randomly by kaslr feature. To be more accurate, + * kaslr_offset = actual address - original address + * + * phys_base: + * Physical address where the kerenel is placed. In other words, it's a + * physical address of __START_KERNEL_map. This is also decided randomly by + * kaslr. + * + * kaslr offset and phys_base are calculated as follows: + * + * kaslr_offset: + * 1) Get IDTR and CR3 value from the dump header. + * 2) Get a virtual address of IDT from IDTR value + * --- (A) + * 3) Translate (A) to physical address using CR3, the upper 52 bits + * of which points a top of page table. + * --- (B) + * 4) Get an address of vector0 (Devide Error) interrupt handler from + * IDT, which are pointed by (B). + * --- (C) + * 5) Get an address of symbol "divide_error" form vmlinux + * --- (D) + * + * Now we have two addresses: + * (C)-> Actual address of "divide_error" + * (D)-> Original address of "divide_error" in the vmlinux + * + * kaslr_offset can be calculated by the difference between these two + * value. + * + * phys_base; + * 1) Get IDT virtual address from vmlinux + * --- (E) + * + * So phys_base can be calculated using relationship of directly mapped + * address. + * + * phys_base = + * Physical address(B) - + * (Virtual address(E) + kaslr_offset - __START_KERNEL_map) + * + * Note that the address (A) cannot be used instead of (E) because (A) is + * not direct map address, it's a fixed map address. + * + * This solution works in most every case, but does not work in the + * following case. + * + * 1) If the dump is captured on early stage of kernel boot, IDTR points + * early IDT table(early_idts) instead of normal IDT(idt_table). + * 2) If the dump is captured whle kdump is working, IDTR points + * IDT table of 2nd kernel, not 1st kernel. + * + * Current implementation does not support the case 1), need + * enhancement in the future. For the case 2), get kaslr_offset and + * phys_base as follows. + * + * 1) Get kaslr_offset and phys_base using the above solution. + * 2) Get kernel boot parameter from "saved_command_line" + * 3) If "elfcorehdr=" is not included in boot parameter, we are in the + * first kernel, nothing to do any more. + * 4) If "elfcorehdr=" is included in boot parameter, we are in the 2nd + * kernel. Retrieve vmcoreinfo from address of "elfcorehdr=" and + * get kaslr_offset and phys_base from vmcoreinfo. + */ +#define PTI_USER_PGTABLE_BIT PAGE_SHIFT +#define PTI_USER_PGTABLE_MASK (1 << PTI_USER_PGTABLE_BIT) +#define CR3_PCID_MASK 0xFFFull +int +calc_kaslr_offset(ulong *kaslr_offset, ulong *phys_base) +{ + uint64_t cr3 = 0, idtr = 0, pgd = 0, idtr_paddr; + ulong divide_error_vmcore; + ulong kaslr_offset_kdump, phys_base_kdump; + int ret = FALSE; + int verbose = CRASHDEBUG(1)? 1: 0; + + if (!machine_type("X86_64")) + return FALSE; + + if (SADUMP_DUMPFILE()) { + idtr = sadump_get_idtr(); + cr3 = sadump_get_cr3(); + } else { + return FALSE; + } + + if (st->pti_init_vmlinux || st->kaiser_init_vmlinux) + pgd = cr3 & ~(CR3_PCID_MASK|PTI_USER_PGTABLE_MASK); + else + pgd = cr3 & ~CR3_PCID_MASK; + + /* + * Set up for kvtop. + * + * calc_kaslr_offset() is called before machdep_init(PRE_GDB), so some + * variables are not initialized yet. Set up them here to call kvtop(). + * + * TODO: XEN and 5-level is not supported + */ + vt->kernel_pgd[0] = pgd; + machdep->last_pgd_read = vt->kernel_pgd[0]; + machdep->machspec->physical_mask_shift = __PHYSICAL_MASK_SHIFT_2_6; + machdep->machspec->pgdir_shift = PGDIR_SHIFT; + machdep->machspec->ptrs_per_pgd = PTRS_PER_PGD; + if (!readmem(pgd, PHYSADDR, machdep->pgd, PAGESIZE(), + "pgd", RETURN_ON_ERROR)) + goto quit; + + /* Convert virtual address of IDT table to physical address */ + if (!kvtop(NULL, idtr, &idtr_paddr, verbose)) + goto quit; + + /* Now we can calculate kaslr_offset and phys_base */ + divide_error_vmcore = get_vec0_addr(idtr_paddr); + *kaslr_offset = divide_error_vmcore - st->divide_error_vmlinux; + *phys_base = idtr_paddr - + (st->idt_table_vmlinux + *kaslr_offset - __START_KERNEL_map); + + if (CRASHDEBUG(1)) { + fprintf(fp, "calc_kaslr_offset: idtr=%lx\n", idtr); + fprintf(fp, "calc_kaslr_offset: pgd=%lx\n", pgd); + fprintf(fp, "calc_kaslr_offset: idtr(phys)=%lx\n", idtr_paddr); + fprintf(fp, "calc_kaslr_offset: divide_error(vmlinux): %lx\n", + st->divide_error_vmlinux); + fprintf(fp, "calc_kaslr_offset: divide_error(vmcore): %lx\n", + divide_error_vmcore); + } + + /* + * Check if current kaslr_offset/phys_base is for 1st kernel or 2nd + * kernel. If we are in 2nd kernel, get kaslr_offset/phys_base + * from vmcoreinfo + */ + if (get_kaslr_offset_from_vmcoreinfo( + *kaslr_offset, &kaslr_offset_kdump, &phys_base_kdump)) { + *kaslr_offset = kaslr_offset_kdump; + *phys_base = phys_base_kdump; + } else if (CRASHDEBUG(1)) { + fprintf(fp, "kaslr_helper: failed to determine which kernel was running at crash,\n"); + fprintf(fp, "kaslr_helper: asssuming the kdump 1st kernel.\n"); + } + + if (CRASHDEBUG(1)) { + fprintf(fp, "calc_kaslr_offset: kaslr_offset=%lx\n", + *kaslr_offset); + fprintf(fp, "calc_kaslr_offset: phys_base=%lx\n", *phys_base); + } + + ret = TRUE; +quit: + vt->kernel_pgd[0] = 0; + machdep->last_pgd_read = 0; + return ret; +} +#else +int +calc_kaslr_offset(ulong *kaslr_offset, ulong *phys_page) +{ + return FALSE; +} +#endif /* X86_64 */ diff --git a/sadump.c b/sadump.c index d19b40a..250d181 100644 --- a/sadump.c +++ b/sadump.c @@ -1569,6 +1569,13 @@ int sadump_phys_base(ulong *phys_base) return FALSE; } +int sadump_set_phys_base(ulong phys_base) +{ + sd->phys_base = phys_base; + + return TRUE; +} + /* * Used by "sys" command to show diskset disk names. */ @@ -1656,466 +1663,46 @@ get_sadump_data(void) static int get_sadump_smram_cpu_state_any(struct sadump_smram_cpu_state *smram) { - ulong offset; - struct sadump_header *sh = sd->dump_header; - int apicid; - struct sadump_smram_cpu_state scs, zero; - - offset = sd->sub_hdr_offset + sizeof(uint32_t) + - sd->dump_header->nr_cpus * sizeof(struct sadump_apic_state); - - memset(&zero, 0, sizeof(zero)); - - for (apicid = 0; apicid < sh->nr_cpus; ++apicid) { - if (!read_device(&scs, sizeof(scs), &offset)) { - error(INFO, "sadump: cannot read sub header " - "cpu_state\n"); - return FALSE; - } - if (memcmp(&scs, &zero, sizeof(scs)) != 0) { - *smram = scs; - return TRUE; - } - } - - return FALSE; -} - -/* - * Get address of vector0 interrupt handler (Devide Error) from Interrupt - * Descriptor Table. - */ -static ulong -get_vec0_addr(ulong idtr) -{ - struct gate_struct64 { - uint16_t offset_low; - uint16_t segment; - uint32_t ist : 3, zero0 : 5, type : 5, dpl : 2, p : 1; - uint16_t offset_middle; - uint32_t offset_high; - uint32_t zero1; - } __attribute__((packed)) gate; - - readmem(idtr, PHYSADDR, &gate, sizeof(gate), "idt_table", FAULT_ON_ERROR); - - return ((ulong)gate.offset_high << 32) - + ((ulong)gate.offset_middle << 16) - + gate.offset_low; -} - -/* - * Parse a string of [size[KMG] ]offset[KMG] - * Import from Linux kernel(lib/cmdline.c) - */ -static ulong memparse(char *ptr, char **retptr) -{ - char *endptr; - - unsigned long long ret = strtoull(ptr, &endptr, 0); - - switch (*endptr) { - case 'E': - case 'e': - ret <<= 10; - case 'P': - case 'p': - ret <<= 10; - case 'T': - case 't': - ret <<= 10; - case 'G': - case 'g': - ret <<= 10; - case 'M': - case 'm': - ret <<= 10; - case 'K': - case 'k': - ret <<= 10; - endptr++; - default: - break; - } - - if (retptr) - *retptr = endptr; - - return ret; -} - -/* - * Find "elfcorehdr=" in the boot parameter of kernel and return the address - * of elfcorehdr. - */ -static ulong -get_elfcorehdr(ulong kaslr_offset) -{ - char cmdline[BUFSIZE], *ptr; - ulong cmdline_vaddr; - ulong cmdline_paddr; - ulong buf_vaddr, buf_paddr; - char *end; - ulong elfcorehdr_addr = 0, elfcorehdr_size = 0; - int verbose = CRASHDEBUG(1)? 1: 0; - - cmdline_vaddr = st->saved_command_line_vmlinux + kaslr_offset; - if (!kvtop(NULL, cmdline_vaddr, &cmdline_paddr, verbose)) - return 0; - - if (CRASHDEBUG(1)) { - fprintf(fp, "cmdline vaddr=%lx\n", cmdline_vaddr); - fprintf(fp, "cmdline paddr=%lx\n", cmdline_paddr); - } - - if (!readmem(cmdline_paddr, PHYSADDR, &buf_vaddr, sizeof(ulong), - "saved_command_line", RETURN_ON_ERROR)) - return 0; - - if (!kvtop(NULL, buf_vaddr, &buf_paddr, verbose)) - return 0; - - if (CRASHDEBUG(1)) { - fprintf(fp, "cmdline buffer vaddr=%lx\n", buf_vaddr); - fprintf(fp, "cmdline buffer paddr=%lx\n", buf_paddr); - } - - memset(cmdline, 0, BUFSIZE); - if (!readmem(buf_paddr, PHYSADDR, cmdline, BUFSIZE, - "saved_command_line", RETURN_ON_ERROR)) - return 0; - - ptr = strstr(cmdline, "elfcorehdr="); - if (!ptr) - return 0; - - if (CRASHDEBUG(1)) - fprintf(fp, "2nd kernel detected\n"); - - ptr += strlen("elfcorehdr="); - elfcorehdr_addr = memparse(ptr, &end); - if (*end == '@') { - elfcorehdr_size = elfcorehdr_addr; - elfcorehdr_addr = memparse(end + 1, &end); - } - - if (CRASHDEBUG(1)) { - fprintf(fp, "elfcorehdr_addr=%lx\n", elfcorehdr_addr); - fprintf(fp, "elfcorehdr_size=%lx\n", elfcorehdr_size); - } - - return elfcorehdr_addr; -} - - /* - * Get vmcoreinfo from elfcorehdr. - * Some codes are imported from Linux kernel(fs/proc/vmcore.c) - */ -static int -get_vmcoreinfo(ulong elfcorehdr, ulong *addr, int *len) -{ - unsigned char e_ident[EI_NIDENT]; - Elf64_Ehdr ehdr; - Elf64_Phdr phdr; - Elf64_Nhdr nhdr; - ulong ptr; - ulong nhdr_offset = 0; - int i; - - if (!readmem(elfcorehdr, PHYSADDR, e_ident, EI_NIDENT, - "EI_NIDENT", RETURN_ON_ERROR)) - return FALSE; - - if (e_ident[EI_CLASS] != ELFCLASS64) { - error(INFO, "Only ELFCLASS64 is supportd\n"); - return FALSE; - } - - if (!readmem(elfcorehdr, PHYSADDR, &ehdr, sizeof(ehdr), - "Elf64_Ehdr", RETURN_ON_ERROR)) - return FALSE; - - /* Sanity Check */ - if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || - (ehdr.e_type != ET_CORE) || - ehdr.e_ident[EI_CLASS] != ELFCLASS64 || - ehdr.e_ident[EI_VERSION] != EV_CURRENT || - ehdr.e_version != EV_CURRENT || - ehdr.e_ehsize != sizeof(Elf64_Ehdr) || - ehdr.e_phentsize != sizeof(Elf64_Phdr) || - ehdr.e_phnum == 0) { - error(INFO, "Invalid elf header\n"); - return FALSE; - } - - ptr = elfcorehdr + ehdr.e_phoff; - for (i = 0; i < ehdr.e_phnum; i++) { - ulong offset; - char name[16]; - - if (!readmem(ptr, PHYSADDR, &phdr, sizeof(phdr), - "Elf64_Phdr", RETURN_ON_ERROR)) - return FALSE; - - ptr += sizeof(phdr); - if (phdr.p_type != PT_NOTE) - continue; - - offset = phdr.p_offset; - if (!readmem(offset, PHYSADDR, &nhdr, sizeof(nhdr), - "Elf64_Nhdr", RETURN_ON_ERROR)) - return FALSE; - - offset += DIV_ROUND_UP(sizeof(Elf64_Nhdr), sizeof(Elf64_Word))* - sizeof(Elf64_Word); - memset(name, 0, sizeof(name)); - if (!readmem(offset, PHYSADDR, name, sizeof(name), - "Elf64_Nhdr name", RETURN_ON_ERROR)) - return FALSE; - - if(!strcmp(name, "VMCOREINFO")) { - nhdr_offset = offset; - break; - } - } - - if (!nhdr_offset) - return FALSE; - - *addr = nhdr_offset + - DIV_ROUND_UP(nhdr.n_namesz, sizeof(Elf64_Word))* - sizeof(Elf64_Word); - *len = nhdr.n_descsz; - - if (CRASHDEBUG(1)) { - fprintf(fp, "vmcoreinfo addr=%lx\n", *addr); - fprintf(fp, "vmcoreinfo len=%d\n", *len); - } - - return TRUE; + ulong offset; + struct sadump_header *sh = sd->dump_header; + int apicid; + struct sadump_smram_cpu_state scs, zero; + + offset = sd->sub_hdr_offset + sizeof(uint32_t) + + sd->dump_header->nr_cpus * sizeof(struct sadump_apic_state); + + memset(&zero, 0, sizeof(zero)); + + for (apicid = 0; apicid < sh->nr_cpus; ++apicid) { + if (!read_device(&scs, sizeof(scs), &offset)) { + error(INFO, "sadump: cannot read sub header " + "cpu_state\n"); + return FALSE; + } + if (memcmp(&scs, &zero, sizeof(scs)) != 0) { + *smram = scs; + return TRUE; + } + } + + return FALSE; } -/* - * Check if current kaslr_offset/phys_base is for 1st kernel or 2nd kernel. - * If we are in 2nd kernel, get kaslr_offset/phys_base from vmcoreinfo. - * - * 1. Get command line and try to retrieve "elfcorehdr=" boot parameter - * 2. If "elfcorehdr=" is not found in command line, we are in 1st kernel. - * There is nothing to do. - * 3. If "elfcorehdr=" is found, we are in 2nd kernel. Find vmcoreinfo - * using "elfcorehdr=" and retrieve kaslr_offset/phys_base from vmcoreinfo. - */ -static int -get_kaslr_offset_from_vmcoreinfo(ulong orig_kaslr_offset, - ulong *kaslr_offset, ulong *phys_base) +ulong sadump_get_cr3() { - ulong elfcorehdr_addr = 0; - ulong vmcoreinfo_addr; - int vmcoreinfo_len; - char *buf, *pos; - int ret = FALSE; - - /* Find "elfcorehdr=" in the kernel boot parameter */ - elfcorehdr_addr = get_elfcorehdr(orig_kaslr_offset); - if (!elfcorehdr_addr) - return FALSE; - - /* Get vmcoreinfo from the address of "elfcorehdr=" */ - if (!get_vmcoreinfo(elfcorehdr_addr, &vmcoreinfo_addr, &vmcoreinfo_len)) - return FALSE; - - if (!vmcoreinfo_len) - return FALSE; + struct sadump_smram_cpu_state scs; - if (CRASHDEBUG(1)) - fprintf(fp, "Find vmcoreinfo in kdump memory\n"); - - buf = GETBUF(vmcoreinfo_len); - if (!readmem(vmcoreinfo_addr, PHYSADDR, buf, vmcoreinfo_len, - "vmcoreinfo", RETURN_ON_ERROR)) - goto quit; - - /* Get phys_base form vmcoreinfo */ - pos = strstr(buf, "NUMBER(phys_base)="); - if (!pos) - goto quit; - *phys_base = strtoull(pos + strlen("NUMBER(phys_base)="), NULL, 0); - - /* Get kaslr_offset form vmcoreinfo */ - pos = strstr(buf, "KERNELOFFSET="); - if (!pos) - goto quit; - *kaslr_offset = strtoull(pos + strlen("KERNELOFFSET="), NULL, 16); - - ret = TRUE; - -quit: - FREEBUF(buf); - return ret; + memset(&scs, 0, sizeof(scs)); + get_sadump_smram_cpu_state_any(&scs); + return scs.Cr3; } -/* - * Calculate kaslr_offset and phys_base - * - * kaslr_offset: - * The difference between original address in System.map or vmlinux and - * actual address placed randomly by kaslr feature. To be more accurate, - * kaslr_offset = actual address - original address - * - * phys_base: - * Physical address where the kerenel is placed. In other words, it's a - * physical address of __START_KERNEL_map. This is also decided randomly by - * kaslr. - * - * kaslr offset and phys_base are calculated as follows: - * - * kaslr_offset: - * 1) Get IDTR and CR3 value from the dump header. - * 2) Get a virtual address of IDT from IDTR value - * --- (A) - * 3) Translate (A) to physical address using CR3, the upper 52 bits - * of which points a top of page table. - * --- (B) - * 4) Get an address of vector0 (Devide Error) interrupt handler from - * IDT, which are pointed by (B). - * --- (C) - * 5) Get an address of symbol "divide_error" form vmlinux - * --- (D) - * - * Now we have two addresses: - * (C)-> Actual address of "divide_error" - * (D)-> Original address of "divide_error" in the vmlinux - * - * kaslr_offset can be calculated by the difference between these two - * value. - * - * phys_base; - * 1) Get IDT virtual address from vmlinux - * --- (E) - * - * So phys_base can be calculated using relationship of directly mapped - * address. - * - * phys_base = - * Physical address(B) - - * (Virtual address(E) + kaslr_offset - __START_KERNEL_map) - * - * Note that the address (A) cannot be used instead of (E) because (A) is - * not direct map address, it's a fixed map address. - * - * This solution works in most every case, but does not work in the - * following case. - * - * 1) If the dump is captured on early stage of kernel boot, IDTR points - * early IDT table(early_idts) instead of normal IDT(idt_table). - * 2) If the dump is captured whle kdump is working, IDTR points - * IDT table of 2nd kernel, not 1st kernel. - * - * Current implementation does not support the case 1), need - * enhancement in the future. For the case 2), get kaslr_offset and - * phys_base as follows. - * - * 1) Get kaslr_offset and phys_base using the above solution. - * 2) Get kernel boot parameter from "saved_command_line" - * 3) If "elfcorehdr=" is not included in boot parameter, we are in the - * first kernel, nothing to do any more. - * 4) If "elfcorehdr=" is included in boot parameter, we are in the 2nd - * kernel. Retrieve vmcoreinfo from address of "elfcorehdr=" and - * get kaslr_offset and phys_base from vmcoreinfo. - */ -#define PTI_USER_PGTABLE_BIT PAGE_SHIFT -#define PTI_USER_PGTABLE_MASK (1 << PTI_USER_PGTABLE_BIT) -#define CR3_PCID_MASK 0xFFFull -int -sadump_calc_kaslr_offset(ulong *kaslr_offset) +ulong sadump_get_idtr() { - ulong phys_base = 0; struct sadump_smram_cpu_state scs; - uint64_t idtr = 0, pgd = 0, idtr_paddr; - ulong divide_error_vmcore; - ulong kaslr_offset_kdump, phys_base_kdump; - int ret = FALSE; - int verbose = CRASHDEBUG(1)? 1: 0; - - if (!machine_type("X86_64")) - return FALSE; memset(&scs, 0, sizeof(scs)); get_sadump_smram_cpu_state_any(&scs); - if (st->pti_init_vmlinux || st->kaiser_init_vmlinux) - pgd = scs.Cr3 & ~(CR3_PCID_MASK|PTI_USER_PGTABLE_MASK); - else - pgd = scs.Cr3 & ~CR3_PCID_MASK; - idtr = ((uint64_t)scs.IdtUpper)<<32 | (uint64_t)scs.IdtLower; - - /* - * Set up for kvtop. - * - * calc_kaslr_offset() is called before machdep_init(PRE_GDB), so some - * variables are not initialized yet. Set up them here to call kvtop(). - * - * TODO: XEN and 5-level is not supported - */ - vt->kernel_pgd[0] = pgd; - machdep->last_pgd_read = vt->kernel_pgd[0]; - machdep->machspec->physical_mask_shift = __PHYSICAL_MASK_SHIFT_2_6; - machdep->machspec->pgdir_shift = PGDIR_SHIFT; - machdep->machspec->ptrs_per_pgd = PTRS_PER_PGD; - if (!readmem(pgd, PHYSADDR, machdep->pgd, PAGESIZE(), - "pgd", RETURN_ON_ERROR)) - goto quit; - - /* Convert virtual address of IDT table to physical address */ - if (!kvtop(NULL, idtr, &idtr_paddr, verbose)) - goto quit; - - /* Now we can calculate kaslr_offset and phys_base */ - divide_error_vmcore = get_vec0_addr(idtr_paddr); - *kaslr_offset = divide_error_vmcore - st->divide_error_vmlinux; - phys_base = idtr_paddr - - (st->idt_table_vmlinux + *kaslr_offset - __START_KERNEL_map); - - if (CRASHDEBUG(1)) { - fprintf(fp, "calc_kaslr_offset: idtr=%lx\n", idtr); - fprintf(fp, "calc_kaslr_offset: pgd=%lx\n", pgd); - fprintf(fp, "calc_kaslr_offset: idtr(phys)=%lx\n", idtr_paddr); - fprintf(fp, "calc_kaslr_offset: divide_error(vmlinux): %lx\n", - st->divide_error_vmlinux); - fprintf(fp, "calc_kaslr_offset: divide_error(vmcore): %lx\n", - divide_error_vmcore); - } - - /* - * Check if current kaslr_offset/phys_base is for 1st kernel or 2nd - * kernel. If we are in 2nd kernel, get kaslr_offset/phys_base - * from vmcoreinfo - */ - if (get_kaslr_offset_from_vmcoreinfo( - *kaslr_offset, &kaslr_offset_kdump, &phys_base_kdump)) { - *kaslr_offset = kaslr_offset_kdump; - phys_base = phys_base_kdump; - } else if (CRASHDEBUG(1)) { - fprintf(fp, "sadump: failed to determine which kernel was running at crash,\n"); - fprintf(fp, "sadump: asssuming the kdump 1st kernel.\n"); - } - - if (CRASHDEBUG(1)) { - fprintf(fp, "calc_kaslr_offset: kaslr_offset=%lx\n", - *kaslr_offset); - fprintf(fp, "calc_kaslr_offset: phys_base=%lx\n", phys_base); - } - - sd->phys_base = phys_base; - ret = TRUE; -quit: - vt->kernel_pgd[0] = 0; - machdep->last_pgd_read = 0; - return ret; -} -#else -int -sadump_calc_kaslr_offset(ulong *kaslr_offset) -{ - return FALSE; + return ((uint64_t)scs.IdtUpper)<<32 | (uint64_t)scs.IdtLower; } #endif /* X86_64 */ diff --git a/symbols.c b/symbols.c index 7910f53..54aa5b2 100644 --- a/symbols.c +++ b/symbols.c @@ -642,14 +642,18 @@ derive_kaslr_offset(bfd *abfd, int dynamic, bfd_byte *start, bfd_byte *end, if (SADUMP_DUMPFILE()) { ulong kaslr_offset = 0; + ulong phys_base = 0; - sadump_calc_kaslr_offset(&kaslr_offset); + calc_kaslr_offset(&kaslr_offset, &phys_base); if (kaslr_offset) { kt->relocate = kaslr_offset * -1; kt->flags |= RELOC_SET; } + if (phys_base) + sadump_set_phys_base(phys_base); + return; } -- 2.14.3 -- Crash-utility mailing list Crash-utility@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/crash-utility