Commit 0326f5a9 authored by Srikar Dronamraju's avatar Srikar Dronamraju Committed by Ingo Molnar

uprobes/core: Handle breakpoint and singlestep exceptions

Uprobes uses exception notifiers to get to know if a thread hit
a breakpoint or a singlestep exception.

When a thread hits a uprobe or is singlestepping post a uprobe
hit, the uprobe exception notifier sets its TIF_UPROBE bit,
which will then be checked on its return to userspace path
(do_notify_resume() ->uprobe_notify_resume()), where the
consumers handlers are run (in task context) based on the
defined filters.

Uprobe hits are thread specific and hence we need to maintain
information about if a task hit a uprobe, what uprobe was hit,
the slot where the original instruction was copied for xol so
that it can be singlestepped with appropriate fixups.

In some cases, special care is needed for instructions that are
executed out of line (xol). These are architecture specific
artefacts, such as handling RIP relative instructions on x86_64.

Since the instruction at which the uprobe was inserted is
executed out of line, architecture specific fixups are added so
that the thread continues normal execution in the presence of a
uprobe.

Postpone the signals until we execute the probed insn.
post_xol() path does a recalc_sigpending() before return to
user-mode, this ensures the signal can't be lost.

Uprobes relies on DIE_DEBUG notification to notify if a
singlestep is complete.

Adds x86 specific uprobe exception notifiers and appropriate
hooks needed to determine a uprobe hit and subsequent post
processing.

Add requisite x86 fixups for xol for uprobes. Specific cases
needing fixups include relative jumps (x86_64), calls, etc.

Where possible, we check and skip singlestepping the
breakpointed instructions. For now we skip single byte as well
as few multibyte nop instructions. However this can be extended
to other instructions too.

Credits to Oleg Nesterov for suggestions/patches related to
signal, breakpoint, singlestep handling code.
Signed-off-by: default avatarSrikar Dronamraju <srikar@linux.vnet.ibm.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Ananth N Mavinakayanahalli <ananth@in.ibm.com>
Cc: Jim Keniston <jkenisto@linux.vnet.ibm.com>
Cc: Linux-mm <linux-mm@kvack.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Arnaldo Carvalho de Melo <acme@infradead.org>
Cc: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20120313180011.29771.89027.sendpatchset@srdronam.in.ibm.com
[ Performed various cleanliness edits ]
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent ef15eda9
......@@ -85,6 +85,7 @@ struct thread_info {
#define TIF_SECCOMP 8 /* secure computing */
#define TIF_MCE_NOTIFY 10 /* notify userspace of an MCE */
#define TIF_USER_RETURN_NOTIFY 11 /* notify kernel of userspace return */
#define TIF_UPROBE 12 /* breakpointed or singlestepping */
#define TIF_NOTSC 16 /* TSC is not accessible in userland */
#define TIF_IA32 17 /* IA32 compatibility process */
#define TIF_FORK 18 /* ret_from_fork */
......@@ -109,6 +110,7 @@ struct thread_info {
#define _TIF_SECCOMP (1 << TIF_SECCOMP)
#define _TIF_MCE_NOTIFY (1 << TIF_MCE_NOTIFY)
#define _TIF_USER_RETURN_NOTIFY (1 << TIF_USER_RETURN_NOTIFY)
#define _TIF_UPROBE (1 << TIF_UPROBE)
#define _TIF_NOTSC (1 << TIF_NOTSC)
#define _TIF_IA32 (1 << TIF_IA32)
#define _TIF_FORK (1 << TIF_FORK)
......
......@@ -23,6 +23,8 @@
* Jim Keniston
*/
#include <linux/notifier.h>
typedef u8 uprobe_opcode_t;
#define MAX_UINSN_BYTES 16
......@@ -39,5 +41,17 @@ struct arch_uprobe {
#endif
};
extern int arch_uprobes_analyze_insn(struct arch_uprobe *aup, struct mm_struct *mm);
struct arch_uprobe_task {
unsigned long saved_trap_nr;
#ifdef CONFIG_X86_64
unsigned long saved_scratch_register;
#endif
};
extern int arch_uprobe_analyze_insn(struct arch_uprobe *aup, struct mm_struct *mm);
extern int arch_uprobe_pre_xol(struct arch_uprobe *aup, struct pt_regs *regs);
extern int arch_uprobe_post_xol(struct arch_uprobe *aup, struct pt_regs *regs);
extern bool arch_uprobe_xol_was_trapped(struct task_struct *tsk);
extern int arch_uprobe_exception_notify(struct notifier_block *self, unsigned long val, void *data);
extern void arch_uprobe_abort_xol(struct arch_uprobe *aup, struct pt_regs *regs);
#endif /* _ASM_UPROBES_H */
......@@ -18,6 +18,7 @@
#include <linux/personality.h>
#include <linux/uaccess.h>
#include <linux/user-return-notifier.h>
#include <linux/uprobes.h>
#include <asm/processor.h>
#include <asm/ucontext.h>
......@@ -823,6 +824,11 @@ do_notify_resume(struct pt_regs *regs, void *unused, __u32 thread_info_flags)
mce_notify_process();
#endif /* CONFIG_X86_64 && CONFIG_X86_MCE */
if (thread_info_flags & _TIF_UPROBE) {
clear_thread_flag(TIF_UPROBE);
uprobe_notify_resume(regs);
}
/* deal with pending signal delivery */
if (thread_info_flags & _TIF_SIGPENDING)
do_signal(regs);
......
......@@ -24,22 +24,28 @@
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/uprobes.h>
#include <linux/uaccess.h>
#include <linux/kdebug.h>
#include <asm/processor.h>
#include <asm/insn.h>
/* Post-execution fixups. */
/* No fixup needed */
#define UPROBE_FIX_NONE 0x0
/* Adjust IP back to vicinity of actual insn */
#define UPROBE_FIX_IP 0x1
/* Adjust the return address of a call insn */
#define UPROBE_FIX_CALL 0x2
#define UPROBE_FIX_RIP_AX 0x8000
#define UPROBE_FIX_RIP_CX 0x4000
#define UPROBE_TRAP_NR UINT_MAX
/* Adaptations for mhiramat x86 decoder v14. */
#define OPCODE1(insn) ((insn)->opcode.bytes[0])
#define OPCODE2(insn) ((insn)->opcode.bytes[1])
......@@ -221,10 +227,9 @@ static int validate_insn_32bits(struct arch_uprobe *auprobe, struct insn *insn)
}
/*
* Figure out which fixups post_xol() will need to perform, and annotate
* arch_uprobe->fixups accordingly. To start with,
* arch_uprobe->fixups is either zero or it reflects rip-related
* fixups.
* Figure out which fixups arch_uprobe_post_xol() will need to perform, and
* annotate arch_uprobe->fixups accordingly. To start with,
* arch_uprobe->fixups is either zero or it reflects rip-related fixups.
*/
static void prepare_fixups(struct arch_uprobe *auprobe, struct insn *insn)
{
......@@ -401,12 +406,12 @@ static int validate_insn_bits(struct arch_uprobe *auprobe, struct mm_struct *mm,
#endif /* CONFIG_X86_64 */
/**
* arch_uprobes_analyze_insn - instruction analysis including validity and fixups.
* arch_uprobe_analyze_insn - instruction analysis including validity and fixups.
* @mm: the probed address space.
* @arch_uprobe: the probepoint information.
* Return 0 on success or a -ve number on error.
*/
int arch_uprobes_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm)
int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm)
{
int ret;
struct insn insn;
......@@ -421,3 +426,249 @@ int arch_uprobes_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm)
return 0;
}
#ifdef CONFIG_X86_64
/*
* If we're emulating a rip-relative instruction, save the contents
* of the scratch register and store the target address in that register.
*/
static void
pre_xol_rip_insn(struct arch_uprobe *auprobe, struct pt_regs *regs,
struct arch_uprobe_task *autask)
{
if (auprobe->fixups & UPROBE_FIX_RIP_AX) {
autask->saved_scratch_register = regs->ax;
regs->ax = current->utask->vaddr;
regs->ax += auprobe->rip_rela_target_address;
} else if (auprobe->fixups & UPROBE_FIX_RIP_CX) {
autask->saved_scratch_register = regs->cx;
regs->cx = current->utask->vaddr;
regs->cx += auprobe->rip_rela_target_address;
}
}
#else
static void
pre_xol_rip_insn(struct arch_uprobe *auprobe, struct pt_regs *regs,
struct arch_uprobe_task *autask)
{
/* No RIP-relative addressing on 32-bit */
}
#endif
/*
* 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 *auprobe, struct pt_regs *regs)
{
struct arch_uprobe_task *autask;
autask = &current->utask->autask;
autask->saved_trap_nr = current->thread.trap_nr;
current->thread.trap_nr = UPROBE_TRAP_NR;
regs->ip = current->utask->xol_vaddr;
pre_xol_rip_insn(auprobe, regs, autask);
return 0;
}
/*
* This function is called by arch_uprobe_post_xol() to adjust the return
* address pushed by a call instruction executed out of line.
*/
static int adjust_ret_addr(unsigned long sp, long correction)
{
int rasize, ncopied;
long ra = 0;
if (is_ia32_task())
rasize = 4;
else
rasize = 8;
ncopied = copy_from_user(&ra, (void __user *)sp, rasize);
if (unlikely(ncopied))
return -EFAULT;
ra += correction;
ncopied = copy_to_user((void __user *)sp, &ra, rasize);
if (unlikely(ncopied))
return -EFAULT;
return 0;
}
#ifdef CONFIG_X86_64
static bool is_riprel_insn(struct arch_uprobe *auprobe)
{
return ((auprobe->fixups & (UPROBE_FIX_RIP_AX | UPROBE_FIX_RIP_CX)) != 0);
}
static void
handle_riprel_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs, long *correction)
{
if (is_riprel_insn(auprobe)) {
struct arch_uprobe_task *autask;
autask = &current->utask->autask;
if (auprobe->fixups & UPROBE_FIX_RIP_AX)
regs->ax = autask->saved_scratch_register;
else
regs->cx = autask->saved_scratch_register;
/*
* The original instruction includes a displacement, and so
* is 4 bytes longer than what we've just single-stepped.
* Fall through to handle stuff like "jmpq *...(%rip)" and
* "callq *...(%rip)".
*/
if (correction)
*correction += 4;
}
}
#else
static void
handle_riprel_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs, long *correction)
{
/* No RIP-relative addressing on 32-bit */
}
#endif
/*
* 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 *t)
{
if (t->thread.trap_nr != UPROBE_TRAP_NR)
return true;
return false;
}
/*
* Called after single-stepping. To avoid the SMP problems that can
* occur when we temporarily put back the original opcode to
* single-step, we single-stepped a copy of the instruction.
*
* This function prepares to resume execution after the single-step.
* We have to fix things up as follows:
*
* Typically, the new ip is relative to the copied instruction. We need
* to make it relative to the original instruction (FIX_IP). Exceptions
* are return instructions and absolute or indirect jump or call instructions.
*
* If the single-stepped instruction was a call, the return address that
* is atop the stack is the address following the copied instruction. We
* need to make it the address following the original instruction (FIX_CALL).
*
* If the original instruction was a rip-relative instruction such as
* "movl %edx,0xnnnn(%rip)", we have instead executed an equivalent
* instruction using a scratch register -- e.g., "movl %edx,(%rax)".
* We need to restore the contents of the scratch register and adjust
* the ip, keeping in mind that the instruction we executed is 4 bytes
* shorter than the original instruction (since we squeezed out the offset
* field). (FIX_RIP_AX or FIX_RIP_CX)
*/
int arch_uprobe_post_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
{
struct uprobe_task *utask;
long correction;
int result = 0;
WARN_ON_ONCE(current->thread.trap_nr != UPROBE_TRAP_NR);
utask = current->utask;
current->thread.trap_nr = utask->autask.saved_trap_nr;
correction = (long)(utask->vaddr - utask->xol_vaddr);
handle_riprel_post_xol(auprobe, regs, &correction);
if (auprobe->fixups & UPROBE_FIX_IP)
regs->ip += correction;
if (auprobe->fixups & UPROBE_FIX_CALL)
result = adjust_ret_addr(regs->sp, correction);
return result;
}
/* callback routine for handling exceptions. */
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;
int ret = NOTIFY_DONE;
/* We are only interested in userspace traps */
if (regs && !user_mode_vm(regs))
return NOTIFY_DONE;
switch (val) {
case DIE_INT3:
if (uprobe_pre_sstep_notifier(regs))
ret = NOTIFY_STOP;
break;
case DIE_DEBUG:
if (uprobe_post_sstep_notifier(regs))
ret = NOTIFY_STOP;
default:
break;
}
return ret;
}
/*
* This function gets called when XOL instruction either gets trapped or
* the thread has a fatal signal, so reset the instruction pointer to its
* probed address.
*/
void arch_uprobe_abort_xol(struct arch_uprobe *auprobe, struct pt_regs *regs)
{
struct uprobe_task *utask = current->utask;
current->thread.trap_nr = utask->autask.saved_trap_nr;
handle_riprel_post_xol(auprobe, regs, NULL);
instruction_pointer_set(regs, utask->vaddr);
}
/*
* Skip these instructions as per the currently known x86 ISA.
* 0x66* { 0x90 | 0x0f 0x1f | 0x0f 0x19 | 0x87 0xc0 }
*/
bool arch_uprobe_skip_sstep(struct arch_uprobe *auprobe, struct pt_regs *regs)
{
int i;
for (i = 0; i < MAX_UINSN_BYTES; i++) {
if ((auprobe->insn[i] == 0x66))
continue;
if (auprobe->insn[i] == 0x90)
return true;
if (i == (MAX_UINSN_BYTES - 1))
break;
if ((auprobe->insn[i] == 0x0f) && (auprobe->insn[i+1] == 0x1f))
return true;
if ((auprobe->insn[i] == 0x0f) && (auprobe->insn[i+1] == 0x19))
return true;
if ((auprobe->insn[i] == 0x87) && (auprobe->insn[i+1] == 0xc0))
return true;
break;
}
return false;
}
......@@ -1590,6 +1590,10 @@ struct task_struct {
#ifdef CONFIG_HAVE_HW_BREAKPOINT
atomic_t ptrace_bp_refcnt;
#endif
#ifdef CONFIG_UPROBES
struct uprobe_task *utask;
int uprobe_srcu_id;
#endif
};
/* Future-safe accessor for struct task_struct's cpus_allowed. */
......
......@@ -28,8 +28,9 @@
#include <linux/rbtree.h>
struct vm_area_struct;
#ifdef CONFIG_ARCH_SUPPORTS_UPROBES
#include <asm/uprobes.h>
# include <asm/uprobes.h>
#endif
/* flags that denote/change uprobes behaviour */
......@@ -39,6 +40,8 @@ struct vm_area_struct;
/* Dont run handlers when first register/ last unregister in progress*/
#define UPROBE_RUN_HANDLER 0x2
/* Can skip singlestep */
#define UPROBE_SKIP_SSTEP 0x4
struct uprobe_consumer {
int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs);
......@@ -52,13 +55,42 @@ struct uprobe_consumer {
};
#ifdef CONFIG_UPROBES
enum uprobe_task_state {
UTASK_RUNNING,
UTASK_BP_HIT,
UTASK_SSTEP,
UTASK_SSTEP_ACK,
UTASK_SSTEP_TRAPPED,
};
/*
* uprobe_task: Metadata of a task while it singlesteps.
*/
struct uprobe_task {
enum uprobe_task_state state;
struct arch_uprobe_task autask;
struct uprobe *active_uprobe;
unsigned long xol_vaddr;
unsigned long vaddr;
};
extern int __weak set_swbp(struct arch_uprobe *aup, struct mm_struct *mm, unsigned long vaddr);
extern int __weak set_orig_insn(struct arch_uprobe *aup, struct mm_struct *mm, unsigned long vaddr, bool verify);
extern bool __weak is_swbp_insn(uprobe_opcode_t *insn);
extern int uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc);
extern void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc);
extern int uprobe_mmap(struct vm_area_struct *vma);
#else /* CONFIG_UPROBES is not defined */
extern void uprobe_free_utask(struct task_struct *t);
extern void uprobe_copy_process(struct task_struct *t);
extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs);
extern int uprobe_post_sstep_notifier(struct pt_regs *regs);
extern int uprobe_pre_sstep_notifier(struct pt_regs *regs);
extern void uprobe_notify_resume(struct pt_regs *regs);
extern bool uprobe_deny_signal(void);
extern bool __weak arch_uprobe_skip_sstep(struct arch_uprobe *aup, struct pt_regs *regs);
#else /* !CONFIG_UPROBES */
static inline int
uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc)
{
......@@ -72,5 +104,22 @@ static inline int uprobe_mmap(struct vm_area_struct *vma)
{
return 0;
}
#endif /* CONFIG_UPROBES */
static inline void uprobe_notify_resume(struct pt_regs *regs)
{
}
static inline bool uprobe_deny_signal(void)
{
return false;
}
static inline unsigned long uprobe_get_swbp_addr(struct pt_regs *regs)
{
return 0;
}
static inline void uprobe_free_utask(struct task_struct *t)
{
}
static inline void uprobe_copy_process(struct task_struct *t)
{
}
#endif /* !CONFIG_UPROBES */
#endif /* _LINUX_UPROBES_H */
This diff is collapsed.
......@@ -67,6 +67,7 @@
#include <linux/oom.h>
#include <linux/khugepaged.h>
#include <linux/signalfd.h>
#include <linux/uprobes.h>
#include <asm/pgtable.h>
#include <asm/pgalloc.h>
......@@ -701,6 +702,8 @@ void mm_release(struct task_struct *tsk, struct mm_struct *mm)
exit_pi_state_list(tsk);
#endif
uprobe_free_utask(tsk);
/* Get rid of any cached register state */
deactivate_mm(tsk, mm);
......@@ -1295,6 +1298,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
INIT_LIST_HEAD(&p->pi_state_list);
p->pi_state_cache = NULL;
#endif
uprobe_copy_process(p);
/*
* sigaltstack should be cleared when sharing the same VM
*/
......
......@@ -29,6 +29,7 @@
#include <linux/pid_namespace.h>
#include <linux/nsproxy.h>
#include <linux/user_namespace.h>
#include <linux/uprobes.h>
#define CREATE_TRACE_POINTS
#include <trace/events/signal.h>
......@@ -2192,6 +2193,9 @@ int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
struct signal_struct *signal = current->signal;
int signr;
if (unlikely(uprobe_deny_signal()))
return 0;
relock:
/*
* We'll jump back here after any time we were stopped in TASK_STOPPED.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment