diff options
author | Thadeu Lima de Souza Cascardo <cascardo@debian.org> | 2018-02-06 11:53:39 -0200 |
---|---|---|
committer | Thadeu Lima de Souza Cascardo <cascardo@debian.org> | 2018-02-06 11:53:39 -0200 |
commit | ac1ae6e8008c96a0cb6dd935a4ab3bd1901eb3de (patch) | |
tree | 7b3aee5a446a4e4187481762d887e6a7af50ec4a /sadump_info.c | |
parent | 94bcaa1d8936fc2d020b92812db0399d294ed161 (diff) |
New upstream version 1.6.3
Diffstat (limited to 'sadump_info.c')
-rw-r--r-- | sadump_info.c | 417 |
1 files changed, 414 insertions, 3 deletions
diff --git a/sadump_info.c b/sadump_info.c index 257f1be..148d4ba 100644 --- a/sadump_info.c +++ b/sadump_info.c @@ -953,7 +953,8 @@ cpu_online_mask_init(void) return FALSE; } - if (SIZE(cpumask) == NOT_FOUND_STRUCTURE) + if ((SIZE(cpumask) == NOT_FOUND_STRUCTURE) || + (SYMBOL(__cpu_online_mask) != NOT_FOUND_SYMBOL)) cpu_online_mask_addr = SYMBOL(cpu_online_mask); else { @@ -1034,6 +1035,409 @@ sadump_get_max_mapnr(void) #ifdef __x86_64__ +/* + * Get address of vector0 interrupt handler (Devide Error) form Interrupt + * Descriptor Table. + */ +static unsigned long +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(PADDR, idtr, &gate, sizeof(gate)); + + 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 cr3) +{ + char cmdline[BUFSIZE], *ptr; + ulong cmdline_vaddr; + ulong cmdline_paddr; + ulong buf_vaddr, buf_paddr; + char *end; + ulong elfcorehdr_addr = 0, elfcorehdr_size = 0; + + if (SYMBOL(saved_command_line) == NOT_FOUND_SYMBOL) { + ERRMSG("Can't get the symbol of saved_command_line.\n"); + return 0; + } + cmdline_vaddr = SYMBOL(saved_command_line); + if ((cmdline_paddr = vtop4_x86_64_pagetable(cmdline_vaddr, cr3)) == NOT_PADDR) + return 0; + + DEBUG_MSG("sadump: cmdline vaddr: %lx\n", cmdline_vaddr); + DEBUG_MSG("sadump: cmdline paddr: %lx\n", cmdline_paddr); + + if (!readmem(PADDR, cmdline_paddr, &buf_vaddr, sizeof(ulong))) + return 0; + + if ((buf_paddr = vtop4_x86_64_pagetable(buf_vaddr, cr3)) == NOT_PADDR) + return 0; + + DEBUG_MSG("sadump: cmdline buf vaddr: %lx\n", buf_vaddr); + DEBUG_MSG("sadump: cmdline buf paddr: %lx\n", buf_paddr); + + memset(cmdline, 0, BUFSIZE); + if (!readmem(PADDR, buf_paddr, cmdline, BUFSIZE)) + return 0; + + ptr = strstr(cmdline, "elfcorehdr="); + if (!ptr) + return 0; + + DEBUG_MSG("sadump: 2nd kernel detected.\n"); + + ptr += strlen("elfcorehdr="); + elfcorehdr_addr = memparse(ptr, &end); + if (*end == '@') { + elfcorehdr_size = elfcorehdr_addr; + elfcorehdr_addr = memparse(end + 1, &end); + } + + DEBUG_MSG("sadump: elfcorehdr_addr: %lx\n", elfcorehdr_addr); + DEBUG_MSG("sadump: 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_in_kdump_kernel(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(PADDR, elfcorehdr, e_ident, EI_NIDENT)) + return FALSE; + + if (e_ident[EI_CLASS] != ELFCLASS64) { + ERRMSG("Only ELFCLASS64 is supportd\n"); + return FALSE; + } + + if (!readmem(PADDR, elfcorehdr, &ehdr, sizeof(ehdr))) + 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) { + ERRMSG("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(PADDR, ptr, &phdr, sizeof(phdr))) + return FALSE; + + ptr += sizeof(phdr); + if (phdr.p_type != PT_NOTE) + continue; + + offset = phdr.p_offset; + if (!readmem(PADDR, offset, &nhdr, sizeof(nhdr))) + return FALSE; + + offset += divideup(sizeof(Elf64_Nhdr), sizeof(Elf64_Word))* + sizeof(Elf64_Word); + memset(name, 0, sizeof(name)); + if (!readmem(PADDR, offset, name, sizeof(name))) + return FALSE; + + if(!strcmp(name, "VMCOREINFO")) { + nhdr_offset = offset; + break; + } + } + + if (!nhdr_offset) + return FALSE; + + *addr = nhdr_offset + + divideup(nhdr.n_namesz, sizeof(Elf64_Word))* + sizeof(Elf64_Word); + *len = nhdr.n_descsz; + + DEBUG_MSG("sadump: vmcoreinfo addr: %lx\n", *addr); + DEBUG_MSG("sadump: 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. + */ +int +get_kaslr_offset_from_vmcoreinfo(ulong cr3, ulong *kaslr_offset, + ulong *phys_base) +{ + ulong elfcorehdr_addr = 0; + ulong vmcoreinfo_addr; + int vmcoreinfo_len; + char *buf, *pos; + int ret = FALSE; + + elfcorehdr_addr = get_elfcorehdr(cr3); + if (!elfcorehdr_addr) + return FALSE; + + if (!get_vmcoreinfo_in_kdump_kernel(elfcorehdr_addr, &vmcoreinfo_addr, + &vmcoreinfo_len)) + return FALSE; + + if (!vmcoreinfo_len) + return FALSE; + + DEBUG_MSG("sadump: Find vmcoreinfo in kdump memory\n"); + + if (!(buf = malloc(vmcoreinfo_len))) { + ERRMSG("Can't allocate vmcoreinfo buffer.\n"); + return FALSE; + } + + if (!readmem(PADDR, vmcoreinfo_addr, buf, vmcoreinfo_len)) + goto finish; + + pos = strstr(buf, STR_NUMBER("phys_base")); + if (!pos) + goto finish; + *phys_base = strtoull(pos + strlen(STR_NUMBER("phys_base")), NULL, 0); + + pos = strstr(buf, STR_KERNELOFFSET); + if (!pos) + goto finish; + *kaslr_offset = strtoull(pos + strlen(STR_KERNELOFFSET), NULL, 16); + ret = TRUE; + +finish: + free(buf); + return ret; +} + +/* + * Calculate kaslr_offset and phys_base + * + * kaslr_offset: + * The difference between original address in 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, 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. + */ +int +calc_kaslr_offset(void) +{ + struct sadump_header *sh = si->sh_memory; + uint64_t idtr = 0, cr3 = 0, idtr_paddr; + struct sadump_smram_cpu_state smram, zero; + int apicid; + unsigned long divide_error_vmcore, divide_error_vmlinux; + unsigned long kaslr_offset, phys_base; + unsigned long kaslr_offset_kdump, phys_base_kdump; + + memset(&zero, 0, sizeof(zero)); + for (apicid = 0; apicid < sh->nr_cpus; ++apicid) { + if (!get_smram_cpu_state(apicid, &smram)) { + ERRMSG("get_smram_cpu_state error\n"); + return FALSE; + } + + if (memcmp(&smram, &zero, sizeof(smram)) != 0) + break; + } + if (apicid >= sh->nr_cpus) { + ERRMSG("Can't get smram state\n"); + return FALSE; + } + + idtr = ((uint64_t)smram.IdtUpper)<<32 | (uint64_t)smram.IdtLower; + cr3 = smram.Cr3; + + /* Convert virtual address of IDT table to physical address */ + if ((idtr_paddr = vtop4_x86_64_pagetable(idtr, cr3)) == NOT_PADDR) + return FALSE; + + /* Now we can calculate kaslr_offset and phys_base */ + divide_error_vmlinux = SYMBOL(divide_error); + divide_error_vmcore = get_vec0_addr(idtr_paddr); + kaslr_offset = divide_error_vmcore - divide_error_vmlinux; + phys_base = idtr_paddr - + (SYMBOL(idt_table) + kaslr_offset - __START_KERNEL_map); + + info->kaslr_offset = kaslr_offset; + info->phys_base = phys_base; + + DEBUG_MSG("sadump: idtr=%" PRIx64 "\n", idtr); + DEBUG_MSG("sadump: cr3=%" PRIx64 "\n", cr3); + DEBUG_MSG("sadump: idtr(phys)=%" PRIx64 "\n", idtr_paddr); + DEBUG_MSG("sadump: devide_error(vmlinux)=%lx\n", + divide_error_vmlinux); + DEBUG_MSG("sadump: devide_error(vmcore)=%lx\n", + divide_error_vmcore); + + /* Reload symbol */ + if (!get_symbol_info()) + 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 + */ + if (get_kaslr_offset_from_vmcoreinfo(cr3, &kaslr_offset_kdump, + &phys_base_kdump)) { + info->kaslr_offset = kaslr_offset_kdump; + info->phys_base = phys_base_kdump; + + /* Reload symbol */ + if (!get_symbol_info()) + return FALSE; + } + + DEBUG_MSG("sadump: kaslr_offset=%lx\n", info->kaslr_offset); + DEBUG_MSG("sadump: phys_base=%lx\n", info->phys_base); + + return TRUE; +} + int sadump_virt_phys_base(void) { @@ -1064,6 +1468,9 @@ sadump_virt_phys_base(void) } failed: + if (calc_kaslr_offset()) + return TRUE; + info->phys_base = 0; DEBUG_MSG("sadump: failed to calculate phys_base; default to 0\n"); @@ -1517,10 +1924,14 @@ cpu_to_apicid(int cpu, int *apicid) if (!readmem(VADDR, SYMBOL(x86_bios_cpu_apicid_early_ptr), &early_ptr, sizeof(early_ptr))) return FALSE; - + /* + * Note: SYMBOL(name) value is adjusted by info->kaslr_offset, + * but per_cpu symbol does not need to be adjusted becasue it + * is not affected by kaslr. + */ apicid_addr = early_ptr ? SYMBOL(x86_bios_cpu_apicid_early_map)+cpu*sizeof(uint16_t) - : per_cpu_ptr(SYMBOL(x86_bios_cpu_apicid), cpu); + : per_cpu_ptr(SYMBOL(x86_bios_cpu_apicid) - info->kaslr_offset, cpu); if (!readmem(VADDR, apicid_addr, &apicid_u16, sizeof(uint16_t))) return FALSE; |