mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-22 12:46:41 +07:00
d05c513069
Current instruction decoder for uprobe/kprobe handler only handles branches with delay slots. For compact branches the behaviour is rather unpredictable - and depending on the encoding of a compact branch instruction may result in one (or more) of: - executing an instruction that follows a branch which wasn't in a delay slot and shouldn't have been executed - incorrectly emulating a branch leading to a jump to a wrong location - unexpected branching out of the single-stepped code and never reaching the breakpoint that should terminate the probe handler Results of these actions are generally unpredictable, but can end up with a probed application or kernel crash, so disable placing probes on compact branches until they are handled properly. Signed-off-by: Marcin Nowakowski <marcin.nowakowski@imgtec.com> Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/14336/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
265 lines
6.5 KiB
C
265 lines
6.5 KiB
C
#include <linux/highmem.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/types.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/uprobes.h>
|
|
|
|
#include <asm/branch.h>
|
|
#include <asm/cpu-features.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
#include "probes-common.h"
|
|
|
|
static inline int insn_has_delay_slot(const union mips_instruction insn)
|
|
{
|
|
return __insn_has_delay_slot(insn);
|
|
}
|
|
|
|
/**
|
|
* arch_uprobe_analyze_insn - instruction analysis including validity and fixups.
|
|
* @mm: the probed address space.
|
|
* @arch_uprobe: the probepoint information.
|
|
* @addr: virtual address at which to install the probepoint
|
|
* Return 0 on success or a -ve number on error.
|
|
*/
|
|
int arch_uprobe_analyze_insn(struct arch_uprobe *aup,
|
|
struct mm_struct *mm, unsigned long addr)
|
|
{
|
|
union mips_instruction inst;
|
|
|
|
/*
|
|
* For the time being this also blocks attempts to use uprobes with
|
|
* MIPS16 and microMIPS.
|
|
*/
|
|
if (addr & 0x03)
|
|
return -EINVAL;
|
|
|
|
inst.word = aup->insn[0];
|
|
|
|
if (__insn_is_compact_branch(inst)) {
|
|
pr_notice("Uprobes for compact branches are not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
aup->ixol[0] = aup->insn[insn_has_delay_slot(inst)];
|
|
aup->ixol[1] = UPROBE_BRK_UPROBE_XOL; /* NOP */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* is_trap_insn - check if the instruction is a trap variant
|
|
* @insn: instruction to be checked.
|
|
* Returns true if @insn is a trap variant.
|
|
*
|
|
* This definition overrides the weak definition in kernel/events/uprobes.c.
|
|
* and is needed for the case where an architecture has multiple trap
|
|
* instructions (like PowerPC or MIPS). We treat BREAK just like the more
|
|
* modern conditional trap instructions.
|
|
*/
|
|
bool is_trap_insn(uprobe_opcode_t *insn)
|
|
{
|
|
union mips_instruction inst;
|
|
|
|
inst.word = *insn;
|
|
|
|
switch (inst.i_format.opcode) {
|
|
case spec_op:
|
|
switch (inst.r_format.func) {
|
|
case break_op:
|
|
case teq_op:
|
|
case tge_op:
|
|
case tgeu_op:
|
|
case tlt_op:
|
|
case tltu_op:
|
|
case tne_op:
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case bcond_op: /* Yes, really ... */
|
|
switch (inst.u_format.rt) {
|
|
case teqi_op:
|
|
case tgei_op:
|
|
case tgeiu_op:
|
|
case tlti_op:
|
|
case tltiu_op:
|
|
case tnei_op:
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define UPROBE_TRAP_NR ULONG_MAX
|
|
|
|
/*
|
|
* arch_uprobe_pre_xol - prepare to execute out of line.
|
|
* @auprobe: the probepoint information.
|
|
* @regs: reflects the saved user state of current task.
|
|
*/
|
|
int arch_uprobe_pre_xol(struct arch_uprobe *aup, struct pt_regs *regs)
|
|
{
|
|
struct uprobe_task *utask = current->utask;
|
|
|
|
/*
|
|
* Now find the EPC where to resume after the breakpoint has been
|
|
* dealt with. This may require emulation of a branch.
|
|
*/
|
|
aup->resume_epc = regs->cp0_epc + 4;
|
|
if (insn_has_delay_slot((union mips_instruction) aup->insn[0])) {
|
|
unsigned long epc;
|
|
|
|
epc = regs->cp0_epc;
|
|
__compute_return_epc_for_insn(regs,
|
|
(union mips_instruction) aup->insn[0]);
|
|
aup->resume_epc = regs->cp0_epc;
|
|
}
|
|
utask->autask.saved_trap_nr = current->thread.trap_nr;
|
|
current->thread.trap_nr = UPROBE_TRAP_NR;
|
|
regs->cp0_epc = current->utask->xol_vaddr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int arch_uprobe_post_xol(struct arch_uprobe *aup, struct pt_regs *regs)
|
|
{
|
|
struct uprobe_task *utask = current->utask;
|
|
|
|
current->thread.trap_nr = utask->autask.saved_trap_nr;
|
|
regs->cp0_epc = aup->resume_epc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If xol insn itself traps and generates a signal(Say,
|
|
* SIGILL/SIGSEGV/etc), then detect the case where a singlestepped
|
|
* instruction jumps back to its own address. It is assumed that anything
|
|
* like do_page_fault/do_trap/etc sets thread.trap_nr != -1.
|
|
*
|
|
* arch_uprobe_pre_xol/arch_uprobe_post_xol save/restore thread.trap_nr,
|
|
* arch_uprobe_xol_was_trapped() simply checks that ->trap_nr is not equal to
|
|
* UPROBE_TRAP_NR == -1 set by arch_uprobe_pre_xol().
|
|
*/
|
|
bool arch_uprobe_xol_was_trapped(struct task_struct *tsk)
|
|
{
|
|
if (tsk->thread.trap_nr != UPROBE_TRAP_NR)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int arch_uprobe_exception_notify(struct notifier_block *self,
|
|
unsigned long val, void *data)
|
|
{
|
|
struct die_args *args = data;
|
|
struct pt_regs *regs = args->regs;
|
|
|
|
/* regs == NULL is a kernel bug */
|
|
if (WARN_ON(!regs))
|
|
return NOTIFY_DONE;
|
|
|
|
/* We are only interested in userspace traps */
|
|
if (!user_mode(regs))
|
|
return NOTIFY_DONE;
|
|
|
|
switch (val) {
|
|
case DIE_UPROBE:
|
|
if (uprobe_pre_sstep_notifier(regs))
|
|
return NOTIFY_STOP;
|
|
break;
|
|
case DIE_UPROBE_XOL:
|
|
if (uprobe_post_sstep_notifier(regs))
|
|
return NOTIFY_STOP;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function gets called when XOL instruction either gets trapped or
|
|
* the thread has a fatal signal. Reset the instruction pointer to its
|
|
* probed address for the potential restart or for post mortem analysis.
|
|
*/
|
|
void arch_uprobe_abort_xol(struct arch_uprobe *aup,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct uprobe_task *utask = current->utask;
|
|
|
|
instruction_pointer_set(regs, utask->vaddr);
|
|
}
|
|
|
|
unsigned long arch_uretprobe_hijack_return_addr(
|
|
unsigned long trampoline_vaddr, struct pt_regs *regs)
|
|
{
|
|
unsigned long ra;
|
|
|
|
ra = regs->regs[31];
|
|
|
|
/* Replace the return address with the trampoline address */
|
|
regs->regs[31] = trampoline_vaddr;
|
|
|
|
return ra;
|
|
}
|
|
|
|
/**
|
|
* set_swbp - store breakpoint at a given address.
|
|
* @auprobe: arch specific probepoint information.
|
|
* @mm: the probed process address space.
|
|
* @vaddr: the virtual address to insert the opcode.
|
|
*
|
|
* For mm @mm, store the breakpoint instruction at @vaddr.
|
|
* Return 0 (success) or a negative errno.
|
|
*
|
|
* This version overrides the weak version in kernel/events/uprobes.c.
|
|
* It is required to handle MIPS16 and microMIPS.
|
|
*/
|
|
int __weak set_swbp(struct arch_uprobe *auprobe, struct mm_struct *mm,
|
|
unsigned long vaddr)
|
|
{
|
|
return uprobe_write_opcode(mm, vaddr, UPROBE_SWBP_INSN);
|
|
}
|
|
|
|
void __weak arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr,
|
|
void *src, unsigned long len)
|
|
{
|
|
unsigned long kaddr, kstart;
|
|
|
|
/* Initialize the slot */
|
|
kaddr = (unsigned long)kmap_atomic(page);
|
|
kstart = kaddr + (vaddr & ~PAGE_MASK);
|
|
memcpy((void *)kstart, src, len);
|
|
flush_icache_range(kstart, kstart + len);
|
|
kunmap_atomic((void *)kaddr);
|
|
}
|
|
|
|
/**
|
|
* uprobe_get_swbp_addr - compute address of swbp given post-swbp regs
|
|
* @regs: Reflects the saved state of the task after it has hit a breakpoint
|
|
* instruction.
|
|
* Return the address of the breakpoint instruction.
|
|
*
|
|
* This overrides the weak version in kernel/events/uprobes.c.
|
|
*/
|
|
unsigned long uprobe_get_swbp_addr(struct pt_regs *regs)
|
|
{
|
|
return instruction_pointer(regs);
|
|
}
|
|
|
|
/*
|
|
* See if the instruction can be emulated.
|
|
* Returns true if instruction was emulated, false otherwise.
|
|
*
|
|
* For now we always emulate so this function just returns 0.
|
|
*/
|
|
bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs)
|
|
{
|
|
return 0;
|
|
}
|