diff options
Diffstat (limited to 'tools/perf/util/intel-pt.c')
-rw-r--r-- | tools/perf/util/intel-pt.c | 296 |
1 files changed, 288 insertions, 8 deletions
diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c index 8ed51f4e9e304..550db6e779685 100644 --- a/tools/perf/util/intel-pt.c +++ b/tools/perf/util/intel-pt.c @@ -35,6 +35,8 @@ #include "config.h" #include "time-utils.h" +#include "../arch/x86/include/uapi/asm/perf_regs.h" + #include "intel-pt-decoder/intel-pt-log.h" #include "intel-pt-decoder/intel-pt-decoder.h" #include "intel-pt-decoder/intel-pt-insn-decoder.h" @@ -101,6 +103,9 @@ struct intel_pt { u64 pwrx_id; u64 cbr_id; + bool sample_pebs; + struct perf_evsel *pebs_evsel; + u64 tsc_bit; u64 mtc_bit; u64 mtc_freq_bits; @@ -177,13 +182,14 @@ static void intel_pt_dump(struct intel_pt *pt __maybe_unused, int ret, pkt_len, i; char desc[INTEL_PT_PKT_DESC_MAX]; const char *color = PERF_COLOR_BLUE; + enum intel_pt_pkt_ctx ctx = INTEL_PT_NO_CTX; color_fprintf(stdout, color, ". ... Intel Processor Trace data: size %zu bytes\n", len); while (len) { - ret = intel_pt_get_packet(buf, len, &packet); + ret = intel_pt_get_packet(buf, len, &packet, &ctx); if (ret > 0) pkt_len = ret; else @@ -1178,28 +1184,37 @@ static inline bool intel_pt_skip_event(struct intel_pt *pt) pt->num_events++ < pt->synth_opts.initial_skip; } +static void intel_pt_prep_a_sample(struct intel_pt_queue *ptq, + union perf_event *event, + struct perf_sample *sample) +{ + event->sample.header.type = PERF_RECORD_SAMPLE; + event->sample.header.size = sizeof(struct perf_event_header); + + sample->pid = ptq->pid; + sample->tid = ptq->tid; + sample->cpu = ptq->cpu; + sample->insn_len = ptq->insn_len; + memcpy(sample->insn, ptq->insn, INTEL_PT_INSN_BUF_SZ); +} + static void intel_pt_prep_b_sample(struct intel_pt *pt, struct intel_pt_queue *ptq, union perf_event *event, struct perf_sample *sample) { + intel_pt_prep_a_sample(ptq, event, sample); + if (!pt->timeless_decoding) sample->time = tsc_to_perf_time(ptq->timestamp, &pt->tc); sample->ip = ptq->state->from_ip; sample->cpumode = intel_pt_cpumode(pt, sample->ip); - sample->pid = ptq->pid; - sample->tid = ptq->tid; sample->addr = ptq->state->to_ip; sample->period = 1; - sample->cpu = ptq->cpu; sample->flags = ptq->flags; - sample->insn_len = ptq->insn_len; - memcpy(sample->insn, ptq->insn, INTEL_PT_INSN_BUF_SZ); - event->sample.header.type = PERF_RECORD_SAMPLE; event->sample.header.misc = sample->cpumode; - event->sample.header.size = sizeof(struct perf_event_header); } static int intel_pt_inject_event(union perf_event *event, @@ -1534,6 +1549,261 @@ static int intel_pt_synth_pwrx_sample(struct intel_pt_queue *ptq) pt->pwr_events_sample_type); } +/* + * PEBS gp_regs array indexes plus 1 so that 0 means not present. Refer + * intel_pt_add_gp_regs(). + */ +static const int pebs_gp_regs[] = { + [PERF_REG_X86_FLAGS] = 1, + [PERF_REG_X86_IP] = 2, + [PERF_REG_X86_AX] = 3, + [PERF_REG_X86_CX] = 4, + [PERF_REG_X86_DX] = 5, + [PERF_REG_X86_BX] = 6, + [PERF_REG_X86_SP] = 7, + [PERF_REG_X86_BP] = 8, + [PERF_REG_X86_SI] = 9, + [PERF_REG_X86_DI] = 10, + [PERF_REG_X86_R8] = 11, + [PERF_REG_X86_R9] = 12, + [PERF_REG_X86_R10] = 13, + [PERF_REG_X86_R11] = 14, + [PERF_REG_X86_R12] = 15, + [PERF_REG_X86_R13] = 16, + [PERF_REG_X86_R14] = 17, + [PERF_REG_X86_R15] = 18, +}; + +static u64 *intel_pt_add_gp_regs(struct regs_dump *intr_regs, u64 *pos, + const struct intel_pt_blk_items *items, + u64 regs_mask) +{ + const u64 *gp_regs = items->val[INTEL_PT_GP_REGS_POS]; + u32 mask = items->mask[INTEL_PT_GP_REGS_POS]; + u32 bit; + int i; + + for (i = 0, bit = 1; i < PERF_REG_X86_64_MAX; i++, bit <<= 1) { + /* Get the PEBS gp_regs array index */ + int n = pebs_gp_regs[i] - 1; + + if (n < 0) + continue; + /* + * Add only registers that were requested (i.e. 'regs_mask') and + * that were provided (i.e. 'mask'), and update the resulting + * mask (i.e. 'intr_regs->mask') accordingly. + */ + if (mask & 1 << n && regs_mask & bit) { + intr_regs->mask |= bit; + *pos++ = gp_regs[n]; + } + } + + return pos; +} + +#ifndef PERF_REG_X86_XMM0 +#define PERF_REG_X86_XMM0 32 +#endif + +static void intel_pt_add_xmm(struct regs_dump *intr_regs, u64 *pos, + const struct intel_pt_blk_items *items, + u64 regs_mask) +{ + u32 mask = items->has_xmm & (regs_mask >> PERF_REG_X86_XMM0); + const u64 *xmm = items->xmm; + + /* + * If there are any XMM registers, then there should be all of them. + * Nevertheless, follow the logic to add only registers that were + * requested (i.e. 'regs_mask') and that were provided (i.e. 'mask'), + * and update the resulting mask (i.e. 'intr_regs->mask') accordingly. + */ + intr_regs->mask |= (u64)mask << PERF_REG_X86_XMM0; + + for (; mask; mask >>= 1, xmm++) { + if (mask & 1) + *pos++ = *xmm; + } +} + +#define LBR_INFO_MISPRED (1ULL << 63) +#define LBR_INFO_IN_TX (1ULL << 62) +#define LBR_INFO_ABORT (1ULL << 61) +#define LBR_INFO_CYCLES 0xffff + +/* Refer kernel's intel_pmu_store_pebs_lbrs() */ +static u64 intel_pt_lbr_flags(u64 info) +{ + union { + struct branch_flags flags; + u64 result; + } u = { + .flags = { + .mispred = !!(info & LBR_INFO_MISPRED), + .predicted = !(info & LBR_INFO_MISPRED), + .in_tx = !!(info & LBR_INFO_IN_TX), + .abort = !!(info & LBR_INFO_ABORT), + .cycles = info & LBR_INFO_CYCLES, + } + }; + + return u.result; +} + +static void intel_pt_add_lbrs(struct branch_stack *br_stack, + const struct intel_pt_blk_items *items) +{ + u64 *to; + int i; + + br_stack->nr = 0; + + to = &br_stack->entries[0].from; + + for (i = INTEL_PT_LBR_0_POS; i <= INTEL_PT_LBR_2_POS; i++) { + u32 mask = items->mask[i]; + const u64 *from = items->val[i]; + + for (; mask; mask >>= 3, from += 3) { + if ((mask & 7) == 7) { + *to++ = from[0]; + *to++ = from[1]; + *to++ = intel_pt_lbr_flags(from[2]); + br_stack->nr += 1; + } + } + } +} + +/* INTEL_PT_LBR_0, INTEL_PT_LBR_1 and INTEL_PT_LBR_2 */ +#define LBRS_MAX (INTEL_PT_BLK_ITEM_ID_CNT * 3) + +static int intel_pt_synth_pebs_sample(struct intel_pt_queue *ptq) +{ + const struct intel_pt_blk_items *items = &ptq->state->items; + struct perf_sample sample = { .ip = 0, }; + union perf_event *event = ptq->event_buf; + struct intel_pt *pt = ptq->pt; + struct perf_evsel *evsel = pt->pebs_evsel; + u64 sample_type = evsel->attr.sample_type; + u64 id = evsel->id[0]; + u8 cpumode; + + if (intel_pt_skip_event(pt)) + return 0; + + intel_pt_prep_a_sample(ptq, event, &sample); + + sample.id = id; + sample.stream_id = id; + + if (!evsel->attr.freq) + sample.period = evsel->attr.sample_period; + + /* No support for non-zero CS base */ + if (items->has_ip) + sample.ip = items->ip; + else if (items->has_rip) + sample.ip = items->rip; + else + sample.ip = ptq->state->from_ip; + + /* No support for guest mode at this time */ + cpumode = sample.ip < ptq->pt->kernel_start ? + PERF_RECORD_MISC_USER : + PERF_RECORD_MISC_KERNEL; + + event->sample.header.misc = cpumode | PERF_RECORD_MISC_EXACT_IP; + + sample.cpumode = cpumode; + + if (sample_type & PERF_SAMPLE_TIME) { + u64 timestamp = 0; + + if (items->has_timestamp) + timestamp = items->timestamp; + else if (!pt->timeless_decoding) + timestamp = ptq->timestamp; + if (timestamp) + sample.time = tsc_to_perf_time(timestamp, &pt->tc); + } + + if (sample_type & PERF_SAMPLE_CALLCHAIN && + pt->synth_opts.callchain) { + thread_stack__sample(ptq->thread, ptq->cpu, ptq->chain, + pt->synth_opts.callchain_sz, sample.ip, + pt->kernel_start); + sample.callchain = ptq->chain; + } + + if (sample_type & PERF_SAMPLE_REGS_INTR && + items->mask[INTEL_PT_GP_REGS_POS]) { + u64 regs[sizeof(sample.intr_regs.mask)]; + u64 regs_mask = evsel->attr.sample_regs_intr; + u64 *pos; + + sample.intr_regs.abi = items->is_32_bit ? + PERF_SAMPLE_REGS_ABI_32 : + PERF_SAMPLE_REGS_ABI_64; + sample.intr_regs.regs = regs; + + pos = intel_pt_add_gp_regs(&sample.intr_regs, regs, items, regs_mask); + + intel_pt_add_xmm(&sample.intr_regs, pos, items, regs_mask); + } + + if (sample_type & PERF_SAMPLE_BRANCH_STACK) { + struct { + struct branch_stack br_stack; + struct branch_entry entries[LBRS_MAX]; + } br; + + if (items->mask[INTEL_PT_LBR_0_POS] || + items->mask[INTEL_PT_LBR_1_POS] || + items->mask[INTEL_PT_LBR_2_POS]) { + intel_pt_add_lbrs(&br.br_stack, items); + sample.branch_stack = &br.br_stack; + } else if (pt->synth_opts.last_branch) { + intel_pt_copy_last_branch_rb(ptq); + sample.branch_stack = ptq->last_branch; + } else { + br.br_stack.nr = 0; + sample.branch_stack = &br.br_stack; + } + } + + if (sample_type & PERF_SAMPLE_ADDR && items->has_mem_access_address) + sample.addr = items->mem_access_address; + + if (sample_type & PERF_SAMPLE_WEIGHT) { + /* + * Refer kernel's setup_pebs_adaptive_sample_data() and + * intel_hsw_weight(). + */ + if (items->has_mem_access_latency) + sample.weight = items->mem_access_latency; + if (!sample.weight && items->has_tsx_aux_info) { + /* Cycles last block */ + sample.weight = (u32)items->tsx_aux_info; + } + } + + if (sample_type & PERF_SAMPLE_TRANSACTION && items->has_tsx_aux_info) { + u64 ax = items->has_rax ? items->rax : 0; + /* Refer kernel's intel_hsw_transaction() */ + u64 txn = (u8)(items->tsx_aux_info >> 32); + + /* For RTM XABORTs also log the abort code from AX */ + if (txn & PERF_TXN_TRANSACTION && ax & 1) + txn |= ((ax >> 24) & 0xff) << PERF_TXN_ABORT_SHIFT; + sample.transaction = txn; + } + + return intel_pt_deliver_synth_event(pt, ptq, event, &sample, sample_type); +} + static int intel_pt_synth_error(struct intel_pt *pt, int code, int cpu, pid_t pid, pid_t tid, u64 ip, u64 timestamp) { @@ -1621,6 +1891,16 @@ static int intel_pt_sample(struct intel_pt_queue *ptq) ptq->ipc_cyc_cnt = ptq->state->tot_cyc_cnt; } + /* + * Do PEBS first to allow for the possibility that the PEBS timestamp + * precedes the current timestamp. + */ + if (pt->sample_pebs && state->type & INTEL_PT_BLK_ITEMS) { + err = intel_pt_synth_pebs_sample(ptq); + if (err) + return err; + } + if (pt->sample_pwr_events && (state->type & INTEL_PT_PWR_EVT)) { if (state->type & INTEL_PT_CBR_CHG) { err = intel_pt_synth_cbr_sample(ptq); |