From 402351a07f551999ca9cbccafb352f432840fe63 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Thu, 8 Jun 2006 18:48:30 +0000 Subject: [PATCH] Add a new utility, 'pctrack', which dumps program counter tracking data recorded by the kernel. The kernel must be compiled with DEBUG_PCTRACK. --- usr.bin/pctrack/Makefile | 8 + usr.bin/pctrack/pctrack.8 | 102 ++++++++++++ usr.bin/pctrack/pctrack.c | 322 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 432 insertions(+) create mode 100644 usr.bin/pctrack/Makefile create mode 100644 usr.bin/pctrack/pctrack.8 create mode 100644 usr.bin/pctrack/pctrack.c diff --git a/usr.bin/pctrack/Makefile b/usr.bin/pctrack/Makefile new file mode 100644 index 0000000000..8a8be1871c --- /dev/null +++ b/usr.bin/pctrack/Makefile @@ -0,0 +1,8 @@ +# $DragonFly: src/usr.bin/pctrack/Makefile,v 1.1 2006/06/08 18:48:30 dillon Exp $ + +PROG= pctrack +DPADD= ${LIBKVM} +LDADD= -lkvm +MAN= pctrack.8 + +.include diff --git a/usr.bin/pctrack/pctrack.8 b/usr.bin/pctrack/pctrack.8 new file mode 100644 index 0000000000..cd679a999a --- /dev/null +++ b/usr.bin/pctrack/pctrack.8 @@ -0,0 +1,102 @@ +.\" +.\" Copyright (c) 2003,2004 The DragonFly Project. All rights reserved. +.\" +.\" This code is derived from software contributed to The DragonFly Project +.\" by Matthew Dillon +.\" +.\" 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. +.\" 3. Neither the name of The DragonFly Project nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific, prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 +.\" COPYRIGHT HOLDERS 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. +.\" +.\" $DragonFly: src/usr.bin/pctrack/pctrack.8,v 1.1 2006/06/08 18:48:30 dillon Exp $ +.\" +.Dd Jun 8, 2006 +.Dt PCTRACK 8 +.Os +.Sh NAME +.Nm pctrack +.Nd print kernel system/interrupt pc sampling history +.Sh SYNOPSIS +.Nm +.Op Fl fnsi +.Op Fl c Ar cpu +.Op Fl N Ar execfile +.Op Fl M Ar corefile +.Op Ar interval +.Sh DESCRIPTION +Kernels compiled with DEBUG_PCTRACK record the program counter (PC) of +the code interrupted by the statistics clock interrupt. +This information can be dumped with the +.Nm +utility +.Pp +The following options are available: +.Bl -tag -width ".Fl N Ar execfile" +.It Fl c Ar cpu +Specify which cpu to dump. If not specified, all cpus will be dumped. +.It Fl f +If a single cpu and either +.Op Fl i +or +.Op Fl s +is specified, this option will use the repeat interval to detect changes +and dump them as they occur. +.It Fl i +Dump the interrupt tracking buffer. +.It Fl s +Dump the kernel tracking buffer. +If neither +.Op Fl i +or +.Op Fl s +is specified, both tracking buffers will be dumped. +.It Fl n +.Nm +normally tries to translate the PC into symbols. This option forces hex +values to be displayed instead. +.It Fl N Ar execfile +The kernel image to resolve symbols from. +The default is the value returned via +.Xr getbootfile 3 . +.It Fl M Ar corefile +The core file or memory image to read from. +The default is +.Pa /dev/mem . +.El +.Sh OPERATIONAL NOTES +.Sh HISTORY +The +.Nm +utility first appeared in +.Dx 1.5 . +.Sh AUTHORS +.An -nosplit +The +.Nm +utility was originally implemented by +.An Matthew Dillon +for +.Dx . diff --git a/usr.bin/pctrack/pctrack.c b/usr.bin/pctrack/pctrack.c new file mode 100644 index 0000000000..4c4a5042b4 --- /dev/null +++ b/usr.bin/pctrack/pctrack.c @@ -0,0 +1,322 @@ +/*- + * Copyright (c) 2002 Jake Burkholder + * Copyright (c) 2004 Robert Watson + * 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 AUTHOR 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 AUTHOR 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. + * + * $DragonFly: src/usr.bin/pctrack/pctrack.c,v 1.1 2006/06/08 18:48:30 dillon Exp $ + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SBUFLEN 128 + +extern char *optarg; +extern int optind; + +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[] = { + { "_ncpus" }, + { "_cputime_pcheader" }, + { "_cputime_pctrack" }, + { 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; + +/* + * Reads the cputime_pctrack[] structure from the kernel and displays + * the results in a human readable format. + */ +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; + + /* + * Parse commandline arguments. + */ + 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(); + + /* + * Open our execfile and corefile, resolve needed symbols and read in + * the trace buffer. + */ + 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, int track, 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 *execfile) +{ + char buf[256]; + char cmd[256]; + int buflen = sizeof(buf); + FILE *fp; + struct symdata *sym; + char *s1; + char *s2; + char *s3; + + TAILQ_INIT(&symlist); + + if (execfile == NULL) { + if (sysctlbyname("kern.bootfile", buf, &buflen, NULL, 0) < 0) + execfile = "/kernel"; + else + execfile = buf; + } + snprintf(cmd, sizeof(cmd), "nm -n %s", execfile); + 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 (strcmp(s3, "kernbase") == 0) + symbegin = sym->symaddr; + if (strcmp(s3, "end") == 0) + 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); +} -- 2.41.0