123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- // SPDX-License-Identifier: GPL-2.0
- #include <asm/insn.h>
- #include <linux/mm.h>
- #include "perf_event.h"
- static int decode_branch_type(struct insn *insn)
- {
- int ext;
- if (insn_get_opcode(insn))
- return X86_BR_ABORT;
- switch (insn->opcode.bytes[0]) {
- case 0xf:
- switch (insn->opcode.bytes[1]) {
- case 0x05: /* syscall */
- case 0x34: /* sysenter */
- return X86_BR_SYSCALL;
- case 0x07: /* sysret */
- case 0x35: /* sysexit */
- return X86_BR_SYSRET;
- case 0x80 ... 0x8f: /* conditional */
- return X86_BR_JCC;
- }
- return X86_BR_NONE;
- case 0x70 ... 0x7f: /* conditional */
- return X86_BR_JCC;
- case 0xc2: /* near ret */
- case 0xc3: /* near ret */
- case 0xca: /* far ret */
- case 0xcb: /* far ret */
- return X86_BR_RET;
- case 0xcf: /* iret */
- return X86_BR_IRET;
- case 0xcc ... 0xce: /* int */
- return X86_BR_INT;
- case 0xe8: /* call near rel */
- if (insn_get_immediate(insn) || insn->immediate1.value == 0) {
- /* zero length call */
- return X86_BR_ZERO_CALL;
- }
- fallthrough;
- case 0x9a: /* call far absolute */
- return X86_BR_CALL;
- case 0xe0 ... 0xe3: /* loop jmp */
- return X86_BR_JCC;
- case 0xe9 ... 0xeb: /* jmp */
- return X86_BR_JMP;
- case 0xff: /* call near absolute, call far absolute ind */
- if (insn_get_modrm(insn))
- return X86_BR_ABORT;
- ext = (insn->modrm.bytes[0] >> 3) & 0x7;
- switch (ext) {
- case 2: /* near ind call */
- case 3: /* far ind call */
- return X86_BR_IND_CALL;
- case 4:
- case 5:
- return X86_BR_IND_JMP;
- }
- return X86_BR_NONE;
- }
- return X86_BR_NONE;
- }
- /*
- * return the type of control flow change at address "from"
- * instruction is not necessarily a branch (in case of interrupt).
- *
- * The branch type returned also includes the priv level of the
- * target of the control flow change (X86_BR_USER, X86_BR_KERNEL).
- *
- * If a branch type is unknown OR the instruction cannot be
- * decoded (e.g., text page not present), then X86_BR_NONE is
- * returned.
- *
- * While recording branches, some processors can report the "from"
- * address to be that of an instruction preceding the actual branch
- * when instruction fusion occurs. If fusion is expected, attempt to
- * find the type of the first branch instruction within the next
- * MAX_INSN_SIZE bytes and if found, provide the offset between the
- * reported "from" address and the actual branch instruction address.
- */
- static int get_branch_type(unsigned long from, unsigned long to, int abort,
- bool fused, int *offset)
- {
- struct insn insn;
- void *addr;
- int bytes_read, bytes_left, insn_offset;
- int ret = X86_BR_NONE;
- int to_plm, from_plm;
- u8 buf[MAX_INSN_SIZE];
- int is64 = 0;
- /* make sure we initialize offset */
- if (offset)
- *offset = 0;
- to_plm = kernel_ip(to) ? X86_BR_KERNEL : X86_BR_USER;
- from_plm = kernel_ip(from) ? X86_BR_KERNEL : X86_BR_USER;
- /*
- * maybe zero if lbr did not fill up after a reset by the time
- * we get a PMU interrupt
- */
- if (from == 0 || to == 0)
- return X86_BR_NONE;
- if (abort)
- return X86_BR_ABORT | to_plm;
- if (from_plm == X86_BR_USER) {
- /*
- * can happen if measuring at the user level only
- * and we interrupt in a kernel thread, e.g., idle.
- */
- if (!current->mm)
- return X86_BR_NONE;
- /* may fail if text not present */
- bytes_left = copy_from_user_nmi(buf, (void __user *)from,
- MAX_INSN_SIZE);
- bytes_read = MAX_INSN_SIZE - bytes_left;
- if (!bytes_read)
- return X86_BR_NONE;
- addr = buf;
- } else {
- /*
- * The LBR logs any address in the IP, even if the IP just
- * faulted. This means userspace can control the from address.
- * Ensure we don't blindly read any address by validating it is
- * a known text address and not a vsyscall address.
- */
- if (kernel_text_address(from) && !in_gate_area_no_mm(from)) {
- addr = (void *)from;
- /*
- * Assume we can get the maximum possible size
- * when grabbing kernel data. This is not
- * _strictly_ true since we could possibly be
- * executing up next to a memory hole, but
- * it is very unlikely to be a problem.
- */
- bytes_read = MAX_INSN_SIZE;
- } else {
- return X86_BR_NONE;
- }
- }
- /*
- * decoder needs to know the ABI especially
- * on 64-bit systems running 32-bit apps
- */
- #ifdef CONFIG_X86_64
- is64 = kernel_ip((unsigned long)addr) || any_64bit_mode(current_pt_regs());
- #endif
- insn_init(&insn, addr, bytes_read, is64);
- ret = decode_branch_type(&insn);
- insn_offset = 0;
- /* Check for the possibility of branch fusion */
- while (fused && ret == X86_BR_NONE) {
- /* Check for decoding errors */
- if (insn_get_length(&insn) || !insn.length)
- break;
- insn_offset += insn.length;
- bytes_read -= insn.length;
- if (bytes_read < 0)
- break;
- insn_init(&insn, addr + insn_offset, bytes_read, is64);
- ret = decode_branch_type(&insn);
- }
- if (offset)
- *offset = insn_offset;
- /*
- * interrupts, traps, faults (and thus ring transition) may
- * occur on any instructions. Thus, to classify them correctly,
- * we need to first look at the from and to priv levels. If they
- * are different and to is in the kernel, then it indicates
- * a ring transition. If the from instruction is not a ring
- * transition instr (syscall, systenter, int), then it means
- * it was a irq, trap or fault.
- *
- * we have no way of detecting kernel to kernel faults.
- */
- if (from_plm == X86_BR_USER && to_plm == X86_BR_KERNEL
- && ret != X86_BR_SYSCALL && ret != X86_BR_INT)
- ret = X86_BR_IRQ;
- /*
- * branch priv level determined by target as
- * is done by HW when LBR_SELECT is implemented
- */
- if (ret != X86_BR_NONE)
- ret |= to_plm;
- return ret;
- }
- int branch_type(unsigned long from, unsigned long to, int abort)
- {
- return get_branch_type(from, to, abort, false, NULL);
- }
- int branch_type_fused(unsigned long from, unsigned long to, int abort,
- int *offset)
- {
- return get_branch_type(from, to, abort, true, offset);
- }
- #define X86_BR_TYPE_MAP_MAX 16
- static int branch_map[X86_BR_TYPE_MAP_MAX] = {
- PERF_BR_CALL, /* X86_BR_CALL */
- PERF_BR_RET, /* X86_BR_RET */
- PERF_BR_SYSCALL, /* X86_BR_SYSCALL */
- PERF_BR_SYSRET, /* X86_BR_SYSRET */
- PERF_BR_UNKNOWN, /* X86_BR_INT */
- PERF_BR_ERET, /* X86_BR_IRET */
- PERF_BR_COND, /* X86_BR_JCC */
- PERF_BR_UNCOND, /* X86_BR_JMP */
- PERF_BR_IRQ, /* X86_BR_IRQ */
- PERF_BR_IND_CALL, /* X86_BR_IND_CALL */
- PERF_BR_UNKNOWN, /* X86_BR_ABORT */
- PERF_BR_UNKNOWN, /* X86_BR_IN_TX */
- PERF_BR_NO_TX, /* X86_BR_NO_TX */
- PERF_BR_CALL, /* X86_BR_ZERO_CALL */
- PERF_BR_UNKNOWN, /* X86_BR_CALL_STACK */
- PERF_BR_IND, /* X86_BR_IND_JMP */
- };
- int common_branch_type(int type)
- {
- int i;
- type >>= 2; /* skip X86_BR_USER and X86_BR_KERNEL */
- if (type) {
- i = __ffs(type);
- if (i < X86_BR_TYPE_MAP_MAX)
- return branch_map[i];
- }
- return PERF_BR_UNKNOWN;
- }
|