blob: 3b299838878f2b9ec325a91b833d5af4a0f27351 [file] [log] [blame]
/*
* Copyright (c) 2017-2019, Intel Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Intel Corporation nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "pt_sb_session.h"
#include "pt_sb_context.h"
#include "pt_sb_decoder.h"
#include "libipt-sb.h"
#include "intel-pt.h"
#include <string.h>
#include <stdlib.h>
#if defined(_MSC_VER) && (_MSC_VER < 1900)
# define snprintf _snprintf_c
#endif
struct pt_sb_session *pt_sb_alloc(struct pt_image_section_cache *iscache)
{
struct pt_sb_session *session;
struct pt_image *kernel;
kernel = pt_image_alloc("kernel");
if (!kernel)
return NULL;
session = malloc(sizeof(*session));
if (!session) {
pt_image_free(kernel);
return NULL;
}
memset(session, 0, sizeof(*session));
session->iscache = iscache;
session->kernel = kernel;
return session;
}
static void pt_sb_free_decoder(struct pt_sb_decoder *decoder)
{
void (*dtor)(void *);
if (!decoder)
return;
dtor = decoder->dtor;
if (dtor)
dtor(decoder->priv);
free(decoder);
}
static void pt_sb_free_decoder_list(struct pt_sb_decoder *list)
{
while (list) {
struct pt_sb_decoder *trash;
trash = list;
list = trash->next;
pt_sb_free_decoder(trash);
}
}
void pt_sb_free(struct pt_sb_session *session)
{
struct pt_sb_context *context;
if (!session)
return;
pt_sb_free_decoder_list(session->decoders);
pt_sb_free_decoder_list(session->waiting);
pt_sb_free_decoder_list(session->retired);
pt_sb_free_decoder_list(session->removed);
context = session->contexts;
while (context) {
struct pt_sb_context *trash;
trash = context;
context = trash->next;
(void) pt_sb_ctx_put(trash);
}
pt_image_free(session->kernel);
free(session);
}
struct pt_image_section_cache *pt_sb_iscache(struct pt_sb_session *session)
{
if (!session)
return NULL;
return session->iscache;
}
struct pt_image *pt_sb_kernel_image(struct pt_sb_session *session)
{
if (!session)
return NULL;
return session->kernel;
}
static int pt_sb_add_context_by_pid(struct pt_sb_context **pcontext,
struct pt_sb_session *session, uint32_t pid)
{
struct pt_sb_context *context;
struct pt_image *kernel;
char iname[16];
int errcode;
if (!pcontext || !session)
return -pte_invalid;
kernel = pt_sb_kernel_image(session);
if (!kernel)
return -pte_internal;
memset(iname, 0, sizeof(iname));
(void) snprintf(iname, sizeof(iname), "pid-%x", pid);
context = pt_sb_ctx_alloc(iname);
if (!context)
return -pte_nomem;
errcode = pt_image_copy(context->image, kernel);
if (errcode < 0) {
(void) pt_sb_ctx_put(context);
return errcode;
}
context->next = session->contexts;
context->pid = pid;
session->contexts = context;
*pcontext = context;
return 0;
}
int pt_sb_get_context_by_pid(struct pt_sb_context **context,
struct pt_sb_session *session, uint32_t pid)
{
int status;
if (!context || !session)
return -pte_invalid;
status = pt_sb_find_context_by_pid(context, session, pid);
if (status < 0)
return status;
if (*context)
return 0;
return pt_sb_add_context_by_pid(context, session, pid);
}
int pt_sb_find_context_by_pid(struct pt_sb_context **pcontext,
struct pt_sb_session *session, uint32_t pid)
{
struct pt_sb_context *ctx;
if (!pcontext || !session)
return -pte_invalid;
for (ctx = session->contexts; ctx; ctx = ctx->next) {
if (ctx->pid == pid)
break;
}
*pcontext = ctx;
return 0;
}
int pt_sb_remove_context(struct pt_sb_session *session,
struct pt_sb_context *context)
{
struct pt_sb_context **pnext, *ctx;
if (!session || !context)
return -pte_invalid;
pnext = &session->contexts;
for (ctx = *pnext; ctx; pnext = &ctx->next, ctx = *pnext) {
if (ctx == context)
break;
}
if (!ctx)
return -pte_nosync;
*pnext = ctx->next;
return pt_sb_ctx_put(ctx);
}
int pt_sb_alloc_decoder(struct pt_sb_session *session,
const struct pt_sb_decoder_config *config)
{
struct pt_sb_decoder *decoder;
if (!session || !config)
return -pte_invalid;
decoder = malloc(sizeof(*decoder));
if (!decoder)
return -pte_nomem;
memset(decoder, 0, sizeof(*decoder));
decoder->next = session->waiting;
decoder->fetch = config->fetch;
decoder->apply = config->apply;
decoder->print = config->print;
decoder->dtor = config->dtor;
decoder->priv = config->priv;
decoder->primary = config->primary;
session->waiting = decoder;
return 0;
}
/* Add a new decoder to a list of decoders.
*
* Decoders in @list are ordered by their @tsc (ascending) and @list does not
* contain @decoder. Find the right place for @decoder at add it to @list.
*
* Returns zero on success, a negative error code otherwise.
*/
static int pt_sb_add_decoder(struct pt_sb_decoder **list,
struct pt_sb_decoder *decoder)
{
struct pt_sb_decoder *cand;
uint64_t tsc;
if (!list || !decoder || decoder->next)
return -pte_internal;
tsc = decoder->tsc;
for (cand = *list; cand; list = &cand->next, cand = *list) {
if (tsc <= cand->tsc)
break;
}
decoder->next = cand;
*list = decoder;
return 0;
}
static int pt_sb_fetch(struct pt_sb_session *session,
struct pt_sb_decoder *decoder)
{
int (*fetch)(struct pt_sb_session *, uint64_t *, void *);
if (!decoder)
return -pte_internal;
fetch = decoder->fetch;
if (!fetch)
return -pte_bad_config;
return fetch(session, &decoder->tsc, decoder->priv);
}
static int pt_sb_print(struct pt_sb_session *session,
struct pt_sb_decoder *decoder, FILE *stream,
uint32_t flags)
{
int (*print)(struct pt_sb_session *, FILE *, uint32_t, void *);
if (!decoder)
return -pte_internal;
print = decoder->print;
if (!print)
return -pte_bad_config;
return print(session, stream, flags, decoder->priv);
}
static int pt_sb_apply(struct pt_sb_session *session, struct pt_image **image,
struct pt_sb_decoder *decoder,
const struct pt_event *event)
{
int (*apply)(struct pt_sb_session *, struct pt_image **,
const struct pt_event *, void *);
if (!decoder || !event)
return -pte_internal;
apply = decoder->apply;
if (!apply)
return -pte_bad_config;
if (!decoder->primary)
image = NULL;
return apply(session, image, event, decoder->priv);
}
int pt_sb_init_decoders(struct pt_sb_session *session)
{
struct pt_sb_decoder *decoder;
if (!session)
return -pte_invalid;
decoder = session->waiting;
while (decoder) {
int errcode;
session->waiting = decoder->next;
decoder->next = NULL;
errcode = pt_sb_fetch(session, decoder);
if (errcode < 0) {
/* Fetch errors remove @decoder. In this case, they
* prevent it from being added in the first place.
*/
pt_sb_free_decoder(decoder);
} else {
errcode = pt_sb_add_decoder(&session->decoders,
decoder);
if (errcode < 0)
return errcode;
}
decoder = session->waiting;
}
return 0;
}
/* Copy an event provided by an unknown version of libipt.
*
* Copy at most @size bytes of @uevent into @event and zero-initialize any
* additional bytes in @event not covered by @uevent.
*
* Returns zero on success, a negative error code otherwise.
*/
static int pt_sb_event_from_user(struct pt_event *event,
const struct pt_event *uevent, size_t size)
{
if (!event || !uevent)
return -pte_internal;
if (size < offsetof(struct pt_event, reserved))
return -pte_invalid;
/* Ignore fields in the user's event we don't know; zero out fields the
* user didn't know about.
*/
if (sizeof(*event) < size)
size = sizeof(*event);
else
memset(((uint8_t *) event) + size, 0, sizeof(*event) - size);
/* Copy (portions of) the user's event. */
memcpy(event, uevent, size);
return 0;
}
static int pt_sb_event_present(struct pt_sb_session *session,
struct pt_image **image,
struct pt_sb_decoder **pnext,
const struct pt_event *event)
{
struct pt_sb_decoder *decoder;
int errcode;
if (!session || !pnext)
return -pte_internal;
decoder = *pnext;
while (decoder) {
errcode = pt_sb_apply(session, image, decoder, event);
if (errcode < 0) {
struct pt_sb_decoder *trash;
trash = decoder;
decoder = trash->next;
*pnext = decoder;
trash->next = session->removed;
session->removed = trash;
continue;
}
pnext = &decoder->next;
decoder = *pnext;
}
return 0;
}
int pt_sb_event(struct pt_sb_session *session, struct pt_image **image,
const struct pt_event *uevent, size_t size, FILE *stream,
uint32_t flags)
{
struct pt_sb_decoder *decoder;
struct pt_event event;
int errcode;
if (!session || !uevent)
return -pte_invalid;
errcode = pt_sb_event_from_user(&event, uevent, size);
if (errcode < 0)
return errcode;
/* In the initial round, we present the event to all decoders with
* records for a smaller or equal timestamp.
*
* We only need to look at the first decoder. We remove it from the
* list and ask it to apply the event. Then, we ask it to fetch the
* next record and re-add it to the list according to that next record's
* timestamp.
*/
for (;;) {
decoder = session->decoders;
if (!decoder)
break;
/* We don't check @event.has_tsc to support sideband
* correlation based on relative (non-wall clock) time.
*/
if (event.tsc < decoder->tsc)
break;
session->decoders = decoder->next;
decoder->next = NULL;
if (stream) {
errcode = pt_sb_print(session, decoder, stream, flags);
if (errcode < 0) {
decoder->next = session->removed;
session->removed = decoder;
continue;
}
}
errcode = pt_sb_apply(session, image, decoder, &event);
if (errcode < 0) {
decoder->next = session->removed;
session->removed = decoder;
continue;
}
errcode = pt_sb_fetch(session, decoder);
if (errcode < 0) {
if (errcode == -pte_eos) {
decoder->next = session->retired;
session->retired = decoder;
} else {
decoder->next = session->removed;
session->removed = decoder;
}
continue;
}
errcode = pt_sb_add_decoder(&session->decoders, decoder);
if (errcode < 0)
return errcode;
}
/* In the second round, we present the event to all decoders.
*
* This allows decoders to postpone actions until an appropriate event,
* e.g entry into or exit from the kernel.
*/
errcode = pt_sb_event_present(session, image, &session->decoders,
&event);
if (errcode < 0)
return errcode;
return pt_sb_event_present(session, image, &session->retired, &event);
}
int pt_sb_dump(struct pt_sb_session *session, FILE *stream, uint32_t flags,
uint64_t tsc)
{
struct pt_sb_decoder *decoder;
int errcode;
if (!session || !stream)
return -pte_invalid;
for (;;) {
decoder = session->decoders;
if (!decoder)
break;
if (tsc < decoder->tsc)
break;
session->decoders = decoder->next;
decoder->next = NULL;
errcode = pt_sb_print(session, decoder, stream, flags);
if (errcode < 0) {
decoder->next = session->removed;
session->removed = decoder;
continue;
}
errcode = pt_sb_fetch(session, decoder);
if (errcode < 0) {
decoder->next = session->removed;
session->removed = decoder;
continue;
}
errcode = pt_sb_add_decoder(&session->decoders, decoder);
if (errcode < 0)
return errcode;
}
return 0;
}
pt_sb_ctx_switch_notifier_t *
pt_sb_notify_switch(struct pt_sb_session *session,
pt_sb_ctx_switch_notifier_t *notifier, void *priv)
{
pt_sb_ctx_switch_notifier_t *old;
if (!session)
return NULL;
old = session->notify_switch_to;
session->notify_switch_to = notifier;
session->priv_switch_to = priv;
return old;
}
pt_sb_error_notifier_t *
pt_sb_notify_error(struct pt_sb_session *session,
pt_sb_error_notifier_t *notifier, void *priv)
{
pt_sb_error_notifier_t *old;
if (!session)
return NULL;
old = session->notify_error;
session->notify_error = notifier;
session->priv_error = priv;
return old;
}
int pt_sb_error(const struct pt_sb_session *session, int errcode,
const char *filename, uint64_t offset)
{
pt_sb_error_notifier_t *notifier;
if (!session)
return -pte_internal;
notifier = session->notify_error;
if (!notifier)
return 0;
return notifier(errcode, filename, offset, session->priv_error);
}
const char *pt_sb_errstr(enum pt_sb_error_code errcode)
{
switch (errcode) {
case ptse_ok:
return "OK";
case ptse_lost:
return "sideband lost";
case ptse_trace_lost:
return "trace lost";
case ptse_section_lost:
return "image section lost";
}
return "bad errcode";
}