#include <sys/user.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/linker.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/swap_pager.h>
#include <machine/vmparam.h>
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <nlist.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include "kvm.h"
#include "kvm_private.h"
int __fdnlist (int, struct nlist *);
static int
kvm_notrans(kvm_t *kd)
{
return kvm_ishost(kd) || kvm_isvkernel(kd);
}
char *
kvm_geterr(kvm_t *kd)
{
return (kd->errbuf);
}
void
_kvm_err(kvm_t *kd, const char *program, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (program != NULL) {
(void)fprintf(stderr, "%s: ", program);
(void)vfprintf(stderr, fmt, ap);
(void)fputc('\n', stderr);
} else
(void)vsnprintf(kd->errbuf,
sizeof(kd->errbuf), fmt, ap);
va_end(ap);
}
void
_kvm_syserr(kvm_t *kd, const char *program, const char *fmt, ...)
{
va_list ap;
int n;
va_start(ap, fmt);
if (program != NULL) {
(void)fprintf(stderr, "%s: ", program);
(void)vfprintf(stderr, fmt, ap);
(void)fprintf(stderr, ": %s\n", strerror(errno));
} else {
char *cp = kd->errbuf;
(void)vsnprintf(cp, sizeof(kd->errbuf), fmt, ap);
n = strlen(cp);
(void)snprintf(&cp[n], sizeof(kd->errbuf) - n, ": %s",
strerror(errno));
}
va_end(ap);
}
void *
_kvm_malloc(kvm_t *kd, size_t n)
{
void *p;
if ((p = calloc(n, sizeof(char))) == NULL)
_kvm_err(kd, kd->program, "can't allocate %zd bytes: %s",
n, strerror(errno));
return (p);
}
static int
is_proc_mem(const char *p)
{
static char proc[] = "/proc/";
static char mem[] = "/mem";
if (strncmp(proc, p, sizeof(proc) - 1))
return 0;
p += sizeof(proc) - 1;
for (; *p != '\0'; ++p)
if (!isdigit(*p))
break;
if (!isdigit(*(p - 1)))
return 0;
return !strncmp(p, mem, sizeof(mem) - 1);
}
static kvm_t *
_kvm_open(kvm_t *kd, const char *uf, const char *mf, int flag, char *errout)
{
struct stat st;
kd->vmfd = -1;
kd->pmfd = -1;
kd->nlfd = -1;
kd->vmst = 0;
kd->procbase = NULL;
kd->procend = NULL;
kd->argspc = 0;
kd->argv = 0;
kd->flags = 0;
if (uf == NULL)
uf = getbootfile();
else if (strlen(uf) >= MAXPATHLEN) {
_kvm_err(kd, kd->program, "exec file name too long");
goto failed;
}
if (flag & ~O_RDWR) {
_kvm_err(kd, kd->program, "bad flags arg");
goto failed;
}
if (mf == NULL)
mf = _PATH_MEM;
if ((kd->pmfd = open(mf, flag, 0)) < 0) {
_kvm_syserr(kd, kd->program, "%s", mf);
goto failed;
}
if (fstat(kd->pmfd, &st) < 0) {
_kvm_syserr(kd, kd->program, "%s", mf);
goto failed;
}
if (fcntl(kd->pmfd, F_SETFD, FD_CLOEXEC) < 0) {
_kvm_syserr(kd, kd->program, "%s", mf);
goto failed;
}
if (S_ISCHR(st.st_mode)) {
if (strcmp(mf, _PATH_DEVNULL) == 0) {
kd->vmfd = open(_PATH_DEVNULL, O_RDONLY);
} else {
if ((kd->vmfd = open(_PATH_KMEM, flag)) < 0) {
_kvm_syserr(kd, kd->program, "%s", _PATH_KMEM);
goto failed;
}
if (fcntl(kd->vmfd, F_SETFD, FD_CLOEXEC) < 0) {
_kvm_syserr(kd, kd->program, "%s", _PATH_KMEM);
goto failed;
}
}
kd->flags |= KVMF_HOST;
} else {
if ((kd->nlfd = open(uf, O_RDONLY, 0)) < 0) {
_kvm_syserr(kd, kd->program, "%s", uf);
goto failed;
}
if (fcntl(kd->nlfd, F_SETFD, FD_CLOEXEC) < 0) {
_kvm_syserr(kd, kd->program, "%s", uf);
goto failed;
}
if(is_proc_mem(mf)) {
kd->vmfd = kd->pmfd;
kd->pmfd = -1;
kd->flags |= KVMF_VKERN;
} else {
if (st.st_size <= 0) {
errno = EINVAL;
_kvm_syserr(kd, kd->program, "empty file");
goto failed;
}
if (_kvm_initvtop(kd) < 0)
goto failed;
}
}
return (kd);
failed:
if (errout != NULL)
strlcpy(errout, kd->errbuf, _POSIX2_LINE_MAX);
(void)kvm_close(kd);
return (0);
}
kvm_t *
kvm_openfiles(const char *uf, const char *mf, const char *sf __unused, int flag,
char *errout)
{
kvm_t *kd;
if ((kd = malloc(sizeof(*kd))) == NULL) {
(void)strlcpy(errout, strerror(errno), _POSIX2_LINE_MAX);
return (0);
}
memset(kd, 0, sizeof(*kd));
kd->program = 0;
return (_kvm_open(kd, uf, mf, flag, errout));
}
kvm_t *
kvm_open(const char *uf, const char *mf, const char *sf __unused, int flag,
const char *errstr)
{
kvm_t *kd;
if ((kd = malloc(sizeof(*kd))) == NULL) {
if (errstr != NULL)
(void)fprintf(stderr, "%s: %s\n",
errstr, strerror(errno));
return (0);
}
memset(kd, 0, sizeof(*kd));
kd->program = errstr;
return (_kvm_open(kd, uf, mf, flag, NULL));
}
int
kvm_close(kvm_t *kd)
{
int error = 0;
if (kd->pmfd >= 0)
error |= close(kd->pmfd);
if (kd->vmfd >= 0)
error |= close(kd->vmfd);
if (kd->nlfd >= 0)
error |= close(kd->nlfd);
if (kd->vmst)
_kvm_freevtop(kd);
if (kd->procbase != NULL)
free(kd->procbase);
if (kd->argv != 0)
free((void *)kd->argv);
free((void *)kd);
return (error);
}
int
kvm_nlist(kvm_t *kd, struct nlist *nl)
{
struct nlist *p;
int nvalid;
struct kld_sym_lookup lookup;
int error;
if (!kvm_ishost(kd))
return (__fdnlist(kd->nlfd, nl));
nvalid = 0;
for (p = nl; p->n_name && p->n_name[0]; ++p) {
lookup.version = sizeof(lookup);
lookup.symname = p->n_name;
lookup.symvalue = 0;
lookup.symsize = 0;
if (lookup.symname[0] == '_')
lookup.symname++;
if (kldsym(0, KLDSYM_LOOKUP, &lookup) != -1) {
p->n_type = N_TEXT;
p->n_other = 0;
p->n_desc = 0;
p->n_value = lookup.symvalue;
++nvalid;
}
}
error = ((p - nl) - nvalid);
if (error)
_kvm_syserr(kd, kd->program, "kvm_nlist");
return (error);
}
ssize_t
kvm_read(kvm_t *kd, u_long kva, void *buf, size_t len)
{
int cc;
void *cp;
if (kvm_notrans(kd)) {
errno = 0;
if (lseek(kd->vmfd, (off_t)kva, 0) == -1 && errno != 0) {
_kvm_err(kd, 0, "invalid address (%lx)", kva);
return (-1);
}
bzero(buf, len);
cc = read(kd->vmfd, buf, len);
if (cc < 0) {
_kvm_syserr(kd, 0, "kvm_read");
return (-1);
} else if (cc < (ssize_t)len)
_kvm_err(kd, kd->program, "short read");
return (cc);
} else {
cp = buf;
while (len > 0) {
off_t pa;
cc = _kvm_kvatop(kd, kva, &pa);
if (cc == 0)
return (-1);
if (cc > (ssize_t)len)
cc = len;
errno = 0;
if (lseek(kd->pmfd, pa, 0) == -1 && errno != 0) {
_kvm_syserr(kd, 0, _PATH_MEM);
break;
}
bzero(cp, cc);
cc = read(kd->pmfd, cp, cc);
if (cc < 0) {
_kvm_syserr(kd, kd->program, "kvm_read");
break;
}
if (cc == 0)
break;
cp = (char *)cp + cc;
kva += cc;
len -= cc;
}
return ((char *)cp - (char *)buf);
}
}
char *
kvm_readstr(kvm_t *kd, u_long kva, char *buf, size_t *lenp)
{
size_t len, cc, pos;
char ch;
int asize = -1;
if (buf == NULL) {
asize = len = 16;
buf = malloc(len);
if (buf == NULL) {
_kvm_syserr(kd, kd->program, "kvm_readstr");
return NULL;
}
} else {
len = *lenp;
}
if (kvm_notrans(kd)) {
errno = 0;
if (lseek(kd->vmfd, (off_t)kva, 0) == -1 && errno != 0) {
_kvm_err(kd, 0, "invalid address (%lx)", kva);
return NULL;
}
for (pos = 0, ch = -1; ch != 0; pos++) {
cc = read(kd->vmfd, &ch, 1);
if ((ssize_t)cc < 0) {
_kvm_syserr(kd, 0, "kvm_readstr");
return NULL;
} else if (cc < 1)
_kvm_err(kd, kd->program, "short read");
if (asize > 0 && asize == (ssize_t)pos) {
buf = realloc(buf, asize *= 2);
if (buf == NULL) {
_kvm_syserr(kd, kd->program, "kvm_readstr");
return NULL;
}
len = asize;
}
if (pos < len)
buf[pos] = ch;
}
if (lenp != NULL)
*lenp = pos;
if (pos > len)
return NULL;
else
return buf;
} else {
size_t left = 0;
for (pos = 0, ch = -1; ch != 0; pos++, left--, kva++) {
if (left == 0) {
off_t pa;
left = _kvm_kvatop(kd, kva, &pa);
if (left == 0)
return NULL;
errno = 0;
if (lseek(kd->pmfd, (off_t)pa, 0) == -1 && errno != 0) {
_kvm_syserr(kd, 0, _PATH_MEM);
return NULL;
}
}
cc = read(kd->pmfd, &ch, 1);
if ((ssize_t)cc < 0) {
_kvm_syserr(kd, 0, "kvm_readstr");
return NULL;
} else if (cc < 1)
_kvm_err(kd, kd->program, "short read");
if (asize > 0 && asize == (ssize_t)pos) {
buf = realloc(buf, asize *= 2);
if (buf == NULL) {
_kvm_syserr(kd, kd->program, "kvm_readstr");
return NULL;
}
len = asize;
}
if (pos < len)
buf[pos] = ch;
}
if (lenp != NULL)
*lenp = pos;
if (pos > len)
return NULL;
else
return buf;
}
}
ssize_t
kvm_write(kvm_t *kd, u_long kva, const void *buf, size_t len)
{
int cc;
if (kvm_notrans(kd)) {
errno = 0;
if (lseek(kd->vmfd, (off_t)kva, 0) == -1 && errno != 0) {
_kvm_err(kd, 0, "invalid address (%lx)", kva);
return (-1);
}
cc = write(kd->vmfd, buf, len);
if (cc < 0) {
_kvm_syserr(kd, 0, "kvm_write");
return (-1);
} else if (cc < (ssize_t)len)
_kvm_err(kd, kd->program, "short write");
return (cc);
} else {
_kvm_err(kd, kd->program,
"kvm_write not implemented for dead kernels");
return (-1);
}
}