mirror of https://github.com/crash-utility/crash
335 lines
8.0 KiB
C
335 lines
8.0 KiB
C
/*
|
|
* Derive kernel base from a QEMU saved VM file
|
|
*
|
|
* Copyright (C) 2009, 2010 Red Hat, Inc.
|
|
* Written by Paolo Bonzini.
|
|
*
|
|
* Portions Copyright (C) 2009 David Anderson
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#include "qemu-load.h"
|
|
|
|
#include "kvmdump.h"
|
|
|
|
/*
|
|
* Some bits we need to access in the control registers and page tables.
|
|
*/
|
|
|
|
#define MSR_EFER_LMA (1 << 10)
|
|
#define PG_PRESENT_MASK (1 << 0)
|
|
#define PG_PSE_MASK (1 << 7)
|
|
#define CR0_PG_MASK (1 << 31)
|
|
#define CR4_PAE_MASK (1 << 31)
|
|
#define CR4_PSE_MASK (1 << 31)
|
|
|
|
static uint32_t
|
|
ldl (struct qemu_device_x86 *dx86, struct qemu_device_ram *dram, uint64_t addr)
|
|
{
|
|
char buf[4096];
|
|
if (dx86->a20_masked)
|
|
addr &= ~(1LL<<20);
|
|
if (!ram_read_phys_page (dram, buf, addr & ~0xfff))
|
|
return 0;
|
|
|
|
assert ((addr & 0xfff) <= 0xffc);
|
|
return *(uint32_t *)(buf + (addr & 0xfff));
|
|
}
|
|
|
|
static uint64_t
|
|
ldq (struct qemu_device_x86 *dx86, struct qemu_device_ram *dram, uint64_t addr)
|
|
{
|
|
char buf[4096];
|
|
if (dx86->a20_masked)
|
|
addr &= ~(1LL<<20);
|
|
if (!ram_read_phys_page (dram, buf, addr & ~0xfff))
|
|
return 0;
|
|
|
|
assert ((addr & 0xfff) <= 0xff8);
|
|
return *(uint64_t *)(buf + (addr & 0xfff));
|
|
}
|
|
|
|
/*
|
|
* Messy x86 TLB fault logic, walking the page tables to find the physical
|
|
* address corresponding to ADDR. Taken from QEMU.
|
|
*/
|
|
|
|
static uint64_t
|
|
get_phys_page(struct qemu_device_x86 *dx86, struct qemu_device_ram *dram,
|
|
uint64_t addr)
|
|
{
|
|
uint64_t pde_addr, pte_addr;
|
|
uint64_t pte, paddr;
|
|
uint32_t page_offset;
|
|
int page_size;
|
|
|
|
if ((dx86->cr4 & CR4_PAE_MASK) || (dx86->efer & MSR_EFER_LMA)) {
|
|
uint64_t pdpe_addr;
|
|
uint64_t pde, pdpe;
|
|
|
|
if (dx86->cr4 & CR4_PAE_MASK)
|
|
dprintf ("PAE active\n");
|
|
if (dx86->efer & MSR_EFER_LMA) {
|
|
uint64_t pml4e_addr, pml4e;
|
|
int32_t sext;
|
|
|
|
dprintf ("long mode active\n");
|
|
|
|
/* test virtual address sign extension */
|
|
sext = (int64_t) addr >> 47;
|
|
if (sext != 0 && sext != -1)
|
|
return -1;
|
|
|
|
pml4e_addr = ((dx86->cr3 & ~0xfff)
|
|
+ (((addr >> 39) & 0x1ff) << 3));
|
|
pml4e = ldq (dx86, dram, pml4e_addr);
|
|
if (!(pml4e & PG_PRESENT_MASK))
|
|
return -1;
|
|
dprintf ("PML4 page present\n");
|
|
|
|
pdpe_addr = ((pml4e & ~0xfff)
|
|
+ (((addr >> 30) & 0x1ff) << 3));
|
|
pdpe = ldq (dx86, dram, pdpe_addr);
|
|
if (!(pdpe & PG_PRESENT_MASK))
|
|
return -1;
|
|
dprintf ("PDPE page present\n");
|
|
} else {
|
|
dprintf ("long mode inactive\n");
|
|
|
|
pdpe_addr = ((dx86->cr3 & ~0x1f)
|
|
+ ((addr >> 27) & 0x18));
|
|
pdpe = ldq (dx86, dram, pdpe_addr);
|
|
if (!(pdpe & PG_PRESENT_MASK))
|
|
return -1;
|
|
dprintf ("PDPE page present\n");
|
|
}
|
|
|
|
pde_addr = (pdpe & ~0xfff) + (((addr >> 21) & 0x1ff) << 3);
|
|
pde = ldq (dx86, dram, pde_addr);
|
|
if (!(pde & PG_PRESENT_MASK))
|
|
return -1;
|
|
dprintf ("PDE page present\n");
|
|
|
|
if (pde & PG_PSE_MASK) {
|
|
/* 2 MB page */
|
|
dprintf ("2MB page\n");
|
|
|
|
page_size = 2048 * 1024;
|
|
pte = pde & ~((page_size - 1) & ~0xfff);
|
|
} else {
|
|
/* 4 KB page */
|
|
dprintf ("4 KB PAE page\n");
|
|
|
|
pte_addr = ((pde & ~0xfff)
|
|
+ (((addr >> 12) & 0x1ff) << 3));
|
|
page_size = 4096;
|
|
pte = ldq (dx86, dram, pte_addr);
|
|
if (!(pte & PG_PRESENT_MASK))
|
|
return -1;
|
|
dprintf ("PTE page present\n");
|
|
}
|
|
|
|
} else {
|
|
/* Not PAE. */
|
|
|
|
uint32_t pde;
|
|
if (!(dx86->cr0 & CR0_PG_MASK)) {
|
|
dprintf ("Paging inactive\n");
|
|
|
|
pte = addr;
|
|
page_size = 4096;
|
|
} else {
|
|
/* page directory entry */
|
|
pde_addr = ((dx86->cr3 & ~0xfff)
|
|
+ ((addr >> 20) & 0xffc));
|
|
pde = ldl (dx86, dram, pde_addr);
|
|
if (!(pde & PG_PRESENT_MASK))
|
|
return -1;
|
|
dprintf ("PDE page present\n");
|
|
if ((pde & PG_PSE_MASK) && (dx86->cr4 & CR4_PSE_MASK)) {
|
|
page_size = 4096 * 1024;
|
|
pte = pde & ~((page_size - 1) & ~0xfff);
|
|
} else {
|
|
page_size = 4096;
|
|
pte_addr = ((pde & ~0xfff)
|
|
+ ((addr >> 10) & 0xffc));
|
|
pte = ldl (dx86, dram, pte_addr);
|
|
if (!(pte & PG_PRESENT_MASK))
|
|
return -1;
|
|
dprintf ("PTE page present\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
page_offset = (addr & 0xfff) & (page_size - 1);
|
|
paddr = (pte & ~0xfff) + page_offset;
|
|
return paddr;
|
|
}
|
|
|
|
/*
|
|
* I'm using the IDT base as a quick way to find the bottom of the
|
|
* kernel memory.
|
|
*/
|
|
static uint64_t
|
|
get_idt_base(struct qemu_device_list *dl)
|
|
{
|
|
struct qemu_device_x86 *dx86 = (struct qemu_device_x86 *)
|
|
device_find_instance (dl, "cpu", 0);
|
|
|
|
return dx86->idt.base;
|
|
}
|
|
|
|
static uint64_t
|
|
get_kernel_base(struct qemu_device_list *dl)
|
|
{
|
|
int i;
|
|
uint64_t kernel_base = -1;
|
|
uint64_t base_vaddr, last, mask;
|
|
struct qemu_device_x86 *dx86 = (struct qemu_device_x86 *)
|
|
device_find_instance (dl, "cpu", 0);
|
|
struct qemu_device_ram *dram = (struct qemu_device_ram *)
|
|
device_find_instance (dl, "ram", 0);
|
|
|
|
for (i = 30, last = -1; (kernel_base == -1) && (i >= 20); i--)
|
|
{
|
|
mask = ~((1LL << i) - 1);
|
|
base_vaddr = dx86->idt.base & mask;
|
|
if (base_vaddr == last)
|
|
continue;
|
|
if (base_vaddr < kvm->kvbase) {
|
|
fprintf(stderr,
|
|
"WARNING: IDT base contains: %llx\n "
|
|
"cannot determine physical base address: defaulting to 0\n\n",
|
|
(unsigned long long)base_vaddr);
|
|
return 0;
|
|
}
|
|
dprintf("get_kernel_base: %llx\n", (unsigned long long)base_vaddr);
|
|
kernel_base = get_phys_page(dx86, dram, base_vaddr);
|
|
last = base_vaddr;
|
|
}
|
|
|
|
if (kernel_base != -1) {
|
|
dprintf("kvbase: %llx vaddr used: %llx physical: %llx\n",
|
|
(unsigned long long)kvm->kvbase,
|
|
(unsigned long long)base_vaddr,
|
|
(unsigned long long)kernel_base);
|
|
/*
|
|
* Subtract the offset between the virtual address used
|
|
* and the kernel's base virtual address.
|
|
*/
|
|
kernel_base -= (base_vaddr - kvm->kvbase);
|
|
} else {
|
|
dprintf("WARNING: cannot determine physical base address:"
|
|
" defaulting to 0\n\n");
|
|
kernel_base = 0;
|
|
kvm->flags |= NO_PHYS_BASE;
|
|
}
|
|
|
|
return kernel_base;
|
|
}
|
|
|
|
|
|
#ifdef MAIN_FROM_TEST_C
|
|
int main (int argc, char **argv)
|
|
{
|
|
struct qemu_device_list *dl;
|
|
FILE *fp;
|
|
|
|
if (argc != 2) {
|
|
fprintf (stderr, "Usage: test SAVE-FILE\n");
|
|
exit (1);
|
|
}
|
|
|
|
fp = fopen(argv[1], "r");
|
|
if (!fp) {
|
|
fprintf (stderr, "Error: %s\n", strerror (errno));
|
|
exit (1);
|
|
}
|
|
|
|
#ifdef HOST_32BIT
|
|
dl = qemu_load (devices_x86_32, QEMU_FEATURE_CPU|QEMU_FEATURE_RAM, fp);
|
|
#else
|
|
dl = qemu_load (devices_x86_64, QEMU_FEATURE_CPU|QEMU_FEATURE_RAM, fp);
|
|
#endif
|
|
printf ("IDT at %llx\n", get_idt_base (dl));
|
|
printf ("Physical kernel base at %llx\n", get_kernel_base (dl));
|
|
device_list_free (dl);
|
|
fclose (fp);
|
|
exit (0);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* crash utility adaptation
|
|
*/
|
|
|
|
#include "defs.h"
|
|
|
|
int
|
|
qemu_init(char *filename)
|
|
{
|
|
struct qemu_device_list *dl;
|
|
struct qemu_device_ram *dram;
|
|
uint64_t idt = 0;
|
|
|
|
if (CRASHDEBUG(1))
|
|
dump_qemu_header(kvm->ofp);
|
|
|
|
rewind(kvm->vmp);
|
|
|
|
if (kvm->flags & (MAPFILE|MAPFILE_APPENDED))
|
|
return TRUE;
|
|
|
|
please_wait("scanning KVM dumpfile");
|
|
|
|
if (kvm->flags & KVMHOST_32)
|
|
dl = qemu_load(devices_x86_32,
|
|
QEMU_FEATURE_CPU|QEMU_FEATURE_RAM, kvm->vmp);
|
|
else
|
|
dl = qemu_load(devices_x86_64,
|
|
QEMU_FEATURE_CPU|QEMU_FEATURE_RAM, kvm->vmp);
|
|
|
|
please_wait_done();
|
|
|
|
if (dl) {
|
|
if (machine_type("X86_64")) {
|
|
idt = get_idt_base(dl);
|
|
kvm->mapinfo.phys_base = get_kernel_base(dl);
|
|
}
|
|
|
|
dram = (struct qemu_device_ram *)
|
|
device_find_instance (dl, "ram", 0);
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
if (machine_type("X86_64")) {
|
|
fprintf(kvm->ofp, "IDT: %llx\n",
|
|
(ulonglong)idt);
|
|
fprintf(kvm->ofp, "physical kernel base: %llx\n",
|
|
(ulonglong)kvm->mapinfo.phys_base);
|
|
}
|
|
fprintf(kvm->ofp, "last RAM offset: %llx\n",
|
|
(ulonglong)dram->last_ram_offset);
|
|
}
|
|
|
|
device_list_free (dl);
|
|
} else
|
|
fclose(kvm->vmp);
|
|
|
|
return dl ? TRUE : FALSE;
|
|
}
|