#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/tree.h>
#include "evtr.h"
#include "internal.h"
unsigned evtr_debug;
static
void
printd_set_flags(const char *str, unsigned int *flags)
{
for (; *str; ++str) {
if ('A' == *str) {
*flags = -1;
return;
}
if (!islower(*str))
err(2, "invalid debug flag %c\n", *str);
*flags |= 1 << (*str - 'a');
}
}
enum {
MAX_EVHDR_SIZE = PATH_MAX + 200,
EVTR_NS_PATH = 0x1,
EVTR_NS_FUNC,
EVTR_NS_DSTR,
EVTR_NS_MAX,
NR_BUCKETS = 1021,
PARSE_ERR_BUFSIZE = 256,
REC_ALIGN = 8,
REC_BOUNDARY = 1 << 14,
FILTF_ID = 0x10,
EVTRF_WR = 0x1,
EVTRQF_PENDING = 0x1,
};
typedef uint16_t fileid_t;
typedef uint16_t funcid_t;
typedef uint16_t fmtid_t;
struct trace_event_header {
uint8_t type;
uint64_t ts;
} __attribute__((packed));
struct probe_event_header {
struct trace_event_header eh;
fileid_t file;
funcid_t caller1;
funcid_t caller2;
funcid_t func;
uint16_t line;
fmtid_t fmt;
uint16_t datalen;
uint8_t cpu;
} __attribute__((packed));
struct string_event_header {
struct trace_event_header eh;
uint16_t ns;
uint32_t id;
uint16_t len;
} __attribute__((packed));
struct fmt_event_header {
struct trace_event_header eh;
uint16_t id;
uint8_t subsys_len;
uint8_t fmt_len;
} __attribute__((packed));
struct cpuinfo_event_header {
double freq;
uint8_t cpu;
} __attribute__((packed));
struct hashentry {
uintptr_t key;
uintptr_t val;
struct hashentry *next;
};
struct hashtab {
struct hashentry *buckets[NR_BUCKETS];
uintptr_t (*hashfunc)(uintptr_t);
uintptr_t (*cmpfunc)(uintptr_t, uintptr_t);
};
struct symtab {
struct hashtab tab;
};
struct event_fmt {
const char *subsys;
const char *fmt;
};
struct event_filter_unresolved {
TAILQ_ENTRY(event_filter_unresolved) link;
evtr_filter_t filt;
};
struct id_map {
RB_ENTRY(id_map) rb_node;
int id;
const void *data;
};
RB_HEAD(id_tree, id_map);
struct string_map {
struct id_tree root;
};
struct fmt_map {
struct id_tree root;
};
RB_HEAD(thread_tree, evtr_thread);
struct thread_map {
struct thread_tree root;
};
struct event_callback {
void (*cb)(evtr_event_t, void *data);
void *data;
};
struct cpu {
struct evtr_thread *td;
double freq;
};
struct evtr {
FILE *f;
int flags;
int err;
const char *errmsg;
off_t bytes;
union {
struct hashtab_str *strings[EVTR_NS_MAX - 1];
struct string_map maps[EVTR_NS_MAX - 1];
};
union {
struct fmt_map fmtmap;
struct hashtab_str *fmts;
};
struct thread_map threads;
struct cpu *cpus;
int ncpus;
};
struct evtr_query {
evtr_t evtr;
off_t off;
evtr_filter_t filt;
int nfilt;
int nmatched;
int ntried;
void *buf;
int bufsize;
struct symtab *symtab;
int ncbs;
struct event_callback **cbs;
TAILQ_HEAD(, event_filter_unresolved) unresolved_filtq;
int err;
const char *errmsg;
char parse_err_buf[PARSE_ERR_BUFSIZE];
int flags;
struct evtr_event pending_event;
};
void
evtr_set_debug(const char *str)
{
printd_set_flags(str, &evtr_debug);
}
static int id_map_cmp(struct id_map *, struct id_map *);
RB_PROTOTYPE2(id_tree, id_map, rb_node, id_map_cmp, int);
RB_GENERATE2(id_tree, id_map, rb_node, id_map_cmp, int, id);
static int thread_cmp(struct evtr_thread *, struct evtr_thread *);
RB_PROTOTYPE2(thread_tree, evtr_thread, rb_node, thread_cmp, void *);
RB_GENERATE2(thread_tree, evtr_thread, rb_node, thread_cmp, void *, id);
static inline
void
validate_string(const char *str)
{
if (!(evtr_debug & MISC))
return;
for (; *str; ++str)
assert(isprint(*str));
}
static
void
id_tree_free(struct id_tree *root)
{
struct id_map *v, *n;
for (v = RB_MIN(id_tree, root); v; v = n) {
n = RB_NEXT(id_tree, root, v);
RB_REMOVE(id_tree, root, v);
}
}
static
int
evtr_register_callback(evtr_query_t q, void (*fn)(evtr_event_t, void *), void *d)
{
struct event_callback *cb;
void *cbs;
if (!(cb = malloc(sizeof(*cb)))) {
q->err = ENOMEM;
return !0;
}
cb->cb = fn;
cb->data = d;
if (!(cbs = realloc(q->cbs, (++q->ncbs) * sizeof(cb)))) {
--q->ncbs;
free(cb);
q->err = ENOMEM;
return !0;
}
q->cbs = cbs;
q->cbs[q->ncbs - 1] = cb;
return 0;
}
static
void
evtr_deregister_callbacks(evtr_query_t q)
{
int i;
for (i = 0; i < q->ncbs; ++i) {
free(q->cbs[i]);
}
free(q->cbs);
q->cbs = NULL;
}
static
void
evtr_run_callbacks(evtr_event_t ev, evtr_query_t q)
{
struct event_callback *cb;
int i;
for (i = 0; i < q->ncbs; ++i) {
cb = q->cbs[i];
cb->cb(ev, cb->data);
}
}
static
struct cpu *
evtr_cpu(evtr_t evtr, int c)
{
if ((c < 0) || (c >= evtr->ncpus))
return NULL;
return &evtr->cpus[c];
}
static int parse_format_data(evtr_event_t ev, const char *fmt, ...)
__printflike(2, 3) __scanflike(2, 3);
static
int
parse_format_data(evtr_event_t ev, const char *fmt, ...)
{
va_list ap;
char buf[2048];
if (strcmp(fmt, ev->fmt))
return 0;
vsnprintf(buf, sizeof(buf), fmt, __DECONST(void *, ev->fmtdata));
printd(MISC, "string is: %s\n", buf);
va_start(ap, fmt);
return vsscanf(buf, fmt, ap);
}
static
void
evtr_deregister_filters(evtr_query_t q, evtr_filter_t filt, int nfilt)
{
struct event_filter_unresolved *u, *tmp;
int i;
TAILQ_FOREACH_MUTABLE(u, &q->unresolved_filtq, link, tmp) {
for (i = 0; i < nfilt; ++i) {
if (u->filt == &filt[i]) {
TAILQ_REMOVE(&q->unresolved_filtq, u, link);
}
}
}
}
static
int
evtr_filter_register(evtr_query_t q, evtr_filter_t filt)
{
struct event_filter_unresolved *res;
if (!(res = malloc(sizeof(*res)))) {
q->err = ENOMEM;
return !0;
}
res->filt = filt;
TAILQ_INSERT_TAIL(&q->unresolved_filtq, res, link);
return 0;
}
static
int
evtr_query_needs_parsing(evtr_query_t q)
{
int i;
for (i = 0; i < q->nfilt; ++i)
if (q->filt[i].ev_type == EVTR_TYPE_STMT)
return !0;
return 0;
}
void
evtr_event_data(evtr_event_t ev, char *buf, size_t len)
{
if (ev->fmtdatalen) {
vsnprintf(buf, len, ev->fmt, __DECONST(void *, ev->fmtdata));
} else {
strlcpy(buf, ev->fmt, len);
}
}
int
evtr_error(evtr_t evtr)
{
return evtr->err || (evtr->errmsg != NULL);
}
const char *
evtr_errmsg(evtr_t evtr)
{
return evtr->errmsg ? evtr->errmsg : strerror(evtr->err);
}
int
evtr_query_error(evtr_query_t q)
{
return q->err || (q->errmsg != NULL) || evtr_error(q->evtr);
}
const char *
evtr_query_errmsg(evtr_query_t q)
{
return q->errmsg ? q->errmsg :
(q->err ? strerror(q->err) :
(evtr_errmsg(q->evtr)));
}
static
int
id_map_cmp(struct id_map *a, struct id_map *b)
{
return a->id - b->id;
}
static
int
thread_cmp(struct evtr_thread *a, struct evtr_thread *b)
{
ptrdiff_t d;
d = a->id - b->id;
if (d < 0)
return -1;
if (!d)
return 0;
return 1;
}
#define DEFINE_MAP_FIND(prefix, type) \
static \
type \
prefix ## _map_find(struct id_tree *tree, int id)\
{ \
struct id_map *sid; \
\
sid = id_tree_RB_LOOKUP(tree, id); \
return sid ? sid->data : NULL; \
}
DEFINE_MAP_FIND(string, const char *)
DEFINE_MAP_FIND(fmt, const struct event_fmt *)
static
struct evtr_thread *
thread_map_find(struct thread_map *map, void *id)
{
return thread_tree_RB_LOOKUP(&map->root, id);
}
#define DEFINE_MAP_INSERT(prefix, type, _cmp, _dup) \
static \
int \
prefix ## _map_insert(struct id_tree *tree, type data, int id) \
{ \
struct id_map *sid, *osid; \
\
sid = malloc(sizeof(*sid)); \
if (!sid) { \
return ENOMEM; \
} \
sid->id = id; \
sid->data = data; \
if ((osid = id_tree_RB_INSERT(tree, sid))) { \
free(sid); \
if (_cmp((type)osid->data, data)) { \
return EEXIST; \
} \
printd(DS, "mapping already exists, skipping\n"); \
\
return 0; \
} \
\
sid->data = _dup(data); \
return 0; \
}
static
void
thread_map_insert(struct thread_map *map, struct evtr_thread *td)
{
struct evtr_thread *otd;
if ((otd = thread_tree_RB_INSERT(&map->root, td))) {
free((void *)otd->comm);
otd->comm = td->comm;
free(td);
}
}
static
int
event_fmt_cmp(const struct event_fmt *a, const struct event_fmt *b)
{
int ret = 0;
if (a->subsys) {
if (b->subsys) {
ret = strcmp(a->subsys, b->subsys);
} else {
ret = strcmp(a->subsys, "");
}
} else if (b->subsys) {
ret = strcmp("", b->subsys);
}
if (ret)
return ret;
return strcmp(a->fmt, b->fmt);
}
static
struct event_fmt *
event_fmt_dup(const struct event_fmt *o)
{
struct event_fmt *n;
if (!(n = malloc(sizeof(*n)))) {
return n;
}
memcpy(n, o, sizeof(*n));
return n;
}
DEFINE_MAP_INSERT(string, const char *, strcmp, strdup)
DEFINE_MAP_INSERT(fmt, const struct event_fmt *, event_fmt_cmp, event_fmt_dup)
int
hash_find(const struct hashtab *tab, uintptr_t key, uintptr_t *val)
{
struct hashentry *ent;
for(ent = tab->buckets[tab->hashfunc(key)];
ent && tab->cmpfunc(ent->key, key);
ent = ent->next);
if (!ent)
return !0;
*val = ent->val;
return 0;
}
struct hashentry *
hash_insert(struct hashtab *tab, uintptr_t key, uintptr_t val)
{
struct hashentry *ent;
int hsh;
if (!(ent = malloc(sizeof(*ent)))) {
fprintf(stderr, "out of memory\n");
return NULL;
}
hsh = tab->hashfunc(key);
ent->next = tab->buckets[hsh];
ent->key = key;
ent->val = val;
tab->buckets[hsh] = ent;
return ent;
}
static
uintptr_t
cmpfunc_pointer(uintptr_t a, uintptr_t b)
{
return b - a;
}
static
uintptr_t
hashfunc_pointer(uintptr_t p)
{
return p % NR_BUCKETS;
}
struct hashtab *
hash_new(void)
{
struct hashtab *tab;
if (!(tab = calloc(sizeof(struct hashtab), 1)))
return tab;
tab->hashfunc = &hashfunc_pointer;
tab->cmpfunc = &cmpfunc_pointer;
return tab;
}
struct hashtab_str {
struct hashtab tab;
uint16_t id;
};
static
uintptr_t
hashfunc_string(uintptr_t p)
{
const char *str = (char *)p;
unsigned long hash = 5381;
int c;
while ((c = *str++))
hash = ((hash << 5) + hash) + c;
return hash % NR_BUCKETS;
}
static
uintptr_t
cmpfunc_string(uintptr_t a, uintptr_t b)
{
return strcmp((char *)a, (char *)b);
}
static
struct hashtab_str *
strhash_new(void)
{
struct hashtab_str *strtab;
if (!(strtab = calloc(sizeof(struct hashtab_str), 1)))
return strtab;
strtab->tab.hashfunc = &hashfunc_string;
strtab->tab.cmpfunc = &cmpfunc_string;
return strtab;
}
static
void
strhash_destroy(struct hashtab_str *strtab)
{
free(strtab);
}
static
int
strhash_find(struct hashtab_str *strtab, const char *str, uint16_t *id)
{
uintptr_t val;
if (hash_find(&strtab->tab, (uintptr_t)str, &val))
return !0;
*id = (uint16_t)val;
return 0;
}
static
int
strhash_insert(struct hashtab_str *strtab, const char *str, uint16_t *id)
{
uintptr_t val;
val = ++strtab->id;
if (strtab->id == 0) {
fprintf(stderr, "too many strings\n");
return ERANGE;
}
str = strdup(str);
if (!str) {
fprintf(stderr, "out of memory\n");
--strtab->id;
return ENOMEM;
}
hash_insert(&strtab->tab, (uintptr_t)str, (uintptr_t)val);
*id = strtab->id;
return 0;
}
struct symtab *
symtab_new(void)
{
struct symtab *symtab;
if (!(symtab = calloc(sizeof(struct symtab), 1)))
return symtab;
symtab->tab.hashfunc = &hashfunc_string;
symtab->tab.cmpfunc = &cmpfunc_string;
return symtab;
}
void
symtab_destroy(struct symtab *symtab)
{
free(symtab);
}
struct evtr_variable *
symtab_find(const struct symtab *symtab, const char *str)
{
uintptr_t val;
if (hash_find(&symtab->tab, (uintptr_t)str, &val))
return NULL;
return (struct evtr_variable *)val;
}
int
symtab_insert(struct symtab *symtab, const char *name,
struct evtr_variable *var)
{
name = strdup(name);
if (!name) {
fprintf(stderr, "out of memory\n");
return ENOMEM;
}
hash_insert(&symtab->tab, (uintptr_t)name, (uintptr_t)var);
return 0;
}
static
int
evtr_filter_match(evtr_query_t q, evtr_filter_t f, evtr_event_t ev)
{
if ((f->cpu != -1) && (f->cpu != ev->cpu))
return 0;
assert(!(f->flags & FILTF_ID));
if (ev->type != f->ev_type)
return 0;
if (ev->type == EVTR_TYPE_PROBE) {
if (f->fmt && strcmp(ev->fmt, f->fmt))
return 0;
} else if (ev->type == EVTR_TYPE_STMT) {
struct evtr_variable *var;
parse_var(f->var, q->symtab, &var, &q->parse_err_buf[0],
PARSE_ERR_BUFSIZE);
if (var != ev->stmt.var)
return 0;
}
return !0;
}
static
int
evtr_match_filters(struct evtr_query *q, evtr_event_t ev)
{
int i;
if (!q->nfilt)
return !0;
++q->ntried;
for (i = 0; i < q->nfilt; ++i) {
if (evtr_filter_match(q, &q->filt[i], ev)) {
++q->nmatched;
return !0;
}
}
return 0;
}
static
void
parse_callback(evtr_event_t ev, void *d)
{
evtr_query_t q = (evtr_query_t)d;
if (ev->type != EVTR_TYPE_PROBE)
return;
if (!ev->fmt || (ev->fmt[0] != '#'))
return;
memcpy(&q->pending_event, ev, sizeof(*ev));
parse_string(&q->pending_event, q->symtab, &ev->fmt[1],
&q->parse_err_buf[0], PARSE_ERR_BUFSIZE);
if (q->parse_err_buf[0]) {
q->errmsg = &q->parse_err_buf[0];
return;
}
if (!evtr_match_filters(q, &q->pending_event))
return;
q->flags |= EVTRQF_PENDING;
}
static
void
thread_creation_callback(evtr_event_t ev, void *d)
{
evtr_query_t q = (evtr_query_t)d;
evtr_t evtr = q->evtr;
struct evtr_thread *td;
void *ktd;
char buf[20];
if (parse_format_data(ev, "new_td %p %s", &ktd, buf) != 2) {
return;
}
buf[19] = '\0';
if (!(td = malloc(sizeof(*td)))) {
q->err = ENOMEM;
return;
}
td->id = ktd;
td->userdata = NULL;
if (!(td->comm = strdup(buf))) {
free(td);
q->err = ENOMEM;
return;
}
printd(DS, "inserting new thread %p: %s\n", td->id, td->comm);
thread_map_insert(&evtr->threads, td);
}
static
void
thread_switch_callback(evtr_event_t ev, void *d)
{
evtr_t evtr = ((evtr_query_t)d)->evtr;
struct evtr_thread *tdp, *tdn;
void *ktdp, *ktdn;
struct cpu *cpu;
static struct evtr_event tdcr;
static char *fmt = "new_td %p %s";
char tidstr[40];
void *fmtdata[2];
cpu = evtr_cpu(evtr, ev->cpu);
if (!cpu) {
printw("invalid cpu %d\n", ev->cpu);
return;
}
if (parse_format_data(ev, "sw %p > %p", &ktdp, &ktdn) != 2) {
return;
}
tdp = thread_map_find(&evtr->threads, ktdp);
if (!tdp) {
printd(DS, "switching from unknown thread %p\n", ktdp);
}
tdn = thread_map_find(&evtr->threads, ktdn);
if (!tdn) {
tdcr.type = EVTR_TYPE_PROBE;
tdcr.ts = ev->ts;
tdcr.file = NULL;
tdcr.func = NULL;
tdcr.line = 0;
tdcr.fmt = fmt;
tdcr.fmtdata = &fmtdata;
tdcr.fmtdatalen = sizeof(fmtdata);
tdcr.cpu = ev->cpu;
tdcr.td = NULL;
snprintf(tidstr, sizeof(tidstr), "%p", ktdn);
fmtdata[0] = ktdn;
fmtdata[1] = tidstr;
thread_creation_callback(&tdcr, d);
tdn = thread_map_find(&evtr->threads, ktdn);
assert(tdn != NULL);
printd(DS, "switching to unknown thread %p\n", ktdn);
cpu->td = tdn;
return;
}
printd(DS, "cpu %d: switching to thread %p\n", ev->cpu, ktdn);
cpu->td = tdn;
}
static
void
assert_foff_in_sync(evtr_t evtr)
{
off_t off;
off = ftello(evtr->f);
if (evtr->bytes != off) {
fprintf(stderr, "bytes %jd, off %jd\n", evtr->bytes, off);
abort();
}
}
static
int
evtr_write(evtr_t evtr, const void *buf, size_t bytes)
{
assert_foff_in_sync(evtr);
if (fwrite(buf, bytes, 1, evtr->f) != 1) {
evtr->err = errno;
evtr->errmsg = strerror(errno);
return !0;
}
evtr->bytes += bytes;
assert_foff_in_sync(evtr);
return 0;
}
static
int
evtr_dump_pad(evtr_t evtr)
{
size_t pad;
static char buf[REC_ALIGN];
pad = REC_ALIGN - (evtr->bytes % REC_ALIGN);
if (pad > 0) {
return evtr_write(evtr, buf, pad);
}
return 0;
}
static
int
evtr_dump_avoid_boundary(evtr_t evtr, size_t bytes)
{
unsigned pad, i;
static char buf[256];
pad = REC_BOUNDARY - (evtr->bytes % REC_BOUNDARY);
if (bytes > pad) {
for (i = 0; i < (pad / sizeof(buf)); ++i) {
if (evtr_write(evtr, buf, sizeof(buf))) {
return !0;
}
}
i = pad % sizeof(buf);
if (i) {
if (evtr_write(evtr, buf, i)) {
return !0;
}
}
}
return 0;
}
static
int
evtr_dump_fmt(evtr_t evtr, uint64_t ts, const evtr_event_t ev)
{
struct fmt_event_header fmt;
uint16_t id;
int err;
char *subsys = "", buf[1024];
if (strlcpy(buf, subsys, sizeof(buf)) >= sizeof(buf)) {
evtr->errmsg = "name of subsystem is too large";
evtr->err = ERANGE;
return 0;
}
if (strlcat(buf, ev->fmt, sizeof(buf)) >= sizeof(buf)) {
evtr->errmsg = "fmt + name of subsystem is too large";
evtr->err = ERANGE;
return 0;
}
if (!strhash_find(evtr->fmts, buf, &id)) {
return id;
}
if ((err = strhash_insert(evtr->fmts, buf, &id))) {
evtr->err = err;
return 0;
}
fmt.eh.type = EVTR_TYPE_FMT;
fmt.eh.ts = ts;
fmt.subsys_len = strlen(subsys);
fmt.fmt_len = strlen(ev->fmt);
fmt.id = id;
if (evtr_dump_avoid_boundary(evtr, sizeof(fmt) + fmt.subsys_len +
fmt.fmt_len))
return 0;
if (evtr_write(evtr, &fmt, sizeof(fmt)))
return 0;
if (evtr_write(evtr, subsys, fmt.subsys_len))
return 0;
if (evtr_write(evtr, ev->fmt, fmt.fmt_len))
return 0;
if (evtr_dump_pad(evtr))
return 0;
return fmt.id;
}
static
int
mangle_string_ptrs(const char *fmt, uint8_t *fmtdata,
const char *(*replace)(void *, const char *), void *ctx)
{
const char *f, *p;
size_t skipsize, intsz;
int ret = 0;
for (f = fmt; f[0] != '\0'; ++f) {
if (f[0] != '%')
continue;
++f;
skipsize = 0;
for (p = f; p[0]; ++p) {
int again = 0;
switch (p[0]) {
case '#':
case '0':
case '-':
case ' ':
case '+':
case '\'':
again = !0;
break;
}
if (!again)
break;
}
for (; isdigit(p[0]); ++p)
;
if (p[0] == '.')
++p;
for (; isdigit(p[0]); ++p)
;
intsz = 0;
switch (p[0]) {
case 'l':
if (p[1] == 'l') {
++p;
intsz = sizeof(long long);
} else {
intsz = sizeof(long);
}
break;
case 'j':
intsz = sizeof(intmax_t);
break;
case 't':
intsz = sizeof(ptrdiff_t);
break;
case 'z':
intsz = sizeof(size_t);
break;
default:
break;
}
if (intsz != 0)
++p;
else
intsz = sizeof(int);
switch (p[0]) {
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
case 'c':
skipsize = intsz;
break;
case 'p':
skipsize = sizeof(void *);
break;
case 'f':
if (p[-1] == 'l')
skipsize = sizeof(double);
else
skipsize = sizeof(float);
break;
case 's':
((const char **)fmtdata)[0] =
replace(ctx, ((char **)fmtdata)[0]);
skipsize = sizeof(char *);
++ret;
break;
default:
fprintf(stderr, "Unknown conversion specifier %c "
"in fmt starting with %s", p[0], f - 1);
return -1;
}
fmtdata += skipsize;
}
return ret;
}
static
int
evtr_dump_string(evtr_t evtr, uint64_t ts, const char *str, int ns)
{
struct string_event_header s;
int err;
uint16_t id;
assert((0 <= ns) && (ns < EVTR_NS_MAX));
if (!strhash_find(evtr->strings[ns], str, &id)) {
return id;
}
if ((err = strhash_insert(evtr->strings[ns], str, &id))) {
evtr->err = err;
return 0;
}
printd(DS, "hash_insert %s ns %d id %d\n", str, ns, id);
s.eh.type = EVTR_TYPE_STR;
s.eh.ts = ts;
s.ns = ns;
s.id = id;
s.len = strnlen(str, PATH_MAX);
if (evtr_dump_avoid_boundary(evtr, sizeof(s) + s.len))
return 0;
if (evtr_write(evtr, &s, sizeof(s)))
return 0;
if (evtr_write(evtr, str, s.len))
return 0;
if (evtr_dump_pad(evtr))
return 0;
return s.id;
}
struct replace_ctx {
evtr_t evtr;
uint64_t ts;
};
static
const char *
replace_strptr(void *_ctx, const char *s)
{
struct replace_ctx *ctx = _ctx;
return (const char *)(uintptr_t)evtr_dump_string(ctx->evtr, ctx->ts, s,
EVTR_NS_DSTR);
}
static
const char *
replace_strid(void *_ctx, const char *s)
{
struct replace_ctx *ctx = _ctx;
const char *ret;
ret = string_map_find(&ctx->evtr->maps[EVTR_NS_DSTR - 1].root,
(int)(uintptr_t)s);
if (!ret) {
fprintf(stderr, "Unknown id for data string\n");
ctx->evtr->errmsg = "unknown id for data string";
ctx->evtr->err = !0;
}
validate_string(ret);
printd(DS, "replacing strid %d (ns %d) with string '%s' (or int %#x)\n",
(int)(uintptr_t)s, EVTR_NS_DSTR, ret ? ret : "NULL", (int)(uintptr_t)ret);
return ret;
}
static
int
evtr_dump_probe(evtr_t evtr, evtr_event_t ev)
{
struct probe_event_header kev;
char buf[1024];
memset(&kev, '\0', sizeof(kev));
kev.eh.type = ev->type;
kev.eh.ts = ev->ts;
kev.line = ev->line;
kev.cpu = ev->cpu;
if (ev->file) {
kev.file = evtr_dump_string(evtr, kev.eh.ts, ev->file,
EVTR_NS_PATH);
}
if (ev->func) {
kev.func = evtr_dump_string(evtr, kev.eh.ts, ev->func,
EVTR_NS_FUNC);
}
if (ev->fmt) {
kev.fmt = evtr_dump_fmt(evtr, kev.eh.ts, ev);
}
if (ev->fmtdata) {
struct replace_ctx replctx = {
.evtr = evtr,
.ts = ev->ts,
};
assert(ev->fmtdatalen <= (int)sizeof(buf));
kev.datalen = ev->fmtdatalen;
memcpy(buf, ev->fmtdata, ev->fmtdatalen);
if (mangle_string_ptrs(ev->fmt, buf,
replace_strptr, &replctx) < 0)
return !0;
if (evtr->err)
return evtr->err;
}
if (evtr_dump_avoid_boundary(evtr, sizeof(kev) + ev->fmtdatalen))
return !0;
if (evtr_write(evtr, &kev, sizeof(kev)))
return !0;
if (evtr_write(evtr, buf, ev->fmtdatalen))
return !0;
if (evtr_dump_pad(evtr))
return !0;
return 0;
}
static
int
evtr_dump_sysinfo(evtr_t evtr, evtr_event_t ev)
{
uint8_t type = EVTR_TYPE_SYSINFO;
uint16_t ncpus = ev->ncpus;
if (ncpus <= 0) {
evtr->errmsg = "invalid number of cpus";
return !0;
}
if (evtr_dump_avoid_boundary(evtr, sizeof(type) + sizeof(ncpus)))
return !0;
if (evtr_write(evtr, &type, sizeof(type))) {
return !0;
}
if (evtr_write(evtr, &ncpus, sizeof(ncpus))) {
return !0;
}
if (evtr_dump_pad(evtr))
return !0;
return 0;
}
static
int
evtr_dump_cpuinfo(evtr_t evtr, evtr_event_t ev)
{
struct cpuinfo_event_header ci;
uint8_t type;
if (evtr_dump_avoid_boundary(evtr, sizeof(type) + sizeof(ci)))
return !0;
type = EVTR_TYPE_CPUINFO;
if (evtr_write(evtr, &type, sizeof(type))) {
return !0;
}
ci.cpu = ev->cpu;
ci.freq = ev->cpuinfo.freq;
if (evtr_dump_avoid_boundary(evtr, sizeof(ci)))
return !0;
if (evtr_write(evtr, &ci, sizeof(ci))) {
return !0;
}
if (evtr_dump_pad(evtr))
return !0;
return 0;
}
int
evtr_rewind(evtr_t evtr)
{
assert((evtr->flags & EVTRF_WR) == 0);
evtr->bytes = 0;
if (fseek(evtr->f, 0, SEEK_SET)) {
evtr->err = errno;
return !0;
}
return 0;
}
int
evtr_dump_event(evtr_t evtr, evtr_event_t ev)
{
switch (ev->type) {
case EVTR_TYPE_PROBE:
return evtr_dump_probe(evtr, ev);
case EVTR_TYPE_SYSINFO:
return evtr_dump_sysinfo(evtr, ev);
case EVTR_TYPE_CPUINFO:
return evtr_dump_cpuinfo(evtr, ev);
}
evtr->errmsg = "unknown event type";
return !0;
}
static
evtr_t
evtr_alloc(FILE *f)
{
evtr_t evtr;
if (!(evtr = malloc(sizeof(*evtr)))) {
return NULL;
}
evtr->f = f;
evtr->err = 0;
evtr->errmsg = NULL;
evtr->bytes = 0;
return evtr;
}
static int evtr_next_event(evtr_t, evtr_event_t);
evtr_t
evtr_open_read(FILE *f)
{
evtr_t evtr;
struct evtr_event ev;
int i;
if (!(evtr = evtr_alloc(f))) {
return NULL;
}
evtr->flags = 0;
for (i = 0; i < (EVTR_NS_MAX - 1); ++i) {
RB_INIT(&evtr->maps[i].root);
}
RB_INIT(&evtr->fmtmap.root);
RB_INIT(&evtr->threads.root);
evtr->cpus = NULL;
evtr->ncpus = 0;
if (evtr_next_event(evtr, &ev)) {
goto free_evtr;
}
if (evtr_rewind(evtr))
goto free_evtr;
return evtr;
free_evtr:
free(evtr);
return NULL;
}
evtr_t
evtr_open_write(FILE *f)
{
evtr_t evtr;
int i, j;
if (!(evtr = evtr_alloc(f))) {
return NULL;
}
evtr->flags = EVTRF_WR;
if (!(evtr->fmts = strhash_new()))
goto free_evtr;
for (i = 0; i < EVTR_NS_MAX; ++i) {
evtr->strings[i] = strhash_new();
if (!evtr->strings[i]) {
for (j = 0; j < i; ++j) {
strhash_destroy(evtr->strings[j]);
}
goto free_fmts;
}
}
return evtr;
free_fmts:
strhash_destroy(evtr->fmts);
free_evtr:
free(evtr);
return NULL;
}
static
void
hashtab_destroy(struct hashtab *h)
{
struct hashentry *ent, *next;
int i;
for (i = 0; i < NR_BUCKETS; ++i) {
for (ent = h->buckets[i]; ent; ent = next) {
next = ent->next;
free(ent);
}
}
free(h);
}
void
evtr_close(evtr_t evtr)
{
int i;
if (evtr->flags & EVTRF_WR) {
hashtab_destroy(&evtr->fmts->tab);
for (i = 0; i < EVTR_NS_MAX - 1; ++i)
hashtab_destroy(&evtr->strings[i]->tab);
} else {
id_tree_free(&evtr->fmtmap.root);
for (i = 0; i < EVTR_NS_MAX - 1; ++i) {
id_tree_free(&evtr->maps[i].root);
}
}
free(evtr);
}
static
int
evtr_read(evtr_t evtr, void *buf, size_t size)
{
assert(size > 0);
assert_foff_in_sync(evtr);
printd(IO, "evtr_read at %#jx, %zu bytes\n", evtr->bytes, size);
if (fread(buf, size, 1, evtr->f) != 1) {
if (feof(evtr->f)) {
evtr->errmsg = "incomplete record";
} else {
evtr->errmsg = strerror(errno);
}
return !0;
}
evtr->bytes += size;
assert_foff_in_sync(evtr);
return 0;
}
static
int
evtr_load_fmt(evtr_query_t q, char *buf)
{
evtr_t evtr = q->evtr;
struct fmt_event_header *evh = (struct fmt_event_header *)buf;
struct event_fmt *fmt;
char *subsys = NULL, *fmtstr;
if (!(fmt = malloc(sizeof(*fmt)))) {
evtr->err = errno;
return !0;
}
if (evtr_read(evtr, buf + sizeof(struct trace_event_header),
sizeof(*evh) - sizeof(evh->eh))) {
goto free_fmt;
}
assert(!evh->subsys_len);
if (evh->subsys_len) {
if (!(subsys = malloc(evh->subsys_len))) {
evtr->err = errno;
goto free_fmt;
}
if (evtr_read(evtr, subsys, evh->subsys_len)) {
goto free_subsys;
}
fmt->subsys = subsys;
} else {
fmt->subsys = "";
}
if (!(fmtstr = malloc(evh->fmt_len + 1))) {
evtr->err = errno;
goto free_subsys;
}
if (evtr_read(evtr, fmtstr, evh->fmt_len)) {
goto free_fmtstr;
}
fmtstr[evh->fmt_len] = '\0';
fmt->fmt = fmtstr;
printd(DS, "fmt_map_insert (%d, %s)\n", evh->id, fmt->fmt);
evtr->err = fmt_map_insert(&evtr->fmtmap.root, fmt, evh->id);
switch (evtr->err) {
case ENOMEM:
evtr->errmsg = "out of memory";
break;
case EEXIST:
evtr->errmsg = "redefinition of an id to a "
"different format (corrupt input)";
break;
default:
;
}
return evtr->err;
free_fmtstr:
free(fmtstr);
free_subsys:
if (subsys)
free(subsys);
free_fmt:
free(fmt);
return !0;
}
static
int
evtr_load_string(evtr_t evtr, char *buf)
{
char sbuf[PATH_MAX + 1];
struct string_event_header *evh = (struct string_event_header *)buf;
if (evtr_read(evtr, buf + sizeof(struct trace_event_header),
sizeof(*evh) - sizeof(evh->eh))) {
return !0;
}
if (evh->len > PATH_MAX) {
evtr->errmsg = "string too large (corrupt input)";
return !0;
}
if (evh->len && evtr_read(evtr, sbuf, evh->len)) {
return !0;
}
sbuf[evh->len] = 0;
if (evh->ns >= EVTR_NS_MAX) {
evtr->errmsg = "invalid namespace (corrupt input)";
return !0;
}
validate_string(sbuf);
printd(DS, "evtr_load_string:ns %d id %d : \"%s\"\n", evh->ns, evh->id,
sbuf);
evtr->err = string_map_insert(&evtr->maps[evh->ns - 1].root, sbuf, evh->id);
switch (evtr->err) {
case ENOMEM:
evtr->errmsg = "out of memory";
break;
case EEXIST:
evtr->errmsg = "redefinition of an id to a "
"different string (corrupt input)";
break;
default:
;
}
return 0;
}
static
int
evtr_skip(evtr_t evtr, off_t bytes)
{
if (fseek(evtr->f, bytes, SEEK_CUR)) {
evtr->err = errno;
evtr->errmsg = strerror(errno);
return !0;
}
evtr->bytes += bytes;
return 0;
}
static
int
evtr_query_reserve_buf(struct evtr_query *q, int len)
{
void *tmp;
if (q->bufsize >= len)
return 0;
if (!(tmp = realloc(q->buf, len)))
return !0;
q->buf = tmp;
q->bufsize = len;
return 0;
}
static
int
evtr_load_probe(evtr_t evtr, evtr_event_t ev, char *buf, struct evtr_query *q)
{
struct probe_event_header *evh = (struct probe_event_header *)buf;
struct cpu *cpu;
if (evtr_read(evtr, buf + sizeof(struct trace_event_header),
sizeof(*evh) - sizeof(evh->eh)))
return !0;
memset(ev, '\0', sizeof(*ev));
ev->ts = evh->eh.ts;
ev->type = EVTR_TYPE_PROBE;
ev->line = evh->line;
ev->cpu = evh->cpu;
if ((cpu = evtr_cpu(evtr, evh->cpu))) {
ev->td = cpu->td;
} else {
ev->td = NULL;
}
if (evh->file) {
ev->file = string_map_find(
&evtr->maps[EVTR_NS_PATH - 1].root,
evh->file);
if (!ev->file) {
evtr->errmsg = "unknown id for file path";
evtr->err = !0;
ev->file = "<unknown>";
} else {
validate_string(ev->file);
}
} else {
ev->file = "<unknown>";
}
if (evh->fmt) {
const struct event_fmt *fmt;
if (!(fmt = fmt_map_find(&evtr->fmtmap.root, evh->fmt))) {
evtr->errmsg = "unknown id for event fmt";
evtr->err = !0;
ev->fmt = NULL;
} else {
ev->fmt = fmt->fmt;
validate_string(fmt->fmt);
}
}
if (evh->datalen) {
if (evtr_query_reserve_buf(q, evh->datalen + 1)) {
evtr->err = ENOMEM;
} else if (!evtr_read(evtr, q->buf, evh->datalen)) {
struct replace_ctx replctx = {
.evtr = evtr,
.ts = ev->ts,
};
assert(ev->fmt);
ev->fmtdata = q->buf;
if (mangle_string_ptrs(ev->fmt, __DECONST(uint8_t *,
ev->fmtdata),
replace_strid, &replctx) < 0)
return evtr->err;
if (evtr->err)
return evtr->err;
((char *)ev->fmtdata)[evh->datalen] = '\0';
ev->fmtdatalen = evh->datalen;
}
}
evtr_run_callbacks(ev, q);
return evtr->err;
}
static
int
evtr_skip_to_record(evtr_t evtr)
{
int skip;
skip = REC_ALIGN - (evtr->bytes % REC_ALIGN);
if (skip > 0) {
if (fseek(evtr->f, skip, SEEK_CUR)) {
evtr->err = errno;
evtr->errmsg = strerror(errno);
return !0;
}
evtr->bytes += skip;
}
return 0;
}
static
int
evtr_load_sysinfo(evtr_t evtr)
{
uint16_t ncpus;
int i;
if (evtr_read(evtr, &ncpus, sizeof(ncpus))) {
return !0;
}
if (evtr->cpus)
return 0;
evtr->cpus = malloc(ncpus * sizeof(struct cpu));
if (!evtr->cpus) {
evtr->err = ENOMEM;
return !0;
}
evtr->ncpus = ncpus;
for (i = 0; i < ncpus; ++i) {
evtr->cpus[i].td = NULL;
evtr->cpus[i].freq = -1.0;
}
return 0;
}
static
int
evtr_load_cpuinfo(evtr_t evtr)
{
struct cpuinfo_event_header cih;
struct cpu *cpu;
if (evtr_read(evtr, &cih, sizeof(cih))) {
return !0;
}
if (cih.freq < 0.0) {
evtr->errmsg = "cpu freq is negative";
evtr->err = EINVAL;
return !0;
}
if (!(cpu = evtr_cpu(evtr, cih.cpu))) {
evtr->errmsg = "freq for invalid cpu";
evtr->err = EINVAL;
return !0;
}
cpu->freq = cih.freq;
return 0;
}
static
int
_evtr_next_event(evtr_t evtr, evtr_event_t ev, struct evtr_query *q)
{
char buf[MAX_EVHDR_SIZE];
int ret, err;
struct trace_event_header *evhdr = (struct trace_event_header *)buf;
for (ret = 0; !ret;) {
if (q->flags & EVTRQF_PENDING) {
q->off = evtr->bytes;
memcpy(ev, &q->pending_event, sizeof(*ev));
q->flags &= ~EVTRQF_PENDING;
return 0;
}
if (evtr_read(evtr, &evhdr->type, 1)) {
if (feof(evtr->f)) {
evtr->errmsg = NULL;
evtr->err = 0;
return -1;
}
return !0;
}
if (evhdr->type == EVTR_TYPE_PAD) {
evtr_skip_to_record(evtr);
continue;
}
if (evhdr->type == EVTR_TYPE_SYSINFO) {
evtr_load_sysinfo(evtr);
continue;
} else if (evhdr->type == EVTR_TYPE_CPUINFO) {
evtr_load_cpuinfo(evtr);
continue;
}
if (evtr_read(evtr, buf + 1, sizeof(*evhdr) - 1))
return feof(evtr->f) ? -1 : !0;
switch (evhdr->type) {
case EVTR_TYPE_PROBE:
if ((err = evtr_load_probe(evtr, ev, buf, q))) {
if (err == -1) {
ret = 0;
} else {
return !0;
}
} else {
ret = !0;
}
break;
case EVTR_TYPE_STR:
if (evtr_load_string(evtr, buf)) {
return !0;
}
break;
case EVTR_TYPE_FMT:
if (evtr_load_fmt(q, buf)) {
return !0;
}
break;
default:
evtr->err = !0;
evtr->errmsg = "unknown event type (corrupt input?)";
return !0;
}
evtr_skip_to_record(evtr);
if (ret) {
if (!evtr_match_filters(q, ev)) {
ret = 0;
continue;
}
q->off = evtr->bytes;
return 0;
}
}
return !0;
}
static
int
evtr_next_event(evtr_t evtr, evtr_event_t ev)
{
struct evtr_query *q;
int ret;
if (!(q = evtr_query_init(evtr, NULL, 0))) {
evtr->err = ENOMEM;
return !0;
}
ret = _evtr_next_event(evtr, ev, q);
evtr_query_destroy(q);
return ret;
}
int
evtr_last_event(evtr_t evtr, evtr_event_t ev)
{
struct stat st;
int fd;
off_t last_boundary;
if (evtr_error(evtr))
return !0;
fd = fileno(evtr->f);
if (fstat(fd, &st))
return !0;
if (0 && (st.st_mode & S_IFREG)) {
last_boundary = rounddown(st.st_size, REC_BOUNDARY);
assert(evtr->bytes == 0);
evtr_skip(evtr, last_boundary);
}
while (!evtr_next_event(evtr, ev))
;
if (evtr_error(evtr))
return !0;
evtr_rewind(evtr);
return 0;
}
struct evtr_query *
evtr_query_init(evtr_t evtr, evtr_filter_t filt, int nfilt)
{
struct evtr_query *q;
int i;
if (!(q = malloc(sizeof(*q)))) {
return q;
}
q->bufsize = 2;
if (!(q->buf = malloc(q->bufsize))) {
goto free_q;
}
if (!(q->symtab = symtab_new()))
goto free_buf;
q->evtr = evtr;
q->off = 0;
q->filt = filt;
q->nfilt = nfilt;
TAILQ_INIT(&q->unresolved_filtq);
q->nmatched = 0;
q->cbs = NULL;
q->ncbs = 0;
q->flags = 0;
memset(&q->pending_event, '\0', sizeof(q->pending_event));
if (evtr_register_callback(q, &thread_creation_callback, q)) {
goto free_symtab;
}
if (evtr_register_callback(q, &thread_switch_callback, q)) {
goto free_cbs;
}
if (evtr_query_needs_parsing(q) &&
evtr_register_callback(q, &parse_callback, q)) {
goto free_cbs;
}
for (i = 0; i < nfilt; ++i) {
filt[i].flags = 0;
if (filt[i].fmt == NULL)
continue;
if (evtr_filter_register(q, &filt[i])) {
evtr_deregister_filters(q, filt, i);
goto free_symtab;
}
}
return q;
free_cbs:
evtr_deregister_callbacks(q);
free_symtab:
symtab_destroy(q->symtab);
free_buf:
free(q->buf);
free_q:
free(q);
return NULL;
}
void
evtr_query_destroy(struct evtr_query *q)
{
evtr_deregister_filters(q, q->filt, q->nfilt);
free(q->buf);
free(q);
}
int
evtr_query_next(struct evtr_query *q, evtr_event_t ev)
{
if (evtr_query_error(q))
return !0;
if (q->off != q->evtr->bytes) {
q->errmsg = "evtr/query offset mismatch";
return !0;
}
return _evtr_next_event(q->evtr, ev, q);
}
int
evtr_ncpus(evtr_t evtr)
{
return evtr->ncpus;
}
int
evtr_cpufreqs(evtr_t evtr, double *freqs)
{
int i;
if (!freqs)
return EINVAL;
for (i = 0; i < evtr->ncpus; ++i) {
freqs[i] = evtr->cpus[i].freq;
}
return 0;
}