#include <sys/kinfo.h>
#include <sys/types.h>
#include <sys/ktr.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/queue.h>
#include <err.h>
#include <fcntl.h>
#include <kvm.h>
#include <limits.h>
#include <nlist.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
#define SBUFLEN 128
static void usage(void);
static void do_output(int, int, struct kinfo_pcheader *, struct kinfo_pctrack *, int);
static void read_symbols(const char *);
static const char *address_to_symbol(void *);
static struct nlist nl[] = {
{ .n_name = "_ncpus" },
{ .n_name = "_cputime_pcheader" },
{ .n_name = "_cputime_pctrack" },
{ .n_name = NULL }
};
static char corefile[PATH_MAX];
static char execfile[PATH_MAX];
static char errbuf[_POSIX2_LINE_MAX];
static int sflag;
static int iflag;
static int nflag;
static int fflag;
static int cflag = -1;
static int Nflag;
static int Mflag;
int
main(int ac, char **av)
{
struct kinfo_pcheader pchead;
struct kinfo_pctrack pctrack;
kvm_t *kd;
int ntrack;
int ncpus;
int cpu;
int repeat;
int c;
while ((c = getopt(ac, av, "nsifc:N:M:")) != -1) {
switch (c) {
case 'N':
if (strlcpy(execfile, optarg, sizeof(execfile))
>= sizeof(execfile))
errx(1, "%s: File name too long", optarg);
Nflag = 1;
break;
case 'M':
if (strlcpy(corefile, optarg, sizeof(corefile))
>= sizeof(corefile))
errx(1, "%s: File name too long", optarg);
Mflag = 1;
break;
case 'c':
cflag = strtol(optarg, NULL, 0);
break;
case 's':
sflag = 1;
break;
case 'i':
iflag = 1;
break;
case 'n':
nflag = 1;
break;
case 'f':
fflag = 1;
break;
default:
usage();
}
}
if (sflag == 0 && iflag == 0) {
sflag = 1;
iflag = 1;
}
if (nflag == 0)
read_symbols(Nflag ? execfile : NULL);
if (fflag && (cflag < 0 || sflag + iflag > 1)) {
fprintf(stderr, "-f can only be specified with a particular cpu and just one of -i or -s\n");
exit(1);
}
ac -= optind;
av += optind;
if (ac != 0 && strtod(av[0], NULL) > 0.0) {
repeat = (int)(strtod(av[0], NULL) * 1000000.0);
++av;
--ac;
} else if (fflag) {
repeat = 1000000 / 10;
} else {
repeat = 0;
}
if (ac != 0)
usage();
if ((kd = kvm_openfiles(Nflag ? execfile : NULL,
Mflag ? corefile : NULL, NULL, O_RDONLY, errbuf)) == NULL)
errx(1, "%s", errbuf);
if (kvm_nlist(kd, nl) != 0)
errx(1, "%s", kvm_geterr(kd));
if (kvm_read(kd, nl[0].n_value, &ncpus, sizeof(ncpus)) == -1)
errx(1, "%s", kvm_geterr(kd));
if (kvm_read(kd, nl[1].n_value, &pchead, sizeof(pchead)) == -1)
errx(1, "%s", kvm_geterr(kd));
again:
for (cpu = 0; cpu < ncpus; ++cpu) {
for (ntrack = 0; ntrack < pchead.pc_ntrack; ++ntrack) {
int offset;
if (ntrack == PCTRACK_SYS && sflag == 0)
continue;
if (ntrack == PCTRACK_INT && iflag == 0)
continue;
if (cflag >= 0 && cflag != cpu)
continue;
offset = offsetof(struct kinfo_pctrack,
pc_array[pchead.pc_arysize]);
offset = (offset * pchead.pc_ntrack * cpu) +
(offset * ntrack);
if (kvm_read(kd, nl[2].n_value + offset, &pctrack, sizeof(pctrack)) < 0)
errx(1, "%s", kvm_geterr(kd));
printf("CPU %d %s:\n", cpu,
(ntrack == PCTRACK_SYS) ? "SYSTEM" :
(ntrack == PCTRACK_INT) ? "INTERRUPT" : "?"
);
do_output(cpu, ntrack, &pchead, &pctrack, pctrack.pc_index - pchead.pc_arysize);
while (fflag) {
usleep(repeat);
int last_index = pctrack.pc_index;
kvm_read(kd, nl[2].n_value + offset, &pctrack,
sizeof(pctrack));
do_output(cpu, ntrack, &pchead, &pctrack, last_index);
}
}
}
if (repeat) {
usleep(repeat);
goto again;
}
return(0);
}
static void
do_output(int cpu __unused, int track __unused, struct kinfo_pcheader *pchead, struct kinfo_pctrack *pctrack, int base_index)
{
int i;
i = base_index;
if (pctrack->pc_index - base_index > pchead->pc_arysize) {
i = pctrack->pc_index - pchead->pc_arysize;
}
while (i < pctrack->pc_index) {
void *data = pctrack->pc_array[i & (pchead->pc_arysize - 1)];
if (nflag)
printf("\t%p\n", data);
else
printf("\t%s\n", address_to_symbol(data));
++i;
}
}
struct symdata {
TAILQ_ENTRY(symdata) link;
const char *symname;
char *symaddr;
char symtype;
};
static TAILQ_HEAD(symlist, symdata) symlist;
static struct symdata *symcache;
static char *symbegin;
static char *symend;
static void
read_symbols(const char *file)
{
char buf[256];
char cmd[256];
size_t buflen = sizeof(buf);
FILE *fp;
struct symdata *sym;
char *s1;
char *s2;
char *s3;
TAILQ_INIT(&symlist);
if (file == NULL) {
if (sysctlbyname("kern.bootfile", buf, &buflen, NULL, 0) < 0)
file = "/boot/kernel";
else
file = buf;
}
symend = NULL;
symbegin = (void *)(intptr_t)-1;
snprintf(cmd, sizeof(cmd), "nm -n %s", file);
if ((fp = popen(cmd, "r")) != NULL) {
while (fgets(buf, sizeof(buf), fp) != NULL) {
s1 = strtok(buf, " \t\n");
s2 = strtok(NULL, " \t\n");
s3 = strtok(NULL, " \t\n");
if (s1 && s2 && s3) {
sym = malloc(sizeof(struct symdata));
sym->symaddr = (char *)strtoul(s1, NULL, 16);
sym->symtype = s2[0];
sym->symname = strdup(s3);
if (s2[0] == 't' || s2[0] == 'T') {
if (symbegin > sym->symaddr)
symbegin = sym->symaddr;
if (symend < sym->symaddr)
symend = sym->symaddr;
}
TAILQ_INSERT_TAIL(&symlist, sym, link);
}
}
pclose(fp);
}
symcache = TAILQ_FIRST(&symlist);
}
static const char *
address_to_symbol(void *kptr)
{
static char buf[64];
if (symcache == NULL ||
(char *)kptr < symbegin || (char *)kptr >= symend
) {
snprintf(buf, sizeof(buf), "%p", kptr);
return(buf);
}
while ((char *)symcache->symaddr < (char *)kptr) {
if (TAILQ_NEXT(symcache, link) == NULL)
break;
symcache = TAILQ_NEXT(symcache, link);
}
while ((char *)symcache->symaddr > (char *)kptr) {
if (symcache != TAILQ_FIRST(&symlist))
symcache = TAILQ_PREV(symcache, symlist, link);
}
snprintf(buf, sizeof(buf), "%s+%d", symcache->symname,
(int)((char *)kptr - symcache->symaddr));
return(buf);
}
static void
usage(void)
{
fprintf(stderr, "usage: pctrack [-nsi] [-c cpu] [-N execfile] "
"[-M corefile]\n");
exit(1);
}