#include "libipt-sb.h"
#include "intel-pt.h"
#ifndef FEATURE_PEVENT
int pt_sb_alloc_pevent_decoder(struct pt_sb_session *session,
const struct pt_sb_pevent_config *config)
{
(void) session;
(void) config;
return -pte_not_supported;
}
#else
#include "pt_sb_pevent.h"
#include "pt_sb_session.h"
#include "pt_sb_context.h"
#include "pt_sb_file.h"
#include "pt_compiler.h"
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#if defined(_MSC_VER) && (_MSC_VER < 1900)
# define snprintf _snprintf_c
#endif
#ifndef FEATURE_ELF
static int elf_get_abi(FILE *file)
{
if (!file)
return -pte_internal;
return pt_sb_abi_unknown;
}
#else
#include <elf.h>
static int elf_get_abi(FILE *file)
{
uint8_t e_ident[EI_NIDENT];
size_t count;
int status;
if (!file)
return -pte_internal;
status = fseek(file, 0, SEEK_SET);
if (status < 0)
return pt_sb_abi_unknown;
count = fread(e_ident, sizeof(e_ident), 1, file);
if (count != 1)
return pt_sb_abi_unknown;
status = memcmp(e_ident, ELFMAG, SELFMAG);
if (status != 0)
return pt_sb_abi_unknown;
if (e_ident[EI_VERSION] != EV_CURRENT)
return pt_sb_abi_unknown;
switch (e_ident[EI_CLASS]) {
default:
break;
case ELFCLASS64:
return pt_sb_abi_x64;
case ELFCLASS32: {
Elf32_Ehdr ehdr;
status = fseek(file, 0, SEEK_SET);
if (status < 0)
break;
count = fread(&ehdr, sizeof(ehdr), 1, file);
if (count != 1)
break;
switch (ehdr.e_machine) {
default:
break;
case EM_386:
return pt_sb_abi_ia32;
case EM_X86_64:
return pt_sb_abi_x32;
}
}
break;
}
return pt_sb_abi_unknown;
}
#endif
static int pt_sb_pevent_error(const struct pt_sb_session *session, int errcode,
const struct pt_sb_pevent_priv *priv)
{
const char *filename;
uint64_t offset;
filename = NULL;
offset = 0ull;
if (priv) {
const uint8_t *pos, *begin;
pos = priv->current;
if (!pos)
pos = priv->next;
begin = priv->begin;
if (pos < begin)
return -pte_internal;
filename = priv->filename;
offset = (uint64_t) (int64_t) (pos - begin);
}
return pt_sb_error(session, errcode, filename, offset);
}
static int pt_sb_pevent_track_abi(struct pt_sb_context *context,
const char *filename)
{
FILE *file;
int abi;
if (!context || !filename)
return -pte_internal;
if (context->abi)
return 0;
file = fopen(filename, "rb");
if (!file)
return 0;
abi = elf_get_abi(file);
fclose(file);
if (abi < 0)
return abi;
context->abi = (enum pt_sb_abi) abi;
return 0;
}
static int pt_sb_pevent_find_vdso(const char **pvdso,
const struct pt_sb_pevent_priv *priv,
const struct pt_sb_context *context)
{
const char *vdso;
if (!pvdso || !priv || !context)
return -pte_internal;
vdso = NULL;
switch (context->abi) {
case pt_sb_abi_unknown:
break;
case pt_sb_abi_x64:
vdso = priv->vdso_x64;
break;
case pt_sb_abi_x32:
vdso = priv->vdso_x32;
break;
case pt_sb_abi_ia32:
vdso = priv->vdso_ia32;
break;
}
if (!vdso)
return -pte_bad_config;
*pvdso = vdso;
return 0;
}
static void pt_sb_pevent_dtor(void *priv_arg)
{
struct pt_sb_pevent_priv *priv;
struct pt_sb_context *context;
priv = (struct pt_sb_pevent_priv *) priv_arg;
if (!priv)
return;
context = priv->next_context;
if (context)
pt_sb_ctx_put(context);
context = priv->context;
if (context)
pt_sb_ctx_put(context);
free(priv->filename);
free(priv->sysroot);
free(priv->vdso_x64);
free(priv->vdso_x32);
free(priv->vdso_ia32);
free(priv->begin);
free(priv);
}
static int pt_sb_pevent_init_path(char **dst, const char *src)
{
size_t len;
char *copy;
if (!dst)
return -pte_internal;
if (!src) {
*dst = NULL;
return 0;
}
len = strnlen(src, FILENAME_MAX);
if (len == FILENAME_MAX)
return -pte_invalid;
len += 1;
copy = malloc(len);
if (!copy)
return -pte_nomem;
memcpy(copy, src, len);
*dst = copy;
return 0;
}
int pt_sb_pevent_init(struct pt_sb_pevent_priv *priv,
const struct pt_sb_pevent_config *config)
{
const char *filename;
size_t size;
void *buffer;
int errcode;
if (!priv || !config)
return -pte_internal;
if (config->size < sizeof(*config))
return -pte_invalid;
filename = config->filename;
if (!filename)
return -pte_invalid;
buffer = NULL;
size = 0;
errcode = pt_sb_file_load(&buffer, &size, filename,
config->begin, config->end);
if (errcode < 0)
return errcode;
memset(priv, 0, sizeof(*priv));
priv->begin = (uint8_t *) buffer;
priv->end = (uint8_t *) buffer + size;
priv->next = (uint8_t *) buffer;
errcode = pt_sb_pevent_init_path(&priv->filename, filename);
if (errcode < 0) {
pt_sb_pevent_dtor(priv);
return errcode;
}
errcode = pt_sb_pevent_init_path(&priv->sysroot, config->sysroot);
if (errcode < 0) {
pt_sb_pevent_dtor(priv);
return errcode;
}
errcode = pt_sb_pevent_init_path(&priv->vdso_x64, config->vdso_x64);
if (errcode < 0) {
pt_sb_pevent_dtor(priv);
return errcode;
}
errcode = pt_sb_pevent_init_path(&priv->vdso_x32, config->vdso_x32);
if (errcode < 0) {
pt_sb_pevent_dtor(priv);
return errcode;
}
errcode = pt_sb_pevent_init_path(&priv->vdso_ia32, config->vdso_ia32);
if (errcode < 0) {
pt_sb_pevent_dtor(priv);
return errcode;
}
pev_config_init(&priv->pev);
priv->pev.sample_type = config->sample_type;
priv->pev.time_shift = config->time_shift;
priv->pev.time_mult = config->time_mult;
priv->pev.time_zero = config->time_zero;
priv->kernel_start = config->kernel_start;
priv->tsc_offset = config->tsc_offset;
priv->location = ploc_unknown;
return 0;
}
static int pt_sb_pevent_fetch(uint64_t *ptsc, struct pt_sb_pevent_priv *priv)
{
struct pev_event *event;
const uint8_t *pos;
uint64_t tsc, offset;
int size;
if (!ptsc || !priv)
return -pte_internal;
pos = priv->next;
event = &priv->event;
priv->current = pos;
size = pev_read(event, pos, priv->end, &priv->pev);
if (size < 0)
return size;
priv->next = pos + size;
if (!event->sample.time) {
*ptsc = 0ull;
return 0;
}
offset = priv->tsc_offset;
tsc = event->sample.tsc;
if (offset <= tsc)
tsc -= offset;
else {
if (0ll <= (int64_t) offset)
tsc = 0ull;
else {
if (tsc <= offset)
tsc -= offset;
else
tsc = UINT64_MAX;
}
}
event->sample.tsc = tsc;
*ptsc = tsc;
return 0;
}
static int pt_sb_pevent_print_event(const struct pev_event *event,
FILE *stream, uint32_t flags)
{
if (!event)
return -pte_internal;
switch (event->type) {
default:
if (flags & ptsbp_compact)
fprintf(stream, "UNKNOWN (%x, %x)", event->type,
event->misc);
if (flags & ptsbp_verbose) {
fprintf(stream, "UNKNOWN");
fprintf(stream, "\n type: %x", event->type);
fprintf(stream, "\n misc: %x", event->misc);
}
break;
case PERF_RECORD_MMAP: {
const struct pev_record_mmap *mmap;
mmap = event->record.mmap;
if (!mmap)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_MMAP %x/%x, %" PRIx64
", %" PRIx64 ", %" PRIx64 ", %s",
mmap->pid, mmap->tid, mmap->addr, mmap->len,
mmap->pgoff, mmap->filename);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_MMAP");
fprintf(stream, "\n pid: %x", mmap->pid);
fprintf(stream, "\n tid: %x", mmap->tid);
fprintf(stream, "\n addr: %" PRIx64, mmap->addr);
fprintf(stream, "\n len: %" PRIx64, mmap->len);
fprintf(stream, "\n pgoff: %" PRIx64, mmap->pgoff);
fprintf(stream, "\n filename: %s", mmap->filename);
}
}
break;
case PERF_RECORD_LOST: {
const struct pev_record_lost *lost;
lost = event->record.lost;
if (!lost)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_LOST %" PRIx64 ", %"
PRIx64, lost->id, lost->lost);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_LOST");
fprintf(stream, "\n id: %" PRIx64, lost->id);
fprintf(stream, "\n lost: %" PRIx64, lost->lost);
}
}
break;
case PERF_RECORD_COMM: {
const struct pev_record_comm *comm;
const char *sfx;
comm = event->record.comm;
if (!comm)
return -pte_bad_packet;
sfx = event->misc & PERF_RECORD_MISC_COMM_EXEC ? ".EXEC" : "";
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_COMM%s %x/%x, %s", sfx,
comm->pid, comm->tid, comm->comm);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_COMM%s", sfx);
fprintf(stream, "\n pid: %x", comm->pid);
fprintf(stream, "\n tid: %x", comm->tid);
fprintf(stream, "\n comm: %s", comm->comm);
}
}
break;
case PERF_RECORD_EXIT: {
const struct pev_record_exit *exit;
exit = event->record.exit;
if (!exit)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_EXIT %x/%x, %x/%x, %"
PRIx64, exit->pid, exit->tid, exit->ppid,
exit->ptid, exit->time);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_EXIT");
fprintf(stream, "\n pid: %x", exit->pid);
fprintf(stream, "\n ppid: %x", exit->ppid);
fprintf(stream, "\n tid: %x", exit->tid);
fprintf(stream, "\n ptid: %x", exit->ptid);
fprintf(stream, "\n time: %" PRIx64, exit->time);
}
}
break;
case PERF_RECORD_THROTTLE: {
const struct pev_record_throttle *throttle;
throttle = event->record.throttle;
if (!throttle)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_THROTTLE %" PRIx64 ", %"
PRIx64 ", %" PRIx64, throttle->time,
throttle->id, throttle->stream_id);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_THROTTLE");
fprintf(stream, "\n time: %" PRIx64, throttle->time);
fprintf(stream, "\n id: %" PRIx64, throttle->id);
fprintf(stream, "\n stream_id: %" PRIx64,
throttle->stream_id);
}
}
break;
case PERF_RECORD_UNTHROTTLE: {
const struct pev_record_throttle *throttle;
throttle = event->record.throttle;
if (!throttle)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_UNTHROTTLE %" PRIx64
", %" PRIx64 ", %" PRIx64, throttle->time,
throttle->id, throttle->stream_id);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_UNTHROTTLE");
fprintf(stream, "\n time: %" PRIx64, throttle->time);
fprintf(stream, "\n id: %" PRIx64, throttle->id);
fprintf(stream, "\n stream_id: %" PRIx64,
throttle->stream_id);
}
}
break;
case PERF_RECORD_FORK: {
const struct pev_record_fork *fork;
fork = event->record.fork;
if (!fork)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_FORK %x/%x, %x/%x, %"
PRIx64, fork->pid, fork->tid, fork->ppid,
fork->ptid, fork->time);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_FORK");
fprintf(stream, "\n pid: %x", fork->pid);
fprintf(stream, "\n ppid: %x", fork->ppid);
fprintf(stream, "\n tid: %x", fork->tid);
fprintf(stream, "\n ptid: %x", fork->ptid);
fprintf(stream, "\n time: %" PRIx64, fork->time);
}
}
break;
case PERF_RECORD_MMAP2: {
const struct pev_record_mmap2 *mmap2;
mmap2 = event->record.mmap2;
if (!mmap2)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_MMAP2 %x/%x, %" PRIx64
", %" PRIx64 ", %" PRIx64 ", %x, %x, %" PRIx64
", %" PRIx64 ", %x, %x, %s", mmap2->pid,
mmap2->tid, mmap2->addr, mmap2->len,
mmap2->pgoff, mmap2->maj, mmap2->min,
mmap2->ino, mmap2->ino_generation, mmap2->prot,
mmap2->flags, mmap2->filename);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_MMAP2");
fprintf(stream, "\n pid: %x", mmap2->pid);
fprintf(stream, "\n tid: %x", mmap2->tid);
fprintf(stream, "\n addr: %" PRIx64, mmap2->addr);
fprintf(stream, "\n len: %" PRIx64, mmap2->len);
fprintf(stream, "\n pgoff: %" PRIx64, mmap2->pgoff);
fprintf(stream, "\n maj: %x", mmap2->maj);
fprintf(stream, "\n min: %x", mmap2->min);
fprintf(stream, "\n ino: %" PRIx64, mmap2->ino);
fprintf(stream, "\n ino_generation: %" PRIx64,
mmap2->ino_generation);
fprintf(stream, "\n prot: %x", mmap2->prot);
fprintf(stream, "\n flags: %x", mmap2->flags);
fprintf(stream, "\n filename: %s", mmap2->filename);
}
}
break;
case PERF_RECORD_AUX: {
const struct pev_record_aux *aux;
const char *sfx;
aux = event->record.aux;
if (!aux)
return -pte_bad_packet;
sfx = aux->flags & PERF_AUX_FLAG_TRUNCATED ? ".TRUNCATED" : "";
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_AUX%s %" PRIx64 ", %"
PRIx64 ", %" PRIx64, sfx, aux->aux_offset,
aux->aux_size, aux->flags);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_AUX%s", sfx);
fprintf(stream, "\n aux offset: %" PRIx64,
aux->aux_offset);
fprintf(stream, "\n aux size: %" PRIx64,
aux->aux_size);
fprintf(stream, "\n flags: %" PRIx64, aux->flags);
}
}
break;
case PERF_RECORD_ITRACE_START: {
const struct pev_record_itrace_start *itrace_start;
itrace_start = event->record.itrace_start;
if (!itrace_start)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_ITRACE_START %x/%x",
itrace_start->pid, itrace_start->tid);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_ITRACE_START");
fprintf(stream, "\n pid: %x", itrace_start->pid);
fprintf(stream, "\n tid: %x", itrace_start->tid);
}
}
break;
case PERF_RECORD_LOST_SAMPLES: {
const struct pev_record_lost_samples *lost_samples;
lost_samples = event->record.lost_samples;
if (!lost_samples)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_LOST_SAMPLES %" PRIx64,
lost_samples->lost);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_LOST_SAMPLES");
fprintf(stream, "\n lost: %" PRIx64,
lost_samples->lost);
}
}
break;
case PERF_RECORD_SWITCH: {
const char *sfx;
sfx = event->misc & PERF_RECORD_MISC_SWITCH_OUT ? "OUT" : "IN";
if (flags & (ptsbp_compact | ptsbp_verbose))
fprintf(stream, "PERF_RECORD_SWITCH.%s", sfx);
}
break;
case PERF_RECORD_SWITCH_CPU_WIDE: {
const struct pev_record_switch_cpu_wide *switch_cpu_wide;
const char *sfx, *pfx;
if (event->misc & PERF_RECORD_MISC_SWITCH_OUT) {
sfx = "OUT";
pfx = "next";
} else {
sfx = "IN";
pfx = "prev";
}
switch_cpu_wide = event->record.switch_cpu_wide;
if (!switch_cpu_wide)
return -pte_bad_packet;
if (flags & ptsbp_compact)
fprintf(stream, "PERF_RECORD_SWITCH_CPU_WIDE.%s %x/%x",
sfx, switch_cpu_wide->next_prev_pid,
switch_cpu_wide->next_prev_tid);
if (flags & ptsbp_verbose) {
fprintf(stream, "PERF_RECORD_SWITCH_CPU_WIDE.%s", sfx);
fprintf(stream, "\n %s pid: %x", pfx,
switch_cpu_wide->next_prev_pid);
fprintf(stream, "\n %s tid: %x", pfx,
switch_cpu_wide->next_prev_tid);
}
}
break;
}
return 0;
}
static int pt_sb_pevent_print_samples_compact(const struct pev_event *event,
FILE *stream)
{
if (!event)
return -pte_internal;
fprintf(stream, " {");
if (event->sample.pid && event->sample.tid)
fprintf(stream, " %x/%x", *event->sample.pid,
*event->sample.tid);
if (event->sample.time)
fprintf(stream, " %" PRIx64, *event->sample.time);
if (event->sample.id)
fprintf(stream, " %" PRIx64, *event->sample.id);
if (event->sample.cpu)
fprintf(stream, " cpu-%x", *event->sample.cpu);
if (event->sample.stream_id)
fprintf(stream, " %" PRIx64, *event->sample.stream_id);
if (event->sample.identifier)
fprintf(stream, " %" PRIx64, *event->sample.identifier);
fprintf(stream, " }");
return 0;
}
static int pt_sb_pevent_print_samples_verbose(const struct pev_event *event,
FILE *stream)
{
if (!event)
return -pte_internal;
if (event->sample.pid && event->sample.tid) {
fprintf(stream, "\n pid: %x", *event->sample.pid);
fprintf(stream, "\n tid: %x", *event->sample.tid);
}
if (event->sample.time)
fprintf(stream, "\n time: %" PRIx64, *event->sample.time);
if (event->sample.id)
fprintf(stream, "\n id: %" PRIx64, *event->sample.id);
if (event->sample.cpu)
fprintf(stream, "\n cpu: %x", *event->sample.cpu);
if (event->sample.stream_id)
fprintf(stream, "\n stream id: %" PRIx64,
*event->sample.stream_id);
if (event->sample.identifier)
fprintf(stream, "\n identifier: %" PRIx64,
*event->sample.identifier);
return 0;
}
static int pt_sb_pevent_print_samples(const struct pev_event *event,
FILE *stream, uint32_t flags)
{
int errcode;
if (flags & ptsbp_compact) {
errcode = pt_sb_pevent_print_samples_compact(event, stream);
if (errcode < 0)
return errcode;
}
if (flags & ptsbp_verbose) {
errcode = pt_sb_pevent_print_samples_verbose(event, stream);
if (errcode < 0)
return errcode;
}
return 0;
}
static int pt_sb_pevent_print(struct pt_sb_pevent_priv *priv, FILE *stream,
uint32_t flags)
{
struct pev_event *event;
const uint8_t *pos, *begin;
const char *filename;
int errcode;
if (!priv)
return -pte_internal;
pos = priv->current;
if (!pos)
return -pte_internal;
begin = priv->begin;
if (pos < begin)
return -pte_internal;
filename = priv->filename;
if (!filename)
return -pte_internal;
event = &priv->event;
switch (flags & (ptsbp_filename | ptsbp_file_offset)) {
case ptsbp_filename | ptsbp_file_offset:
fprintf(stream, "%s:%016" PRIx64 " ", filename,
(uint64_t) (int64_t) (pos - begin));
break;
case ptsbp_filename:
fprintf(stream, "%s ", filename);
break;
case ptsbp_file_offset:
fprintf(stream, "%016" PRIx64 " ",
(uint64_t) (int64_t) (pos - begin));
break;
}
if ((flags & ptsbp_tsc) && event->sample.time)
fprintf(stream, "%016" PRIx64 " ", event->sample.tsc);
errcode = pt_sb_pevent_print_event(event, stream, flags);
if (errcode < 0)
return errcode;
if (priv->pev.sample_type) {
errcode = pt_sb_pevent_print_samples(event, stream, flags);
if (errcode < 0)
return errcode;
}
if (flags)
fprintf(stream, "\n");
return 0;
}
static int pt_sb_pevent_switch_contexts(struct pt_sb_session *session,
struct pt_image **image,
struct pt_sb_pevent_priv *priv)
{
struct pt_sb_context *prev, *next;
int errcode;
if (!priv || !image)
return -pte_internal;
prev = priv->context;
next = priv->next_context;
if (!next)
return -pte_internal;
errcode = pt_sb_ctx_switch_to(image, session, next);
if (errcode < 0)
return errcode;
priv->next_context = NULL;
priv->context = next;
return prev ? pt_sb_ctx_put(prev) : 0;
}
static int pt_sb_pevent_cancel_context_switch(struct pt_sb_pevent_priv *priv)
{
struct pt_sb_context *context;
if (!priv)
return -pte_internal;
context = priv->next_context;
if (!context)
return 0;
priv->next_context = NULL;
return pt_sb_ctx_put(context);
}
static int pt_sb_pevent_prepare_context_switch(struct pt_sb_pevent_priv *priv,
struct pt_sb_context *context)
{
int errcode;
if (!priv || !context)
return -pte_internal;
if (priv->next_context == context)
return 0;
errcode = pt_sb_pevent_cancel_context_switch(priv);
if (errcode < 0)
return errcode;
if (priv->context == context)
return 0;
errcode = pt_sb_ctx_get(context);
if (errcode < 0)
return errcode;
priv->next_context = context;
return 0;
}
static int pt_sb_pevent_prepare_switch_to_pid(struct pt_sb_session *session,
struct pt_sb_pevent_priv *priv,
uint32_t pid)
{
struct pt_sb_context *context;
int errcode;
context = NULL;
errcode = pt_sb_get_context_by_pid(&context, session, pid);
if (errcode < 0)
return errcode;
return pt_sb_pevent_prepare_context_switch(priv, context);
}
static int pt_sb_pevent_remove_context_for_pid(struct pt_sb_session *session,
uint32_t pid)
{
struct pt_sb_context *context;
int errcode;
context = NULL;
errcode = pt_sb_find_context_by_pid(&context, session, pid);
if (errcode < 0)
return errcode;
if (!context)
return 0;
return pt_sb_remove_context(session, context);
}
static int
pt_sb_pevent_itrace_start(struct pt_sb_session *session,
struct pt_image **image,
struct pt_sb_pevent_priv *priv,
const struct pev_record_itrace_start *record)
{
int errcode;
if (!image || !record)
return -pte_internal;
errcode = pt_sb_pevent_prepare_switch_to_pid(session, priv,
record->pid);
if (errcode < 0)
return errcode;
if (!priv->next_context)
return 0;
return pt_sb_pevent_switch_contexts(session, image, priv);
}
static int pt_sb_pevent_fork(struct pt_sb_session *session,
const struct pev_record_fork *record)
{
struct pt_sb_context *context, *parent;
struct pt_image *image, *pimage;
uint32_t ppid, pid;
int errcode;
if (!record)
return -pte_internal;
ppid = record->ppid;
pid = record->pid;
if (ppid == pid)
return 0;
if (pid != record->tid)
return -pte_internal;
errcode = pt_sb_pevent_remove_context_for_pid(session, pid);
if (errcode < 0)
return errcode;
context = NULL;
errcode = pt_sb_get_context_by_pid(&context, session, pid);
if (errcode < 0)
return errcode;
parent = NULL;
errcode = pt_sb_find_context_by_pid(&parent, session, ppid);
if (errcode < 0)
return errcode;
if (!parent)
return 0;
pimage = pt_sb_ctx_image(parent);
image = pt_sb_ctx_image(context);
if (!pimage || !image)
return -pte_internal;
return pt_image_copy(image, pimage);
}
static int pt_sb_pevent_exec(struct pt_sb_session *session,
struct pt_image **image,
struct pt_sb_pevent_priv *priv,
const struct pev_record_comm *record)
{
struct pt_sb_context *context;
uint32_t pid;
int errcode;
if (!record)
return -pte_internal;
pid = record->pid;
errcode = pt_sb_pevent_remove_context_for_pid(session, pid);
if (errcode < 0)
return errcode;
context = NULL;
errcode = pt_sb_get_context_by_pid(&context, session, pid);
if (errcode < 0)
return errcode;
if (!image)
return 0;
return pt_sb_pevent_prepare_context_switch(priv, context);
}
static int pt_sb_pevent_switch(struct pt_sb_session *session,
struct pt_sb_pevent_priv *priv,
const uint32_t *pid)
{
if (!pid)
return -pte_bad_config;
return pt_sb_pevent_prepare_switch_to_pid(session, priv, *pid);
}
static int
pt_sb_pevent_switch_cpu(struct pt_sb_session *session,
struct pt_sb_pevent_priv *priv,
const struct pev_record_switch_cpu_wide *record)
{
if (!record)
return -pte_internal;
return pt_sb_pevent_prepare_switch_to_pid(session, priv,
record->next_prev_pid);
}
static int pt_sb_pevent_map(struct pt_sb_session *session,
const struct pt_sb_pevent_priv *priv, uint32_t pid,
const char *filename, uint64_t offset,
uint64_t size, uint64_t vaddr)
{
struct pt_sb_context *context;
const char *sysroot;
char buffer[FILENAME_MAX];
int errcode;
if (!priv || !filename)
return -pte_internal;
context = NULL;
errcode = pt_sb_get_context_by_pid(&context, session, pid);
if (errcode < 0)
return errcode;
sysroot = priv->sysroot;
if (filename[0] == '[') {
if (strcmp(filename, "[vdso]") == 0) {
errcode = pt_sb_pevent_find_vdso(&filename, priv,
context);
if (errcode != 0)
return pt_sb_pevent_error(session, errcode,
priv);
} else
return pt_sb_pevent_error(session, ptse_section_lost,
priv);
} else if (strcmp(filename, "//anon") == 0) {
return pt_sb_pevent_error(session, ptse_section_lost, priv);
} else if (strstr(filename, " (deleted)")) {
return pt_sb_pevent_error(session, ptse_section_lost, priv);
} else if (sysroot) {
errcode = snprintf(buffer, sizeof(buffer), "%s%s", sysroot,
filename);
if (errcode < 0)
return -pte_overflow;
filename = buffer;
}
errcode = pt_sb_pevent_track_abi(context, filename);
if (errcode < 0)
return errcode;
return pt_sb_ctx_mmap(session, context, filename, offset, size, vaddr);
}
static int pt_sb_pevent_mmap(struct pt_sb_session *session,
const struct pt_sb_pevent_priv *priv,
const struct pev_record_mmap *record)
{
if (!record)
return -pte_internal;
return pt_sb_pevent_map(session, priv, record->pid, record->filename,
record->pgoff, record->len, record->addr);
}
static int pt_sb_pevent_mmap2(struct pt_sb_session *session,
const struct pt_sb_pevent_priv *priv,
const struct pev_record_mmap2 *record)
{
if (!record)
return -pte_internal;
return pt_sb_pevent_map(session, priv, record->pid, record->filename,
record->pgoff, record->len, record->addr);
}
static int pt_sb_pevent_aux(const struct pt_sb_session *session,
const struct pt_sb_pevent_priv *priv,
const struct pev_record_aux *record)
{
if (!record)
return -pte_internal;
if (record->flags & PERF_AUX_FLAG_TRUNCATED)
return pt_sb_pevent_error(session, ptse_trace_lost, priv);
return 0;
}
static int pt_sb_pevent_ignore_mmap(uint16_t misc)
{
switch (misc & PERF_RECORD_MISC_CPUMODE_MASK) {
case PERF_RECORD_MISC_KERNEL:
return 1;
default:
return 0;
}
}
static int pt_sb_pevent_apply_event_record(struct pt_sb_session *session,
struct pt_image **image,
struct pt_sb_pevent_priv *priv,
const struct pev_event *event)
{
if (!event)
return -pte_internal;
switch (event->type) {
default:
break;
case PERF_RECORD_ITRACE_START:
if (!image)
break;
return pt_sb_pevent_itrace_start(session, image, priv,
event->record.itrace_start);
case PERF_RECORD_FORK:
return pt_sb_pevent_fork(session, event->record.fork);
case PERF_RECORD_COMM:
if (!(event->misc & PERF_RECORD_MISC_COMM_EXEC))
break;
return pt_sb_pevent_exec(session, image, priv,
event->record.comm);
case PERF_RECORD_SWITCH:
if (!image)
break;
if (event->misc & PERF_RECORD_MISC_SWITCH_OUT)
break;
return pt_sb_pevent_switch(session, priv, event->sample.pid);
case PERF_RECORD_SWITCH_CPU_WIDE:
if (!image)
break;
if (!(event->misc & PERF_RECORD_MISC_SWITCH_OUT)) {
if (!event->sample.pid)
break;
return pt_sb_pevent_switch(session, priv,
event->sample.pid);
}
return pt_sb_pevent_switch_cpu(session, priv,
event->record.switch_cpu_wide);
case PERF_RECORD_MMAP:
if (pt_sb_pevent_ignore_mmap(event->misc))
break;
return pt_sb_pevent_mmap(session, priv, event->record.mmap);
case PERF_RECORD_MMAP2:
if (pt_sb_pevent_ignore_mmap(event->misc))
break;
return pt_sb_pevent_mmap2(session, priv, event->record.mmap2);
case PERF_RECORD_LOST:
return pt_sb_pevent_error(session, ptse_lost, priv);
case PERF_RECORD_AUX:
if (!image)
break;
return pt_sb_pevent_aux(session, priv, event->record.aux);
}
return 0;
}
static int ploc_from_ip(enum pt_sb_pevent_loc *loc,
const struct pt_sb_pevent_priv *priv, uint64_t ip)
{
if (!loc || !priv)
return -pte_internal;
*loc = (ip < priv->kernel_start) ? ploc_in_user : ploc_in_kernel;
return 0;
}
static int ploc_from_suppressed_ip(enum pt_sb_pevent_loc *loc,
enum pt_sb_pevent_loc from)
{
if (!loc)
return -pte_internal;
switch (from) {
default:
*loc = ploc_unknown;
break;
case ploc_likely_in_kernel:
case ploc_in_kernel:
*loc = ploc_likely_in_user;
break;
case ploc_likely_in_user:
case ploc_in_user:
*loc = ploc_likely_in_kernel;
break;
}
return 0;
}
static int ploc_from_event(enum pt_sb_pevent_loc *loc,
const struct pt_sb_pevent_priv *priv,
const struct pt_event *event)
{
if (!loc || !priv || !event)
return -pte_internal;
switch (event->type) {
default:
break;
case ptev_enabled:
return ploc_from_ip(loc, priv, event->variant.enabled.ip);
case ptev_disabled:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.disabled.ip);
return ploc_from_suppressed_ip(loc, priv->location);
case ptev_async_disabled: {
enum pt_sb_pevent_loc fromloc;
int errcode;
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.async_disabled.ip);
errcode = ploc_from_ip(&fromloc, priv,
event->variant.async_disabled.at);
if (errcode < 0)
return errcode;
return ploc_from_suppressed_ip(loc, fromloc);
}
case ptev_async_branch:
return ploc_from_ip(loc, priv, event->variant.async_branch.to);
case ptev_async_paging:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.async_paging.ip);
fallthrough;
case ptev_paging:
*loc = ploc_likely_in_kernel;
return 0;
case ptev_overflow:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.overflow.ip);
break;
case ptev_exec_mode:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.exec_mode.ip);
break;
case ptev_tsx:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.tsx.ip);
break;
case ptev_exstop:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.exstop.ip);
break;
case ptev_mwait:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.mwait.ip);
break;
case ptev_ptwrite:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.ptwrite.ip);
break;
case ptev_tick:
if (!event->ip_suppressed)
return ploc_from_ip(loc, priv,
event->variant.tick.ip);
break;
}
*loc = ploc_unknown;
return 0;
}
static int pt_sb_pevent_apply(struct pt_sb_session *session,
struct pt_image **image,
const struct pt_event *event,
struct pt_sb_pevent_priv *priv)
{
const struct pev_event *record;
enum pt_sb_pevent_loc oldloc;
int errcode;
if (!priv || !event)
return -pte_internal;
record = &priv->event;
if ((priv->current != priv->next) &&
(!record->sample.time || (record->sample.tsc <= event->tsc)))
return pt_sb_pevent_apply_event_record(session, image, priv,
record);
oldloc = priv->location;
errcode = ploc_from_event(&priv->location, priv, event);
if (errcode < 0)
return errcode;
if (!priv->next_context) {
if (priv->current == priv->next)
return -pte_eos;
return 0;
}
switch (oldloc) {
case ploc_likely_in_kernel:
case ploc_in_kernel:
case ploc_unknown:
return pt_sb_pevent_switch_contexts(session, image, priv);
default:
switch (priv->location) {
case ploc_likely_in_kernel:
case ploc_in_kernel:
case ploc_unknown:
return pt_sb_pevent_switch_contexts(session, image,
priv);
default:
break;
}
break;
};
return 0;
}
static int pt_sb_pevent_fetch_callback(struct pt_sb_session *session,
uint64_t *tsc, void *priv)
{
int errcode;
errcode = pt_sb_pevent_fetch(tsc, (struct pt_sb_pevent_priv *) priv);
if ((errcode < 0) && (errcode != -pte_eos))
pt_sb_pevent_error(session, errcode,
(struct pt_sb_pevent_priv *) priv);
return errcode;
}
static int pt_sb_pevent_print_callback(struct pt_sb_session *session,
FILE *stream, uint32_t flags, void *priv)
{
int errcode;
errcode = pt_sb_pevent_print((struct pt_sb_pevent_priv *) priv, stream,
flags);
if (errcode < 0)
return pt_sb_pevent_error(session, errcode,
(struct pt_sb_pevent_priv *) priv);
return 0;
}
static int pt_sb_pevent_apply_callback(struct pt_sb_session *session,
struct pt_image **image,
const struct pt_event *event, void *priv)
{
int errcode;
errcode = pt_sb_pevent_apply(session, image, event,
(struct pt_sb_pevent_priv *) priv);
if ((errcode < 0) && (errcode != -pte_eos))
return pt_sb_pevent_error(session, errcode,
(struct pt_sb_pevent_priv *) priv);
return errcode;
}
int pt_sb_alloc_pevent_decoder(struct pt_sb_session *session,
const struct pt_sb_pevent_config *pev)
{
struct pt_sb_decoder_config config;
struct pt_sb_pevent_priv *priv;
int errcode;
if (!session || !pev)
return -pte_invalid;
priv = malloc(sizeof(*priv));
if (!priv)
return -pte_nomem;
errcode = pt_sb_pevent_init(priv, pev);
if (errcode < 0) {
free(priv);
return errcode;
}
memset(&config, 0, sizeof(config));
config.fetch = pt_sb_pevent_fetch_callback;
config.apply = pt_sb_pevent_apply_callback;
config.print = pt_sb_pevent_print_callback;
config.dtor = pt_sb_pevent_dtor;
config.priv = priv;
config.primary = pev->primary;
errcode = pt_sb_alloc_decoder(session, &config);
if (errcode < 0)
free(priv);
return errcode;
}
#endif