// SPDX-License-Identifier: GPL-2.0 #include #include #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; }