diff options
Diffstat (limited to 'libipt/src/pt_block_decoder.c')
-rw-r--r-- | libipt/src/pt_block_decoder.c | 3473 |
1 files changed, 3473 insertions, 0 deletions
diff --git a/libipt/src/pt_block_decoder.c b/libipt/src/pt_block_decoder.c new file mode 100644 index 0000000000000..e6890f5bed27d --- /dev/null +++ b/libipt/src/pt_block_decoder.c @@ -0,0 +1,3473 @@ +/* + * Copyright (c) 2016-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_block_decoder.h" +#include "pt_block_cache.h" +#include "pt_section.h" +#include "pt_image.h" +#include "pt_insn.h" +#include "pt_config.h" +#include "pt_asid.h" +#include "pt_compiler.h" + +#include "intel-pt.h" + +#include <string.h> +#include <stdlib.h> + + +static int pt_blk_proceed_trailing_event(struct pt_block_decoder *, + struct pt_block *); + + +static int pt_blk_status(const struct pt_block_decoder *decoder, int flags) +{ + int status; + + if (!decoder) + return -pte_internal; + + status = decoder->status; + + /* Indicate whether tracing is disabled or enabled. + * + * This duplicates the indication in struct pt_insn and covers the case + * where we indicate the status after synchronizing. + */ + if (!decoder->enabled) + flags |= pts_ip_suppressed; + + /* Forward end-of-trace indications. + * + * Postpone it as long as we're still processing events, though. + */ + if ((status & pts_eos) && !decoder->process_event) + flags |= pts_eos; + + return flags; +} + +static void pt_blk_reset(struct pt_block_decoder *decoder) +{ + if (!decoder) + return; + + decoder->mode = ptem_unknown; + decoder->ip = 0ull; + decoder->status = 0; + decoder->enabled = 0; + decoder->process_event = 0; + decoder->speculative = 0; + decoder->process_insn = 0; + decoder->bound_paging = 0; + decoder->bound_vmcs = 0; + decoder->bound_ptwrite = 0; + + memset(&decoder->event, 0, sizeof(decoder->event)); + pt_retstack_init(&decoder->retstack); + pt_asid_init(&decoder->asid); +} + +/* Initialize the query decoder flags based on our flags. */ + +static int pt_blk_init_qry_flags(struct pt_conf_flags *qflags, + const struct pt_conf_flags *flags) +{ + if (!qflags || !flags) + return -pte_internal; + + memset(qflags, 0, sizeof(*qflags)); + qflags->variant.query.keep_tcal_on_ovf = + flags->variant.block.keep_tcal_on_ovf; + + return 0; +} + +int pt_blk_decoder_init(struct pt_block_decoder *decoder, + const struct pt_config *uconfig) +{ + struct pt_config config; + int errcode; + + if (!decoder) + return -pte_internal; + + errcode = pt_config_from_user(&config, uconfig); + if (errcode < 0) + return errcode; + + /* The user supplied decoder flags. */ + decoder->flags = config.flags; + + /* Set the flags we need for the query decoder we use. */ + errcode = pt_blk_init_qry_flags(&config.flags, &decoder->flags); + if (errcode < 0) + return errcode; + + errcode = pt_qry_decoder_init(&decoder->query, &config); + if (errcode < 0) + return errcode; + + pt_image_init(&decoder->default_image, NULL); + decoder->image = &decoder->default_image; + + errcode = pt_msec_cache_init(&decoder->scache); + if (errcode < 0) + return errcode; + + pt_blk_reset(decoder); + + return 0; +} + +void pt_blk_decoder_fini(struct pt_block_decoder *decoder) +{ + if (!decoder) + return; + + pt_msec_cache_fini(&decoder->scache); + pt_image_fini(&decoder->default_image); + pt_qry_decoder_fini(&decoder->query); +} + +struct pt_block_decoder * +pt_blk_alloc_decoder(const struct pt_config *config) +{ + struct pt_block_decoder *decoder; + int errcode; + + decoder = malloc(sizeof(*decoder)); + if (!decoder) + return NULL; + + errcode = pt_blk_decoder_init(decoder, config); + if (errcode < 0) { + free(decoder); + return NULL; + } + + return decoder; +} + +void pt_blk_free_decoder(struct pt_block_decoder *decoder) +{ + if (!decoder) + return; + + pt_blk_decoder_fini(decoder); + free(decoder); +} + +/* Maybe synthesize a tick event. + * + * If we're not already processing events, check the current time against the + * last event's time. If it changed, synthesize a tick event with the new time. + * + * Returns zero if no tick event has been created. + * Returns a positive integer if a tick event has been created. + * Returns a negative error code otherwise. + */ +static int pt_blk_tick(struct pt_block_decoder *decoder, uint64_t ip) +{ + struct pt_event *ev; + uint64_t tsc; + uint32_t lost_mtc, lost_cyc; + int errcode; + + if (!decoder) + return -pte_internal; + + /* We're not generating tick events if tracing is disabled. */ + if (!decoder->enabled) + return -pte_internal; + + /* Events already provide a timestamp so there is no need to synthesize + * an artificial tick event. There's no room, either, since this would + * overwrite the in-progress event. + * + * In rare cases where we need to proceed to an event location using + * trace this may cause us to miss a timing update if the event is not + * forwarded to the user. + * + * The only case I can come up with at the moment is a MODE.EXEC binding + * to the TIP IP of a far branch. + */ + if (decoder->process_event) + return 0; + + errcode = pt_qry_time(&decoder->query, &tsc, &lost_mtc, &lost_cyc); + if (errcode < 0) { + /* If we don't have wall-clock time, we use relative time. */ + if (errcode != -pte_no_time) + return errcode; + } + + ev = &decoder->event; + + /* We're done if time has not changed since the last event. */ + if (tsc == ev->tsc) + return 0; + + /* Time has changed so we create a new tick event. */ + memset(ev, 0, sizeof(*ev)); + ev->type = ptev_tick; + ev->variant.tick.ip = ip; + + /* Indicate if we have wall-clock time or only relative time. */ + if (errcode != -pte_no_time) + ev->has_tsc = 1; + ev->tsc = tsc; + ev->lost_mtc = lost_mtc; + ev->lost_cyc = lost_cyc; + + /* We now have an event to process. */ + decoder->process_event = 1; + + return 1; +} + +/* Query an indirect branch. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_indirect_branch(struct pt_block_decoder *decoder, + uint64_t *ip) +{ + uint64_t evip; + int status, errcode; + + if (!decoder) + return -pte_internal; + + evip = decoder->ip; + + status = pt_qry_indirect_branch(&decoder->query, ip); + if (status < 0) + return status; + + if (decoder->flags.variant.block.enable_tick_events) { + errcode = pt_blk_tick(decoder, evip); + if (errcode < 0) + return errcode; + } + + return status; +} + +/* Query a conditional branch. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_cond_branch(struct pt_block_decoder *decoder, int *taken) +{ + int status, errcode; + + if (!decoder) + return -pte_internal; + + status = pt_qry_cond_branch(&decoder->query, taken); + if (status < 0) + return status; + + if (decoder->flags.variant.block.enable_tick_events) { + errcode = pt_blk_tick(decoder, decoder->ip); + if (errcode < 0) + return errcode; + } + + return status; +} + +static int pt_blk_start(struct pt_block_decoder *decoder, int status) +{ + if (!decoder) + return -pte_internal; + + if (status < 0) + return status; + + decoder->status = status; + if (!(status & pts_ip_suppressed)) + decoder->enabled = 1; + + /* We will always have an event. + * + * If we synchronized onto an empty PSB+, tracing is disabled and we'll + * process events until the enabled event. + * + * If tracing is enabled, PSB+ must at least provide the execution mode, + * which we're going to forward to the user. + */ + return pt_blk_proceed_trailing_event(decoder, NULL); +} + +static int pt_blk_sync_reset(struct pt_block_decoder *decoder) +{ + if (!decoder) + return -pte_internal; + + pt_blk_reset(decoder); + + return 0; +} + +int pt_blk_sync_forward(struct pt_block_decoder *decoder) +{ + int errcode, status; + + if (!decoder) + return -pte_invalid; + + errcode = pt_blk_sync_reset(decoder); + if (errcode < 0) + return errcode; + + status = pt_qry_sync_forward(&decoder->query, &decoder->ip); + + return pt_blk_start(decoder, status); +} + +int pt_blk_sync_backward(struct pt_block_decoder *decoder) +{ + int errcode, status; + + if (!decoder) + return -pte_invalid; + + errcode = pt_blk_sync_reset(decoder); + if (errcode < 0) + return errcode; + + status = pt_qry_sync_backward(&decoder->query, &decoder->ip); + + return pt_blk_start(decoder, status); +} + +int pt_blk_sync_set(struct pt_block_decoder *decoder, uint64_t offset) +{ + int errcode, status; + + if (!decoder) + return -pte_invalid; + + errcode = pt_blk_sync_reset(decoder); + if (errcode < 0) + return errcode; + + status = pt_qry_sync_set(&decoder->query, &decoder->ip, offset); + + return pt_blk_start(decoder, status); +} + +int pt_blk_get_offset(const struct pt_block_decoder *decoder, uint64_t *offset) +{ + if (!decoder) + return -pte_invalid; + + return pt_qry_get_offset(&decoder->query, offset); +} + +int pt_blk_get_sync_offset(const struct pt_block_decoder *decoder, + uint64_t *offset) +{ + if (!decoder) + return -pte_invalid; + + return pt_qry_get_sync_offset(&decoder->query, offset); +} + +struct pt_image *pt_blk_get_image(struct pt_block_decoder *decoder) +{ + if (!decoder) + return NULL; + + return decoder->image; +} + +int pt_blk_set_image(struct pt_block_decoder *decoder, struct pt_image *image) +{ + if (!decoder) + return -pte_invalid; + + if (!image) + image = &decoder->default_image; + + decoder->image = image; + return 0; +} + +const struct pt_config * +pt_blk_get_config(const struct pt_block_decoder *decoder) +{ + if (!decoder) + return NULL; + + return pt_qry_get_config(&decoder->query); +} + +int pt_blk_time(struct pt_block_decoder *decoder, uint64_t *time, + uint32_t *lost_mtc, uint32_t *lost_cyc) +{ + if (!decoder || !time) + return -pte_invalid; + + return pt_qry_time(&decoder->query, time, lost_mtc, lost_cyc); +} + +int pt_blk_core_bus_ratio(struct pt_block_decoder *decoder, uint32_t *cbr) +{ + if (!decoder || !cbr) + return -pte_invalid; + + return pt_qry_core_bus_ratio(&decoder->query, cbr); +} + +int pt_blk_asid(const struct pt_block_decoder *decoder, struct pt_asid *asid, + size_t size) +{ + if (!decoder || !asid) + return -pte_invalid; + + return pt_asid_to_user(asid, &decoder->asid, size); +} + +/* Fetch the next pending event. + * + * Checks for pending events. If an event is pending, fetches it (if not + * already in process). + * + * Returns zero if no event is pending. + * Returns a positive integer if an event is pending or in process. + * Returns a negative error code otherwise. + */ +static inline int pt_blk_fetch_event(struct pt_block_decoder *decoder) +{ + int status; + + if (!decoder) + return -pte_internal; + + if (decoder->process_event) + return 1; + + if (!(decoder->status & pts_event_pending)) + return 0; + + status = pt_qry_event(&decoder->query, &decoder->event, + sizeof(decoder->event)); + if (status < 0) + return status; + + decoder->process_event = 1; + decoder->status = status; + + return 1; +} + +static inline int pt_blk_block_is_empty(const struct pt_block *block) +{ + if (!block) + return 1; + + return !block->ninsn; +} + +static inline int block_to_user(struct pt_block *ublock, size_t size, + const struct pt_block *block) +{ + if (!ublock || !block) + return -pte_internal; + + if (ublock == block) + return 0; + + /* Zero out any unknown bytes. */ + if (sizeof(*block) < size) { + memset(ublock + sizeof(*block), 0, size - sizeof(*block)); + + size = sizeof(*block); + } + + memcpy(ublock, block, size); + + return 0; +} + +static int pt_insn_false(const struct pt_insn *insn, + const struct pt_insn_ext *iext) +{ + (void) insn; + (void) iext; + + return 0; +} + +/* Determine the next IP using trace. + * + * Tries to determine the IP of the next instruction using trace and provides it + * in @pip. + * + * Not requiring trace to determine the IP is treated as an internal error. + * + * Does not update the return compression stack for indirect calls. This is + * expected to have been done, already, when trying to determine the next IP + * without using trace. + * + * Does not update @decoder->status. The caller is expected to do that. + * + * Returns a non-negative pt_status_flag bit-vector on success, a negative error + * code otherwise. + * Returns -pte_internal if @pip, @decoder, @insn, or @iext are NULL. + * Returns -pte_internal if no trace is required. + */ +static int pt_blk_next_ip(uint64_t *pip, struct pt_block_decoder *decoder, + const struct pt_insn *insn, + const struct pt_insn_ext *iext) +{ + int status, errcode; + + if (!pip || !decoder || !insn || !iext) + return -pte_internal; + + /* We handle non-taken conditional branches, and compressed returns + * directly in the switch. + * + * All kinds of branches are handled below the switch. + */ + switch (insn->iclass) { + case ptic_cond_jump: { + uint64_t ip; + int taken; + + status = pt_blk_cond_branch(decoder, &taken); + if (status < 0) + return status; + + ip = insn->ip + insn->size; + if (taken) + ip += (uint64_t) (int64_t) + iext->variant.branch.displacement; + + *pip = ip; + return status; + } + + case ptic_return: { + int taken; + + /* Check for a compressed return. */ + status = pt_blk_cond_branch(decoder, &taken); + if (status < 0) { + if (status != -pte_bad_query) + return status; + + break; + } + + /* A compressed return is indicated by a taken conditional + * branch. + */ + if (!taken) + return -pte_bad_retcomp; + + errcode = pt_retstack_pop(&decoder->retstack, pip); + if (errcode < 0) + return errcode; + + return status; + } + + case ptic_jump: + case ptic_call: + /* A direct jump or call wouldn't require trace. */ + if (iext->variant.branch.is_direct) + return -pte_internal; + + break; + + case ptic_far_call: + case ptic_far_return: + case ptic_far_jump: + break; + + case ptic_ptwrite: + case ptic_other: + return -pte_internal; + + case ptic_error: + return -pte_bad_insn; + } + + /* Process an indirect branch. + * + * This covers indirect jumps and calls, non-compressed returns, and all + * flavors of far transfers. + */ + return pt_blk_indirect_branch(decoder, pip); +} + +/* Proceed to the next IP using trace. + * + * We failed to proceed without trace. This ends the current block. Now use + * trace to do one final step to determine the start IP of the next block. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_proceed_with_trace(struct pt_block_decoder *decoder, + const struct pt_insn *insn, + const struct pt_insn_ext *iext) +{ + int status; + + if (!decoder) + return -pte_internal; + + status = pt_blk_next_ip(&decoder->ip, decoder, insn, iext); + if (status < 0) + return status; + + /* Preserve the query decoder's response which indicates upcoming + * events. + */ + decoder->status = status; + + /* We do need an IP in order to proceed. */ + if (status & pts_ip_suppressed) + return -pte_noip; + + return 0; +} + +/* Decode one instruction in a known section. + * + * Decode the instruction at @insn->ip in @msec assuming execution mode + * @insn->mode. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_decode_in_section(struct pt_insn *insn, + struct pt_insn_ext *iext, + const struct pt_mapped_section *msec) +{ + int status; + + if (!insn || !iext) + return -pte_internal; + + /* We know that @ip is contained in @section. + * + * Note that we need to translate @ip into a section offset. + */ + status = pt_msec_read(msec, insn->raw, sizeof(insn->raw), insn->ip); + if (status < 0) + return status; + + /* We initialize @insn->size to the maximal possible size. It will be + * set to the actual size during instruction decode. + */ + insn->size = (uint8_t) status; + + return pt_ild_decode(insn, iext); +} + +/* Update the return-address stack if @insn is a near call. + * + * Returns zero on success, a negative error code otherwise. + */ +static inline int pt_blk_log_call(struct pt_block_decoder *decoder, + const struct pt_insn *insn, + const struct pt_insn_ext *iext) +{ + if (!decoder || !insn || !iext) + return -pte_internal; + + if (insn->iclass != ptic_call) + return 0; + + /* Ignore direct calls to the next instruction that are used for + * position independent code. + */ + if (iext->variant.branch.is_direct && + !iext->variant.branch.displacement) + return 0; + + return pt_retstack_push(&decoder->retstack, insn->ip + insn->size); +} + +/* Proceed by one instruction. + * + * Tries to decode the instruction at @decoder->ip and, on success, adds it to + * @block and provides it in @pinsn and @piext. + * + * The instruction will not be added if: + * + * - the memory could not be read: return error + * - it could not be decoded: return error + * - @block is already full: return zero + * - @block would switch sections: return zero + * + * Returns a positive integer if the instruction was added. + * Returns zero if the instruction didn't fit into @block. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_one_insn(struct pt_block_decoder *decoder, + struct pt_block *block, + struct pt_insn *pinsn, + struct pt_insn_ext *piext) +{ + struct pt_insn_ext iext; + struct pt_insn insn; + uint16_t ninsn; + int status; + + if (!decoder || !block || !pinsn || !piext) + return -pte_internal; + + /* There's nothing to do if there is no room in @block. */ + ninsn = block->ninsn + 1; + if (!ninsn) + return 0; + + /* The truncated instruction must be last. */ + if (block->truncated) + return 0; + + memset(&insn, 0, sizeof(insn)); + memset(&iext, 0, sizeof(iext)); + + insn.mode = decoder->mode; + insn.ip = decoder->ip; + + status = pt_insn_decode(&insn, &iext, decoder->image, &decoder->asid); + if (status < 0) + return status; + + /* We do not switch sections inside a block. */ + if (insn.isid != block->isid) { + if (!pt_blk_block_is_empty(block)) + return 0; + + block->isid = insn.isid; + } + + /* If we couldn't read @insn's memory in one chunk from @insn.isid, we + * provide the memory in @block. + */ + if (insn.truncated) { + memcpy(block->raw, insn.raw, insn.size); + block->size = insn.size; + block->truncated = 1; + } + + /* Log calls' return addresses for return compression. */ + status = pt_blk_log_call(decoder, &insn, &iext); + if (status < 0) + return status; + + /* We have a new instruction. */ + block->iclass = insn.iclass; + block->end_ip = insn.ip; + block->ninsn = ninsn; + + *pinsn = insn; + *piext = iext; + + return 1; +} + + +/* Proceed to a particular type of instruction without using trace. + * + * Proceed until we reach an instruction for which @predicate returns a positive + * integer or until: + * + * - @predicate returns an error: return error + * - @block is full: return zero + * - @block would switch sections: return zero + * - we would need trace: return -pte_bad_query + * + * Provide the last instruction that was reached in @insn and @iext. + * + * Update @decoder->ip to point to the last IP that was reached. If we fail due + * to lack of trace or if we reach a desired instruction, this is @insn->ip; + * otherwise this is the next instruction's IP. + * + * Returns a positive integer if a suitable instruction was reached. + * Returns zero if no such instruction was reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_insn(struct pt_block_decoder *decoder, + struct pt_block *block, + struct pt_insn *insn, + struct pt_insn_ext *iext, + int (*predicate)(const struct pt_insn *, + const struct pt_insn_ext *)) +{ + int status; + + if (!decoder || !insn || !predicate) + return -pte_internal; + + for (;;) { + status = pt_blk_proceed_one_insn(decoder, block, insn, iext); + if (status <= 0) + return status; + + /* We're done if this instruction matches the spec (positive + * status) or we run into an error (negative status). + */ + status = predicate(insn, iext); + if (status != 0) + return status; + + /* Let's see if we can proceed to the next IP without trace. */ + status = pt_insn_next_ip(&decoder->ip, insn, iext); + if (status < 0) + return status; + + /* End the block if the user asked us to. + * + * We only need to take care about direct near branches. + * Indirect and far branches require trace and will naturally + * end a block. + */ + if ((decoder->flags.variant.block.end_on_call && + (insn->iclass == ptic_call)) || + (decoder->flags.variant.block.end_on_jump && + (insn->iclass == ptic_jump))) + return 0; + } +} + +/* Proceed to a particular IP without using trace. + * + * Proceed until we reach @ip or until: + * + * - @block is full: return zero + * - @block would switch sections: return zero + * - we would need trace: return -pte_bad_query + * + * Provide the last instruction that was reached in @insn and @iext. If we + * reached @ip, this is the instruction preceding it. + * + * Update @decoder->ip to point to the last IP that was reached. If we fail due + * to lack of trace, this is @insn->ip; otherwise this is the next instruction's + * IP. + * + * Returns a positive integer if @ip was reached. + * Returns zero if no such instruction was reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_ip(struct pt_block_decoder *decoder, + struct pt_block *block, struct pt_insn *insn, + struct pt_insn_ext *iext, uint64_t ip) +{ + int status; + + if (!decoder || !insn) + return -pte_internal; + + for (;;) { + /* We're done when we reach @ip. We may not even have to decode + * a single instruction in some cases. + */ + if (decoder->ip == ip) + return 1; + + status = pt_blk_proceed_one_insn(decoder, block, insn, iext); + if (status <= 0) + return status; + + /* Let's see if we can proceed to the next IP without trace. */ + status = pt_insn_next_ip(&decoder->ip, insn, iext); + if (status < 0) + return status; + + /* End the block if the user asked us to. + * + * We only need to take care about direct near branches. + * Indirect and far branches require trace and will naturally + * end a block. + * + * The call at the end of the block may have reached @ip; make + * sure to indicate that. + */ + if ((decoder->flags.variant.block.end_on_call && + (insn->iclass == ptic_call)) || + (decoder->flags.variant.block.end_on_jump && + (insn->iclass == ptic_jump))) { + return (decoder->ip == ip ? 1 : 0); + } + } +} + +/* Proceed to a particular IP with trace, if necessary. + * + * Proceed until we reach @ip or until: + * + * - @block is full: return zero + * - @block would switch sections: return zero + * - we need trace: return zero + * + * Update @decoder->ip to point to the last IP that was reached. + * + * A return of zero ends @block. + * + * Returns a positive integer if @ip was reached. + * Returns zero if no such instruction was reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_ip_with_trace(struct pt_block_decoder *decoder, + struct pt_block *block, + uint64_t ip) +{ + struct pt_insn_ext iext; + struct pt_insn insn; + int status; + + /* Try to reach @ip without trace. + * + * We're also OK if @block overflowed or we switched sections and we + * have to try again in the next iteration. + */ + status = pt_blk_proceed_to_ip(decoder, block, &insn, &iext, ip); + if (status != -pte_bad_query) + return status; + + /* Needing trace is not an error. We use trace to determine the next + * start IP and end the block. + */ + return pt_blk_proceed_with_trace(decoder, &insn, &iext); +} + +static int pt_insn_skl014(const struct pt_insn *insn, + const struct pt_insn_ext *iext) +{ + if (!insn || !iext) + return 0; + + switch (insn->iclass) { + default: + return 0; + + case ptic_call: + case ptic_jump: + return iext->variant.branch.is_direct; + + case ptic_other: + return pt_insn_changes_cr3(insn, iext); + } +} + +/* Proceed to the location of a synchronous disabled event with suppressed IP + * considering SKL014. + * + * We have a (synchronous) disabled event pending. Proceed to the event + * location and indicate whether we were able to reach it. + * + * With SKL014 a TIP.PGD with suppressed IP may also be generated by a direct + * unconditional branch that clears FilterEn by jumping out of a filter region + * or into a TraceStop region. Use the filter configuration to determine the + * exact branch the event binds to. + * + * The last instruction that was reached is stored in @insn/@iext. + * + * Returns a positive integer if the event location was reached. + * Returns zero if the event location was not reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_skl014(struct pt_block_decoder *decoder, + struct pt_block *block, struct pt_insn *insn, + struct pt_insn_ext *iext) +{ + const struct pt_conf_addr_filter *addr_filter; + int status; + + if (!decoder || !block || !insn || !iext) + return -pte_internal; + + addr_filter = &decoder->query.config.addr_filter; + for (;;) { + uint64_t ip; + + status = pt_blk_proceed_to_insn(decoder, block, insn, iext, + pt_insn_skl014); + if (status <= 0) + break; + + /* The erratum doesn't apply if we can bind the event to a + * CR3-changing instruction. + */ + if (pt_insn_changes_cr3(insn, iext)) + break; + + /* Check the filter against the branch target. */ + status = pt_insn_next_ip(&ip, insn, iext); + if (status < 0) + break; + + status = pt_filter_addr_check(addr_filter, ip); + if (status <= 0) { + /* We need to flip the indication. + * + * We reached the event location when @ip lies inside a + * tracing-disabled region. + */ + if (!status) + status = 1; + + break; + } + + /* This is not the correct instruction. Proceed past it and try + * again. + */ + decoder->ip = ip; + + /* End the block if the user asked us to. + * + * We only need to take care about direct near branches. + * Indirect and far branches require trace and will naturally + * end a block. + */ + if ((decoder->flags.variant.block.end_on_call && + (insn->iclass == ptic_call)) || + (decoder->flags.variant.block.end_on_jump && + (insn->iclass == ptic_jump))) + break; + } + + return status; +} + +/* Proceed to the event location for a disabled event. + * + * We have a (synchronous) disabled event pending. Proceed to the event + * location and indicate whether we were able to reach it. + * + * The last instruction that was reached is stored in @insn/@iext. + * + * Returns a positive integer if the event location was reached. + * Returns zero if the event location was not reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_disabled(struct pt_block_decoder *decoder, + struct pt_block *block, + struct pt_insn *insn, + struct pt_insn_ext *iext, + const struct pt_event *ev) +{ + if (!decoder || !block || !ev) + return -pte_internal; + + if (ev->ip_suppressed) { + /* Due to SKL014 the TIP.PGD payload may be suppressed also for + * direct branches. + * + * If we don't have a filter configuration we assume that no + * address filters were used and the erratum does not apply. + * + * We might otherwise disable tracing too early. + */ + if (decoder->query.config.addr_filter.config.addr_cfg && + decoder->query.config.errata.skl014) + return pt_blk_proceed_skl014(decoder, block, insn, + iext); + + /* A synchronous disabled event also binds to far branches and + * CPL-changing instructions. Both would require trace, + * however, and are thus implicitly handled by erroring out. + * + * The would-require-trace error is handled by our caller. + */ + return pt_blk_proceed_to_insn(decoder, block, insn, iext, + pt_insn_changes_cr3); + } else + return pt_blk_proceed_to_ip(decoder, block, insn, iext, + ev->variant.disabled.ip); +} + +/* Set the expected resume address for a synchronous disable. + * + * On a synchronous disable, @decoder->ip still points to the instruction to + * which the event bound. That's not where we expect tracing to resume. + * + * For calls, a fair assumption is that tracing resumes after returning from the + * called function. For other types of instructions, we simply don't know. + * + * Returns zero on success, a negative pt_error_code otherwise. + */ +static int pt_blk_set_disable_resume_ip(struct pt_block_decoder *decoder, + const struct pt_insn *insn) +{ + if (!decoder || !insn) + return -pte_internal; + + switch (insn->iclass) { + case ptic_call: + case ptic_far_call: + decoder->ip = insn->ip + insn->size; + break; + + default: + decoder->ip = 0ull; + break; + } + + return 0; +} + +/* Proceed to the event location for an async paging event. + * + * We have an async paging event pending. Proceed to the event location and + * indicate whether we were able to reach it. Needing trace in order to proceed + * is not an error in this case but ends the block. + * + * Returns a positive integer if the event location was reached. + * Returns zero if the event location was not reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_async_paging(struct pt_block_decoder *decoder, + struct pt_block *block, + const struct pt_event *ev) +{ + int status; + + if (!decoder || !ev) + return -pte_internal; + + /* Apply the event immediately if we don't have an IP. */ + if (ev->ip_suppressed) + return 1; + + status = pt_blk_proceed_to_ip_with_trace(decoder, block, + ev->variant.async_paging.ip); + if (status < 0) + return status; + + /* We may have reached the IP. */ + return (decoder->ip == ev->variant.async_paging.ip ? 1 : 0); +} + +/* Proceed to the event location for an async vmcs event. + * + * We have an async vmcs event pending. Proceed to the event location and + * indicate whether we were able to reach it. Needing trace in order to proceed + * is not an error in this case but ends the block. + * + * Returns a positive integer if the event location was reached. + * Returns zero if the event location was not reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_async_vmcs(struct pt_block_decoder *decoder, + struct pt_block *block, + const struct pt_event *ev) +{ + int status; + + if (!decoder || !ev) + return -pte_internal; + + /* Apply the event immediately if we don't have an IP. */ + if (ev->ip_suppressed) + return 1; + + status = pt_blk_proceed_to_ip_with_trace(decoder, block, + ev->variant.async_vmcs.ip); + if (status < 0) + return status; + + /* We may have reached the IP. */ + return (decoder->ip == ev->variant.async_vmcs.ip ? 1 : 0); +} + +/* Proceed to the event location for an exec mode event. + * + * We have an exec mode event pending. Proceed to the event location and + * indicate whether we were able to reach it. Needing trace in order to proceed + * is not an error in this case but ends the block. + * + * Returns a positive integer if the event location was reached. + * Returns zero if the event location was not reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_exec_mode(struct pt_block_decoder *decoder, + struct pt_block *block, + const struct pt_event *ev) +{ + int status; + + if (!decoder || !ev) + return -pte_internal; + + /* Apply the event immediately if we don't have an IP. */ + if (ev->ip_suppressed) + return 1; + + status = pt_blk_proceed_to_ip_with_trace(decoder, block, + ev->variant.exec_mode.ip); + if (status < 0) + return status; + + /* We may have reached the IP. */ + return (decoder->ip == ev->variant.exec_mode.ip ? 1 : 0); +} + +/* Proceed to the event location for a ptwrite event. + * + * We have a ptwrite event pending. Proceed to the event location and indicate + * whether we were able to reach it. + * + * In case of the event binding to a ptwrite instruction, we pass beyond that + * instruction and update the event to provide the instruction's IP. + * + * In the case of the event binding to an IP provided in the event, we move + * beyond the instruction at that IP. + * + * Returns a positive integer if the event location was reached. + * Returns zero if the event location was not reached. + * Returns a negative error code otherwise. + */ +static int pt_blk_proceed_to_ptwrite(struct pt_block_decoder *decoder, + struct pt_block *block, + struct pt_insn *insn, + struct pt_insn_ext *iext, + struct pt_event *ev) +{ + int status; + + if (!insn || !ev) + return -pte_internal; + + /* If we don't have an IP, the event binds to the next PTWRITE + * instruction. + * + * If we have an IP it still binds to the next PTWRITE instruction but + * now the IP tells us where that instruction is. This makes most sense + * when tracing is disabled and we don't have any other means of finding + * the PTWRITE instruction. We nevertheless distinguish the two cases, + * here. + * + * In both cases, we move beyond the PTWRITE instruction, so it will be + * the last instruction in the current block and @decoder->ip will point + * to the instruction following it. + */ + if (ev->ip_suppressed) { + status = pt_blk_proceed_to_insn(decoder, block, insn, iext, + pt_insn_is_ptwrite); + if (status <= 0) + return status; + + /* We now know the IP of the PTWRITE instruction corresponding + * to this event. Fill it in to make it more convenient for the + * user to process the event. + */ + ev->variant.ptwrite.ip = insn->ip; + ev->ip_suppressed = 0; + } else { + status = pt_blk_proceed_to_ip(decoder, block, insn, iext, + ev->variant.ptwrite.ip); + if (status <= 0) + return status; + + /* We reached the PTWRITE instruction and @decoder->ip points to + * it; @insn/@iext still contain the preceding instruction. + * + * Proceed beyond the PTWRITE to account for it. Note that we + * may still overflow the block, which would cause us to + * postpone both instruction and event to the next block. + */ + status = pt_blk_proceed_one_insn(decoder, block, insn, iext); + if (status <= 0) + return status; + } + + return 1; +} + +/* Try to work around erratum SKD022. + * + * If we get an asynchronous disable on VMLAUNCH or VMRESUME, the FUP that + * caused the disable to be asynchronous might have been bogous. + * + * Returns a positive integer if the erratum has been handled. + * Returns zero if the erratum does not apply. + * Returns a negative error code otherwise. + */ +static int pt_blk_handle_erratum_skd022(struct pt_block_decoder *decoder, + struct pt_event *ev) +{ + struct pt_insn_ext iext; + struct pt_insn insn; + int errcode; + + if (!decoder || !ev) + return -pte_internal; + + insn.mode = decoder->mode; + insn.ip = ev->variant.async_disabled.at; + + errcode = pt_insn_decode(&insn, &iext, decoder->image, &decoder->asid); + if (errcode < 0) + return 0; + + switch (iext.iclass) { + default: + /* The erratum does not apply. */ + return 0; + + case PTI_INST_VMLAUNCH: + case PTI_INST_VMRESUME: + /* The erratum may apply. We can't be sure without a lot more + * analysis. Let's assume it does. + * + * We turn the async disable into a sync disable. Our caller + * will restart event processing. + */ + ev->type = ptev_disabled; + ev->variant.disabled.ip = ev->variant.async_disabled.ip; + + return 1; + } +} + +/* Postpone proceeding past @insn/@iext and indicate a pending event. + * + * There may be further events pending on @insn/@iext. Postpone proceeding past + * @insn/@iext until we processed all events that bind to it. + * + * Returns a non-negative pt_status_flag bit-vector indicating a pending event + * on success, a negative pt_error_code otherwise. + */ +static int pt_blk_postpone_insn(struct pt_block_decoder *decoder, + const struct pt_insn *insn, + const struct pt_insn_ext *iext) +{ + if (!decoder || !insn || !iext) + return -pte_internal; + + /* Only one can be active. */ + if (decoder->process_insn) + return -pte_internal; + + decoder->process_insn = 1; + decoder->insn = *insn; + decoder->iext = *iext; + + return pt_blk_status(decoder, pts_event_pending); +} + +/* Remove any postponed instruction from @decoder. + * + * Returns zero on success, a negative pt_error_code otherwise. + */ +static int pt_blk_clear_postponed_insn(struct pt_block_decoder *decoder) +{ + if (!decoder) + return -pte_internal; + + decoder->process_insn = 0; + decoder->bound_paging = 0; + decoder->bound_vmcs = 0; + decoder->bound_ptwrite = 0; + + return 0; +} + +/* Proceed past a postponed instruction. + * + * If an instruction has been postponed in @decoder, proceed past it. + * + * Returns zero on success, a negative pt_error_code otherwise. + */ +static int pt_blk_proceed_postponed_insn(struct pt_block_decoder *decoder) +{ + int status; + + if (!decoder) + return -pte_internal; + + /* There's nothing to do if we have no postponed instruction. */ + if (!decoder->process_insn) + return 0; + + /* There's nothing to do if tracing got disabled. */ + if (!decoder->enabled) + return pt_blk_clear_postponed_insn(decoder); + + status = pt_insn_next_ip(&decoder->ip, &decoder->insn, &decoder->iext); + if (status < 0) { + if (status != -pte_bad_query) + return status; + + status = pt_blk_proceed_with_trace(decoder, &decoder->insn, + &decoder->iext); + if (status < 0) + return status; + } + + return pt_blk_clear_postponed_insn(decoder); +} + +/* Proceed to the next event. + * + * We have an event pending. Proceed to the event location and indicate the + * event to the user. + * + * On our way to the event location we may also be forced to postpone the event + * to the next block, e.g. if we overflow the number of instructions in the + * block or if we need trace in order to reach the event location. + * + * If we're not able to reach the event location, we return zero. This is what + * pt_blk_status() would return since: + * + * - we suppress pts_eos as long as we're processing events + * - we do not set pts_ip_suppressed since tracing must be enabled + * + * Returns a non-negative pt_status_flag bit-vector on success, a negative error + * code otherwise. + */ +static int pt_blk_proceed_event(struct pt_block_decoder *decoder, + struct pt_block *block) +{ + struct pt_insn_ext iext; + struct pt_insn insn; + struct pt_event *ev; + int status; + + if (!decoder || !decoder->process_event || !block) + return -pte_internal; + + ev = &decoder->event; + switch (ev->type) { + case ptev_enabled: + break; + + case ptev_disabled: + status = pt_blk_proceed_to_disabled(decoder, block, &insn, + &iext, ev); + if (status <= 0) { + /* A synchronous disable event also binds to the next + * indirect or conditional branch, i.e. to any branch + * that would have required trace. + */ + if (status != -pte_bad_query) + return status; + + status = pt_blk_set_disable_resume_ip(decoder, &insn); + if (status < 0) + return status; + } + + break; + + case ptev_async_disabled: + status = pt_blk_proceed_to_ip(decoder, block, &insn, &iext, + ev->variant.async_disabled.at); + if (status <= 0) + return status; + + if (decoder->query.config.errata.skd022) { + status = pt_blk_handle_erratum_skd022(decoder, ev); + if (status != 0) { + if (status < 0) + return status; + + /* If the erratum hits, we modify the event. + * Try again. + */ + return pt_blk_proceed_event(decoder, block); + } + } + + break; + + case ptev_async_branch: + status = pt_blk_proceed_to_ip(decoder, block, &insn, &iext, + ev->variant.async_branch.from); + if (status <= 0) + return status; + + break; + + case ptev_paging: + if (!decoder->enabled) + break; + + status = pt_blk_proceed_to_insn(decoder, block, &insn, &iext, + pt_insn_binds_to_pip); + if (status <= 0) + return status; + + /* We bound a paging event. Make sure we do not bind further + * paging events to this instruction. + */ + decoder->bound_paging = 1; + + return pt_blk_postpone_insn(decoder, &insn, &iext); + + case ptev_async_paging: + status = pt_blk_proceed_to_async_paging(decoder, block, ev); + if (status <= 0) + return status; + + break; + + case ptev_vmcs: + if (!decoder->enabled) + break; + + status = pt_blk_proceed_to_insn(decoder, block, &insn, &iext, + pt_insn_binds_to_vmcs); + if (status <= 0) + return status; + + /* We bound a vmcs event. Make sure we do not bind further vmcs + * events to this instruction. + */ + decoder->bound_vmcs = 1; + + return pt_blk_postpone_insn(decoder, &insn, &iext); + + case ptev_async_vmcs: + status = pt_blk_proceed_to_async_vmcs(decoder, block, ev); + if (status <= 0) + return status; + + break; + + case ptev_overflow: + break; + + case ptev_exec_mode: + status = pt_blk_proceed_to_exec_mode(decoder, block, ev); + if (status <= 0) + return status; + + break; + + case ptev_tsx: + if (ev->ip_suppressed) + break; + + status = pt_blk_proceed_to_ip(decoder, block, &insn, &iext, + ev->variant.tsx.ip); + if (status <= 0) + return status; + + break; + + case ptev_stop: + break; + + case ptev_exstop: + if (!decoder->enabled || ev->ip_suppressed) + break; + + status = pt_blk_proceed_to_ip(decoder, block, &insn, &iext, + ev->variant.exstop.ip); + if (status <= 0) + return status; + + break; + + case ptev_mwait: + if (!decoder->enabled || ev->ip_suppressed) + break; + + status = pt_blk_proceed_to_ip(decoder, block, &insn, &iext, + ev->variant.mwait.ip); + if (status <= 0) + return status; + + break; + + case ptev_pwre: + case ptev_pwrx: + break; + + case ptev_ptwrite: + if (!decoder->enabled) + break; + + status = pt_blk_proceed_to_ptwrite(decoder, block, &insn, + &iext, ev); + if (status <= 0) + return status; + + /* We bound a ptwrite event. Make sure we do not bind further + * ptwrite events to this instruction. + */ + decoder->bound_ptwrite = 1; + + return pt_blk_postpone_insn(decoder, &insn, &iext); + + case ptev_tick: + case ptev_cbr: + case ptev_mnt: + break; + } + + return pt_blk_status(decoder, pts_event_pending); +} + +/* Proceed to the next decision point without using the block cache. + * + * Tracing is enabled and we don't have an event pending. Proceed as far as + * we get without trace. Stop when we either: + * + * - need trace in order to continue + * - overflow the max number of instructions in a block + * + * We actually proceed one instruction further to get the start IP for the next + * block. This only updates @decoder's internal state, though. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_proceed_no_event_uncached(struct pt_block_decoder *decoder, + struct pt_block *block) +{ + struct pt_insn_ext iext; + struct pt_insn insn; + int status; + + if (!decoder || !block) + return -pte_internal; + + /* This is overly conservative, really. We shouldn't get a bad-query + * status unless we decoded at least one instruction successfully. + */ + memset(&insn, 0, sizeof(insn)); + memset(&iext, 0, sizeof(iext)); + + /* Proceed as far as we get without trace. */ + status = pt_blk_proceed_to_insn(decoder, block, &insn, &iext, + pt_insn_false); + if (status < 0) { + if (status != -pte_bad_query) + return status; + + return pt_blk_proceed_with_trace(decoder, &insn, &iext); + } + + return 0; +} + +/* Check if @ip is contained in @section loaded at @laddr. + * + * Returns non-zero if it is. + * Returns zero if it isn't or of @section is NULL. + */ +static inline int pt_blk_is_in_section(const struct pt_mapped_section *msec, + uint64_t ip) +{ + uint64_t begin, end; + + begin = pt_msec_begin(msec); + end = pt_msec_end(msec); + + return (begin <= ip && ip < end); +} + +/* Insert a trampoline block cache entry. + * + * Add a trampoline block cache entry at @ip to continue at @nip, where @nip + * must be the next instruction after @ip. + * + * Both @ip and @nip must be section-relative + * + * Returns zero on success, a negative error code otherwise. + */ +static inline int pt_blk_add_trampoline(struct pt_block_cache *bcache, + uint64_t ip, uint64_t nip, + enum pt_exec_mode mode) +{ + struct pt_bcache_entry bce; + int64_t disp; + + /* The displacement from @ip to @nip for the trampoline. */ + disp = (int64_t) (nip - ip); + + memset(&bce, 0, sizeof(bce)); + bce.displacement = (int32_t) disp; + bce.ninsn = 1; + bce.mode = mode; + bce.qualifier = ptbq_again; + + /* If we can't reach @nip without overflowing the displacement field, we + * have to stop and re-decode the instruction at @ip. + */ + if ((int64_t) bce.displacement != disp) { + + memset(&bce, 0, sizeof(bce)); + bce.ninsn = 1; + bce.mode = mode; + bce.qualifier = ptbq_decode; + } + + return pt_bcache_add(bcache, ip, bce); +} + +/* Insert a decode block cache entry. + * + * Add a decode block cache entry at @ioff. + * + * Returns zero on success, a negative error code otherwise. + */ +static inline int pt_blk_add_decode(struct pt_block_cache *bcache, + uint64_t ioff, enum pt_exec_mode mode) +{ + struct pt_bcache_entry bce; + + memset(&bce, 0, sizeof(bce)); + bce.ninsn = 1; + bce.mode = mode; + bce.qualifier = ptbq_decode; + + return pt_bcache_add(bcache, ioff, bce); +} + +enum { + /* The maximum number of steps when filling the block cache. */ + bcache_fill_steps = 0x400 +}; + +/* Proceed to the next instruction and fill the block cache for @decoder->ip. + * + * Tracing is enabled and we don't have an event pending. The current IP is not + * yet cached. + * + * Proceed one instruction without using the block cache, then try to proceed + * further using the block cache. + * + * On our way back, add a block cache entry for the IP before proceeding. Note + * that the recursion is bounded by @steps and ultimately by the maximum number + * of instructions in a block. + * + * Returns zero on success, a negative error code otherwise. + */ +static int +pt_blk_proceed_no_event_fill_cache(struct pt_block_decoder *decoder, + struct pt_block *block, + struct pt_block_cache *bcache, + const struct pt_mapped_section *msec, + size_t steps) +{ + struct pt_bcache_entry bce; + struct pt_insn_ext iext; + struct pt_insn insn; + uint64_t nip, dip, ioff, noff; + int64_t disp; + int status; + + if (!decoder || !steps) + return -pte_internal; + + /* Proceed one instruction by decoding and examining it. + * + * Note that we also return on a status of zero that indicates that the + * instruction didn't fit into @block. + */ + status = pt_blk_proceed_one_insn(decoder, block, &insn, &iext); + if (status <= 0) + return status; + + ioff = pt_msec_unmap(msec, insn.ip); + + /* Let's see if we can proceed to the next IP without trace. + * + * If we can't, this is certainly a decision point. + */ + status = pt_insn_next_ip(&decoder->ip, &insn, &iext); + if (status < 0) { + if (status != -pte_bad_query) + return status; + + memset(&bce, 0, sizeof(bce)); + bce.ninsn = 1; + bce.mode = insn.mode; + bce.isize = insn.size; + + /* Clear the instruction size in case of overflows. */ + if ((uint8_t) bce.isize != insn.size) + bce.isize = 0; + + switch (insn.iclass) { + case ptic_ptwrite: + case ptic_error: + case ptic_other: + return -pte_internal; + + case ptic_jump: + /* A direct jump doesn't require trace. */ + if (iext.variant.branch.is_direct) + return -pte_internal; + + bce.qualifier = ptbq_indirect; + break; + + case ptic_call: + /* A direct call doesn't require trace. */ + if (iext.variant.branch.is_direct) + return -pte_internal; + + bce.qualifier = ptbq_ind_call; + break; + + case ptic_return: + bce.qualifier = ptbq_return; + break; + + case ptic_cond_jump: + bce.qualifier = ptbq_cond; + break; + + case ptic_far_call: + case ptic_far_return: + case ptic_far_jump: + bce.qualifier = ptbq_indirect; + break; + } + + /* If the block was truncated, we have to decode its last + * instruction each time. + * + * We could have skipped the above switch and size assignment in + * this case but this is already a slow and hopefully infrequent + * path. + */ + if (block->truncated) + bce.qualifier = ptbq_decode; + + status = pt_bcache_add(bcache, ioff, bce); + if (status < 0) + return status; + + return pt_blk_proceed_with_trace(decoder, &insn, &iext); + } + + /* The next instruction's IP. */ + nip = decoder->ip; + noff = pt_msec_unmap(msec, nip); + + /* Even if we were able to proceed without trace, we might have to stop + * here for various reasons: + * + * - at near direct calls to update the return-address stack + * + * We are forced to re-decode @insn to get the branch displacement. + * + * Even though it is constant, we don't cache it to avoid increasing + * the size of a cache entry. Note that the displacement field is + * zero for this entry and we might be tempted to use it - but other + * entries that point to this decision point will have non-zero + * displacement. + * + * We could proceed after a near direct call but we migh as well + * postpone it to the next iteration. Make sure to end the block if + * @decoder->flags.variant.block.end_on_call is set, though. + * + * - at near direct backwards jumps to detect section splits + * + * In case the current section is split underneath us, we must take + * care to detect that split. + * + * There is one corner case where the split is in the middle of a + * linear sequence of instructions that branches back into the + * originating section. + * + * Calls, indirect branches, and far branches are already covered + * since they either require trace or already require us to stop + * (i.e. near direct calls) for other reasons. That leaves near + * direct backward jumps. + * + * Instead of the decode stop at the jump instruction we're using we + * could have made sure that other block cache entries that extend + * this one insert a trampoline to the jump's entry. This would + * have been a bit more complicated. + * + * - if we switched sections + * + * This ends a block just like a branch that requires trace. + * + * We need to re-decode @insn in order to determine the start IP of + * the next block. + * + * - if the block is truncated + * + * We need to read the last instruction's memory from multiple + * sections and provide it to the user. + * + * We could still use the block cache but then we'd have to handle + * this case for each qualifier. Truncation is hopefully rare and + * having to read the memory for the instruction from multiple + * sections is already slow. Let's rather keep things simple and + * route it through the decode flow, where we already have + * everything in place. + */ + switch (insn.iclass) { + case ptic_call: + return pt_blk_add_decode(bcache, ioff, insn.mode); + + case ptic_jump: + /* An indirect branch requires trace and should have been + * handled above. + */ + if (!iext.variant.branch.is_direct) + return -pte_internal; + + if (iext.variant.branch.displacement < 0 || + decoder->flags.variant.block.end_on_jump) + return pt_blk_add_decode(bcache, ioff, insn.mode); + + fallthrough; + default: + if (!pt_blk_is_in_section(msec, nip) || block->truncated) + return pt_blk_add_decode(bcache, ioff, insn.mode); + + break; + } + + /* We proceeded one instruction. Let's see if we have a cache entry for + * the next instruction. + */ + status = pt_bcache_lookup(&bce, bcache, noff); + if (status < 0) + return status; + + /* If we don't have a valid cache entry, yet, fill the cache some more. + * + * On our way back, we add a cache entry for this instruction based on + * the cache entry of the succeeding instruction. + */ + if (!pt_bce_is_valid(bce)) { + /* If we exceeded the maximum number of allowed steps, we insert + * a trampoline to the next instruction. + * + * The next time we encounter the same code, we will use the + * trampoline to jump directly to where we left off this time + * and continue from there. + */ + steps -= 1; + if (!steps) + return pt_blk_add_trampoline(bcache, ioff, noff, + insn.mode); + + status = pt_blk_proceed_no_event_fill_cache(decoder, block, + bcache, msec, + steps); + if (status < 0) + return status; + + /* Let's see if we have more luck this time. */ + status = pt_bcache_lookup(&bce, bcache, noff); + if (status < 0) + return status; + + /* If we still don't have a valid cache entry, we're done. Most + * likely, @block overflowed and we couldn't proceed past the + * next instruction. + */ + if (!pt_bce_is_valid(bce)) + return 0; + } + + /* We must not have switched execution modes. + * + * This would require an event and we're on the no-event flow. + */ + if (pt_bce_exec_mode(bce) != insn.mode) + return -pte_internal; + + /* The decision point IP and the displacement from @insn.ip. */ + dip = nip + (uint64_t) (int64_t) bce.displacement; + disp = (int64_t) (dip - insn.ip); + + /* We may have switched sections if the section was split. See + * pt_blk_proceed_no_event_cached() for a more elaborate comment. + * + * We're not adding a block cache entry since this won't apply to the + * original section which may be shared with other decoders. + * + * We will instead take the slow path until the end of the section. + */ + if (!pt_blk_is_in_section(msec, dip)) + return 0; + + /* Let's try to reach @nip's decision point from @insn.ip. + * + * There are two fields that may overflow: @bce.ninsn and + * @bce.displacement. + */ + bce.ninsn += 1; + bce.displacement = (int32_t) disp; + + /* If none of them overflowed, we're done. + * + * If one or both overflowed, let's try to insert a trampoline, i.e. we + * try to reach @dip via a ptbq_again entry to @nip. + */ + if (!bce.ninsn || ((int64_t) bce.displacement != disp)) + return pt_blk_add_trampoline(bcache, ioff, noff, insn.mode); + + /* We're done. Add the cache entry. + * + * There's a chance that other decoders updated the cache entry in the + * meantime. They should have come to the same conclusion as we, + * though, and the cache entries should be identical. + * + * Cache updates are atomic so even if the two versions were not + * identical, we wouldn't care because they are both correct. + */ + return pt_bcache_add(bcache, ioff, bce); +} + +/* Proceed at a potentially truncated instruction. + * + * We were not able to decode the instruction at @decoder->ip in @decoder's + * cached section. This is typically caused by not having enough bytes. + * + * Try to decode the instruction again using the entire image. If this succeeds + * we expect to end up with an instruction that was truncated in the section it + * started. We provide the full instruction in this case and end the block. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_proceed_truncated(struct pt_block_decoder *decoder, + struct pt_block *block) +{ + struct pt_insn_ext iext; + struct pt_insn insn; + int errcode; + + if (!decoder || !block) + return -pte_internal; + + memset(&iext, 0, sizeof(iext)); + memset(&insn, 0, sizeof(insn)); + + insn.mode = decoder->mode; + insn.ip = decoder->ip; + + errcode = pt_insn_decode(&insn, &iext, decoder->image, &decoder->asid); + if (errcode < 0) + return errcode; + + /* We shouldn't use this function if the instruction isn't truncated. */ + if (!insn.truncated) + return -pte_internal; + + /* Provide the instruction in the block. This ends the block. */ + memcpy(block->raw, insn.raw, insn.size); + block->iclass = insn.iclass; + block->size = insn.size; + block->truncated = 1; + + /* Log calls' return addresses for return compression. */ + errcode = pt_blk_log_call(decoder, &insn, &iext); + if (errcode < 0) + return errcode; + + /* Let's see if we can proceed to the next IP without trace. + * + * The truncated instruction ends the block but we still need to get the + * next block's start IP. + */ + errcode = pt_insn_next_ip(&decoder->ip, &insn, &iext); + if (errcode < 0) { + if (errcode != -pte_bad_query) + return errcode; + + return pt_blk_proceed_with_trace(decoder, &insn, &iext); + } + + return 0; +} + +/* Proceed to the next decision point using the block cache. + * + * Tracing is enabled and we don't have an event pending. We already set + * @block's isid. All reads are done within @msec as we're not switching + * sections between blocks. + * + * Proceed as far as we get without trace. Stop when we either: + * + * - need trace in order to continue + * - overflow the max number of instructions in a block + * + * We actually proceed one instruction further to get the start IP for the next + * block. This only updates @decoder's internal state, though. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_proceed_no_event_cached(struct pt_block_decoder *decoder, + struct pt_block *block, + struct pt_block_cache *bcache, + const struct pt_mapped_section *msec) +{ + struct pt_bcache_entry bce; + uint16_t binsn, ninsn; + uint64_t offset, nip; + int status; + + if (!decoder || !block) + return -pte_internal; + + offset = pt_msec_unmap(msec, decoder->ip); + status = pt_bcache_lookup(&bce, bcache, offset); + if (status < 0) + return status; + + /* If we don't find a valid cache entry, fill the cache. */ + if (!pt_bce_is_valid(bce)) + return pt_blk_proceed_no_event_fill_cache(decoder, block, + bcache, msec, + bcache_fill_steps); + + /* If we switched sections, the origianl section must have been split + * underneath us. A split preserves the block cache of the original + * section. + * + * Crossing sections requires ending the block so we can indicate the + * proper isid for the entire block. + * + * Plus there's the chance that the new section that caused the original + * section to split changed instructions. + * + * This check will also cover changes to a linear sequence of code we + * would otherwise have jumped over as long as the start and end are in + * different sub-sections. + * + * Since we stop on every (backwards) branch (through an artificial stop + * in the case of a near direct backward branch) we will detect all + * section splits. + * + * Switch to the slow path until we reach the end of this section. + */ + nip = decoder->ip + (uint64_t) (int64_t) bce.displacement; + if (!pt_blk_is_in_section(msec, nip)) + return pt_blk_proceed_no_event_uncached(decoder, block); + + /* We have a valid cache entry. Let's first check if the way to the + * decision point still fits into @block. + * + * If it doesn't, we end the block without filling it as much as we + * could since this would require us to switch to the slow path. + * + * On the next iteration, we will start with an empty block, which is + * guaranteed to have enough room for at least one block cache entry. + */ + binsn = block->ninsn; + ninsn = binsn + (uint16_t) bce.ninsn; + if (ninsn < binsn) + return 0; + + /* Jump ahead to the decision point and proceed from there. + * + * We're not switching execution modes so even if @block already has an + * execution mode, it will be the one we're going to set. + */ + decoder->ip = nip; + + /* We don't know the instruction class so we should be setting it to + * ptic_error. Since we will be able to fill it back in later in most + * cases, we move the clearing to the switch cases that don't. + */ + block->end_ip = nip; + block->ninsn = ninsn; + block->mode = pt_bce_exec_mode(bce); + + + switch (pt_bce_qualifier(bce)) { + case ptbq_again: + /* We're not able to reach the actual decision point due to + * overflows so we inserted a trampoline. + * + * We don't know the instruction and it is not guaranteed that + * we will proceed further (e.g. if @block overflowed). Let's + * clear any previously stored instruction class which has + * become invalid when we updated @block->ninsn. + */ + block->iclass = ptic_error; + + return pt_blk_proceed_no_event_cached(decoder, block, bcache, + msec); + + case ptbq_cond: + /* We're at a conditional branch. */ + block->iclass = ptic_cond_jump; + + /* Let's first check whether we know the size of the + * instruction. If we do, we might get away without decoding + * the instruction. + * + * If we don't know the size we might as well do the full decode + * and proceed-with-trace flow we do for ptbq_decode. + */ + if (bce.isize) { + uint64_t ip; + int taken; + + /* If the branch is not taken, we don't need to decode + * the instruction at @decoder->ip. + * + * If it is taken, we have to implement everything here. + * We can't use the normal decode and proceed-with-trace + * flow since we already consumed the TNT bit. + */ + status = pt_blk_cond_branch(decoder, &taken); + if (status < 0) + return status; + + /* Preserve the query decoder's response which indicates + * upcoming events. + */ + decoder->status = status; + + ip = decoder->ip; + if (taken) { + struct pt_insn_ext iext; + struct pt_insn insn; + + memset(&iext, 0, sizeof(iext)); + memset(&insn, 0, sizeof(insn)); + + insn.mode = pt_bce_exec_mode(bce); + insn.ip = ip; + + status = pt_blk_decode_in_section(&insn, &iext, + msec); + if (status < 0) + return status; + + ip += (uint64_t) (int64_t) + iext.variant.branch.displacement; + } + + decoder->ip = ip + bce.isize; + break; + } + + fallthrough; + case ptbq_decode: { + struct pt_insn_ext iext; + struct pt_insn insn; + + /* We need to decode the instruction at @decoder->ip and decide + * what to do based on that. + * + * We already accounted for the instruction so we can't just + * call pt_blk_proceed_one_insn(). + */ + + memset(&iext, 0, sizeof(iext)); + memset(&insn, 0, sizeof(insn)); + + insn.mode = pt_bce_exec_mode(bce); + insn.ip = decoder->ip; + + status = pt_blk_decode_in_section(&insn, &iext, msec); + if (status < 0) { + if (status != -pte_bad_insn) + return status; + + return pt_blk_proceed_truncated(decoder, block); + } + + /* We just decoded @insn so we know the instruction class. */ + block->iclass = insn.iclass; + + /* Log calls' return addresses for return compression. */ + status = pt_blk_log_call(decoder, &insn, &iext); + if (status < 0) + return status; + + /* Let's see if we can proceed to the next IP without trace. + * + * Note that we also stop due to displacement overflows or to + * maintain the return-address stack for near direct calls. + */ + status = pt_insn_next_ip(&decoder->ip, &insn, &iext); + if (status < 0) { + if (status != -pte_bad_query) + return status; + + /* We can't, so let's proceed with trace, which + * completes the block. + */ + return pt_blk_proceed_with_trace(decoder, &insn, &iext); + } + + /* End the block if the user asked us to. + * + * We only need to take care about direct near branches. + * Indirect and far branches require trace and will naturally + * end a block. + */ + if ((decoder->flags.variant.block.end_on_call && + (insn.iclass == ptic_call)) || + (decoder->flags.variant.block.end_on_jump && + (insn.iclass == ptic_jump))) + break; + + /* If we can proceed without trace and we stay in @msec we may + * proceed further. + * + * We're done if we switch sections, though. + */ + if (!pt_blk_is_in_section(msec, decoder->ip)) + break; + + return pt_blk_proceed_no_event_cached(decoder, block, bcache, + msec); + } + + case ptbq_ind_call: { + uint64_t ip; + + /* We're at a near indirect call. */ + block->iclass = ptic_call; + + /* We need to update the return-address stack and query the + * destination IP. + */ + ip = decoder->ip; + + /* If we already know the size of the instruction, we don't need + * to re-decode it. + */ + if (bce.isize) + ip += bce.isize; + else { + struct pt_insn_ext iext; + struct pt_insn insn; + + memset(&iext, 0, sizeof(iext)); + memset(&insn, 0, sizeof(insn)); + + insn.mode = pt_bce_exec_mode(bce); + insn.ip = ip; + + status = pt_blk_decode_in_section(&insn, &iext, msec); + if (status < 0) + return status; + + ip += insn.size; + } + + status = pt_retstack_push(&decoder->retstack, ip); + if (status < 0) + return status; + + status = pt_blk_indirect_branch(decoder, &decoder->ip); + if (status < 0) + return status; + + /* Preserve the query decoder's response which indicates + * upcoming events. + */ + decoder->status = status; + break; + } + + case ptbq_return: { + int taken; + + /* We're at a near return. */ + block->iclass = ptic_return; + + /* Check for a compressed return. */ + status = pt_blk_cond_branch(decoder, &taken); + if (status < 0) { + if (status != -pte_bad_query) + return status; + + /* The return is not compressed. We need another query + * to determine the destination IP. + */ + status = pt_blk_indirect_branch(decoder, &decoder->ip); + if (status < 0) + return status; + + /* Preserve the query decoder's response which indicates + * upcoming events. + */ + decoder->status = status; + break; + } + + /* Preserve the query decoder's response which indicates + * upcoming events. + */ + decoder->status = status; + + /* A compressed return is indicated by a taken conditional + * branch. + */ + if (!taken) + return -pte_bad_retcomp; + + return pt_retstack_pop(&decoder->retstack, &decoder->ip); + } + + case ptbq_indirect: + /* We're at an indirect jump or far transfer. + * + * We don't know the exact instruction class and there's no + * reason to decode the instruction for any other purpose. + * + * Indicate that we don't know the instruction class and leave + * it to our caller to decode the instruction if needed. + */ + block->iclass = ptic_error; + + /* This is neither a near call nor return so we don't need to + * touch the return-address stack. + * + * Just query the destination IP. + */ + status = pt_blk_indirect_branch(decoder, &decoder->ip); + if (status < 0) + return status; + + /* Preserve the query decoder's response which indicates + * upcoming events. + */ + decoder->status = status; + break; + } + + return 0; +} + +static int pt_blk_msec_fill(struct pt_block_decoder *decoder, + const struct pt_mapped_section **pmsec) +{ + const struct pt_mapped_section *msec; + struct pt_section *section; + int isid, errcode; + + if (!decoder || !pmsec) + return -pte_internal; + + isid = pt_msec_cache_fill(&decoder->scache, &msec, decoder->image, + &decoder->asid, decoder->ip); + if (isid < 0) + return isid; + + section = pt_msec_section(msec); + if (!section) + return -pte_internal; + + *pmsec = msec; + + errcode = pt_section_request_bcache(section); + if (errcode < 0) + return errcode; + + return isid; +} + +static inline int pt_blk_msec_lookup(struct pt_block_decoder *decoder, + const struct pt_mapped_section **pmsec) +{ + int isid; + + if (!decoder) + return -pte_internal; + + isid = pt_msec_cache_read(&decoder->scache, pmsec, decoder->image, + decoder->ip); + if (isid < 0) { + if (isid != -pte_nomap) + return isid; + + return pt_blk_msec_fill(decoder, pmsec); + } + + return isid; +} + +/* Proceed to the next decision point - try using the cache. + * + * Tracing is enabled and we don't have an event pending. Proceed as far as + * we get without trace. Stop when we either: + * + * - need trace in order to continue + * - overflow the max number of instructions in a block + * + * We actually proceed one instruction further to get the start IP for the next + * block. This only updates @decoder's internal state, though. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_proceed_no_event(struct pt_block_decoder *decoder, + struct pt_block *block) +{ + const struct pt_mapped_section *msec; + struct pt_block_cache *bcache; + struct pt_section *section; + int isid; + + if (!decoder || !block) + return -pte_internal; + + isid = pt_blk_msec_lookup(decoder, &msec); + if (isid < 0) { + if (isid != -pte_nomap) + return isid; + + /* Even if there is no such section in the image, we may still + * read the memory via the callback function. + */ + return pt_blk_proceed_no_event_uncached(decoder, block); + } + + /* We do not switch sections inside a block. */ + if (isid != block->isid) { + if (!pt_blk_block_is_empty(block)) + return 0; + + block->isid = isid; + } + + section = pt_msec_section(msec); + if (!section) + return -pte_internal; + + bcache = pt_section_bcache(section); + if (!bcache) + return pt_blk_proceed_no_event_uncached(decoder, block); + + return pt_blk_proceed_no_event_cached(decoder, block, bcache, msec); +} + +/* Proceed to the next event or decision point. + * + * Returns a non-negative pt_status_flag bit-vector on success, a negative error + * code otherwise. + */ +static int pt_blk_proceed(struct pt_block_decoder *decoder, + struct pt_block *block) +{ + int status; + + status = pt_blk_fetch_event(decoder); + if (status != 0) { + if (status < 0) + return status; + + return pt_blk_proceed_event(decoder, block); + } + + /* If tracing is disabled we should either be out of trace or we should + * have taken the event flow above. + */ + if (!decoder->enabled) { + if (decoder->status & pts_eos) + return -pte_eos; + + return -pte_no_enable; + } + + status = pt_blk_proceed_no_event(decoder, block); + if (status < 0) + return status; + + return pt_blk_proceed_trailing_event(decoder, block); +} + +enum { + /* The maximum number of steps to take when determining whether the + * event location can be reached. + */ + bdm64_max_steps = 0x100 +}; + +/* Try to work around erratum BDM64. + * + * If we got a transaction abort immediately following a branch that produced + * trace, the trace for that branch might have been corrupted. + * + * Returns a positive integer if the erratum was handled. + * Returns zero if the erratum does not seem to apply. + * Returns a negative error code otherwise. + */ +static int pt_blk_handle_erratum_bdm64(struct pt_block_decoder *decoder, + const struct pt_block *block, + const struct pt_event *ev) +{ + struct pt_insn_ext iext; + struct pt_insn insn; + int status; + + if (!decoder || !block || !ev) + return -pte_internal; + + /* This only affects aborts. */ + if (!ev->variant.tsx.aborted) + return 0; + + /* This only affects branches that require trace. + * + * If the erratum hits, that branch ended the current block and brought + * us to the trailing event flow. + */ + if (pt_blk_block_is_empty(block)) + return 0; + + insn.mode = block->mode; + insn.ip = block->end_ip; + + status = pt_insn_decode(&insn, &iext, decoder->image, &decoder->asid); + if (status < 0) + return 0; + + if (!pt_insn_is_branch(&insn, &iext)) + return 0; + + /* Let's check if we can reach the event location from here. + * + * If we can, let's assume the erratum did not hit. We might still be + * wrong but we're not able to tell. + */ + status = pt_insn_range_is_contiguous(decoder->ip, ev->variant.tsx.ip, + decoder->mode, decoder->image, + &decoder->asid, bdm64_max_steps); + if (status > 0) + return status; + + /* We can't reach the event location. This could either mean that we + * stopped too early (and status is zero) or that the erratum hit. + * + * We assume the latter and pretend that the previous branch brought us + * to the event location, instead. + */ + decoder->ip = ev->variant.tsx.ip; + + return 1; +} + +/* Check whether a trailing TSX event should be postponed. + * + * This involves handling erratum BDM64. + * + * Returns a positive integer if the event is to be postponed. + * Returns zero if the event should be processed. + * Returns a negative error code otherwise. + */ +static inline int pt_blk_postpone_trailing_tsx(struct pt_block_decoder *decoder, + struct pt_block *block, + const struct pt_event *ev) +{ + int status; + + if (!decoder || !ev) + return -pte_internal; + + if (ev->ip_suppressed) + return 0; + + if (block && decoder->query.config.errata.bdm64) { + status = pt_blk_handle_erratum_bdm64(decoder, block, ev); + if (status < 0) + return 1; + } + + if (decoder->ip != ev->variant.tsx.ip) + return 1; + + return 0; +} + +/* Proceed with events that bind to the current decoder IP. + * + * This function is used in the following scenarios: + * + * - we just synchronized onto the trace stream + * - we ended a block and proceeded to the next IP + * - we processed an event that was indicated by this function + * + * Check if there is an event at the current IP that needs to be indicated to + * the user. + * + * Returns a non-negative pt_status_flag bit-vector on success, a negative error + * code otherwise. + */ +static int pt_blk_proceed_trailing_event(struct pt_block_decoder *decoder, + struct pt_block *block) +{ + struct pt_event *ev; + int status; + + if (!decoder) + return -pte_internal; + + status = pt_blk_fetch_event(decoder); + if (status <= 0) { + if (status < 0) + return status; + + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + return pt_blk_status(decoder, 0); + } + + ev = &decoder->event; + switch (ev->type) { + case ptev_disabled: + /* Synchronous disable events are normally indicated on the + * event flow. + */ + if (!decoder->process_insn) + break; + + /* A sync disable may bind to a CR3 changing instruction. */ + if (ev->ip_suppressed && + pt_insn_changes_cr3(&decoder->insn, &decoder->iext)) + return pt_blk_status(decoder, pts_event_pending); + + /* Or it binds to the next branch that would require trace. + * + * Try to complete processing the current instruction by + * proceeding past it. If that fails because it would require + * trace, we can apply the disabled event. + */ + status = pt_insn_next_ip(&decoder->ip, &decoder->insn, + &decoder->iext); + if (status < 0) { + if (status != -pte_bad_query) + return status; + + status = pt_blk_set_disable_resume_ip(decoder, + &decoder->insn); + if (status < 0) + return status; + + return pt_blk_status(decoder, pts_event_pending); + } + + /* We proceeded past the current instruction. */ + status = pt_blk_clear_postponed_insn(decoder); + if (status < 0) + return status; + + /* This might have brought us to the disable IP. */ + if (!ev->ip_suppressed && + decoder->ip == ev->variant.disabled.ip) + return pt_blk_status(decoder, pts_event_pending); + + break; + + case ptev_enabled: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_async_disabled: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + if (decoder->ip != ev->variant.async_disabled.at) + break; + + if (decoder->query.config.errata.skd022) { + status = pt_blk_handle_erratum_skd022(decoder, ev); + if (status != 0) { + if (status < 0) + return status; + + /* If the erratum applies, the event is modified + * to a synchronous disable event that will be + * processed on the next pt_blk_proceed_event() + * call. We're done. + */ + break; + } + } + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_async_branch: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + if (decoder->ip != ev->variant.async_branch.from) + break; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_paging: + /* We apply the event immediately if we're not tracing. */ + if (!decoder->enabled) + return pt_blk_status(decoder, pts_event_pending); + + /* Synchronous paging events are normally indicated on the event + * flow, unless they bind to the same instruction as a previous + * event. + * + * We bind at most one paging event to an instruction, though. + */ + if (!decoder->process_insn || decoder->bound_paging) + break; + + /* We're done if we're not binding to the currently postponed + * instruction. We will process the event on the normal event + * flow in the next iteration. + */ + if (!pt_insn_binds_to_pip(&decoder->insn, &decoder->iext)) + break; + + /* We bound a paging event. Make sure we do not bind further + * paging events to this instruction. + */ + decoder->bound_paging = 1; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_async_paging: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + if (!ev->ip_suppressed && + decoder->ip != ev->variant.async_paging.ip) + break; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_vmcs: + /* We apply the event immediately if we're not tracing. */ + if (!decoder->enabled) + return pt_blk_status(decoder, pts_event_pending); + + /* Synchronous vmcs events are normally indicated on the event + * flow, unless they bind to the same instruction as a previous + * event. + * + * We bind at most one vmcs event to an instruction, though. + */ + if (!decoder->process_insn || decoder->bound_vmcs) + break; + + /* We're done if we're not binding to the currently postponed + * instruction. We will process the event on the normal event + * flow in the next iteration. + */ + if (!pt_insn_binds_to_vmcs(&decoder->insn, &decoder->iext)) + break; + + /* We bound a vmcs event. Make sure we do not bind further vmcs + * events to this instruction. + */ + decoder->bound_vmcs = 1; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_async_vmcs: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + if (!ev->ip_suppressed && + decoder->ip != ev->variant.async_vmcs.ip) + break; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_overflow: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_exec_mode: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + if (!ev->ip_suppressed && + decoder->ip != ev->variant.exec_mode.ip) + break; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_tsx: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + status = pt_blk_postpone_trailing_tsx(decoder, block, ev); + if (status != 0) { + if (status < 0) + return status; + + break; + } + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_stop: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_exstop: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + if (!ev->ip_suppressed && decoder->enabled && + decoder->ip != ev->variant.exstop.ip) + break; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_mwait: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + if (!ev->ip_suppressed && decoder->enabled && + decoder->ip != ev->variant.mwait.ip) + break; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_pwre: + case ptev_pwrx: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_ptwrite: + /* We apply the event immediately if we're not tracing. */ + if (!decoder->enabled) + return pt_blk_status(decoder, pts_event_pending); + + /* Ptwrite events are normally indicated on the event flow, + * unless they bind to the same instruction as a previous event. + * + * We bind at most one ptwrite event to an instruction, though. + */ + if (!decoder->process_insn || decoder->bound_ptwrite) + break; + + /* We're done if we're not binding to the currently postponed + * instruction. We will process the event on the normal event + * flow in the next iteration. + */ + if (!ev->ip_suppressed || + !pt_insn_is_ptwrite(&decoder->insn, &decoder->iext)) + break; + + /* We bound a ptwrite event. Make sure we do not bind further + * ptwrite events to this instruction. + */ + decoder->bound_ptwrite = 1; + + return pt_blk_status(decoder, pts_event_pending); + + case ptev_tick: + case ptev_cbr: + case ptev_mnt: + /* This event does not bind to an instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + return pt_blk_status(decoder, pts_event_pending); + } + + /* No further events. Proceed past any postponed instruction. */ + status = pt_blk_proceed_postponed_insn(decoder); + if (status < 0) + return status; + + return pt_blk_status(decoder, 0); +} + +int pt_blk_next(struct pt_block_decoder *decoder, struct pt_block *ublock, + size_t size) +{ + struct pt_block block, *pblock; + int errcode, status; + + if (!decoder || !ublock) + return -pte_invalid; + + pblock = size == sizeof(block) ? ublock : █ + + /* Zero-initialize the block in case of error returns. */ + memset(pblock, 0, sizeof(*pblock)); + + /* Fill in a few things from the current decode state. + * + * This reflects the state of the last pt_blk_next() or pt_blk_start() + * call. Note that, unless we stop with tracing disabled, we proceed + * already to the start IP of the next block. + * + * Some of the state may later be overwritten as we process events. + */ + pblock->ip = decoder->ip; + pblock->mode = decoder->mode; + if (decoder->speculative) + pblock->speculative = 1; + + /* Proceed one block. */ + status = pt_blk_proceed(decoder, pblock); + + errcode = block_to_user(ublock, size, pblock); + if (errcode < 0) + return errcode; + + return status; +} + +/* Process an enabled event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_enabled(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + if (!decoder || !ev) + return -pte_internal; + + /* This event can't be a status update. */ + if (ev->status_update) + return -pte_bad_context; + + /* We must have an IP in order to start decoding. */ + if (ev->ip_suppressed) + return -pte_noip; + + /* We must currently be disabled. */ + if (decoder->enabled) + return -pte_bad_context; + + decoder->ip = ev->variant.enabled.ip; + decoder->enabled = 1; + decoder->process_event = 0; + + return 0; +} + +/* Process a disabled event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_disabled(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + if (!decoder || !ev) + return -pte_internal; + + /* This event can't be a status update. */ + if (ev->status_update) + return -pte_bad_context; + + /* We must currently be enabled. */ + if (!decoder->enabled) + return -pte_bad_context; + + /* We preserve @decoder->ip. This is where we expect tracing to resume + * and we'll indicate that on the subsequent enabled event if tracing + * actually does resume from there. + */ + decoder->enabled = 0; + decoder->process_event = 0; + + return 0; +} + +/* Process an asynchronous branch event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_async_branch(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + if (!decoder || !ev) + return -pte_internal; + + /* This event can't be a status update. */ + if (ev->status_update) + return -pte_bad_context; + + /* We must currently be enabled. */ + if (!decoder->enabled) + return -pte_bad_context; + + /* Jump to the branch destination. We will continue from there in the + * next iteration. + */ + decoder->ip = ev->variant.async_branch.to; + decoder->process_event = 0; + + return 0; +} + +/* Process a paging event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_paging(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + uint64_t cr3; + int errcode; + + if (!decoder || !ev) + return -pte_internal; + + cr3 = ev->variant.paging.cr3; + if (decoder->asid.cr3 != cr3) { + errcode = pt_msec_cache_invalidate(&decoder->scache); + if (errcode < 0) + return errcode; + + decoder->asid.cr3 = cr3; + } + + decoder->process_event = 0; + + return 0; +} + +/* Process a vmcs event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_vmcs(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + uint64_t vmcs; + int errcode; + + if (!decoder || !ev) + return -pte_internal; + + vmcs = ev->variant.vmcs.base; + if (decoder->asid.vmcs != vmcs) { + errcode = pt_msec_cache_invalidate(&decoder->scache); + if (errcode < 0) + return errcode; + + decoder->asid.vmcs = vmcs; + } + + decoder->process_event = 0; + + return 0; +} + +/* Process an overflow event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_overflow(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + if (!decoder || !ev) + return -pte_internal; + + /* This event can't be a status update. */ + if (ev->status_update) + return -pte_bad_context; + + /* If the IP is suppressed, the overflow resolved while tracing was + * disabled. Otherwise it resolved while tracing was enabled. + */ + if (ev->ip_suppressed) { + /* Tracing is disabled. It doesn't make sense to preserve the + * previous IP. This will just be misleading. Even if tracing + * had been disabled before, as well, we might have missed the + * re-enable in the overflow. + */ + decoder->enabled = 0; + decoder->ip = 0ull; + } else { + /* Tracing is enabled and we're at the IP at which the overflow + * resolved. + */ + decoder->enabled = 1; + decoder->ip = ev->variant.overflow.ip; + } + + /* We don't know the TSX state. Let's assume we execute normally. + * + * We also don't know the execution mode. Let's keep what we have + * in case we don't get an update before we have to decode the next + * instruction. + */ + decoder->speculative = 0; + decoder->process_event = 0; + + return 0; +} + +/* Process an exec mode event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_exec_mode(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + enum pt_exec_mode mode; + + if (!decoder || !ev) + return -pte_internal; + + /* Use status update events to diagnose inconsistencies. */ + mode = ev->variant.exec_mode.mode; + if (ev->status_update && decoder->enabled && + decoder->mode != ptem_unknown && decoder->mode != mode) + return -pte_bad_status_update; + + decoder->mode = mode; + decoder->process_event = 0; + + return 0; +} + +/* Process a tsx event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_tsx(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + if (!decoder || !ev) + return -pte_internal; + + decoder->speculative = ev->variant.tsx.speculative; + decoder->process_event = 0; + + return 0; +} + +/* Process a stop event. + * + * Returns zero on success, a negative error code otherwise. + */ +static int pt_blk_process_stop(struct pt_block_decoder *decoder, + const struct pt_event *ev) +{ + if (!decoder || !ev) + return -pte_internal; + + /* This event can't be a status update. */ + if (ev->status_update) + return -pte_bad_context; + + /* Tracing is always disabled before it is stopped. */ + if (decoder->enabled) + return -pte_bad_context; + + decoder->process_event = 0; + + return 0; +} + +int pt_blk_event(struct pt_block_decoder *decoder, struct pt_event *uevent, + size_t size) +{ + struct pt_event *ev; + int status; + + if (!decoder || !uevent) + return -pte_invalid; + + /* We must currently process an event. */ + if (!decoder->process_event) + return -pte_bad_query; + + ev = &decoder->event; + switch (ev->type) { + case ptev_enabled: + /* Indicate that tracing resumes from the IP at which tracing + * had been disabled before (with some special treatment for + * calls). + */ + if (ev->variant.enabled.ip == decoder->ip) + ev->variant.enabled.resumed = 1; + + status = pt_blk_process_enabled(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_async_disabled: + if (decoder->ip != ev->variant.async_disabled.at) + return -pte_bad_query; + + fallthrough; + case ptev_disabled: + + status = pt_blk_process_disabled(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_async_branch: + if (decoder->ip != ev->variant.async_branch.from) + return -pte_bad_query; + + status = pt_blk_process_async_branch(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_async_paging: + if (!ev->ip_suppressed && + decoder->ip != ev->variant.async_paging.ip) + return -pte_bad_query; + + fallthrough; + case ptev_paging: + status = pt_blk_process_paging(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_async_vmcs: + if (!ev->ip_suppressed && + decoder->ip != ev->variant.async_vmcs.ip) + return -pte_bad_query; + + fallthrough; + case ptev_vmcs: + status = pt_blk_process_vmcs(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_overflow: + status = pt_blk_process_overflow(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_exec_mode: + if (!ev->ip_suppressed && + decoder->ip != ev->variant.exec_mode.ip) + return -pte_bad_query; + + status = pt_blk_process_exec_mode(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_tsx: + if (!ev->ip_suppressed && decoder->ip != ev->variant.tsx.ip) + return -pte_bad_query; + + status = pt_blk_process_tsx(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_stop: + status = pt_blk_process_stop(decoder, ev); + if (status < 0) + return status; + + break; + + case ptev_exstop: + if (!ev->ip_suppressed && decoder->enabled && + decoder->ip != ev->variant.exstop.ip) + return -pte_bad_query; + + decoder->process_event = 0; + break; + + case ptev_mwait: + if (!ev->ip_suppressed && decoder->enabled && + decoder->ip != ev->variant.mwait.ip) + return -pte_bad_query; + + decoder->process_event = 0; + break; + + case ptev_pwre: + case ptev_pwrx: + case ptev_ptwrite: + case ptev_tick: + case ptev_cbr: + case ptev_mnt: + decoder->process_event = 0; + break; + } + + /* Copy the event to the user. Make sure we're not writing beyond the + * memory provided by the user. + * + * We might truncate details of an event but only for those events the + * user can't know about, anyway. + */ + if (sizeof(*ev) < size) + size = sizeof(*ev); + + memcpy(uevent, ev, size); + + /* Indicate further events. */ + return pt_blk_proceed_trailing_event(decoder, NULL); +} |