File: [local] / src / usr.sbin / btrace / btrace.c (download)
Revision 1.90, Sat Mar 30 07:41:45 2024 UTC (2 months, 1 week ago) by mpi
Branch: MAIN
Changes since 1.89: +18 -16 lines
Implement else branching logic including 'else if'.
Statement lists for if & else conditions are now wrapped in a new
'struct bt_cond'. Handling B_AC_TEST statements moved to stmt_eval()
to handle nested conditional statements.
From Christian Ludwig christian_ludwig at genua.de
|
/* $OpenBSD: btrace.c,v 1.90 2024/03/30 07:41:45 mpi Exp $ */
/*
* Copyright (c) 2019 - 2023 Martin Pieuchot <mpi@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/queue.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#include <paths.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <dev/dt/dtvar.h>
#include "btrace.h"
#include "bt_parser.h"
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
/*
* Maximum number of operands an arithmetic operation can have. This
* is necessary to stop infinite recursion when evaluating expressions.
*/
#define __MAXOPERANDS 5
#define __PATH_DEVDT "/dev/dt"
__dead void usage(void);
char *read_btfile(const char *, size_t *);
/*
* Retrieve & parse probe information.
*/
void dtpi_cache(int);
void dtpi_print_list(int);
const char *dtpi_func(struct dtioc_probe_info *);
int dtpi_is_unit(const char *);
struct dtioc_probe_info *dtpi_get_by_value(const char *, const char *,
const char *);
/*
* Main loop and rule evaluation.
*/
void rules_do(int);
int rules_setup(int);
int rules_apply(int, struct dt_evt *);
void rules_teardown(int);
int rule_eval(struct bt_rule *, struct dt_evt *);
void rule_printmaps(struct bt_rule *);
/*
* Language builtins & functions.
*/
uint64_t builtin_nsecs(struct dt_evt *);
const char *builtin_kstack(struct dt_evt *);
const char *builtin_arg(struct dt_evt *, enum bt_argtype);
struct bt_arg *fn_str(struct bt_arg *, struct dt_evt *, char *);
int stmt_eval(struct bt_stmt *, struct dt_evt *);
void stmt_bucketize(struct bt_stmt *, struct dt_evt *);
void stmt_clear(struct bt_stmt *);
void stmt_delete(struct bt_stmt *, struct dt_evt *);
void stmt_insert(struct bt_stmt *, struct dt_evt *);
void stmt_print(struct bt_stmt *, struct dt_evt *);
void stmt_store(struct bt_stmt *, struct dt_evt *);
bool stmt_test(struct bt_stmt *, struct dt_evt *);
void stmt_time(struct bt_stmt *, struct dt_evt *);
void stmt_zero(struct bt_stmt *);
struct bt_arg *ba_read(struct bt_arg *);
struct bt_arg *baeval(struct bt_arg *, struct dt_evt *);
const char *ba2hash(struct bt_arg *, struct dt_evt *);
long baexpr2long(struct bt_arg *, struct dt_evt *);
const char *ba2bucket(struct bt_arg *, struct bt_arg *,
struct dt_evt *, long *);
int ba2dtflags(struct bt_arg *);
/*
* Debug routines.
*/
__dead void xabort(const char *, ...);
void debug(const char *, ...);
void debugx(const char *, ...);
const char *debug_probe_name(struct bt_probe *);
void debug_dump_term(struct bt_arg *);
void debug_dump_expr(struct bt_arg *);
void debug_dump_filter(struct bt_rule *);
struct dtioc_probe_info *dt_dtpis; /* array of available probes */
size_t dt_ndtpi; /* # of elements in the array */
struct dtioc_arg_info **dt_args; /* array of probe arguments */
struct dt_evt bt_devt; /* fake event for BEGIN/END */
#define EVENT_BEGIN 0
#define EVENT_END (unsigned int)(-1)
uint64_t bt_filtered; /* # of events filtered out */
struct syms *kelf, *uelf;
char **vargs;
int nargs = 0;
int verbose = 0;
int dtfd;
volatile sig_atomic_t quit_pending;
static void
signal_handler(int sig)
{
quit_pending = sig;
}
int
main(int argc, char *argv[])
{
int fd = -1, ch, error = 0;
const char *filename = NULL, *btscript = NULL;
int showprobes = 0, noaction = 0;
size_t btslen = 0;
setlocale(LC_ALL, "");
while ((ch = getopt(argc, argv, "e:lnp:v")) != -1) {
switch (ch) {
case 'e':
btscript = optarg;
btslen = strlen(btscript);
break;
case 'l':
showprobes = 1;
break;
case 'n':
noaction = 1;
break;
case 'p':
uelf = kelf_open(optarg);
break;
case 'v':
verbose++;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc > 0 && btscript == NULL)
filename = argv[0];
/* Cannot pledge due to special ioctl()s */
if (unveil(__PATH_DEVDT, "r") == -1)
err(1, "unveil %s", __PATH_DEVDT);
if (unveil(_PATH_KSYMS, "r") == -1)
err(1, "unveil %s", _PATH_KSYMS);
if (filename != NULL) {
if (unveil(filename, "r") == -1)
err(1, "unveil %s", filename);
}
if (unveil(NULL, NULL) == -1)
err(1, "unveil");
if (filename != NULL) {
btscript = read_btfile(filename, &btslen);
argc--;
argv++;
}
nargs = argc;
vargs = argv;
if (btscript == NULL && !showprobes)
usage();
if (btscript != NULL) {
error = btparse(btscript, btslen, filename, 1);
if (error)
return error;
}
if (noaction)
return error;
if (showprobes || g_nprobes > 0) {
fd = open(__PATH_DEVDT, O_RDONLY);
if (fd == -1)
err(1, "could not open %s", __PATH_DEVDT);
dtfd = fd;
}
if (showprobes) {
dtpi_cache(fd);
dtpi_print_list(fd);
}
if (!TAILQ_EMPTY(&g_rules))
rules_do(fd);
if (fd != -1)
close(fd);
return error;
}
__dead void
usage(void)
{
fprintf(stderr, "usage: %s [-lnv] [-e program | file] [-p file] "
"[argument ...]\n", getprogname());
exit(1);
}
char *
read_btfile(const char *filename, size_t *len)
{
FILE *fp;
char *fcontent;
struct stat st;
size_t fsize;
if (stat(filename, &st))
err(1, "can't stat '%s'", filename);
fsize = st.st_size;
fcontent = malloc(fsize + 1);
if (fcontent == NULL)
err(1, "malloc");
fp = fopen(filename, "r");
if (fp == NULL)
err(1, "can't open '%s'", filename);
if (fread(fcontent, 1, fsize, fp) != fsize)
err(1, "can't read '%s'", filename);
fcontent[fsize] = '\0';
fclose(fp);
*len = fsize;
return fcontent;
}
void
dtpi_cache(int fd)
{
struct dtioc_probe dtpr;
if (dt_dtpis != NULL)
return;
memset(&dtpr, 0, sizeof(dtpr));
if (ioctl(fd, DTIOCGPLIST, &dtpr))
err(1, "DTIOCGPLIST");
dt_ndtpi = dtpr.dtpr_size / sizeof(*dt_dtpis);
dt_dtpis = reallocarray(NULL, dt_ndtpi, sizeof(*dt_dtpis));
if (dt_dtpis == NULL)
err(1, NULL);
dtpr.dtpr_probes = dt_dtpis;
if (ioctl(fd, DTIOCGPLIST, &dtpr))
err(1, "DTIOCGPLIST");
}
void
dtai_cache(int fd, struct dtioc_probe_info *dtpi)
{
struct dtioc_arg dtar;
if (dt_args == NULL) {
dt_args = calloc(dt_ndtpi, sizeof(*dt_args));
if (dt_args == NULL)
err(1, NULL);
}
if (dt_args[dtpi->dtpi_pbn - 1] != NULL)
return;
dt_args[dtpi->dtpi_pbn - 1] = reallocarray(NULL, dtpi->dtpi_nargs,
sizeof(**dt_args));
if (dt_args[dtpi->dtpi_pbn - 1] == NULL)
err(1, NULL);
dtar.dtar_pbn = dtpi->dtpi_pbn;
dtar.dtar_size = dtpi->dtpi_nargs * sizeof(**dt_args);
dtar.dtar_args = dt_args[dtpi->dtpi_pbn - 1];
if (ioctl(fd, DTIOCGARGS, &dtar))
err(1, "DTIOCGARGS");
}
void
dtpi_print_list(int fd)
{
struct dtioc_probe_info *dtpi;
struct dtioc_arg_info *dtai;
size_t i, j;
dtpi = dt_dtpis;
for (i = 0; i < dt_ndtpi; i++, dtpi++) {
printf("%s:%s:%s", dtpi->dtpi_prov, dtpi_func(dtpi),
dtpi->dtpi_name);
if (strncmp(dtpi->dtpi_prov, "tracepoint", DTNAMESIZE) == 0) {
dtai_cache(fd, dtpi);
dtai = dt_args[dtpi->dtpi_pbn - 1];
printf("(");
for (j = 0; j < dtpi->dtpi_nargs; j++, dtai++) {
if (j > 0)
printf(", ");
printf("%s", dtai->dtai_argtype);
}
printf(")");
}
printf("\n");
}
}
const char *
dtpi_func(struct dtioc_probe_info *dtpi)
{
char *sysnb, func[DTNAMESIZE];
const char *errstr;
int idx;
if (strncmp(dtpi->dtpi_prov, "syscall", DTNAMESIZE))
return dtpi->dtpi_func;
/* Translate syscall names */
strlcpy(func, dtpi->dtpi_func, sizeof(func));
sysnb = func;
if (strsep(&sysnb, "%") == NULL)
return dtpi->dtpi_func;
idx = strtonum(sysnb, 1, SYS_MAXSYSCALL, &errstr);
if (errstr != NULL)
return dtpi->dtpi_func;
return syscallnames[idx];
}
int
dtpi_is_unit(const char *unit)
{
return !strncmp("hz", unit, sizeof("hz"));
}
struct dtioc_probe_info *
dtpi_get_by_value(const char *prov, const char *func, const char *name)
{
struct dtioc_probe_info *dtpi;
size_t i;
dtpi = dt_dtpis;
for (i = 0; i < dt_ndtpi; i++, dtpi++) {
if (prov != NULL &&
strncmp(prov, dtpi->dtpi_prov, DTNAMESIZE))
continue;
if (func != NULL) {
if (dtpi_is_unit(func))
return dtpi;
if (strncmp(func, dtpi_func(dtpi), DTNAMESIZE))
continue;
}
if (strncmp(name, dtpi->dtpi_name, DTNAMESIZE))
continue;
debug("matched probe %s:%s:%s\n", dtpi->dtpi_prov,
dtpi_func(dtpi), dtpi->dtpi_name);
return dtpi;
}
return NULL;
}
void
rules_do(int fd)
{
struct sigaction sa;
int halt = 0;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = signal_handler;
if (sigaction(SIGINT, &sa, NULL))
err(1, "sigaction");
if (sigaction(SIGTERM, &sa, NULL))
err(1, "sigaction");
halt = rules_setup(fd);
while (!quit_pending && !halt && g_nprobes > 0) {
static struct dt_evt devtbuf[64];
ssize_t rlen;
size_t i;
rlen = read(fd, devtbuf, sizeof(devtbuf) - 1);
if (rlen == -1) {
if (errno == EINTR && quit_pending) {
printf("\n");
break;
}
err(1, "read");
}
if ((rlen % sizeof(struct dt_evt)) != 0)
err(1, "incorrect read");
for (i = 0; i < rlen / sizeof(struct dt_evt); i++) {
halt = rules_apply(fd, &devtbuf[i]);
if (halt)
break;
}
}
rules_teardown(fd);
if (verbose && fd != -1) {
struct dtioc_stat dtst;
memset(&dtst, 0, sizeof(dtst));
if (ioctl(fd, DTIOCGSTATS, &dtst))
warn("DTIOCGSTATS");
fprintf(stderr, "%llu events read\n", dtst.dtst_readevt);
fprintf(stderr, "%llu events dropped\n", dtst.dtst_dropevt);
fprintf(stderr, "%llu events filtered\n", bt_filtered);
}
}
static uint64_t
rules_action_scan(struct bt_stmt *bs)
{
struct bt_arg *ba;
struct bt_cond *bc;
uint64_t evtflags = 0;
while (bs != NULL) {
SLIST_FOREACH(ba, &bs->bs_args, ba_next)
evtflags |= ba2dtflags(ba);
/* Also check the value for map/hist insertion */
switch (bs->bs_act) {
case B_AC_BUCKETIZE:
case B_AC_INSERT:
ba = (struct bt_arg *)bs->bs_var;
evtflags |= ba2dtflags(ba);
break;
case B_AC_TEST:
bc = (struct bt_cond *)bs->bs_var;
evtflags |= rules_action_scan(bc->bc_condbs);
evtflags |= rules_action_scan(bc->bc_elsebs);
break;
default:
break;
}
bs = SLIST_NEXT(bs, bs_next);
}
return evtflags;
}
int
rules_setup(int fd)
{
struct dtioc_probe_info *dtpi;
struct dtioc_req *dtrq;
struct bt_rule *r, *rbegin = NULL;
struct bt_probe *bp;
struct bt_stmt *bs;
struct bt_arg *ba;
int dokstack = 0, halt = 0, on = 1;
uint64_t evtflags;
TAILQ_FOREACH(r, &g_rules, br_next) {
evtflags = 0;
if (r->br_filter != NULL &&
r->br_filter->bf_condition != NULL) {
bs = r->br_filter->bf_condition;
ba = SLIST_FIRST(&bs->bs_args);
evtflags |= ba2dtflags(ba);
}
evtflags |= rules_action_scan(SLIST_FIRST(&r->br_action));
SLIST_FOREACH(bp, &r->br_probes, bp_next) {
debug("parsed probe '%s'", debug_probe_name(bp));
debug_dump_filter(r);
if (bp->bp_type != B_PT_PROBE) {
if (bp->bp_type == B_PT_BEGIN)
rbegin = r;
continue;
}
dtpi_cache(fd);
dtpi = dtpi_get_by_value(bp->bp_prov, bp->bp_func,
bp->bp_name);
if (dtpi == NULL) {
errx(1, "probe '%s:%s:%s' not found",
bp->bp_prov, bp->bp_func, bp->bp_name);
}
dtrq = calloc(1, sizeof(*dtrq));
if (dtrq == NULL)
err(1, "dtrq: 1alloc");
bp->bp_pbn = dtpi->dtpi_pbn;
dtrq->dtrq_pbn = dtpi->dtpi_pbn;
dtrq->dtrq_rate = bp->bp_rate;
dtrq->dtrq_evtflags = evtflags;
if (dtrq->dtrq_evtflags & DTEVT_KSTACK)
dokstack = 1;
bp->bp_cookie = dtrq;
}
}
if (dokstack)
kelf = kelf_open(_PATH_KSYMS);
/* Initialize "fake" event for BEGIN/END */
bt_devt.dtev_pbn = EVENT_BEGIN;
strlcpy(bt_devt.dtev_comm, getprogname(), sizeof(bt_devt.dtev_comm));
bt_devt.dtev_pid = getpid();
bt_devt.dtev_tid = getthrid();
clock_gettime(CLOCK_REALTIME, &bt_devt.dtev_tsp);
if (rbegin)
halt = rule_eval(rbegin, &bt_devt);
/* Enable all probes */
TAILQ_FOREACH(r, &g_rules, br_next) {
SLIST_FOREACH(bp, &r->br_probes, bp_next) {
if (bp->bp_type != B_PT_PROBE)
continue;
dtrq = bp->bp_cookie;
if (ioctl(fd, DTIOCPRBENABLE, dtrq))
err(1, "DTIOCPRBENABLE");
}
}
if (g_nprobes > 0) {
if (ioctl(fd, DTIOCRECORD, &on))
err(1, "DTIOCRECORD");
}
return halt;
}
/*
* Returns non-zero if the program should halt.
*/
int
rules_apply(int fd, struct dt_evt *dtev)
{
struct bt_rule *r;
struct bt_probe *bp;
TAILQ_FOREACH(r, &g_rules, br_next) {
SLIST_FOREACH(bp, &r->br_probes, bp_next) {
if (bp->bp_type != B_PT_PROBE ||
bp->bp_pbn != dtev->dtev_pbn)
continue;
dtai_cache(fd, &dt_dtpis[dtev->dtev_pbn - 1]);
if (rule_eval(r, dtev))
return 1;
}
}
return 0;
}
void
rules_teardown(int fd)
{
struct dtioc_req *dtrq;
struct bt_probe *bp;
struct bt_rule *r, *rend = NULL;
int dokstack = 0, off = 0;
if (g_nprobes > 0) {
if (ioctl(fd, DTIOCRECORD, &off))
err(1, "DTIOCRECORD");
}
TAILQ_FOREACH(r, &g_rules, br_next) {
SLIST_FOREACH(bp, &r->br_probes, bp_next) {
if (bp->bp_type != B_PT_PROBE) {
if (bp->bp_type == B_PT_END)
rend = r;
continue;
}
dtrq = bp->bp_cookie;
if (ioctl(fd, DTIOCPRBDISABLE, dtrq))
err(1, "DTIOCPRBDISABLE");
if (dtrq->dtrq_evtflags & DTEVT_KSTACK)
dokstack = 1;
}
}
kelf_close(kelf);
kelf = NULL;
kelf_close(uelf);
uelf = NULL;
/* Update "fake" event for BEGIN/END */
bt_devt.dtev_pbn = EVENT_END;
clock_gettime(CLOCK_REALTIME, &bt_devt.dtev_tsp);
if (rend)
rule_eval(rend, &bt_devt);
/* Print non-empty map & hist */
TAILQ_FOREACH(r, &g_rules, br_next)
rule_printmaps(r);
}
/*
* Returns non-zero if the program should halt.
*/
int
rule_eval(struct bt_rule *r, struct dt_evt *dtev)
{
struct bt_stmt *bs;
struct bt_probe *bp;
SLIST_FOREACH(bp, &r->br_probes, bp_next) {
debug("eval rule '%s'", debug_probe_name(bp));
debug_dump_filter(r);
}
if (r->br_filter != NULL && r->br_filter->bf_condition != NULL) {
if (stmt_test(r->br_filter->bf_condition, dtev) == false) {
bt_filtered++;
return 0;
}
}
SLIST_FOREACH(bs, &r->br_action, bs_next) {
if (stmt_eval(bs, dtev))
return 1;
}
return 0;
}
void
rule_printmaps(struct bt_rule *r)
{
struct bt_stmt *bs;
SLIST_FOREACH(bs, &r->br_action, bs_next) {
struct bt_arg *ba;
SLIST_FOREACH(ba, &bs->bs_args, ba_next) {
struct bt_var *bv = ba->ba_value;
struct map *map;
if (ba->ba_type != B_AT_MAP && ba->ba_type != B_AT_HIST)
continue;
map = (struct map *)bv->bv_value;
if (map == NULL)
continue;
if (ba->ba_type == B_AT_MAP)
map_print(map, SIZE_T_MAX, bv_name(bv));
else
hist_print((struct hist *)map, bv_name(bv));
map_clear(map);
bv->bv_value = NULL;
}
}
}
time_t
builtin_gettime(struct dt_evt *dtev)
{
struct timespec ts;
if (dtev == NULL) {
clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec;
}
return dtev->dtev_tsp.tv_sec;
}
static inline uint64_t
TIMESPEC_TO_NSEC(struct timespec *ts)
{
return (ts->tv_sec * 1000000000L + ts->tv_nsec);
}
uint64_t
builtin_nsecs(struct dt_evt *dtev)
{
struct timespec ts;
if (dtev == NULL) {
clock_gettime(CLOCK_REALTIME, &ts);
return TIMESPEC_TO_NSEC(&ts);
}
return TIMESPEC_TO_NSEC(&dtev->dtev_tsp);
}
const char *
builtin_stack(struct dt_evt *dtev, int kernel, unsigned long offset)
{
struct stacktrace *st = &dtev->dtev_kstack;
static char buf[4096];
const char *last = "\nkernel\n";
char *bp;
size_t i;
int sz;
if (!kernel) {
st = &dtev->dtev_ustack;
last = "\nuserland\n";
} else if (st->st_count == 0) {
return "\nuserland\n";
}
buf[0] = '\0';
bp = buf;
sz = sizeof(buf);
for (i = 0; i < st->st_count; i++) {
int l;
if (!kernel)
l = kelf_snprintsym(uelf, bp, sz - 1, st->st_pc[i],
offset);
else
l = kelf_snprintsym(kelf, bp, sz - 1, st->st_pc[i],
offset);
if (l < 0)
break;
if (l >= sz - 1) {
bp += sz - 1;
sz = 1;
break;
}
bp += l;
sz -= l;
}
snprintf(bp, sz, "%s", last);
return buf;
}
const char *
builtin_arg(struct dt_evt *dtev, enum bt_argtype dat)
{
static char buf[sizeof("18446744073709551615")]; /* UINT64_MAX */
struct dtioc_probe_info *dtpi;
struct dtioc_arg_info *dtai;
const char *argtype, *fmt;
unsigned int argn;
long value;
argn = dat - B_AT_BI_ARG0;
dtpi = &dt_dtpis[dtev->dtev_pbn - 1];
if (dtpi == NULL || argn >= dtpi->dtpi_nargs)
return "0";
dtai = dt_args[dtev->dtev_pbn - 1];
argtype = dtai[argn].dtai_argtype;
if (strncmp(argtype, "int", DTNAMESIZE) == 0) {
fmt = "%d";
value = (int)dtev->dtev_args[argn];
} else {
fmt = "%lu";
value = dtev->dtev_args[argn];
}
snprintf(buf, sizeof(buf), fmt, dtev->dtev_args[argn]);
return buf;
}
/*
* Returns non-zero if the program should halt.
*/
int
stmt_eval(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_stmt *bbs;
struct bt_cond *bc;
int halt = 0;
switch (bs->bs_act) {
case B_AC_BUCKETIZE:
stmt_bucketize(bs, dtev);
break;
case B_AC_CLEAR:
stmt_clear(bs);
break;
case B_AC_DELETE:
stmt_delete(bs, dtev);
break;
case B_AC_EXIT:
halt = 1;
break;
case B_AC_INSERT:
stmt_insert(bs, dtev);
break;
case B_AC_PRINT:
stmt_print(bs, dtev);
break;
case B_AC_PRINTF:
stmt_printf(bs, dtev);
break;
case B_AC_STORE:
stmt_store(bs, dtev);
break;
case B_AC_TEST:
bc = (struct bt_cond *)bs->bs_var;
if (stmt_test(bs, dtev) == true)
bbs = bc->bc_condbs;
else
bbs = bc->bc_elsebs;
while (bbs != NULL) {
if (stmt_eval(bbs, dtev))
return 1;
bbs = SLIST_NEXT(bbs, bs_next);
}
break;
case B_AC_TIME:
stmt_time(bs, dtev);
break;
case B_AC_ZERO:
stmt_zero(bs);
break;
default:
xabort("no handler for action type %d", bs->bs_act);
}
return halt;
}
/*
* Increment a bucket: { @h = hist(v); } or { @h = lhist(v, min, max, step); }
*
* In this case 'h' is represented by `bv' and '(min, max, step)' by `brange'.
*/
void
stmt_bucketize(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_arg *brange, *bhist = SLIST_FIRST(&bs->bs_args);
struct bt_arg *bval = (struct bt_arg *)bs->bs_var;
struct bt_var *bv = bhist->ba_value;
struct hist *hist;
const char *bucket;
long step = 0;
assert(bhist->ba_type == B_AT_HIST);
assert(SLIST_NEXT(bval, ba_next) == NULL);
brange = bhist->ba_key;
bucket = ba2bucket(bval, brange, dtev, &step);
if (bucket == NULL) {
debug("hist=%p '%s' value=%lu out of range\n", bv->bv_value,
bv_name(bv), ba2long(bval, dtev));
return;
}
debug("hist=%p '%s' increment bucket '%s'\n", bv->bv_value,
bv_name(bv), bucket);
/* hist is NULL before first insert or after clear() */
hist = (struct hist *)bv->bv_value;
if (hist == NULL)
hist = hist_new(step);
hist_increment(hist, bucket);
debug("hist=%p '%s' increment bucket=%p '%s' bval=%p\n", hist,
bv_name(bv), brange, bucket, bval);
bv->bv_value = (struct bt_arg *)hist;
bv->bv_type = B_VT_HIST;
}
/*
* Empty a map: { clear(@map); }
*/
void
stmt_clear(struct bt_stmt *bs)
{
struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
struct bt_var *bv = ba->ba_value;
struct map *map;
assert(bs->bs_var == NULL);
assert(ba->ba_type == B_AT_VAR);
map = (struct map *)bv->bv_value;
if (map == NULL)
return;
if (bv->bv_type != B_VT_MAP && bv->bv_type != B_VT_HIST)
errx(1, "invalid variable type for clear(%s)", ba_name(ba));
map_clear(map);
bv->bv_value = NULL;
debug("map=%p '%s' clear\n", map, bv_name(bv));
}
/*
* Map delete: { delete(@map[key]); }
*
* In this case 'map' is represented by `bv' and 'key' by `bkey'.
*/
void
stmt_delete(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_arg *bkey, *bmap = SLIST_FIRST(&bs->bs_args);
struct bt_var *bv = bmap->ba_value;
struct map *map;
const char *hash;
assert(bmap->ba_type == B_AT_MAP);
assert(bs->bs_var == NULL);
map = (struct map *)bv->bv_value;
if (map == NULL)
return;
bkey = bmap->ba_key;
hash = ba2hash(bkey, dtev);
debug("map=%p '%s' delete key=%p '%s'\n", map, bv_name(bv), bkey, hash);
map_delete(map, hash);
}
/*
* Map insert: { @map[key] = 42; }
*
* In this case 'map' is represented by `bv', 'key' by `bkey' and
* '42' by `bval'.
*/
void
stmt_insert(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_arg *bkey, *bmap = SLIST_FIRST(&bs->bs_args);
struct bt_arg *bval = (struct bt_arg *)bs->bs_var;
struct bt_var *bv = bmap->ba_value;
struct map *map;
const char *hash;
long val;
assert(bmap->ba_type == B_AT_MAP);
assert(SLIST_NEXT(bval, ba_next) == NULL);
bkey = bmap->ba_key;
hash = ba2hash(bkey, dtev);
/* map is NULL before first insert or after clear() */
map = (struct map *)bv->bv_value;
if (map == NULL)
map = map_new();
/* Operate on existring value for count(), max(), min() and sum(). */
switch (bval->ba_type) {
case B_AT_MF_COUNT:
val = ba2long(map_get(map, hash), NULL);
val++;
bval = ba_new(val, B_AT_LONG);
break;
case B_AT_MF_MAX:
val = ba2long(map_get(map, hash), NULL);
val = MAXIMUM(val, ba2long(bval->ba_value, dtev));
bval = ba_new(val, B_AT_LONG);
break;
case B_AT_MF_MIN:
val = ba2long(map_get(map, hash), NULL);
val = MINIMUM(val, ba2long(bval->ba_value, dtev));
bval = ba_new(val, B_AT_LONG);
break;
case B_AT_MF_SUM:
val = ba2long(map_get(map, hash), NULL);
val += ba2long(bval->ba_value, dtev);
bval = ba_new(val, B_AT_LONG);
break;
default:
bval = baeval(bval, dtev);
break;
}
map_insert(map, hash, bval);
debug("map=%p '%s' insert key=%p '%s' bval=%p\n", map,
bv_name(bv), bkey, hash, bval);
bv->bv_value = (struct bt_arg *)map;
bv->bv_type = B_VT_MAP;
}
/*
* Print variables: { print(890); print(@map[, 8]); print(comm); }
*
* In this case the global variable 'map' is pointed at by `ba'
* and '8' is represented by `btop'.
*/
void
stmt_print(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_arg *btop, *ba = SLIST_FIRST(&bs->bs_args);
struct bt_var *bv = ba->ba_value;
struct map *map;
size_t top = SIZE_T_MAX;
assert(bs->bs_var == NULL);
/* Parse optional `top' argument. */
btop = SLIST_NEXT(ba, ba_next);
if (btop != NULL) {
assert(SLIST_NEXT(btop, ba_next) == NULL);
top = ba2long(btop, dtev);
}
/* Static argument. */
if (ba->ba_type != B_AT_VAR) {
assert(btop == NULL);
printf("%s\n", ba2str(ba, dtev));
return;
}
map = (struct map *)bv->bv_value;
if (map == NULL)
return;
debug("map=%p '%s' print (top=%d)\n", bv->bv_value, bv_name(bv), top);
if (bv->bv_type == B_VT_MAP)
map_print(map, top, bv_name(bv));
else if (bv->bv_type == B_VT_HIST)
hist_print((struct hist *)map, bv_name(bv));
else
printf("%s\n", ba2str(ba, dtev));
}
/*
* Variable store: { @var = 3; }
*
* In this case '3' is represented by `ba', the argument of a STORE
* action.
*
* If the argument depends of the value of an event (builtin) or is
* the result of an operation, its evaluation is stored in a new `ba'.
*/
void
stmt_store(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
struct bt_var *bvar, *bv = bs->bs_var;
struct map *map;
assert(SLIST_NEXT(ba, ba_next) == NULL);
switch (ba->ba_type) {
case B_AT_STR:
bv->bv_value = ba;
bv->bv_type = B_VT_STR;
break;
case B_AT_LONG:
bv->bv_value = ba;
bv->bv_type = B_VT_LONG;
break;
case B_AT_VAR:
bvar = ba->ba_value;
bv->bv_type = bvar->bv_type;
bv->bv_value = bvar->bv_value;
break;
case B_AT_MAP:
bvar = ba->ba_value;
map = (struct map *)bvar->bv_value;
/* Uninitialized map */
if (map == NULL)
bv->bv_value = 0;
else
bv->bv_value = map_get(map, ba2hash(ba->ba_key, dtev));
bv->bv_type = B_VT_LONG; /* XXX should we type map? */
break;
case B_AT_TUPLE:
bv->bv_value = baeval(ba, dtev);
bv->bv_type = B_VT_TUPLE;
break;
case B_AT_BI_PID:
case B_AT_BI_TID:
case B_AT_BI_CPU:
case B_AT_BI_NSECS:
case B_AT_BI_RETVAL:
case B_AT_BI_ARG0 ... B_AT_BI_ARG9:
case B_AT_OP_PLUS ... B_AT_OP_LOR:
bv->bv_value = baeval(ba, dtev);
bv->bv_type = B_VT_LONG;
break;
case B_AT_BI_COMM:
case B_AT_BI_KSTACK:
case B_AT_BI_USTACK:
case B_AT_BI_PROBE:
case B_AT_FN_STR:
bv->bv_value = baeval(ba, dtev);
bv->bv_type = B_VT_STR;
break;
default:
xabort("store not implemented for type %d", ba->ba_type);
}
debug("bv=%p var '%s' store (%p)='%s'\n", bv, bv_name(bv), bv->bv_value,
ba2str(bv->bv_value, dtev));
}
/*
* String conversion { str($1); string($1, 3); }
*
* Since fn_str is currently only called in ba2str, *buf should be a pointer
* to the static buffer provided by ba2str.
*/
struct bt_arg *
fn_str(struct bt_arg *ba, struct dt_evt *dtev, char *buf)
{
struct bt_arg *arg, *index;
ssize_t len = STRLEN;
assert(ba->ba_type == B_AT_FN_STR);
arg = (struct bt_arg*)ba->ba_value;
assert(arg != NULL);
index = SLIST_NEXT(arg, ba_next);
if (index != NULL) {
/* Should have only 1 optional argument. */
assert(SLIST_NEXT(index, ba_next) == NULL);
len = MINIMUM(ba2long(index, dtev) + 1, STRLEN);
}
/* All negative lengths behave the same as a zero length. */
if (len < 1)
return ba_new("", B_AT_STR);
strlcpy(buf, ba2str(arg, dtev), len);
return ba_new(buf, B_AT_STR);
}
/*
* Expression test: { if (expr) stmt; }
*/
bool
stmt_test(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_arg *ba;
if (bs == NULL)
return true;
ba = SLIST_FIRST(&bs->bs_args);
return baexpr2long(ba, dtev) != 0;
}
/*
* Print time: { time("%H:%M:%S"); }
*/
void
stmt_time(struct bt_stmt *bs, struct dt_evt *dtev)
{
struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
time_t time;
struct tm *tm;
char buf[64];
assert(bs->bs_var == NULL);
assert(ba->ba_type == B_AT_STR);
assert(strlen(ba2str(ba, dtev)) < (sizeof(buf) - 1));
time = builtin_gettime(dtev);
tm = localtime(&time);
strftime(buf, sizeof(buf), ba2str(ba, dtev), tm);
printf("%s", buf);
}
/*
* Set entries to 0: { zero(@map); }
*/
void
stmt_zero(struct bt_stmt *bs)
{
struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
struct bt_var *bv = ba->ba_value;
struct map *map;
assert(bs->bs_var == NULL);
assert(ba->ba_type == B_AT_VAR);
map = (struct map *)bv->bv_value;
if (map == NULL)
return;
if (bv->bv_type != B_VT_MAP && bv->bv_type != B_VT_HIST)
errx(1, "invalid variable type for zero(%s)", ba_name(ba));
map_zero(map);
debug("map=%p '%s' zero\n", map, bv_name(bv));
}
struct bt_arg *
ba_read(struct bt_arg *ba)
{
struct bt_var *bv = ba->ba_value;
assert(ba->ba_type == B_AT_VAR);
debug("bv=%p read '%s' (%p)\n", bv, bv_name(bv), bv->bv_value);
/* Handle map/hist access after clear(). */
if (bv->bv_value == NULL)
return &g_nullba;
return bv->bv_value;
}
// XXX
extern struct bt_arg *ba_append(struct bt_arg *, struct bt_arg *);
/*
* Return a new argument that doesn't depend on `dtev'. This is used
* when storing values in variables, maps, etc.
*/
struct bt_arg *
baeval(struct bt_arg *bval, struct dt_evt *dtev)
{
struct bt_arg *ba, *bh = NULL;
switch (bval->ba_type) {
case B_AT_VAR:
ba = baeval(ba_read(bval), NULL);
break;
case B_AT_LONG:
case B_AT_BI_PID:
case B_AT_BI_TID:
case B_AT_BI_CPU:
case B_AT_BI_NSECS:
case B_AT_BI_ARG0 ... B_AT_BI_ARG9:
case B_AT_BI_RETVAL:
case B_AT_OP_PLUS ... B_AT_OP_LOR:
ba = ba_new(ba2long(bval, dtev), B_AT_LONG);
break;
case B_AT_STR:
case B_AT_BI_COMM:
case B_AT_BI_KSTACK:
case B_AT_BI_USTACK:
case B_AT_BI_PROBE:
case B_AT_FN_STR:
ba = ba_new(ba2str(bval, dtev), B_AT_STR);
break;
case B_AT_TUPLE:
ba = bval->ba_value;
do {
bh = ba_append(bh, baeval(ba, dtev));
} while ((ba = SLIST_NEXT(ba, ba_next)) != NULL);
ba = ba_new(bh, B_AT_TUPLE);
break;
default:
xabort("no eval support for type %d", bval->ba_type);
}
return ba;
}
/*
* Return a string of coma-separated values
*/
const char *
ba2hash(struct bt_arg *ba, struct dt_evt *dtev)
{
static char buf[KLEN];
char *hash;
int l, len;
buf[0] = '\0';
l = snprintf(buf, sizeof(buf), "%s", ba2str(ba, dtev));
if (l < 0 || (size_t)l > sizeof(buf)) {
warn("string too long %d > %lu", l, sizeof(buf));
return buf;
}
len = 0;
while ((ba = SLIST_NEXT(ba, ba_next)) != NULL) {
len += l;
hash = buf + len;
l = snprintf(hash, sizeof(buf) - len, ", %s", ba2str(ba, dtev));
if (l < 0 || (size_t)l > (sizeof(buf) - len)) {
warn("hash too long %d > %lu", l + len, sizeof(buf));
break;
}
}
return buf;
}
static unsigned long
next_pow2(unsigned long x)
{
size_t i;
x--;
for (i = 0; i < (sizeof(x) * 8) - 1; i++)
x |= (x >> 1);
return x + 1;
}
/*
* Return the ceiling value the interval holding `ba' or NULL if it is
* out of the (min, max) values.
*/
const char *
ba2bucket(struct bt_arg *ba, struct bt_arg *brange, struct dt_evt *dtev,
long *pstep)
{
static char buf[KLEN];
long val, bucket;
int l;
val = ba2long(ba, dtev);
if (brange == NULL)
bucket = next_pow2(val);
else {
long min, max, step;
assert(brange->ba_type == B_AT_LONG);
min = ba2long(brange, NULL);
brange = SLIST_NEXT(brange, ba_next);
assert(brange->ba_type == B_AT_LONG);
max = ba2long(brange, NULL);
if ((val < min) || (val > max))
return NULL;
brange = SLIST_NEXT(brange, ba_next);
assert(brange->ba_type == B_AT_LONG);
step = ba2long(brange, NULL);
bucket = ((val / step) + 1) * step;
*pstep = step;
}
buf[0] = '\0';
l = snprintf(buf, sizeof(buf), "%lu", bucket);
if (l < 0 || (size_t)l > sizeof(buf)) {
warn("string too long %d > %lu", l, sizeof(buf));
return buf;
}
return buf;
}
/*
* Evaluate the operation encoded in `ba' and return its result.
*/
long
baexpr2long(struct bt_arg *ba, struct dt_evt *dtev)
{
static long recursions;
struct bt_arg *lhs, *rhs;
long lval, rval, result;
if (++recursions >= __MAXOPERANDS)
errx(1, "too many operands (>%d) in expression", __MAXOPERANDS);
lhs = ba->ba_value;
rhs = SLIST_NEXT(lhs, ba_next);
/*
* String comparison also use '==' and '!='.
*/
if (lhs->ba_type == B_AT_STR ||
(rhs != NULL && rhs->ba_type == B_AT_STR)) {
char lstr[STRLEN], rstr[STRLEN];
strlcpy(lstr, ba2str(lhs, dtev), sizeof(lstr));
strlcpy(rstr, ba2str(rhs, dtev), sizeof(rstr));
result = strncmp(lstr, rstr, STRLEN) == 0;
switch (ba->ba_type) {
case B_AT_OP_EQ:
break;
case B_AT_OP_NE:
result = !result;
break;
default:
warnx("operation '%d' unsupported on strings",
ba->ba_type);
result = 1;
}
debug("ba=%p eval '(%s %s %s) = %d'\n", ba, lstr, ba_name(ba),
rstr, result);
goto out;
}
lval = ba2long(lhs, dtev);
if (rhs == NULL) {
rval = 0;
} else {
assert(SLIST_NEXT(rhs, ba_next) == NULL);
rval = ba2long(rhs, dtev);
}
switch (ba->ba_type) {
case B_AT_OP_PLUS:
result = lval + rval;
break;
case B_AT_OP_MINUS:
result = lval - rval;
break;
case B_AT_OP_MULT:
result = lval * rval;
break;
case B_AT_OP_DIVIDE:
result = lval / rval;
break;
case B_AT_OP_MODULO:
result = lval % rval;
break;
case B_AT_OP_BAND:
result = lval & rval;
break;
case B_AT_OP_XOR:
result = lval ^ rval;
break;
case B_AT_OP_BOR:
result = lval | rval;
break;
case B_AT_OP_EQ:
result = (lval == rval);
break;
case B_AT_OP_NE:
result = (lval != rval);
break;
case B_AT_OP_LE:
result = (lval <= rval);
break;
case B_AT_OP_LT:
result = (lval < rval);
break;
case B_AT_OP_GE:
result = (lval >= rval);
break;
case B_AT_OP_GT:
result = (lval > rval);
break;
case B_AT_OP_LAND:
result = (lval && rval);
break;
case B_AT_OP_LOR:
result = (lval || rval);
break;
default:
xabort("unsupported operation %d", ba->ba_type);
}
debug("ba=%p eval '(%ld %s %ld) = %d'\n", ba, lval, ba_name(ba),
rval, result);
out:
--recursions;
return result;
}
const char *
ba_name(struct bt_arg *ba)
{
switch (ba->ba_type) {
case B_AT_STR:
return (const char *)ba->ba_value;
case B_AT_LONG:
return ba2str(ba, NULL);
case B_AT_NIL:
return "0";
case B_AT_VAR:
case B_AT_MAP:
case B_AT_HIST:
break;
case B_AT_BI_PID:
return "pid";
case B_AT_BI_TID:
return "tid";
case B_AT_BI_COMM:
return "comm";
case B_AT_BI_CPU:
return "cpu";
case B_AT_BI_NSECS:
return "nsecs";
case B_AT_BI_KSTACK:
return "kstack";
case B_AT_BI_USTACK:
return "ustack";
case B_AT_BI_ARG0:
return "arg0";
case B_AT_BI_ARG1:
return "arg1";
case B_AT_BI_ARG2:
return "arg2";
case B_AT_BI_ARG3:
return "arg3";
case B_AT_BI_ARG4:
return "arg4";
case B_AT_BI_ARG5:
return "arg5";
case B_AT_BI_ARG6:
return "arg6";
case B_AT_BI_ARG7:
return "arg7";
case B_AT_BI_ARG8:
return "arg8";
case B_AT_BI_ARG9:
return "arg9";
case B_AT_BI_ARGS:
return "args";
case B_AT_BI_RETVAL:
return "retval";
case B_AT_BI_PROBE:
return "probe";
case B_AT_FN_STR:
return "str";
case B_AT_OP_PLUS:
return "+";
case B_AT_OP_MINUS:
return "-";
case B_AT_OP_MULT:
return "*";
case B_AT_OP_DIVIDE:
return "/";
case B_AT_OP_MODULO:
return "%";
case B_AT_OP_BAND:
return "&";
case B_AT_OP_XOR:
return "^";
case B_AT_OP_BOR:
return "|";
case B_AT_OP_EQ:
return "==";
case B_AT_OP_NE:
return "!=";
case B_AT_OP_LE:
return "<=";
case B_AT_OP_LT:
return "<";
case B_AT_OP_GE:
return ">=";
case B_AT_OP_GT:
return ">";
case B_AT_OP_LAND:
return "&&";
case B_AT_OP_LOR:
return "||";
default:
xabort("unsupported type %d", ba->ba_type);
}
assert(ba->ba_type == B_AT_VAR || ba->ba_type == B_AT_MAP ||
ba->ba_type == B_AT_HIST);
static char buf[64];
size_t sz;
int l;
buf[0] = '@';
buf[1] = '\0';
sz = sizeof(buf) - 1;
l = snprintf(buf+1, sz, "%s", bv_name(ba->ba_value));
if (l < 0 || (size_t)l > sz) {
warn("string too long %d > %zu", l, sz);
return buf;
}
if (ba->ba_type == B_AT_MAP) {
sz -= l;
l = snprintf(buf+1+l, sz, "[%s]", ba_name(ba->ba_key));
if (l < 0 || (size_t)l > sz) {
warn("string too long %d > %zu", l, sz);
return buf;
}
}
return buf;
}
/*
* Return the representation of `ba' as long.
*/
long
ba2long(struct bt_arg *ba, struct dt_evt *dtev)
{
struct bt_var *bv;
long val;
switch (ba->ba_type) {
case B_AT_STR:
val = (*ba2str(ba, dtev) == '\0') ? 0 : 1;
break;
case B_AT_LONG:
val = (long)ba->ba_value;
break;
case B_AT_VAR:
ba = ba_read(ba);
val = (long)ba->ba_value;
break;
case B_AT_MAP:
bv = ba->ba_value;
/* Uninitialized map */
if (bv->bv_value == NULL)
return 0;
val = ba2long(map_get((struct map *)bv->bv_value,
ba2hash(ba->ba_key, dtev)), dtev);
break;
case B_AT_NIL:
val = 0L;
break;
case B_AT_BI_PID:
val = dtev->dtev_pid;
break;
case B_AT_BI_TID:
val = dtev->dtev_tid;
break;
case B_AT_BI_CPU:
val = dtev->dtev_cpu;
break;
case B_AT_BI_NSECS:
val = builtin_nsecs(dtev);
break;
case B_AT_BI_ARG0 ... B_AT_BI_ARG9:
val = dtev->dtev_args[ba->ba_type - B_AT_BI_ARG0];
break;
case B_AT_BI_RETVAL:
val = dtev->dtev_retval[0];
break;
case B_AT_BI_PROBE:
val = dtev->dtev_pbn;
break;
case B_AT_OP_PLUS ... B_AT_OP_LOR:
val = baexpr2long(ba, dtev);
break;
default:
xabort("no long conversion for type %d", ba->ba_type);
}
return val;
}
/*
* Return the representation of `ba' as string.
*/
const char *
ba2str(struct bt_arg *ba, struct dt_evt *dtev)
{
static char buf[STRLEN];
struct bt_var *bv;
struct dtioc_probe_info *dtpi;
unsigned long idx;
const char *str;
buf[0] = '\0';
switch (ba->ba_type) {
case B_AT_STR:
str = (const char *)ba->ba_value;
break;
case B_AT_LONG:
snprintf(buf, sizeof(buf), "%ld",(long)ba->ba_value);
str = buf;
break;
case B_AT_TUPLE:
snprintf(buf, sizeof(buf), "(%s)", ba2hash(ba->ba_value, dtev));
str = buf;
break;
case B_AT_TMEMBER:
idx = (unsigned long)ba->ba_key;
bv = ba->ba_value;
/* Uninitialized tuple */
if (bv->bv_value == NULL) {
str = buf;
break;
}
ba = bv->bv_value;
assert(ba->ba_type == B_AT_TUPLE);
ba = ba->ba_value;
while (ba != NULL && idx-- > 0) {
ba = SLIST_NEXT(ba, ba_next);
}
str = ba2str(ba, dtev);
break;
case B_AT_NIL:
str = "";
break;
case B_AT_BI_KSTACK:
str = builtin_stack(dtev, 1, 0);
break;
case B_AT_BI_USTACK:
str = builtin_stack(dtev, 0, dt_get_offset(dtev->dtev_pid));
break;
case B_AT_BI_COMM:
str = dtev->dtev_comm;
break;
case B_AT_BI_CPU:
snprintf(buf, sizeof(buf), "%u", dtev->dtev_cpu);
str = buf;
break;
case B_AT_BI_PID:
snprintf(buf, sizeof(buf), "%d", dtev->dtev_pid);
str = buf;
break;
case B_AT_BI_TID:
snprintf(buf, sizeof(buf), "%d", dtev->dtev_tid);
str = buf;
break;
case B_AT_BI_NSECS:
snprintf(buf, sizeof(buf), "%llu", builtin_nsecs(dtev));
str = buf;
break;
case B_AT_BI_ARG0 ... B_AT_BI_ARG9:
str = builtin_arg(dtev, ba->ba_type);
break;
case B_AT_BI_RETVAL:
snprintf(buf, sizeof(buf), "%ld", (long)dtev->dtev_retval[0]);
str = buf;
break;
case B_AT_BI_PROBE:
if (dtev->dtev_pbn == EVENT_BEGIN) {
str = "BEGIN";
break;
} else if (dtev->dtev_pbn == EVENT_END) {
str = "END";
break;
}
dtpi = &dt_dtpis[dtev->dtev_pbn - 1];
if (dtpi != NULL)
snprintf(buf, sizeof(buf), "%s:%s:%s",
dtpi->dtpi_prov, dtpi_func(dtpi), dtpi->dtpi_name);
else
snprintf(buf, sizeof(buf), "%u", dtev->dtev_pbn);
str = buf;
break;
case B_AT_MAP:
bv = ba->ba_value;
/* Uninitialized map */
if (bv->bv_value == NULL) {
str = buf;
break;
}
str = ba2str(map_get((struct map *)bv->bv_value,
ba2hash(ba->ba_key, dtev)), dtev);
break;
case B_AT_VAR:
str = ba2str(ba_read(ba), dtev);
break;
case B_AT_FN_STR:
str = (const char*)(fn_str(ba, dtev, buf))->ba_value;
break;
case B_AT_OP_PLUS ... B_AT_OP_LOR:
snprintf(buf, sizeof(buf), "%ld", ba2long(ba, dtev));
str = buf;
break;
case B_AT_MF_COUNT:
case B_AT_MF_MAX:
case B_AT_MF_MIN:
case B_AT_MF_SUM:
assert(0);
break;
default:
xabort("no string conversion for type %d", ba->ba_type);
}
return str;
}
int
ba2flags(struct bt_arg *ba)
{
int flags = 0;
assert(ba->ba_type != B_AT_MAP);
assert(ba->ba_type != B_AT_TUPLE);
switch (ba->ba_type) {
case B_AT_STR:
case B_AT_LONG:
case B_AT_TMEMBER:
case B_AT_VAR:
case B_AT_HIST:
case B_AT_NIL:
break;
case B_AT_BI_KSTACK:
flags |= DTEVT_KSTACK;
break;
case B_AT_BI_USTACK:
flags |= DTEVT_USTACK;
break;
case B_AT_BI_COMM:
flags |= DTEVT_EXECNAME;
break;
case B_AT_BI_CPU:
case B_AT_BI_PID:
case B_AT_BI_TID:
case B_AT_BI_NSECS:
break;
case B_AT_BI_ARG0 ... B_AT_BI_ARG9:
flags |= DTEVT_FUNCARGS;
break;
case B_AT_BI_RETVAL:
case B_AT_BI_PROBE:
break;
case B_AT_MF_COUNT:
case B_AT_MF_MAX:
case B_AT_MF_MIN:
case B_AT_MF_SUM:
case B_AT_FN_STR:
break;
case B_AT_OP_PLUS ... B_AT_OP_LOR:
flags |= ba2dtflags(ba->ba_value);
break;
default:
xabort("invalid argument type %d", ba->ba_type);
}
return flags;
}
/*
* Return dt(4) flags indicating which data should be recorded by the
* kernel, if any, for a given `ba'.
*/
int
ba2dtflags(struct bt_arg *ba)
{
static long recursions;
struct bt_arg *bval;
int flags = 0;
if (++recursions >= __MAXOPERANDS)
errx(1, "too many operands (>%d) in expression", __MAXOPERANDS);
do {
if (ba->ba_type == B_AT_MAP)
flags |= ba2flags(ba->ba_key);
else if (ba->ba_type == B_AT_TUPLE) {
bval = ba->ba_value;
do {
flags |= ba2flags(bval);
} while ((bval = SLIST_NEXT(bval, ba_next)) != NULL);
} else
flags |= ba2flags(ba);
} while ((ba = SLIST_NEXT(ba, ba_next)) != NULL);
--recursions;
return flags;
}
long
bacmp(struct bt_arg *a, struct bt_arg *b)
{
char astr[STRLEN];
long val;
if (a->ba_type != b->ba_type)
return a->ba_type - b->ba_type;
switch (a->ba_type) {
case B_AT_LONG:
return ba2long(a, NULL) - ba2long(b, NULL);
case B_AT_STR:
strlcpy(astr, ba2str(a, NULL), sizeof(astr));
return strcmp(astr, ba2str(b, NULL));
case B_AT_TUPLE:
/* Compare two lists of arguments one by one. */
a = a->ba_value;
b = b->ba_value;
do {
val = bacmp(a, b);
if (val != 0)
break;
a = SLIST_NEXT(a, ba_next);
b = SLIST_NEXT(b, ba_next);
if (a == NULL && b != NULL)
val = -1;
else if (a != NULL && b == NULL)
val = 1;
} while (a != NULL && b != NULL);
return val;
default:
xabort("no compare support for type %d", a->ba_type);
}
}
__dead void
xabort(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
abort();
}
void
debug(const char *fmt, ...)
{
va_list ap;
if (verbose < 2)
return;
fprintf(stderr, "debug: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
void
debugx(const char *fmt, ...)
{
va_list ap;
if (verbose < 2)
return;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
void
debug_dump_term(struct bt_arg *ba)
{
switch (ba->ba_type) {
case B_AT_LONG:
debugx("%s", ba2str(ba, NULL));
break;
case B_AT_OP_PLUS ... B_AT_OP_LOR:
debug_dump_expr(ba);
break;
default:
debugx("%s", ba_name(ba));
}
}
void
debug_dump_expr(struct bt_arg *ba)
{
struct bt_arg *lhs, *rhs;
lhs = ba->ba_value;
rhs = SLIST_NEXT(lhs, ba_next);
/* Left */
debug_dump_term(lhs);
/* Right */
if (rhs != NULL) {
debugx(" %s ", ba_name(ba));
debug_dump_term(rhs);
} else {
if (ba->ba_type != B_AT_OP_NE)
debugx(" %s NULL", ba_name(ba));
}
}
void
debug_dump_filter(struct bt_rule *r)
{
struct bt_stmt *bs;
if (verbose < 2)
return;
if (r->br_filter == NULL) {
debugx("\n");
return;
}
bs = r->br_filter->bf_condition;
debugx(" /");
debug_dump_expr(SLIST_FIRST(&bs->bs_args));
debugx("/\n");
}
const char *
debug_probe_name(struct bt_probe *bp)
{
static char buf[64];
if (verbose < 2)
return "";
if (bp->bp_type == B_PT_BEGIN)
return "BEGIN";
if (bp->bp_type == B_PT_END)
return "END";
assert(bp->bp_type == B_PT_PROBE);
if (bp->bp_rate) {
snprintf(buf, sizeof(buf), "%s:%s:%u", bp->bp_prov,
bp->bp_unit, bp->bp_rate);
} else {
snprintf(buf, sizeof(buf), "%s:%s:%s", bp->bp_prov,
bp->bp_unit, bp->bp_name);
}
return buf;
}
unsigned long
dt_get_offset(pid_t pid)
{
static struct dtioc_getaux cache[32];
static int next;
struct dtioc_getaux *aux = NULL;
int i;
for (i = 0; i < 32; i++) {
if (cache[i].dtga_pid != pid)
continue;
aux = cache + i;
break;
}
if (aux == NULL) {
aux = &cache[next++];
next %= 32;
aux->dtga_pid = pid;
if (ioctl(dtfd, DTIOCGETAUXBASE, aux))
aux->dtga_auxbase = 0;
}
return aux->dtga_auxbase;
}