Changeset View
Changeset View
Standalone View
Standalone View
head/lib/libkvm/kvm_amd64.c
Show All 40 Lines | |||||
#endif /* LIBC_SCCS and not lint */ | #endif /* LIBC_SCCS and not lint */ | ||||
/* | /* | ||||
* AMD64 machine dependent routines for kvm. Hopefully, the forthcoming | * AMD64 machine dependent routines for kvm. Hopefully, the forthcoming | ||||
* vm code will one day obsolete this module. | * vm code will one day obsolete this module. | ||||
*/ | */ | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/user.h> | #include <sys/endian.h> | ||||
#include <sys/proc.h> | #include <stdint.h> | ||||
#include <sys/stat.h> | |||||
#include <sys/mman.h> | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <nlist.h> | |||||
#include <kvm.h> | #include <kvm.h> | ||||
#include <vm/vm.h> | |||||
#include <vm/vm_param.h> | |||||
#include <machine/elf.h> | |||||
#include <limits.h> | #include <limits.h> | ||||
#include "kvm_private.h" | #include "kvm_private.h" | ||||
#include "kvm_amd64.h" | |||||
#ifndef btop | |||||
#define btop(x) (amd64_btop(x)) | |||||
#define ptob(x) (amd64_ptob(x)) | |||||
#endif | |||||
/* minidump must be the first item! */ | |||||
struct vmstate { | struct vmstate { | ||||
int minidump; /* 1 = minidump mode */ | size_t phnum; | ||||
void *mmapbase; | GElf_Phdr *phdr; | ||||
size_t mmapsize; | amd64_pml4e_t *PML4; | ||||
pml4_entry_t *PML4; | |||||
}; | }; | ||||
/* | /* | ||||
* Map the ELF headers into the process' address space. We do this in two | |||||
* steps: first the ELF header itself and using that information the whole | |||||
* set of headers. | |||||
*/ | |||||
static int | |||||
_kvm_maphdrs(kvm_t *kd, size_t sz) | |||||
{ | |||||
struct vmstate *vm = kd->vmst; | |||||
/* munmap() previous mmap(). */ | |||||
if (vm->mmapbase != NULL) { | |||||
munmap(vm->mmapbase, vm->mmapsize); | |||||
vm->mmapbase = NULL; | |||||
} | |||||
vm->mmapsize = sz; | |||||
vm->mmapbase = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, kd->pmfd, 0); | |||||
if (vm->mmapbase == MAP_FAILED) { | |||||
_kvm_err(kd, kd->program, "cannot mmap corefile"); | |||||
return (-1); | |||||
} | |||||
return (0); | |||||
} | |||||
/* | |||||
* Translate a physical memory address to a file-offset in the crash-dump. | * Translate a physical memory address to a file-offset in the crash-dump. | ||||
*/ | */ | ||||
static size_t | static size_t | ||||
_kvm_pa2off(kvm_t *kd, uint64_t pa, off_t *ofs) | _kvm_pa2off(kvm_t *kd, uint64_t pa, off_t *ofs) | ||||
{ | { | ||||
Elf_Ehdr *e = kd->vmst->mmapbase; | struct vmstate *vm = kd->vmst; | ||||
Elf_Phdr *p; | GElf_Phdr *p; | ||||
int n; | size_t n; | ||||
if (kd->rawdump) { | if (kd->rawdump) { | ||||
*ofs = pa; | *ofs = pa; | ||||
return (PAGE_SIZE - ((size_t)pa & PAGE_MASK)); | return (AMD64_PAGE_SIZE - (pa & AMD64_PAGE_MASK)); | ||||
} | } | ||||
p = (Elf_Phdr*)((char*)e + e->e_phoff); | p = vm->phdr; | ||||
n = e->e_phnum; | n = vm->phnum; | ||||
while (n && (pa < p->p_paddr || pa >= p->p_paddr + p->p_memsz)) | while (n && (pa < p->p_paddr || pa >= p->p_paddr + p->p_memsz)) | ||||
p++, n--; | p++, n--; | ||||
if (n == 0) | if (n == 0) | ||||
return (0); | return (0); | ||||
*ofs = (pa - p->p_paddr) + p->p_offset; | *ofs = (pa - p->p_paddr) + p->p_offset; | ||||
return (PAGE_SIZE - ((size_t)pa & PAGE_MASK)); | return (AMD64_PAGE_SIZE - (pa & AMD64_PAGE_MASK)); | ||||
} | } | ||||
void | static void | ||||
_kvm_freevtop(kvm_t *kd) | _amd64_freevtop(kvm_t *kd) | ||||
{ | { | ||||
struct vmstate *vm = kd->vmst; | struct vmstate *vm = kd->vmst; | ||||
if (kd->vmst->minidump) | |||||
return (_kvm_minidump_freevtop(kd)); | |||||
if (vm->mmapbase != NULL) | |||||
munmap(vm->mmapbase, vm->mmapsize); | |||||
if (vm->PML4) | if (vm->PML4) | ||||
free(vm->PML4); | free(vm->PML4); | ||||
free(vm->phdr); | |||||
free(vm); | free(vm); | ||||
kd->vmst = NULL; | kd->vmst = NULL; | ||||
} | } | ||||
int | static int | ||||
_kvm_initvtop(kvm_t *kd) | _amd64_probe(kvm_t *kd) | ||||
{ | { | ||||
struct nlist nl[2]; | |||||
u_long pa; | |||||
u_long kernbase; | |||||
pml4_entry_t *PML4; | |||||
Elf_Ehdr *ehdr; | |||||
size_t hdrsz; | |||||
char minihdr[8]; | |||||
if (!kd->rawdump && pread(kd->pmfd, &minihdr, 8, 0) == 8) | return (_kvm_probe_elf_kernel(kd, ELFCLASS64, EM_X86_64) && | ||||
if (memcmp(&minihdr, "minidump", 8) == 0) | !_kvm_is_minidump(kd)); | ||||
return (_kvm_minidump_initvtop(kd)); | } | ||||
static int | |||||
_amd64_initvtop(kvm_t *kd) | |||||
{ | |||||
struct kvm_nlist nl[2]; | |||||
amd64_physaddr_t pa; | |||||
kvaddr_t kernbase; | |||||
amd64_pml4e_t *PML4; | |||||
kd->vmst = (struct vmstate *)_kvm_malloc(kd, sizeof(*kd->vmst)); | kd->vmst = (struct vmstate *)_kvm_malloc(kd, sizeof(*kd->vmst)); | ||||
if (kd->vmst == 0) { | if (kd->vmst == 0) { | ||||
_kvm_err(kd, kd->program, "cannot allocate vm"); | _kvm_err(kd, kd->program, "cannot allocate vm"); | ||||
return (-1); | return (-1); | ||||
} | } | ||||
kd->vmst->PML4 = 0; | kd->vmst->PML4 = 0; | ||||
if (kd->rawdump == 0) { | if (kd->rawdump == 0) { | ||||
if (_kvm_maphdrs(kd, sizeof(Elf_Ehdr)) == -1) | if (_kvm_read_core_phdrs(kd, &kd->vmst->phnum, | ||||
&kd->vmst->phdr) == -1) | |||||
return (-1); | return (-1); | ||||
ehdr = kd->vmst->mmapbase; | |||||
hdrsz = ehdr->e_phoff + ehdr->e_phentsize * ehdr->e_phnum; | |||||
if (_kvm_maphdrs(kd, hdrsz) == -1) | |||||
return (-1); | |||||
} | } | ||||
nl[0].n_name = "kernbase"; | nl[0].n_name = "kernbase"; | ||||
nl[1].n_name = 0; | nl[1].n_name = 0; | ||||
if (kvm_nlist(kd, nl) != 0) { | if (kvm_nlist2(kd, nl) != 0) { | ||||
_kvm_err(kd, kd->program, "bad namelist - no kernbase"); | _kvm_err(kd, kd->program, "bad namelist - no kernbase"); | ||||
return (-1); | return (-1); | ||||
} | } | ||||
kernbase = nl[0].n_value; | kernbase = nl[0].n_value; | ||||
nl[0].n_name = "KPML4phys"; | nl[0].n_name = "KPML4phys"; | ||||
nl[1].n_name = 0; | nl[1].n_name = 0; | ||||
if (kvm_nlist(kd, nl) != 0) { | if (kvm_nlist2(kd, nl) != 0) { | ||||
_kvm_err(kd, kd->program, "bad namelist - no KPML4phys"); | _kvm_err(kd, kd->program, "bad namelist - no KPML4phys"); | ||||
return (-1); | return (-1); | ||||
} | } | ||||
if (kvm_read(kd, (nl[0].n_value - kernbase), &pa, sizeof(pa)) != | if (kvm_read2(kd, (nl[0].n_value - kernbase), &pa, sizeof(pa)) != | ||||
sizeof(pa)) { | sizeof(pa)) { | ||||
_kvm_err(kd, kd->program, "cannot read KPML4phys"); | _kvm_err(kd, kd->program, "cannot read KPML4phys"); | ||||
return (-1); | return (-1); | ||||
} | } | ||||
PML4 = _kvm_malloc(kd, PAGE_SIZE); | pa = le64toh(pa); | ||||
if (kvm_read(kd, pa, PML4, PAGE_SIZE) != PAGE_SIZE) { | PML4 = _kvm_malloc(kd, AMD64_PAGE_SIZE); | ||||
if (kvm_read2(kd, pa, PML4, AMD64_PAGE_SIZE) != AMD64_PAGE_SIZE) { | |||||
_kvm_err(kd, kd->program, "cannot read KPML4phys"); | _kvm_err(kd, kd->program, "cannot read KPML4phys"); | ||||
return (-1); | return (-1); | ||||
} | } | ||||
kd->vmst->PML4 = PML4; | kd->vmst->PML4 = PML4; | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
_kvm_vatop(kvm_t *kd, u_long va, off_t *pa) | _amd64_vatop(kvm_t *kd, kvaddr_t va, off_t *pa) | ||||
{ | { | ||||
struct vmstate *vm; | struct vmstate *vm; | ||||
u_long offset; | amd64_physaddr_t offset; | ||||
u_long pdpe_pa; | amd64_physaddr_t pdpe_pa; | ||||
u_long pde_pa; | amd64_physaddr_t pde_pa; | ||||
u_long pte_pa; | amd64_physaddr_t pte_pa; | ||||
pml4_entry_t pml4e; | amd64_pml4e_t pml4e; | ||||
pdp_entry_t pdpe; | amd64_pdpe_t pdpe; | ||||
pd_entry_t pde; | amd64_pde_t pde; | ||||
pt_entry_t pte; | amd64_pte_t pte; | ||||
u_long pml4eindex; | kvaddr_t pml4eindex; | ||||
u_long pdpeindex; | kvaddr_t pdpeindex; | ||||
u_long pdeindex; | kvaddr_t pdeindex; | ||||
u_long pteindex; | kvaddr_t pteindex; | ||||
u_long a; | amd64_physaddr_t a; | ||||
off_t ofs; | off_t ofs; | ||||
size_t s; | size_t s; | ||||
vm = kd->vmst; | vm = kd->vmst; | ||||
offset = va & (PAGE_SIZE - 1); | offset = va & AMD64_PAGE_MASK; | ||||
/* | /* | ||||
* If we are initializing (kernel page table descriptor pointer | * If we are initializing (kernel page table descriptor pointer | ||||
* not yet set) then return pa == va to avoid infinite recursion. | * not yet set) then return pa == va to avoid infinite recursion. | ||||
*/ | */ | ||||
if (vm->PML4 == 0) { | if (vm->PML4 == 0) { | ||||
s = _kvm_pa2off(kd, va, pa); | s = _kvm_pa2off(kd, va, pa); | ||||
if (s == 0) { | if (s == 0) { | ||||
_kvm_err(kd, kd->program, | _kvm_err(kd, kd->program, | ||||
"_kvm_vatop: bootstrap data not in dump"); | "_amd64_vatop: bootstrap data not in dump"); | ||||
goto invalid; | goto invalid; | ||||
} else | } else | ||||
return (PAGE_SIZE - offset); | return (AMD64_PAGE_SIZE - offset); | ||||
} | } | ||||
pml4eindex = (va >> PML4SHIFT) & (NPML4EPG - 1); | pml4eindex = (va >> AMD64_PML4SHIFT) & (AMD64_NPML4EPG - 1); | ||||
pml4e = vm->PML4[pml4eindex]; | pml4e = le64toh(vm->PML4[pml4eindex]); | ||||
if (((u_long)pml4e & PG_V) == 0) { | if ((pml4e & AMD64_PG_V) == 0) { | ||||
_kvm_err(kd, kd->program, "_kvm_vatop: pml4e not valid"); | _kvm_err(kd, kd->program, "_amd64_vatop: pml4e not valid"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
pdpeindex = (va >> PDPSHIFT) & (NPDPEPG-1); | pdpeindex = (va >> AMD64_PDPSHIFT) & (AMD64_NPDPEPG - 1); | ||||
pdpe_pa = ((u_long)pml4e & PG_FRAME) + | pdpe_pa = (pml4e & AMD64_PG_FRAME) + (pdpeindex * sizeof(amd64_pdpe_t)); | ||||
(pdpeindex * sizeof(pdp_entry_t)); | |||||
s = _kvm_pa2off(kd, pdpe_pa, &ofs); | s = _kvm_pa2off(kd, pdpe_pa, &ofs); | ||||
if (s < sizeof pdpe) { | if (s < sizeof(pdpe)) { | ||||
_kvm_err(kd, kd->program, "_kvm_vatop: pdpe_pa not found"); | _kvm_err(kd, kd->program, "_amd64_vatop: pdpe_pa not found"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (lseek(kd->pmfd, ofs, 0) == -1) { | if (pread(kd->pmfd, &pdpe, sizeof(pdpe), ofs) != sizeof(pdpe)) { | ||||
_kvm_syserr(kd, kd->program, "_kvm_vatop: lseek pdpe_pa"); | _kvm_syserr(kd, kd->program, "_amd64_vatop: read pdpe"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (read(kd->pmfd, &pdpe, sizeof pdpe) != sizeof pdpe) { | pdpe = le64toh(pdpe); | ||||
_kvm_syserr(kd, kd->program, "_kvm_vatop: read pdpe"); | if ((pdpe & AMD64_PG_V) == 0) { | ||||
_kvm_err(kd, kd->program, "_amd64_vatop: pdpe not valid"); | |||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (((u_long)pdpe & PG_V) == 0) { | |||||
_kvm_err(kd, kd->program, "_kvm_vatop: pdpe not valid"); | if (pdpe & AMD64_PG_PS) { | ||||
/* | |||||
* No next-level page table; pdpe describes one 1GB page. | |||||
*/ | |||||
a = (pde & AMD64_PG_1GB_FRAME) + (va & AMD64_PDPMASK); | |||||
s = _kvm_pa2off(kd, a, pa); | |||||
if (s == 0) { | |||||
_kvm_err(kd, kd->program, | |||||
"_amd64_vatop: 1GB page address not in dump"); | |||||
goto invalid; | goto invalid; | ||||
} else | |||||
return (AMD64_NBPDP - (va & AMD64_PDPMASK)); | |||||
} | } | ||||
pdeindex = (va >> PDRSHIFT) & (NPDEPG-1); | pdeindex = (va >> AMD64_PDRSHIFT) & (AMD64_NPDEPG - 1); | ||||
pde_pa = ((u_long)pdpe & PG_FRAME) + (pdeindex * sizeof(pd_entry_t)); | pde_pa = (pdpe & AMD64_PG_FRAME) + (pdeindex * sizeof(amd64_pde_t)); | ||||
s = _kvm_pa2off(kd, pde_pa, &ofs); | s = _kvm_pa2off(kd, pde_pa, &ofs); | ||||
if (s < sizeof pde) { | if (s < sizeof(pde)) { | ||||
_kvm_syserr(kd, kd->program, "_kvm_vatop: pde_pa not found"); | _kvm_syserr(kd, kd->program, "_amd64_vatop: pde_pa not found"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (lseek(kd->pmfd, ofs, 0) == -1) { | if (pread(kd->pmfd, &pde, sizeof(pde), ofs) != sizeof(pde)) { | ||||
_kvm_err(kd, kd->program, "_kvm_vatop: lseek pde_pa"); | _kvm_syserr(kd, kd->program, "_amd64_vatop: read pde"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (read(kd->pmfd, &pde, sizeof pde) != sizeof pde) { | pde = le64toh(pde); | ||||
_kvm_syserr(kd, kd->program, "_kvm_vatop: read pde"); | if ((pde & AMD64_PG_V) == 0) { | ||||
_kvm_err(kd, kd->program, "_amd64_vatop: pde not valid"); | |||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (((u_long)pde & PG_V) == 0) { | |||||
_kvm_err(kd, kd->program, "_kvm_vatop: pde not valid"); | |||||
goto invalid; | |||||
} | |||||
if ((u_long)pde & PG_PS) { | if (pde & AMD64_PG_PS) { | ||||
/* | /* | ||||
* No final-level page table; ptd describes one 2MB page. | * No final-level page table; pde describes one 2MB page. | ||||
*/ | */ | ||||
#define PAGE2M_MASK (NBPDR - 1) | a = (pde & AMD64_PG_PS_FRAME) + (va & AMD64_PDRMASK); | ||||
#define PG_FRAME2M (~PAGE2M_MASK) | |||||
a = ((u_long)pde & PG_FRAME2M) + (va & PAGE2M_MASK); | |||||
s = _kvm_pa2off(kd, a, pa); | s = _kvm_pa2off(kd, a, pa); | ||||
if (s == 0) { | if (s == 0) { | ||||
_kvm_err(kd, kd->program, | _kvm_err(kd, kd->program, | ||||
"_kvm_vatop: 2MB page address not in dump"); | "_amd64_vatop: 2MB page address not in dump"); | ||||
goto invalid; | goto invalid; | ||||
} else | } else | ||||
return (NBPDR - (va & PAGE2M_MASK)); | return (AMD64_NBPDR - (va & AMD64_PDRMASK)); | ||||
} | } | ||||
pteindex = (va >> PAGE_SHIFT) & (NPTEPG-1); | pteindex = (va >> AMD64_PAGE_SHIFT) & (AMD64_NPTEPG - 1); | ||||
pte_pa = ((u_long)pde & PG_FRAME) + (pteindex * sizeof(pt_entry_t)); | pte_pa = (pde & AMD64_PG_FRAME) + (pteindex * sizeof(amd64_pte_t)); | ||||
s = _kvm_pa2off(kd, pte_pa, &ofs); | s = _kvm_pa2off(kd, pte_pa, &ofs); | ||||
if (s < sizeof pte) { | if (s < sizeof(pte)) { | ||||
_kvm_err(kd, kd->program, "_kvm_vatop: pte_pa not found"); | _kvm_err(kd, kd->program, "_amd64_vatop: pte_pa not found"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (lseek(kd->pmfd, ofs, 0) == -1) { | if (pread(kd->pmfd, &pte, sizeof(pte), ofs) != sizeof(pte)) { | ||||
_kvm_syserr(kd, kd->program, "_kvm_vatop: lseek"); | _kvm_syserr(kd, kd->program, "_amd64_vatop: read"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (read(kd->pmfd, &pte, sizeof pte) != sizeof pte) { | if ((pte & AMD64_PG_V) == 0) { | ||||
_kvm_syserr(kd, kd->program, "_kvm_vatop: read"); | _kvm_err(kd, kd->program, "_amd64_vatop: pte not valid"); | ||||
goto invalid; | goto invalid; | ||||
} | } | ||||
if (((u_long)pte & PG_V) == 0) { | |||||
_kvm_err(kd, kd->program, "_kvm_vatop: pte not valid"); | |||||
goto invalid; | |||||
} | |||||
a = ((u_long)pte & PG_FRAME) + offset; | a = (pte & AMD64_PG_FRAME) + offset; | ||||
s = _kvm_pa2off(kd, a, pa); | s = _kvm_pa2off(kd, a, pa); | ||||
if (s == 0) { | if (s == 0) { | ||||
_kvm_err(kd, kd->program, "_kvm_vatop: address not in dump"); | _kvm_err(kd, kd->program, "_amd64_vatop: address not in dump"); | ||||
goto invalid; | goto invalid; | ||||
} else | } else | ||||
return (PAGE_SIZE - offset); | return (AMD64_PAGE_SIZE - offset); | ||||
invalid: | invalid: | ||||
_kvm_err(kd, 0, "invalid address (0x%lx)", va); | _kvm_err(kd, 0, "invalid address (0x%jx)", (uintmax_t)va); | ||||
return (0); | return (0); | ||||
} | } | ||||
int | static int | ||||
_kvm_kvatop(kvm_t *kd, u_long va, off_t *pa) | _amd64_kvatop(kvm_t *kd, kvaddr_t va, off_t *pa) | ||||
{ | { | ||||
if (kd->vmst->minidump) | |||||
return (_kvm_minidump_kvatop(kd, va, pa)); | |||||
if (ISALIVE(kd)) { | if (ISALIVE(kd)) { | ||||
_kvm_err(kd, 0, "kvm_kvatop called in live kernel!"); | _kvm_err(kd, 0, "kvm_kvatop called in live kernel!"); | ||||
return (0); | return (0); | ||||
} | } | ||||
return (_kvm_vatop(kd, va, pa)); | return (_amd64_vatop(kd, va, pa)); | ||||
} | } | ||||
int | |||||
_amd64_native(kvm_t *kd) | |||||
{ | |||||
#ifdef __amd64__ | |||||
return (1); | |||||
#else | |||||
return (0); | |||||
#endif | |||||
} | |||||
struct kvm_arch kvm_amd64 = { | |||||
.ka_probe = _amd64_probe, | |||||
.ka_initvtop = _amd64_initvtop, | |||||
.ka_freevtop = _amd64_freevtop, | |||||
.ka_kvatop = _amd64_kvatop, | |||||
.ka_native = _amd64_native, | |||||
}; | |||||
KVM_ARCH(kvm_amd64); |