| /* |
| * 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"; |
| } |