summaryrefslogtreecommitdiff
path: root/usr/src/lib/libkvm/common
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/libkvm/common
downloadillumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/libkvm/common')
-rw-r--r--usr/src/lib/libkvm/common/kvm.c429
-rw-r--r--usr/src/lib/libkvm/common/kvm_getcmd.c422
-rw-r--r--usr/src/lib/libkvm/common/llib-lkvm56
-rw-r--r--usr/src/lib/libkvm/common/test.c468
4 files changed, 1375 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);
+}
diff --git a/usr/src/lib/libkvm/common/kvm_getcmd.c b/usr/src/lib/libkvm/common/kvm_getcmd.c
new file mode 100644
index 0000000000..66b3d5c70d
--- /dev/null
+++ b/usr/src/lib/libkvm/common/kvm_getcmd.c
@@ -0,0 +1,422 @@
+/*
+ * 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 <unistd.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <strings.h>
+#include <sys/types32.h>
+
+#define _SYSCALL32
+
+/*
+ * VERSION FOR MACHINES WITH STACKS GROWING DOWNWARD IN MEMORY
+ *
+ * On program entry, the top of the stack frame looks like this:
+ *
+ * hi: |-----------------------|
+ * | unspecified |
+ * |-----------------------|+
+ * | : | \
+ * | arg and env strings | > no more than NCARGS bytes
+ * | : | /
+ * |-----------------------|+
+ * | unspecified |
+ * |-----------------------|
+ * | null auxiliary vector |
+ * |-----------------------|
+ * | auxiliary vector |
+ * | (2-word entries) |
+ * | : |
+ * |-----------------------|
+ * | (char *)0 |
+ * |-----------------------|
+ * | ptrs to env strings |
+ * | : |
+ * |-----------------------|
+ * | (char *)0 |
+ * |-----------------------|
+ * | ptrs to arg strings |
+ * | (argc = # of ptrs) |
+ * | : |
+ * |-----------------------|
+ * | argc |
+ * low: |-----------------------|
+ */
+
+#define RoundUp(v, t) (((v) + sizeof (t) - 1) & ~(sizeof (t) - 1))
+
+static int
+kvm_getcmd32(kvm_t *kd,
+ struct proc *p, struct user *u, char ***arg, char ***env)
+{
+#if defined(_LP64) || defined(lint)
+ size_t size32;
+ void *stack32;
+ int i, argc, envc;
+ int auxc = 0;
+ size_t asize, esize;
+ char **argv = NULL;
+ char **envp = NULL;
+ size_t strpoolsz;
+ int aptrcount;
+ int eptrcount;
+ caddr_t stackp;
+ ptrdiff_t reloc;
+ char *str;
+
+ /*
+ * Bring the entire stack into memory first, size it
+ * as an LP64 user stack, then allocate and copy into
+ * the buffer(s) to be returned to the caller.
+ */
+ size32 = (size_t)p->p_usrstack - (size_t)u->u_argv;
+ if ((stack32 = malloc(size32)) == NULL)
+ return (-1);
+ if (kvm_uread(kd, (uintptr_t)u->u_argv, stack32, size32) != size32) {
+ free(stack32);
+ return (-1);
+ }
+
+ /*
+ * Find the interesting sizes of a 32-bit stack.
+ */
+ argc = u->u_argc;
+ stackp = (caddr_t)stack32 + ((1 + argc) * sizeof (caddr32_t));
+
+ for (envc = 0; *(caddr32_t *)stackp; envc++) {
+ stackp += sizeof (caddr32_t);
+ if ((stackp - (caddr_t)stack32) >= size32) {
+ free(stack32);
+ return (-1);
+ }
+ }
+
+ if (u->u_auxv[0].a_type != AT_NULL) {
+ stackp += sizeof (caddr32_t);
+ for (auxc = 0; *(int32_t *)stackp; auxc++) {
+ stackp += 2 * sizeof (caddr32_t);
+ if ((stackp - (caddr_t)stack32) >= size32) {
+ free(stack32);
+ return (-1);
+ }
+ }
+ auxc++; /* terminating AT_NULL record */
+ }
+
+ /*
+ * Compute the sizes of the stuff we're going to allocate or copy.
+ */
+ eptrcount = (envc + 1) + 2 * auxc;
+ aptrcount = (argc + 1) + eptrcount;
+ strpoolsz = size32 - aptrcount * sizeof (caddr32_t);
+
+ asize = aptrcount * sizeof (uintptr_t) + RoundUp(strpoolsz, uintptr_t);
+ if (arg && (argv = calloc(1, asize + sizeof (uintptr_t))) == NULL) {
+ free(stack32);
+ return (-1);
+ }
+
+ esize = eptrcount * sizeof (uintptr_t) + RoundUp(strpoolsz, uintptr_t);
+ if (env && (envp = calloc(1, esize + sizeof (uintptr_t))) == NULL) {
+ if (argv)
+ free(argv);
+ free(stack32);
+ return (-1);
+ }
+
+ /*
+ * Walk up the 32-bit stack, filling in the 64-bit argv and envp
+ * as we go.
+ */
+ stackp = (caddr_t)stack32;
+
+ /*
+ * argument vector
+ */
+ if (argv) {
+ for (i = 0; i < argc; i++) {
+ argv[i] = (char *)(uintptr_t)(*(caddr32_t *)stackp);
+ stackp += sizeof (caddr32_t);
+ }
+ argv[argc] = 0;
+ stackp += sizeof (caddr32_t);
+ } else
+ stackp += (1 + argc) * sizeof (caddr32_t);
+
+ /*
+ * environment
+ */
+ if (envp) {
+ for (i = 0; i < envc; i++) {
+ envp[i] = (char *)(uintptr_t)(*(caddr32_t *)stackp);
+ stackp += sizeof (caddr32_t);
+ }
+ envp[envc] = 0;
+ stackp += sizeof (caddr32_t);
+ } else
+ stackp += (1 + envc) * sizeof (caddr32_t);
+
+ /*
+ * auxiliary vector (skip it..)
+ */
+ stackp += auxc * (sizeof (int32_t) + sizeof (uint32_t));
+
+ /*
+ * Copy the string pool, untranslated
+ */
+ if (argv)
+ (void) memcpy(argv + aptrcount, (void *)stackp, strpoolsz);
+ if (envp)
+ (void) memcpy(envp + eptrcount, (void *)stackp, strpoolsz);
+
+ free(stack32);
+
+ /*
+ * Relocate the pointers to point at the newly allocated space.
+ * Use the same algorithms as kvm_getcmd to handle naughty
+ * changes to the argv and envp arrays.
+ */
+ if (argv) {
+ char *argv_null = (char *)argv + asize;
+
+ reloc = (char *)(argv + aptrcount) - (char *)
+ ((caddr_t)u->u_argv + aptrcount * sizeof (caddr32_t));
+
+ for (i = 0; i < argc; i++)
+ if (argv[i] != NULL) {
+ str = (argv[i] += reloc);
+ if (str < (char *)argv ||
+ str >= (char *)argv + asize)
+ argv[i] = argv_null;
+ }
+
+ *arg = argv;
+ }
+
+ if (envp) {
+ char *envp_null = (char *)envp + esize;
+ char *last_str;
+
+ reloc = (char *)(envp + eptrcount) - (char *)
+ ((caddr_t)u->u_envp + eptrcount * sizeof (caddr32_t));
+
+ last_str = (char *)((size_t)u->u_argv +
+ (1 + argc) * sizeof (caddr32_t) + reloc);
+ if (last_str < (char *)envp ||
+ last_str >= (char *)envp + esize)
+ last_str = envp_null;
+
+ for (i = 0; i < envc; i++) {
+ str = (envp[i] += reloc);
+ if (str < (char *)envp ||
+ str >= (char *)envp + esize) {
+ if (last_str != envp_null)
+ envp[i] = (char *)((size_t)last_str +
+ strlen(last_str) + 1);
+ else
+ envp[i] = envp_null;
+ }
+ last_str = envp[i];
+ }
+ *env = envp;
+ }
+#endif /* _LP64 || lint */
+ return (0);
+}
+
+/*
+ * reconstruct an argv-like argument list from the target process
+ */
+int
+kvm_getcmd(kvm_t *kd,
+ struct proc *proc, struct user *u, char ***arg, char ***env)
+{
+ size_t asize;
+ size_t esize;
+ size_t offset;
+ int i;
+ int argc;
+ char **argv = NULL;
+ char **envp = NULL;
+ char *str;
+ char *last_str;
+ char *argv_null; /* Known null in the returned argv */
+ char *envp_null; /* Known null in the returned envp */
+
+ if (proc->p_flag & SSYS) /* system process */
+ return (-1);
+
+ /*
+ * Protect against proc structs found by kvm_nextproc()
+ * while the kernel was doing a fork(). Such a proc struct
+ * may have p_usrstack set but a still zeroed uarea.
+ * We wouldn't want to unecessarily allocate 4GB memory ...
+ */
+ if (u->u_argv == NULL || u->u_envp == NULL)
+ return (-1);
+
+ /*
+ * If this is a 32-bit process running on a 64-bit system,
+ * then the stack is laid out using ILP32 pointers, not LP64.
+ * To minimize potential confusion, we blow it up to "LP64
+ * shaped" right here.
+ */
+ if (proc->p_model != DATAMODEL_NATIVE &&
+ proc->p_model == DATAMODEL_ILP32)
+ return (kvm_getcmd32(kd, proc, u, arg, env));
+
+ /*
+ * Space for the stack, from the argument vector. An additional
+ * word is added to guarantee a NULL word terminates the buffer.
+ */
+ if (arg) {
+ asize = (size_t)proc->p_usrstack - (size_t)u->u_argv;
+ if ((argv = malloc(asize + sizeof (uintptr_t))) == NULL)
+ return (-1);
+ argv_null = (char *)argv + asize;
+ *(uintptr_t *)argv_null = 0;
+ }
+
+ /*
+ * Space for the stack, from the environment vector. An additional
+ * word is added to guarantee a NULL word terminates the buffer.
+ */
+ if (env) {
+ esize = (size_t)proc->p_usrstack - (size_t)u->u_envp;
+ if ((envp = malloc(esize + sizeof (uintptr_t))) == NULL) {
+ if (argv)
+ free(argv);
+ return (-1);
+ }
+ envp_null = (char *)envp + esize;
+ *(uintptr_t *)envp_null = 0;
+ }
+
+ argc = u->u_argc;
+
+ if (argv) {
+ /* read the whole initial stack */
+ if (kvm_uread(kd,
+ (uintptr_t)u->u_argv, argv, asize) != asize) {
+ free(argv);
+ if (envp)
+ free(envp);
+ return (-1);
+ }
+ argv[argc] = 0;
+ if (envp) {
+ /*
+ * Copy it to the malloc()d space for the envp array
+ */
+ (void) memcpy(envp, &argv[argc + 1], esize);
+ }
+ } else if (envp) {
+ /* read most of the initial stack (excluding argv) */
+ if (kvm_uread(kd,
+ (uintptr_t)u->u_envp, envp, esize) != esize) {
+ free(envp);
+ return (-1);
+ }
+ }
+
+ /*
+ * Relocate and sanity check the argv array. Entries which have
+ * been explicity nulled are left that way. Entries which have
+ * been replaced are pointed to a null string. Well behaved apps
+ * don't do any of this.
+ */
+ if (argv) {
+ /* relocate the argv[] addresses */
+ offset = (char *)argv - (char *)u->u_argv;
+ for (i = 0; i < argc; i++) {
+ if (argv[i] != NULL) {
+ str = (argv[i] += offset);
+ if (str < (char *)argv ||
+ str >= (char *)argv + asize)
+ argv[i] = argv_null;
+ }
+ }
+ argv[i] = NULL;
+ *arg = argv;
+ }
+
+ /*
+ * Relocate and sanity check the envp array. A null entry indicates
+ * the end of the environment. Entries which point outside of the
+ * initial stack are replaced with what must have been the initial
+ * value based on the known ordering of the string table by the
+ * kernel. If stack corruption prevents the calculation of the
+ * location of an initial string value, a pointer to a null string
+ * is returned. To return a null pointer would prematurely terminate
+ * the list. Well behaved apps do set pointers outside of the
+ * initial stack via the putenv(3C) library routine.
+ */
+ if (envp) {
+
+ /*
+ * Determine the start of the environment strings as one
+ * past the last argument string.
+ */
+ offset = (char *)envp - (char *)u->u_envp;
+
+ if (kvm_uread(kd,
+ (uintptr_t)u->u_argv + (argc - 1) * sizeof (char **),
+ &last_str, sizeof (last_str)) != sizeof (last_str))
+ last_str = envp_null;
+ else {
+ last_str += offset;
+ if (last_str < (char *)envp ||
+ last_str >= (char *)envp + esize)
+ last_str = envp_null;
+ }
+
+ /*
+ * Relocate the envp[] addresses, while ensuring that we
+ * don't return bad addresses.
+ */
+ for (i = 0; envp[i] != NULL; i++) {
+ str = (envp[i] += offset);
+ if (str < (char *)envp || str >= (char *)envp + esize) {
+ if (last_str != envp_null)
+ envp[i] = last_str +
+ strlen(last_str) + 1;
+ else
+ envp[i] = envp_null;
+ }
+ last_str = envp[i];
+ }
+ envp[i] = NULL;
+ *env = envp;
+ }
+
+ return (0);
+}
diff --git a/usr/src/lib/libkvm/common/llib-lkvm b/usr/src/lib/libkvm/common/llib-lkvm
new file mode 100644
index 0000000000..9c04b1c6eb
--- /dev/null
+++ b/usr/src/lib/libkvm/common/llib-lkvm
@@ -0,0 +1,56 @@
+/*
+ * 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
+ */
+/* LINTLIBRARY */
+/* PROTOLIB1 */
+
+/*
+ * Copyright (c) 1997-1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/proc.h>
+#include <sys/user.h>
+#include <kvm.h>
+
+kvm_t *kvm_open(const char *, const char *, const char *, int, const char *);
+int kvm_close(kvm_t *);
+int kvm_nlist(kvm_t *, struct nlist []);
+ssize_t kvm_read(kvm_t *, uintptr_t, void *, size_t);
+ssize_t kvm_kread(kvm_t *, uintptr_t, void *, size_t);
+ssize_t kvm_uread(kvm_t *, uintptr_t, void *, size_t);
+ssize_t kvm_aread(kvm_t *, uintptr_t, void *, size_t, struct as *);
+ssize_t kvm_pread(kvm_t *, uint64_t, void *, size_t);
+ssize_t kvm_write(kvm_t *, uintptr_t, const void *, size_t);
+ssize_t kvm_kwrite(kvm_t *, uintptr_t, const void *, size_t);
+ssize_t kvm_uwrite(kvm_t *, uintptr_t, const void *, size_t);
+ssize_t kvm_awrite(kvm_t *, uintptr_t, const void *, size_t, struct as *);
+ssize_t kvm_pwrite(kvm_t *, uint64_t, const void *, size_t);
+uint64_t kvm_physaddr(kvm_t *, struct as *, uintptr_t);
+proc_t *kvm_getproc(kvm_t *, pid_t);
+proc_t *kvm_nextproc(kvm_t *);
+int kvm_setproc(kvm_t *);
+user_t *kvm_getu(kvm_t *, struct proc *);
+int kvm_getcmd(kvm_t *, proc_t *, user_t *, char ***, char ***);
diff --git a/usr/src/lib/libkvm/common/test.c b/usr/src/lib/libkvm/common/test.c
new file mode 100644
index 0000000000..973c647d5b
--- /dev/null
+++ b/usr/src/lib/libkvm/common/test.c
@@ -0,0 +1,468 @@
+/*
+ * 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 (c) 1996-1998 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "kvm.h"
+
+#include <nlist.h>
+#include <sys/thread.h>
+#include <sys/fcntl.h>
+#include <sys/param.h>
+#include <sys/user.h>
+#include <sys/proc.h>
+#include <sys/elf.h>
+
+#ifdef __sparc
+#include <sys/stack.h> /* for STACK_BIAS */
+#else
+#define STACK_BIAS 0
+#endif
+
+kvm_t *cookie;
+
+struct proc *tst_getproc(pid_t);
+struct proc *tst_nextproc(void);
+struct user *tst_getu(struct proc *);
+int tst_setproc(void);
+int tst_getcmd(struct proc *, struct user *);
+void tst_segkp(void);
+void tst_nlist(struct nlist nl[]);
+void tst_open(char *, char *, char *, int);
+void tst_close(void);
+ssize_t tst_read(uintptr_t, void *, size_t);
+ssize_t tst_write(uintptr_t, void *, size_t);
+int tst_getcmd(struct proc *, struct user *);
+void tst_segkvp(void);
+
+char *name;
+char *core;
+char *swap;
+int wflag;
+
+struct nlist nl[] = {
+ {"free"},
+ {"fragtbl"},
+ {"freemem"},
+ {"allthreads"},
+ {"nbuckets"},
+ {"cputype"},
+ {0}
+};
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ int c, errflg = 0;
+ long xx;
+ struct nlist *nlp;
+ struct proc *proc;
+ struct user *u;
+ int envc, ccnt;
+
+ for (envc = 0; *envp++ != NULL; envc++)
+ continue;
+ envp -= 2;
+ ccnt = (*envp - *argv) + strlen(*envp) + 1;
+ printf("pid %d:: %d args; %d envs; %d chars (%p - %p)\n",
+ getpid(), argc, envc, ccnt,
+ &argv[0], *envp + strlen(*envp));
+
+ while ((c = getopt(argc, argv, "w")) != EOF)
+ switch (c) {
+ case 'w':
+ wflag++;
+ break;
+ case '?':
+ errflg++;
+ }
+ if (errflg) {
+ fprintf(stderr, "usage: %s [-w] [name] [core] [swap]\n",
+ argv[0]);
+ return (2);
+ }
+ if (optind < argc) {
+ name = argv[optind++];
+ if (*name == '\0')
+ name = NULL;
+ } else
+ name = NULL;
+ if (optind < argc) {
+ core = argv[optind++];
+ if (*core == '\0')
+ core = NULL;
+ } else
+ core = NULL;
+ if (optind < argc) {
+ swap = argv[optind++];
+ if (*swap == '\0')
+ swap = NULL;
+ } else
+ swap = NULL;
+
+ tst_open(name, core, swap, (wflag ? O_RDWR : O_RDONLY));
+ if (cookie == NULL)
+ return (1);
+
+ tst_nlist(nl);
+
+ for (nlp = nl; nlp[0].n_type != 0; nlp++)
+ tst_read(nlp[0].n_value, &xx, sizeof (xx));
+
+ while ((proc = tst_nextproc()) != NULL) {
+ struct pid pid;
+ if (kvm_read(cookie, (uintptr_t)proc->p_pidp, &pid,
+ sizeof (pid)) != sizeof (pid)) {
+ printf("ERROR: couldn't get pid\n");
+ break;
+ }
+ tst_getproc(pid.pid_id);
+ }
+
+ tst_setproc();
+
+ while ((proc = tst_nextproc()) != NULL) {
+ if ((u = tst_getu(proc)) != NULL)
+ (void) tst_getcmd(proc, u);
+ }
+
+ tst_segkp();
+ tst_close();
+
+ return (0);
+}
+
+void
+tst_open(char *namelist, char *corefile, char *swapfile, int flag)
+{
+ printf("kvm_open(%s, %s, %s, %s)\n",
+ (namelist == NULL) ? "LIVE_KERNEL" : namelist,
+ (corefile == NULL) ? "LIVE_KERNEL" : corefile,
+ (swapfile == NULL) ?
+ ((corefile == NULL) ? "LIVE_KERNEL" : "(none)") : swapfile,
+ (flag == O_RDONLY) ? "O_RDONLY" : ((flag == O_RDWR) ?
+ "O_RDWR" : "???"));
+
+ if ((cookie = kvm_open(namelist, corefile,
+ swapfile, flag, "libkvm test")) == NULL)
+ printf("ERROR: kvm_open returned %p\n", cookie);
+}
+
+void
+tst_close(void)
+{
+ int i;
+
+ printf("kvm_close()\n");
+ if ((i = kvm_close(cookie)) != 0)
+ printf("ERROR: kvm_close returned %d\n", i);
+}
+
+void
+tst_nlist(struct nlist nl[])
+{
+ int i;
+ char *t, *s;
+
+ printf("kvm_nlist([nl])\n");
+ if ((i = kvm_nlist(cookie, nl)) != 0)
+ printf("ERROR: kvm_nlist returned %d\n", i);
+ for (i = 0; nl[i].n_name != 0 && nl[i].n_name[0] != '\0'; i++) {
+ /*
+ * Debug:
+ * n_value gets filled in with st_value,
+ * n_type gets filled in w/ELF32_ST_TYPE(sym->st_info)
+ * n_scnum gets filled in w/st_shndx
+ */
+ switch (nl[i].n_type) {
+ case STT_NOTYPE:
+ t = "NOTYPE";
+ break;
+ case STT_OBJECT:
+ t = "OBJECT";
+ break;
+ case STT_FUNC:
+ t = "FUNC";
+ break;
+ case STT_SECTION:
+ t = "SECTION";
+ break;
+ case STT_FILE:
+ t = "FILE";
+ break;
+ case STT_NUM:
+ t = "NUM";
+ break;
+ default:
+ t = "???";
+ }
+
+ switch ((unsigned)nl[i].n_scnum) {
+ static char strbuf[40];
+
+ case SHN_UNDEF:
+ s = "UNDEF";
+ break;
+ case SHN_LORESERVE:
+ s = "LORESERVE";
+ break;
+ case SHN_ABS:
+ s = "ABS";
+ break;
+ case SHN_COMMON:
+ s = "COMMON";
+ break;
+ case SHN_HIRESERVE:
+ s = "HIRESERVE";
+ break;
+ default:
+ (void) sprintf(strbuf, "unknown (%d)", nl[i].n_scnum);
+ s = strbuf;
+ break;
+ }
+
+ printf("%s: %lx (%s, %s)\n",
+ nl[i].n_name, nl[i].n_value, s, t);
+ }
+}
+
+ssize_t
+tst_read(uintptr_t addr, void *buf, size_t nbytes)
+{
+ ssize_t e;
+ int i;
+ char *b;
+
+ printf("kvm_read(%lx, [buf], %lu)\n", addr, nbytes);
+ if ((e = kvm_read(cookie, addr, buf, nbytes)) != nbytes)
+ printf("ERROR: kvm_read returned %ld instead of %lu\n",
+ e, nbytes);
+ for (b = buf, i = 0; i < nbytes; b++, i++)
+ printf("%lx: %02x (%04o)\n", addr + i,
+ *b & 0xff, *b & 0xff);
+
+ return (e);
+}
+
+ssize_t
+tst_write(uintptr_t addr, void *buf, size_t nbytes)
+{
+ ssize_t e;
+ ssize_t i;
+ void *b;
+
+ printf("kvm_write(%lx, [buf], %lu)\n", addr, nbytes);
+ if ((e = kvm_write(cookie, addr, buf, nbytes)) != nbytes)
+ printf("ERROR: kvm_write returned %ld instead of %lu\n",
+ e, nbytes);
+ if ((b = malloc(nbytes)) == 0)
+ printf("ERROR: malloc for readback failed\n");
+ else {
+ if ((i = kvm_read(cookie, addr, b, nbytes)) != nbytes)
+ printf("ERROR: readback returned %ld\n", i);
+ else if (memcmp(b, buf, nbytes))
+ printf("ERROR: write check failed!\n");
+ (void) free(b);
+ }
+ return (e);
+}
+
+struct proc *
+tst_getproc(pid_t pid)
+{
+ struct proc *proc;
+ struct pid pidbuf;
+
+ printf("kvm_getproc(%d)\n", pid);
+ if ((proc = kvm_getproc(cookie, pid)) == NULL) {
+ printf("ERROR: kvm_getproc returned NULL\n");
+ return (proc);
+ }
+
+ if (kvm_read(cookie, (uintptr_t)proc->p_pidp, &pidbuf,
+ sizeof (pidbuf)) != sizeof (pidbuf)) {
+ printf("ERROR: couldn't get pid\n");
+ return (proc);
+ }
+
+ printf("p_pid: %d\n", pidbuf.pid_id);
+ return (proc);
+}
+
+struct proc *
+tst_nextproc(void)
+{
+ struct proc *proc;
+ struct pid pidbuf;
+
+ printf("kvm_nextproc()\n");
+ if ((proc = kvm_nextproc(cookie)) == NULL) {
+ printf("kvm_nextproc returned NULL\n");
+ return (proc);
+ }
+
+ /*
+ * p_pid is now a macro which turns into a ptr dereference;
+ * must do a kvm_read to get contents.
+ */
+ if (kvm_read(cookie, (u_long)proc->p_pidp, (char *)&pidbuf,
+ sizeof (struct pid)) != sizeof (struct pid)) {
+ printf("ERROR: couldn't get pid\n");
+ }
+ printf("p_pid: %d\n", pidbuf.pid_id);
+
+ return (proc);
+}
+
+int
+tst_setproc(void)
+{
+ int i;
+
+ printf("kvm_setproc()\n");
+ if ((i = kvm_setproc(cookie)) != 0)
+ printf("ERROR: kvm_setproc returned %d\n", i);
+ return (i);
+}
+
+struct user *
+tst_getu(struct proc *proc)
+{
+ register int e;
+ struct proc tp;
+ struct user *u;
+ struct pid pidbuf;
+
+ if (kvm_read(cookie, (uintptr_t)proc->p_pidp, &pidbuf,
+ sizeof (pidbuf)) != sizeof (pidbuf))
+ printf("ERROR: couldn't get pid\n");
+
+ printf("kvm_getu(pid:%d)\n", pidbuf.pid_id);
+ if ((u = kvm_getu(cookie, proc)) == NULL)
+ printf("ERROR: kvm_getu returned NULL\n");
+ return (u);
+}
+
+static void
+safe_printf(const char *s)
+{
+ char buf[BUFSIZ], *p;
+
+ (void) strncpy(buf, s, BUFSIZ - 1);
+ buf[BUFSIZ - 1] = '\0';
+
+ for (p = buf; *p != '\0'; p++) {
+ if (!isprint(*p))
+ *p = ' ';
+ }
+
+ (void) printf("\"%s\"\n", buf);
+}
+
+int
+tst_getcmd(struct proc *proc, struct user *u)
+{
+ char **arg;
+ char **env;
+ int i;
+ char **p;
+ struct pid pidbuf;
+
+ if (kvm_kread(cookie, (uintptr_t)proc->p_pidp, &pidbuf,
+ sizeof (pidbuf)) != sizeof (pidbuf)) {
+ printf("ERROR: couldn't get pid\n");
+ return (-1);
+ }
+
+ printf("kvm_getcmd(pid:%d, [u], arg, env)\n", pidbuf.pid_id);
+ if ((i = kvm_getcmd(cookie, proc, u, &arg, &env)) != 0) {
+ printf("kvm_getcmd returned %d\n", i);
+ return (i);
+ }
+
+ printf("Args: ");
+ for (p = arg; *p != NULL; p++)
+ safe_printf(*p);
+ printf("Env: ");
+ for (p = env; *p != NULL; p++)
+ safe_printf(*p);
+
+ (void) free(arg);
+ (void) free(env);
+
+ return (0);
+}
+
+void
+tst_segkp(void)
+{
+ kthread_t t;
+ caddr_t tp, alltp;
+ uintptr_t stk[16];
+ int i;
+
+ if (kvm_read(cookie, nl[3].n_value, &alltp, sizeof (alltp))
+ != sizeof (alltp)) {
+ printf("ERROR: couldn't read allthread, addr 0x%lx\n",
+ nl[3].n_value);
+ return;
+ }
+ printf("allthreads 0x%lx\n", nl[3].n_value);
+ printf("next offset 0x%lx\n",
+ (uintptr_t)&(t.t_next) - (uintptr_t)&t);
+
+ for (tp = alltp; tp; tp = (caddr_t)(t.t_next)) {
+ if (kvm_read(cookie,
+ (uintptr_t)tp, &t, sizeof (t)) != sizeof (t)) {
+ printf("ERROR: couldn't read thread, addr 0x%p\n", tp);
+ return;
+ }
+
+ printf("thread 0x%p\n", tp);
+ printf("\tstk 0x%p sp 0x%lx tid %d next 0x%p prev 0x%p\n",
+ tp, t.t_stk, t.t_pcb.val[1], t.t_tid, t.t_next, t.t_prev);
+
+ if (kvm_read(cookie, t.t_pcb.val[1] + STACK_BIAS, stk,
+ sizeof (stk)) != sizeof (stk)) {
+ printf("ERROR: couldn't read stack, taddr 0x%p\n", tp);
+ continue;
+ }
+ for (i = 0; i < 16; i++) {
+ printf("%-16lx ", stk[i]);
+ if (((i + 1) % 4) == 0)
+ printf("\n");
+ }
+
+ if ((caddr_t)(t.t_next) == alltp)
+ break;
+ }
+}