mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-17 01:04:19 +08:00
1eeb66a1bb
This patch moves the die notifier handling to common code. Previous various architectures had exactly the same code for it. Note that the new code is compiled unconditionally, this should be understood as an appel to the other architecture maintainer to implement support for it aswell (aka sprinkling a notify_die or two in the proper place) arm had a notifiy_die that did something totally different, I renamed it to arm_notify_die as part of the patch and made it static to the file it's declared and used at. avr32 used to pass slightly less information through this interface and I brought it into line with the other architectures. [akpm@linux-foundation.org: build fix] [akpm@linux-foundation.org: fix vmalloc_sync_all bustage] [bryan.wu@analog.com: fix vmalloc_sync_all in nommu] Signed-off-by: Christoph Hellwig <hch@lst.de> Cc: <linux-arch@vger.kernel.org> Cc: Russell King <rmk@arm.linux.org.uk> Signed-off-by: Bryan Wu <bryan.wu@analog.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
272 lines
6.2 KiB
C
272 lines
6.2 KiB
C
/*
|
|
* Kernel Probes (KProbes)
|
|
*
|
|
* Copyright (C) 2005-2006 Atmel Corporation
|
|
*
|
|
* Based on arch/ppc64/kernel/kprobes.c
|
|
* Copyright (C) IBM Corporation, 2002, 2004
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/kprobes.h>
|
|
#include <linux/ptrace.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <linux/kdebug.h>
|
|
#include <asm/ocd.h>
|
|
|
|
DEFINE_PER_CPU(struct kprobe *, current_kprobe);
|
|
static unsigned long kprobe_status;
|
|
static struct pt_regs jprobe_saved_regs;
|
|
|
|
int __kprobes arch_prepare_kprobe(struct kprobe *p)
|
|
{
|
|
int ret = 0;
|
|
|
|
if ((unsigned long)p->addr & 0x01) {
|
|
printk("Attempt to register kprobe at an unaligned address\n");
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
/* XXX: Might be a good idea to check if p->addr is a valid
|
|
* kernel address as well... */
|
|
|
|
if (!ret) {
|
|
pr_debug("copy kprobe at %p\n", p->addr);
|
|
memcpy(p->ainsn.insn, p->addr, MAX_INSN_SIZE * sizeof(kprobe_opcode_t));
|
|
p->opcode = *p->addr;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void __kprobes arch_arm_kprobe(struct kprobe *p)
|
|
{
|
|
pr_debug("arming kprobe at %p\n", p->addr);
|
|
*p->addr = BREAKPOINT_INSTRUCTION;
|
|
flush_icache_range((unsigned long)p->addr,
|
|
(unsigned long)p->addr + sizeof(kprobe_opcode_t));
|
|
}
|
|
|
|
void __kprobes arch_disarm_kprobe(struct kprobe *p)
|
|
{
|
|
pr_debug("disarming kprobe at %p\n", p->addr);
|
|
*p->addr = p->opcode;
|
|
flush_icache_range((unsigned long)p->addr,
|
|
(unsigned long)p->addr + sizeof(kprobe_opcode_t));
|
|
}
|
|
|
|
static void __kprobes prepare_singlestep(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
unsigned long dc;
|
|
|
|
pr_debug("preparing to singlestep over %p (PC=%08lx)\n",
|
|
p->addr, regs->pc);
|
|
|
|
BUG_ON(!(sysreg_read(SR) & SYSREG_BIT(SR_D)));
|
|
|
|
dc = __mfdr(DBGREG_DC);
|
|
dc |= DC_SS;
|
|
__mtdr(DBGREG_DC, dc);
|
|
|
|
/*
|
|
* We must run the instruction from its original location
|
|
* since it may actually reference PC.
|
|
*
|
|
* TODO: Do the instruction replacement directly in icache.
|
|
*/
|
|
*p->addr = p->opcode;
|
|
flush_icache_range((unsigned long)p->addr,
|
|
(unsigned long)p->addr + sizeof(kprobe_opcode_t));
|
|
}
|
|
|
|
static void __kprobes resume_execution(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
unsigned long dc;
|
|
|
|
pr_debug("resuming execution at PC=%08lx\n", regs->pc);
|
|
|
|
dc = __mfdr(DBGREG_DC);
|
|
dc &= ~DC_SS;
|
|
__mtdr(DBGREG_DC, dc);
|
|
|
|
*p->addr = BREAKPOINT_INSTRUCTION;
|
|
flush_icache_range((unsigned long)p->addr,
|
|
(unsigned long)p->addr + sizeof(kprobe_opcode_t));
|
|
}
|
|
|
|
static void __kprobes set_current_kprobe(struct kprobe *p)
|
|
{
|
|
__get_cpu_var(current_kprobe) = p;
|
|
}
|
|
|
|
static int __kprobes kprobe_handler(struct pt_regs *regs)
|
|
{
|
|
struct kprobe *p;
|
|
void *addr = (void *)regs->pc;
|
|
int ret = 0;
|
|
|
|
pr_debug("kprobe_handler: kprobe_running=%p\n",
|
|
kprobe_running());
|
|
|
|
/*
|
|
* We don't want to be preempted for the entire
|
|
* duration of kprobe processing
|
|
*/
|
|
preempt_disable();
|
|
|
|
/* Check that we're not recursing */
|
|
if (kprobe_running()) {
|
|
p = get_kprobe(addr);
|
|
if (p) {
|
|
if (kprobe_status == KPROBE_HIT_SS) {
|
|
printk("FIXME: kprobe hit while single-stepping!\n");
|
|
goto no_kprobe;
|
|
}
|
|
|
|
printk("FIXME: kprobe hit while handling another kprobe\n");
|
|
goto no_kprobe;
|
|
} else {
|
|
p = kprobe_running();
|
|
if (p->break_handler && p->break_handler(p, regs))
|
|
goto ss_probe;
|
|
}
|
|
/* If it's not ours, can't be delete race, (we hold lock). */
|
|
goto no_kprobe;
|
|
}
|
|
|
|
p = get_kprobe(addr);
|
|
if (!p)
|
|
goto no_kprobe;
|
|
|
|
kprobe_status = KPROBE_HIT_ACTIVE;
|
|
set_current_kprobe(p);
|
|
if (p->pre_handler && p->pre_handler(p, regs))
|
|
/* handler has already set things up, so skip ss setup */
|
|
return 1;
|
|
|
|
ss_probe:
|
|
prepare_singlestep(p, regs);
|
|
kprobe_status = KPROBE_HIT_SS;
|
|
return 1;
|
|
|
|
no_kprobe:
|
|
preempt_enable_no_resched();
|
|
return ret;
|
|
}
|
|
|
|
static int __kprobes post_kprobe_handler(struct pt_regs *regs)
|
|
{
|
|
struct kprobe *cur = kprobe_running();
|
|
|
|
pr_debug("post_kprobe_handler, cur=%p\n", cur);
|
|
|
|
if (!cur)
|
|
return 0;
|
|
|
|
if (cur->post_handler) {
|
|
kprobe_status = KPROBE_HIT_SSDONE;
|
|
cur->post_handler(cur, regs, 0);
|
|
}
|
|
|
|
resume_execution(cur, regs);
|
|
reset_current_kprobe();
|
|
preempt_enable_no_resched();
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int __kprobes kprobe_fault_handler(struct pt_regs *regs, int trapnr)
|
|
{
|
|
struct kprobe *cur = kprobe_running();
|
|
|
|
pr_debug("kprobe_fault_handler: trapnr=%d\n", trapnr);
|
|
|
|
if (cur->fault_handler && cur->fault_handler(cur, regs, trapnr))
|
|
return 1;
|
|
|
|
if (kprobe_status & KPROBE_HIT_SS) {
|
|
resume_execution(cur, regs);
|
|
preempt_enable_no_resched();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wrapper routine to for handling exceptions.
|
|
*/
|
|
int __kprobes kprobe_exceptions_notify(struct notifier_block *self,
|
|
unsigned long val, void *data)
|
|
{
|
|
struct die_args *args = (struct die_args *)data;
|
|
int ret = NOTIFY_DONE;
|
|
|
|
pr_debug("kprobe_exceptions_notify: val=%lu, data=%p\n",
|
|
val, data);
|
|
|
|
switch (val) {
|
|
case DIE_BREAKPOINT:
|
|
if (kprobe_handler(args->regs))
|
|
ret = NOTIFY_STOP;
|
|
break;
|
|
case DIE_SSTEP:
|
|
if (post_kprobe_handler(args->regs))
|
|
ret = NOTIFY_STOP;
|
|
break;
|
|
case DIE_FAULT:
|
|
if (kprobe_running()
|
|
&& kprobe_fault_handler(args->regs, args->trapnr))
|
|
ret = NOTIFY_STOP;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
struct jprobe *jp = container_of(p, struct jprobe, kp);
|
|
|
|
memcpy(&jprobe_saved_regs, regs, sizeof(struct pt_regs));
|
|
|
|
/*
|
|
* TODO: We should probably save some of the stack here as
|
|
* well, since gcc may pass arguments on the stack for certain
|
|
* functions (lots of arguments, large aggregates, varargs)
|
|
*/
|
|
|
|
/* setup return addr to the jprobe handler routine */
|
|
regs->pc = (unsigned long)jp->entry;
|
|
return 1;
|
|
}
|
|
|
|
void __kprobes jprobe_return(void)
|
|
{
|
|
asm volatile("breakpoint" ::: "memory");
|
|
}
|
|
|
|
int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
/*
|
|
* FIXME - we should ideally be validating that we got here 'cos
|
|
* of the "trap" in jprobe_return() above, before restoring the
|
|
* saved regs...
|
|
*/
|
|
memcpy(regs, &jprobe_saved_regs, sizeof(struct pt_regs));
|
|
return 1;
|
|
}
|
|
|
|
int __init arch_init_kprobes(void)
|
|
{
|
|
printk("KPROBES: Enabling monitor mode (MM|DBE)...\n");
|
|
__mtdr(DBGREG_DC, DC_MM | DC_DBE);
|
|
|
|
/* TODO: Register kretprobe trampoline */
|
|
return 0;
|
|
}
|