freebsd-dev/lib/libprocstat/core.c
Mikolaj Golub b03e2d7729 Embed revision id in the library.
MFC after:	29 days
2013-04-21 17:10:10 +00:00

434 lines
10 KiB
C

/*-
* Copyright (c) 2013 Mikolaj Golub <trociny@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/elf.h>
#include <sys/exec.h>
#include <sys/user.h>
#include <assert.h>
#include <err.h>
#include <fcntl.h>
#include <gelf.h>
#include <libelf.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "core.h"
#define PROCSTAT_CORE_MAGIC 0x012DADB8
struct procstat_core
{
int pc_magic;
int pc_fd;
Elf *pc_elf;
GElf_Ehdr pc_ehdr;
GElf_Phdr pc_phdr;
};
static bool core_offset(struct procstat_core *core, off_t offset);
static bool core_read(struct procstat_core *core, void *buf, size_t len);
static ssize_t core_read_mem(struct procstat_core *core, void *buf,
size_t len, vm_offset_t addr, bool readall);
static void *get_args(struct procstat_core *core, vm_offset_t psstrings,
enum psc_type type, void *buf, size_t *lenp);
struct procstat_core *
procstat_core_open(const char *filename)
{
struct procstat_core *core;
Elf *e;
GElf_Ehdr ehdr;
GElf_Phdr phdr;
size_t nph;
int fd, i;
if (elf_version(EV_CURRENT) == EV_NONE) {
warnx("ELF library too old");
return (NULL);
}
fd = open(filename, O_RDONLY, 0);
if (fd == -1) {
warn("open(%s)", filename);
return (NULL);
}
e = elf_begin(fd, ELF_C_READ, NULL);
if (e == NULL) {
warnx("elf_begin: %s", elf_errmsg(-1));
goto fail;
}
if (elf_kind(e) != ELF_K_ELF) {
warnx("%s is not an ELF object", filename);
goto fail;
}
if (gelf_getehdr(e, &ehdr) == NULL) {
warnx("gelf_getehdr: %s", elf_errmsg(-1));
goto fail;
}
if (ehdr.e_type != ET_CORE) {
warnx("%s is not a CORE file", filename);
goto fail;
}
if (elf_getphnum(e, &nph) == 0) {
warnx("program headers not found");
goto fail;
}
for (i = 0; i < ehdr.e_phnum; i++) {
if (gelf_getphdr(e, i, &phdr) != &phdr) {
warnx("gelf_getphdr: %s", elf_errmsg(-1));
goto fail;
}
if (phdr.p_type == PT_NOTE)
break;
}
if (i == ehdr.e_phnum) {
warnx("NOTE program header not found");
goto fail;
}
core = malloc(sizeof(struct procstat_core));
if (core == NULL) {
warn("malloc(%zu)", sizeof(struct procstat_core));
goto fail;
}
core->pc_magic = PROCSTAT_CORE_MAGIC;
core->pc_fd = fd;
core->pc_elf = e;
core->pc_ehdr = ehdr;
core->pc_phdr = phdr;
return (core);
fail:
if (e != NULL)
elf_end(e);
close(fd);
return (NULL);
}
void
procstat_core_close(struct procstat_core *core)
{
assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
elf_end(core->pc_elf);
close(core->pc_fd);
free(core);
}
void *
procstat_core_get(struct procstat_core *core, enum psc_type type, void *buf,
size_t *lenp)
{
Elf_Note nhdr;
off_t offset, eoffset;
vm_offset_t psstrings;
void *freebuf;
size_t len;
u_int32_t n_type;
int cstructsize, structsize;
char nbuf[8];
assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
switch(type) {
case PSC_TYPE_PROC:
n_type = NT_PROCSTAT_PROC;
structsize = sizeof(struct kinfo_proc);
break;
case PSC_TYPE_FILES:
n_type = NT_PROCSTAT_FILES;
structsize = sizeof(struct kinfo_file);
break;
case PSC_TYPE_VMMAP:
n_type = NT_PROCSTAT_VMMAP;
structsize = sizeof(struct kinfo_vmentry);
break;
case PSC_TYPE_GROUPS:
n_type = NT_PROCSTAT_GROUPS;
structsize = sizeof(gid_t);
break;
case PSC_TYPE_UMASK:
n_type = NT_PROCSTAT_UMASK;
structsize = sizeof(u_short);
break;
case PSC_TYPE_RLIMIT:
n_type = NT_PROCSTAT_RLIMIT;
structsize = sizeof(struct rlimit) * RLIM_NLIMITS;
break;
case PSC_TYPE_OSREL:
n_type = NT_PROCSTAT_OSREL;
structsize = sizeof(int);
break;
case PSC_TYPE_PSSTRINGS:
case PSC_TYPE_ARGV:
case PSC_TYPE_ENVV:
n_type = NT_PROCSTAT_PSSTRINGS;
structsize = sizeof(vm_offset_t);
break;
case PSC_TYPE_AUXV:
n_type = NT_PROCSTAT_AUXV;
structsize = sizeof(Elf_Auxinfo);
break;
default:
warnx("unknown core stat type: %d", type);
return (NULL);
}
offset = core->pc_phdr.p_offset;
eoffset = offset + core->pc_phdr.p_filesz;
while (offset < eoffset) {
if (!core_offset(core, offset))
return (NULL);
if (!core_read(core, &nhdr, sizeof(nhdr)))
return (NULL);
offset += sizeof(nhdr) +
roundup2(nhdr.n_namesz, sizeof(Elf32_Size)) +
roundup2(nhdr.n_descsz, sizeof(Elf32_Size));
if (nhdr.n_namesz == 0 && nhdr.n_descsz == 0)
break;
if (nhdr.n_type != n_type)
continue;
if (nhdr.n_namesz != 8)
continue;
if (!core_read(core, nbuf, sizeof(nbuf)))
return (NULL);
if (strcmp(nbuf, "FreeBSD") != 0)
continue;
if (nhdr.n_descsz < sizeof(cstructsize)) {
warnx("corrupted core file");
return (NULL);
}
if (!core_read(core, &cstructsize, sizeof(cstructsize)))
return (NULL);
if (cstructsize != structsize) {
warnx("version mismatch");
return (NULL);
}
len = nhdr.n_descsz - sizeof(cstructsize);
if (len == 0)
return (NULL);
if (buf != NULL) {
len = MIN(len, *lenp);
freebuf = NULL;
} else {
freebuf = buf = malloc(len);
if (buf == NULL) {
warn("malloc(%zu)", len);
return (NULL);
}
}
if (!core_read(core, buf, len)) {
free(freebuf);
return (NULL);
}
if (type == PSC_TYPE_ARGV || type == PSC_TYPE_ENVV) {
if (len < sizeof(psstrings)) {
free(freebuf);
return (NULL);
}
psstrings = *(vm_offset_t *)buf;
if (freebuf == NULL)
len = *lenp;
else
buf = NULL;
free(freebuf);
buf = get_args(core, psstrings, type, buf, &len);
}
*lenp = len;
return (buf);
}
return (NULL);
}
static bool
core_offset(struct procstat_core *core, off_t offset)
{
assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
if (lseek(core->pc_fd, offset, SEEK_SET) == -1) {
warn("core: lseek(%jd)", (intmax_t)offset);
return (false);
}
return (true);
}
static bool
core_read(struct procstat_core *core, void *buf, size_t len)
{
ssize_t n;
assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
n = read(core->pc_fd, buf, len);
if (n == -1) {
warn("core: read");
return (false);
}
if (n < (ssize_t)len) {
warnx("core: short read");
return (false);
}
return (true);
}
static ssize_t
core_read_mem(struct procstat_core *core, void *buf, size_t len,
vm_offset_t addr, bool readall)
{
GElf_Phdr phdr;
off_t offset;
int i;
assert(core->pc_magic == PROCSTAT_CORE_MAGIC);
for (i = 0; i < core->pc_ehdr.e_phnum; i++) {
if (gelf_getphdr(core->pc_elf, i, &phdr) != &phdr) {
warnx("gelf_getphdr: %s", elf_errmsg(-1));
return (-1);
}
if (phdr.p_type != PT_LOAD)
continue;
if (addr < phdr.p_vaddr || addr > phdr.p_vaddr + phdr.p_memsz)
continue;
offset = phdr.p_offset + (addr - phdr.p_vaddr);
if ((phdr.p_vaddr + phdr.p_memsz) - addr < len) {
if (readall) {
warnx("format error: "
"attempt to read out of segment");
return (-1);
}
len = (phdr.p_vaddr + phdr.p_memsz) - addr;
}
if (!core_offset(core, offset))
return (-1);
if (!core_read(core, buf, len))
return (-1);
return (len);
}
warnx("format error: address %ju not found", (uintmax_t)addr);
return (-1);
}
#define ARGS_CHUNK_SZ 256 /* Chunk size (bytes) for get_args operations. */
static void *
get_args(struct procstat_core *core, vm_offset_t psstrings, enum psc_type type,
void *args, size_t *lenp)
{
struct ps_strings pss;
void *freeargs;
vm_offset_t addr;
char **argv, *p;
size_t chunksz, done, len, nchr, size;
ssize_t n;
u_int i, nstr;
assert(type == PSC_TYPE_ARGV || type == PSC_TYPE_ENVV);
if (core_read_mem(core, &pss, sizeof(pss), psstrings, true) == -1)
return (NULL);
if (type == PSC_TYPE_ARGV) {
addr = (vm_offset_t)pss.ps_argvstr;
nstr = pss.ps_nargvstr;
} else /* type == PSC_TYPE_ENVV */ {
addr = (vm_offset_t)pss.ps_envstr;
nstr = pss.ps_nenvstr;
}
if (addr == 0 || nstr == 0)
return (NULL);
if (nstr > ARG_MAX) {
warnx("format error");
return (NULL);
}
size = nstr * sizeof(char *);
argv = malloc(size);
if (argv == NULL) {
warn("malloc(%zu)", size);
return (NULL);
}
done = 0;
freeargs = NULL;
if (core_read_mem(core, argv, size, addr, true) == -1)
goto fail;
if (args != NULL) {
nchr = MIN(ARG_MAX, *lenp);
} else {
nchr = ARG_MAX;
freeargs = args = malloc(nchr);
if (args == NULL) {
warn("malloc(%zu)", nchr);
goto fail;
}
}
p = args;
for (i = 0; ; i++) {
if (i == nstr)
goto done;
/*
* The program may have scribbled into its argv array, e.g. to
* remove some arguments. If that has happened, break out
* before trying to read from NULL.
*/
if (argv[i] == NULL)
goto done;
for (addr = (vm_offset_t)argv[i]; ; addr += chunksz) {
chunksz = MIN(ARGS_CHUNK_SZ, nchr - 1 - done);
if (chunksz <= 0)
goto done;
n = core_read_mem(core, p, chunksz, addr, false);
if (n == -1)
goto fail;
len = strnlen(p, chunksz);
p += len;
done += len;
if (len != chunksz)
break;
}
*p++ = '\0';
done++;
}
fail:
free(freeargs);
args = NULL;
done:
*lenp = done;
free(argv);
return (args);
}