diff options
Diffstat (limited to 'usr/src/lib/libkvm/common/kvm.c')
-rw-r--r-- | usr/src/lib/libkvm/common/kvm.c | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/usr/src/lib/libkvm/common/kvm.c b/usr/src/lib/libkvm/common/kvm.c new file mode 100644 index 0000000000..d3a5dbd81c --- /dev/null +++ b/usr/src/lib/libkvm/common/kvm.c @@ -0,0 +1,429 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <kvm.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <limits.h> +#include <fcntl.h> +#include <strings.h> +#include <sys/mem.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/dumphdr.h> +#include <sys/sysmacros.h> + +struct _kvmd { + struct dumphdr kvm_dump; + char *kvm_debug; + int kvm_openflag; + int kvm_corefd; + int kvm_kmemfd; + int kvm_memfd; + size_t kvm_coremapsize; + char *kvm_core; + dump_map_t *kvm_map; + pfn_t *kvm_pfn; + struct as *kvm_kas; + proc_t *kvm_practive; + pid_t kvm_pid; + char kvm_namelist[MAXNAMELEN + 1]; + proc_t kvm_proc; +}; + +#define PREAD (ssize_t (*)(int, void *, size_t, offset_t))pread64 +#define PWRITE (ssize_t (*)(int, void *, size_t, offset_t))pwrite64 + +static kvm_t * +fail(kvm_t *kd, const char *err, const char *message, ...) +{ + va_list args; + + va_start(args, message); + if (err || (kd && kd->kvm_debug)) { + (void) fprintf(stderr, "%s: ", err ? err : "KVM_DEBUG"); + (void) vfprintf(stderr, message, args); + (void) fprintf(stderr, "\n"); + } + va_end(args); + if (kd != NULL) + (void) kvm_close(kd); + return (NULL); +} + +/*ARGSUSED*/ +kvm_t * +kvm_open(const char *namelist, const char *corefile, const char *swapfile, + int flag, const char *err) +{ + kvm_t *kd; + struct stat64 memstat, kmemstat, allkmemstat, corestat; + struct nlist nl[3] = { { "kas" }, { "practive" }, { "" } }; + + if ((kd = calloc(1, sizeof (kvm_t))) == NULL) + return (fail(NULL, err, "cannot allocate space for kvm_t")); + + kd->kvm_corefd = kd->kvm_kmemfd = kd->kvm_memfd = -1; + kd->kvm_debug = getenv("KVM_DEBUG"); + + if ((kd->kvm_openflag = flag) != O_RDONLY && flag != O_RDWR) + return (fail(kd, err, "illegal flag 0x%x to kvm_open()", flag)); + + if (corefile == NULL) + corefile = "/dev/kmem"; + + if (stat64(corefile, &corestat) == -1) + return (fail(kd, err, "cannot stat %s", corefile)); + + if (S_ISCHR(corestat.st_mode)) { + if (stat64("/dev/mem", &memstat) == -1) + return (fail(kd, err, "cannot stat /dev/mem")); + + if (stat64("/dev/kmem", &kmemstat) == -1) + return (fail(kd, err, "cannot stat /dev/kmem")); + + if (stat64("/dev/allkmem", &allkmemstat) == -1) + return (fail(kd, err, "cannot stat /dev/allkmem")); + if (corestat.st_rdev == memstat.st_rdev || + corestat.st_rdev == kmemstat.st_rdev || + corestat.st_rdev == allkmemstat.st_rdev) { + char *kmem = (corestat.st_rdev == allkmemstat.st_rdev ? + "/dev/allkmem" : "/dev/kmem"); + + if ((kd->kvm_kmemfd = open64(kmem, flag)) == -1) + return (fail(kd, err, "cannot open %s", kmem)); + if ((kd->kvm_memfd = open64("/dev/mem", flag)) == -1) + return (fail(kd, err, "cannot open /dev/mem")); + } + } else { + if ((kd->kvm_corefd = open64(corefile, flag)) == -1) + return (fail(kd, err, "cannot open %s", corefile)); + if (pread64(kd->kvm_corefd, &kd->kvm_dump, + sizeof (kd->kvm_dump), 0) != sizeof (kd->kvm_dump)) + return (fail(kd, err, "cannot read dump header")); + if (kd->kvm_dump.dump_magic != DUMP_MAGIC) + return (fail(kd, err, "%s is not a kernel core file " + "(bad magic number %x)", corefile, + kd->kvm_dump.dump_magic)); + if (kd->kvm_dump.dump_version != DUMP_VERSION) + return (fail(kd, err, + "libkvm version (%u) != corefile version (%u)", + DUMP_VERSION, kd->kvm_dump.dump_version)); + if (kd->kvm_dump.dump_wordsize != DUMP_WORDSIZE) + return (fail(kd, err, "%s is a %d-bit core file - " + "cannot examine with %d-bit libkvm", corefile, + kd->kvm_dump.dump_wordsize, DUMP_WORDSIZE)); + /* + * We try to mmap(2) the entire corefile for performance + * (so we can use bcopy(3C) rather than pread(2)). Failing + * that, we insist on at least mmap(2)ing the dump map. + */ + kd->kvm_coremapsize = (size_t)corestat.st_size; + if (corestat.st_size > LONG_MAX || + (kd->kvm_core = mmap64(0, kd->kvm_coremapsize, + PROT_READ, MAP_SHARED, kd->kvm_corefd, 0)) == MAP_FAILED) { + kd->kvm_coremapsize = kd->kvm_dump.dump_data; + if ((kd->kvm_core = mmap64(0, kd->kvm_coremapsize, + PROT_READ, MAP_SHARED, kd->kvm_corefd, 0)) == + MAP_FAILED) + return (fail(kd, err, "cannot mmap corefile")); + } + kd->kvm_map = (void *)(kd->kvm_core + kd->kvm_dump.dump_map); + kd->kvm_pfn = (void *)(kd->kvm_core + kd->kvm_dump.dump_pfn); + } + + if (namelist == NULL) + namelist = "/dev/ksyms"; + + (void) strncpy(kd->kvm_namelist, namelist, MAXNAMELEN); + + if (kvm_nlist(kd, nl) == -1) + return (fail(kd, err, "%s is not a %d-bit kernel namelist", + namelist, DUMP_WORDSIZE)); + + kd->kvm_kas = (struct as *)nl[0].n_value; + kd->kvm_practive = (proc_t *)nl[1].n_value; + + (void) kvm_setproc(kd); + return (kd); +} + +int +kvm_close(kvm_t *kd) +{ + if (kd->kvm_core != NULL && kd->kvm_core != MAP_FAILED) + (void) munmap(kd->kvm_core, kd->kvm_coremapsize); + if (kd->kvm_corefd != -1) + (void) close(kd->kvm_corefd); + if (kd->kvm_kmemfd != -1) + (void) close(kd->kvm_kmemfd); + if (kd->kvm_memfd != -1) + (void) close(kd->kvm_memfd); + free(kd); + return (0); +} + +int +kvm_nlist(kvm_t *kd, struct nlist nl[]) +{ + return (nlist(kd->kvm_namelist, nl)); +} + +static offset_t +kvm_lookup(kvm_t *kd, struct as *as, uint64_t addr) +{ + uintptr_t pageoff = addr & (kd->kvm_dump.dump_pagesize - 1); + uint64_t page = addr - pageoff; + offset_t off = 0; + + if (kd->kvm_debug) + fprintf(stderr, "kvm_lookup(%p, %llx):", (void *)as, addr); + + if (as == NULL) { /* physical addressing mode */ + long first = 0; + long last = kd->kvm_dump.dump_npages - 1; + pfn_t target = (pfn_t)(page >> kd->kvm_dump.dump_pageshift); + while (last >= first) { + long middle = (first + last) / 2; + pfn_t pfn = kd->kvm_pfn[middle]; + if (kd->kvm_debug) + fprintf(stderr, " %ld ->", middle); + if (pfn == target) { + off = kd->kvm_dump.dump_data + pageoff + + ((uint64_t)middle << + kd->kvm_dump.dump_pageshift); + break; + } + if (pfn < target) + first = middle + 1; + else + last = middle - 1; + } + } else { + long hash = DUMP_HASH(&kd->kvm_dump, as, page); + off = kd->kvm_map[hash].dm_first; + while (off != 0) { + dump_map_t *dmp = (void *)(kd->kvm_core + off); + if (kd->kvm_debug) + fprintf(stderr, " %llx ->", off); + if (dmp < kd->kvm_map || + dmp > kd->kvm_map + kd->kvm_dump.dump_hashmask || + (off & (sizeof (offset_t) - 1)) != 0 || + DUMP_HASH(&kd->kvm_dump, dmp->dm_as, dmp->dm_va) != + hash) { + if (kd->kvm_debug) + fprintf(stderr, " dump map corrupt\n"); + return (0); + } + if (dmp->dm_va == page && dmp->dm_as == as) { + off = dmp->dm_data + pageoff; + break; + } + off = dmp->dm_next; + } + } + if (kd->kvm_debug) + fprintf(stderr, "%s found: %llx\n", off ? "" : " not", off); + return (off); +} + +static ssize_t +kvm_rw(kvm_t *kd, uint64_t addr, void *buf, size_t size, + struct as *as, ssize_t (*prw)(int, void *, size_t, offset_t)) +{ + offset_t off; + size_t resid = size; + + /* + * read/write of zero bytes always succeeds + */ + if (size == 0) + return (0); + + if (kd->kvm_core == NULL) { + char procbuf[100]; + int procfd; + ssize_t rval; + + if (as == kd->kvm_kas) + return (prw(kd->kvm_kmemfd, buf, size, addr)); + if (as == NULL) + return (prw(kd->kvm_memfd, buf, size, addr)); + + (void) sprintf(procbuf, "/proc/%ld/as", kd->kvm_pid); + if ((procfd = open64(procbuf, kd->kvm_openflag)) == -1) + return (-1); + rval = prw(procfd, buf, size, addr); + (void) close(procfd); + return (rval); + } + + while (resid != 0) { + uintptr_t pageoff = addr & (kd->kvm_dump.dump_pagesize - 1); + ssize_t len = MIN(resid, kd->kvm_dump.dump_pagesize - pageoff); + + if ((off = kvm_lookup(kd, as, addr)) == 0) + break; + + if (prw == PREAD && off < kd->kvm_coremapsize) + bcopy(kd->kvm_core + off, buf, len); + else if ((len = prw(kd->kvm_corefd, buf, len, off)) <= 0) + break; + resid -= len; + addr += len; + buf = (char *)buf + len; + } + return (resid < size ? size - resid : -1); +} + +ssize_t +kvm_read(kvm_t *kd, uintptr_t addr, void *buf, size_t size) +{ + return (kvm_rw(kd, addr, buf, size, kd->kvm_kas, PREAD)); +} + +ssize_t +kvm_kread(kvm_t *kd, uintptr_t addr, void *buf, size_t size) +{ + return (kvm_rw(kd, addr, buf, size, kd->kvm_kas, PREAD)); +} + +ssize_t +kvm_uread(kvm_t *kd, uintptr_t addr, void *buf, size_t size) +{ + return (kvm_rw(kd, addr, buf, size, kd->kvm_proc.p_as, PREAD)); +} + +ssize_t +kvm_aread(kvm_t *kd, uintptr_t addr, void *buf, size_t size, struct as *as) +{ + return (kvm_rw(kd, addr, buf, size, as, PREAD)); +} + +ssize_t +kvm_pread(kvm_t *kd, uint64_t addr, void *buf, size_t size) +{ + return (kvm_rw(kd, addr, buf, size, NULL, PREAD)); +} + +ssize_t +kvm_write(kvm_t *kd, uintptr_t addr, const void *buf, size_t size) +{ + return (kvm_rw(kd, addr, (void *)buf, size, kd->kvm_kas, PWRITE)); +} + +ssize_t +kvm_kwrite(kvm_t *kd, uintptr_t addr, const void *buf, size_t size) +{ + return (kvm_rw(kd, addr, (void *)buf, size, kd->kvm_kas, PWRITE)); +} + +ssize_t +kvm_uwrite(kvm_t *kd, uintptr_t addr, const void *buf, size_t size) +{ + return (kvm_rw(kd, addr, (void *)buf, size, kd->kvm_proc.p_as, PWRITE)); +} + +ssize_t +kvm_awrite(kvm_t *kd, uintptr_t addr, const void *buf, size_t size, + struct as *as) +{ + return (kvm_rw(kd, addr, (void *)buf, size, as, PWRITE)); +} + +ssize_t +kvm_pwrite(kvm_t *kd, uint64_t addr, const void *buf, size_t size) +{ + return (kvm_rw(kd, addr, (void *)buf, size, NULL, PWRITE)); +} + +uint64_t +kvm_physaddr(kvm_t *kd, struct as *as, uintptr_t addr) +{ + mem_vtop_t mem_vtop; + offset_t off; + + if (kd->kvm_core == NULL) { + mem_vtop.m_as = as; + mem_vtop.m_va = (void *)addr; + if (ioctl(kd->kvm_kmemfd, MEM_VTOP, &mem_vtop) == 0) + return ((uint64_t)mem_vtop.m_pfn * getpagesize() + + (addr & (getpagesize() - 1))); + } else { + if ((off = kvm_lookup(kd, as, addr)) != 0) { + long pfn_index = + (u_offset_t)(off - kd->kvm_dump.dump_data) >> + kd->kvm_dump.dump_pageshift; + return (((uint64_t)kd->kvm_pfn[pfn_index] << + kd->kvm_dump.dump_pageshift) + + (addr & (kd->kvm_dump.dump_pagesize - 1))); + } + } + return (-1ULL); +} + +struct proc * +kvm_getproc(kvm_t *kd, pid_t pid) +{ + (void) kvm_setproc(kd); + while (kvm_nextproc(kd) != NULL) + if (kd->kvm_pid == pid) + return (&kd->kvm_proc); + return (NULL); +} + +struct proc * +kvm_nextproc(kvm_t *kd) +{ + if (kd->kvm_proc.p_next == NULL || + kvm_kread(kd, (uintptr_t)kd->kvm_proc.p_next, + &kd->kvm_proc, sizeof (proc_t)) != sizeof (proc_t) || + kvm_kread(kd, (uintptr_t)&kd->kvm_proc.p_pidp->pid_id, + &kd->kvm_pid, sizeof (pid_t)) != sizeof (pid_t)) + return (NULL); + + return (&kd->kvm_proc); +} + +int +kvm_setproc(kvm_t *kd) +{ + (void) kvm_kread(kd, (uintptr_t)kd->kvm_practive, + &kd->kvm_proc.p_next, sizeof (proc_t *)); + kd->kvm_pid = -1; + return (0); +} + +/*ARGSUSED*/ +struct user * +kvm_getu(kvm_t *kd, struct proc *p) +{ + return (&p->p_user); +} |