| // SPDX-License-Identifier: GPL-2.0 | |
| #include <asm/insn.h> | |
| #include <linux/mm.h> | |
| #include <asm/msr.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; | |
| } |