diff options
Diffstat (limited to 'arch/x86/kernel/kgdb.c')
| -rw-r--r-- | arch/x86/kernel/kgdb.c | 172 | 
1 files changed, 128 insertions, 44 deletions
diff --git a/arch/x86/kernel/kgdb.c b/arch/x86/kernel/kgdb.c index ec592caac4b..7ec1d5f8d28 100644 --- a/arch/x86/kernel/kgdb.c +++ b/arch/x86/kernel/kgdb.c @@ -39,15 +39,16 @@  #include <linux/sched.h>  #include <linux/delay.h>  #include <linux/kgdb.h> -#include <linux/init.h>  #include <linux/smp.h>  #include <linux/nmi.h>  #include <linux/hw_breakpoint.h> +#include <linux/uaccess.h> +#include <linux/memory.h>  #include <asm/debugreg.h>  #include <asm/apicdef.h> -#include <asm/system.h>  #include <asm/apic.h> +#include <asm/nmi.h>  struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] =  { @@ -66,8 +67,6 @@ struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] =  	{ "ss", 4, offsetof(struct pt_regs, ss) },  	{ "ds", 4, offsetof(struct pt_regs, ds) },  	{ "es", 4, offsetof(struct pt_regs, es) }, -	{ "fs", 4, -1 }, -	{ "gs", 4, -1 },  #else  	{ "ax", 8, offsetof(struct pt_regs, ax) },  	{ "bx", 8, offsetof(struct pt_regs, bx) }, @@ -89,7 +88,11 @@ struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] =  	{ "flags", 4, offsetof(struct pt_regs, flags) },  	{ "cs", 4, offsetof(struct pt_regs, cs) },  	{ "ss", 4, offsetof(struct pt_regs, ss) }, +	{ "ds", 4, -1 }, +	{ "es", 4, -1 },  #endif +	{ "fs", 4, -1 }, +	{ "gs", 4, -1 },  };  int dbg_set_reg(int regno, void *mem, struct pt_regs *regs) @@ -120,8 +123,8 @@ char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs)  		memcpy(mem, (void *)regs + dbg_reg_def[regno].offset,  		       dbg_reg_def[regno].size); -	switch (regno) {  #ifdef CONFIG_X86_32 +	switch (regno) {  	case GDB_SS:  		if (!user_mode_vm(regs))  			*(unsigned long *)mem = __KERNEL_DS; @@ -134,8 +137,8 @@ char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs)  	case GDB_FS:  		*(unsigned long *)mem = 0xFFFF;  		break; -#endif  	} +#endif  	return dbg_reg_def[regno].name;  } @@ -277,7 +280,7 @@ static int hw_break_release_slot(int breakno)  		pevent = per_cpu_ptr(breakinfo[breakno].pev, cpu);  		if (dbg_release_bp_slot(*pevent))  			/* -			 * The debugger is responisble for handing the retry on +			 * The debugger is responsible for handing the retry on  			 * remove failure.  			 */  			return -1; @@ -315,14 +318,18 @@ static void kgdb_remove_all_hw_break(void)  		if (!breakinfo[i].enabled)  			continue;  		bp = *per_cpu_ptr(breakinfo[i].pev, cpu); -		if (bp->attr.disabled == 1) +		if (!bp->attr.disabled) { +			arch_uninstall_hw_breakpoint(bp); +			bp->attr.disabled = 1;  			continue; +		}  		if (dbg_is_early)  			early_dr7 &= ~encode_dr7(i, breakinfo[i].len,  						 breakinfo[i].type); -		else -			arch_uninstall_hw_breakpoint(bp); -		bp->attr.disabled = 1; +		else if (hw_break_release_slot(i)) +			printk(KERN_ERR "KGDB: hw bpt remove failed %lx\n", +			       breakinfo[i].addr); +		breakinfo[i].enabled = 0;  	}  } @@ -436,12 +443,12 @@ void kgdb_roundup_cpus(unsigned long flags)  /**   *	kgdb_arch_handle_exception - Handle architecture specific GDB packets. - *	@vector: The error vector of the exception that happened. + *	@e_vector: The error vector of the exception that happened.   *	@signo: The signal number of the exception that happened.   *	@err_code: The error code of the exception that happened. - *	@remcom_in_buffer: The buffer of the packet we have read. - *	@remcom_out_buffer: The buffer of %BUFMAX bytes to write a packet into. - *	@regs: The &struct pt_regs of the current process. + *	@remcomInBuffer: The buffer of the packet we have read. + *	@remcomOutBuffer: The buffer of %BUFMAX bytes to write a packet into. + *	@linux_regs: The &struct pt_regs of the current process.   *   *	This function MUST handle the 'c' and 's' command packets,   *	as well packets to set / remove a hardware breakpoint, if used. @@ -506,41 +513,37 @@ single_step_cont(struct pt_regs *regs, struct die_args *args)  static int was_in_debug_nmi[NR_CPUS]; -static int __kgdb_notify(struct die_args *args, unsigned long cmd) +static int kgdb_nmi_handler(unsigned int cmd, struct pt_regs *regs)  { -	struct pt_regs *regs = args->regs; -  	switch (cmd) { -	case DIE_NMI: +	case NMI_LOCAL:  		if (atomic_read(&kgdb_active) != -1) {  			/* KGDB CPU roundup */  			kgdb_nmicallback(raw_smp_processor_id(), regs);  			was_in_debug_nmi[raw_smp_processor_id()] = 1;  			touch_nmi_watchdog(); -			return NOTIFY_STOP; +			return NMI_HANDLED;  		} -		return NOTIFY_DONE; - -	case DIE_NMI_IPI: -		/* Just ignore, we will handle the roundup on DIE_NMI. */ -		return NOTIFY_DONE; +		break; -	case DIE_NMIUNKNOWN: +	case NMI_UNKNOWN:  		if (was_in_debug_nmi[raw_smp_processor_id()]) {  			was_in_debug_nmi[raw_smp_processor_id()] = 0; -			return NOTIFY_STOP; -		} -		return NOTIFY_DONE; - -	case DIE_NMIWATCHDOG: -		if (atomic_read(&kgdb_active) != -1) { -			/* KGDB CPU roundup: */ -			kgdb_nmicallback(raw_smp_processor_id(), regs); -			return NOTIFY_STOP; +			return NMI_HANDLED;  		} -		/* Enter debugger: */  		break; +	default: +		/* do nothing */ +		break; +	} +	return NMI_DONE; +} +static int __kgdb_notify(struct die_args *args, unsigned long cmd) +{ +	struct pt_regs *regs = args->regs; + +	switch (cmd) {  	case DIE_DEBUG:  		if (atomic_read(&kgdb_cpu_doing_single_step) != -1) {  			if (user_mode(regs)) @@ -598,11 +601,6 @@ kgdb_notify(struct notifier_block *self, unsigned long cmd, void *ptr)  static struct notifier_block kgdb_notifier = {  	.notifier_call	= kgdb_notify, - -	/* -	 * Lowest-prio notifier priority, we want to be notified last: -	 */ -	.priority	= -INT_MAX,  };  /** @@ -613,10 +611,34 @@ static struct notifier_block kgdb_notifier = {   */  int kgdb_arch_init(void)  { -	return register_die_notifier(&kgdb_notifier); +	int retval; + +	retval = register_die_notifier(&kgdb_notifier); +	if (retval) +		goto out; + +	retval = register_nmi_handler(NMI_LOCAL, kgdb_nmi_handler, +					0, "kgdb"); +	if (retval) +		goto out1; + +	retval = register_nmi_handler(NMI_UNKNOWN, kgdb_nmi_handler, +					0, "kgdb"); + +	if (retval) +		goto out2; + +	return retval; + +out2: +	unregister_nmi_handler(NMI_LOCAL, "kgdb"); +out1: +	unregister_die_notifier(&kgdb_notifier); +out: +	return retval;  } -static void kgdb_hw_overflow_handler(struct perf_event *event, int nmi, +static void kgdb_hw_overflow_handler(struct perf_event *event,  		struct perf_sample_data *data, struct pt_regs *regs)  {  	struct task_struct *tsk = current; @@ -646,7 +668,7 @@ void kgdb_arch_late(void)  	for (i = 0; i < HBP_NUM; i++) {  		if (breakinfo[i].pev)  			continue; -		breakinfo[i].pev = register_wide_hw_breakpoint(&attr, NULL); +		breakinfo[i].pev = register_wide_hw_breakpoint(&attr, NULL, NULL);  		if (IS_ERR((void * __force)breakinfo[i].pev)) {  			printk(KERN_ERR "kgdb: Could not allocate hw"  			       "breakpoints\nDisabling the kernel debugger\n"); @@ -681,6 +703,8 @@ void kgdb_arch_exit(void)  			breakinfo[i].pev = NULL;  		}  	} +	unregister_nmi_handler(NMI_UNKNOWN, "kgdb"); +	unregister_nmi_handler(NMI_LOCAL, "kgdb");  	unregister_die_notifier(&kgdb_notifier);  } @@ -718,6 +742,66 @@ void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long ip)  	regs->ip = ip;  } +int kgdb_arch_set_breakpoint(struct kgdb_bkpt *bpt) +{ +	int err; +#ifdef CONFIG_DEBUG_RODATA +	char opc[BREAK_INSTR_SIZE]; +#endif /* CONFIG_DEBUG_RODATA */ + +	bpt->type = BP_BREAKPOINT; +	err = probe_kernel_read(bpt->saved_instr, (char *)bpt->bpt_addr, +				BREAK_INSTR_SIZE); +	if (err) +		return err; +	err = probe_kernel_write((char *)bpt->bpt_addr, +				 arch_kgdb_ops.gdb_bpt_instr, BREAK_INSTR_SIZE); +#ifdef CONFIG_DEBUG_RODATA +	if (!err) +		return err; +	/* +	 * It is safe to call text_poke() because normal kernel execution +	 * is stopped on all cores, so long as the text_mutex is not locked. +	 */ +	if (mutex_is_locked(&text_mutex)) +		return -EBUSY; +	text_poke((void *)bpt->bpt_addr, arch_kgdb_ops.gdb_bpt_instr, +		  BREAK_INSTR_SIZE); +	err = probe_kernel_read(opc, (char *)bpt->bpt_addr, BREAK_INSTR_SIZE); +	if (err) +		return err; +	if (memcmp(opc, arch_kgdb_ops.gdb_bpt_instr, BREAK_INSTR_SIZE)) +		return -EINVAL; +	bpt->type = BP_POKE_BREAKPOINT; +#endif /* CONFIG_DEBUG_RODATA */ +	return err; +} + +int kgdb_arch_remove_breakpoint(struct kgdb_bkpt *bpt) +{ +#ifdef CONFIG_DEBUG_RODATA +	int err; +	char opc[BREAK_INSTR_SIZE]; + +	if (bpt->type != BP_POKE_BREAKPOINT) +		goto knl_write; +	/* +	 * It is safe to call text_poke() because normal kernel execution +	 * is stopped on all cores, so long as the text_mutex is not locked. +	 */ +	if (mutex_is_locked(&text_mutex)) +		goto knl_write; +	text_poke((void *)bpt->bpt_addr, bpt->saved_instr, BREAK_INSTR_SIZE); +	err = probe_kernel_read(opc, (char *)bpt->bpt_addr, BREAK_INSTR_SIZE); +	if (err || memcmp(opc, bpt->saved_instr, BREAK_INSTR_SIZE)) +		goto knl_write; +	return err; +knl_write: +#endif /* CONFIG_DEBUG_RODATA */ +	return probe_kernel_write((char *)bpt->bpt_addr, +				  (char *)bpt->saved_instr, BREAK_INSTR_SIZE); +} +  struct kgdb_arch arch_kgdb_ops = {  	/* Breakpoint instruction: */  	.gdb_bpt_instr		= { 0xcc },  | 
