From: Ingo Molnar <[EMAIL PROTECTED]> simplified and streamlined kgdb support on x86, both 32-bit and 64-bit, based on patch from:
Subject: kgdb: core-lite From: Jason Wessel <[EMAIL PROTECTED]> [ and countless other authors - see the patch for details. ] Signed-off-by: Ingo Molnar <[EMAIL PROTECTED]> Reviewed-by: Thomas Gleixner <[EMAIL PROTECTED]> --- arch/x86/Kconfig | 4 arch/x86/kernel/Makefile | 1 arch/x86/kernel/kgdb.c | 550 +++++++++++++++++++++++++++++++++++++++++++++++ include/asm-x86/kgdb.h | 87 +++++++ 4 files changed, 642 insertions(+) Index: linux-kgdb.q/arch/x86/Kconfig =================================================================== --- linux-kgdb.q.orig/arch/x86/Kconfig +++ linux-kgdb.q/arch/x86/Kconfig @@ -14,6 +14,7 @@ config X86_32 config X86_64 def_bool 64BIT + select KGDB_ARCH_HAS_SHADOW_INFO ### Arch settings config X86 @@ -139,6 +140,9 @@ config AUDIT_ARCH config ARCH_SUPPORTS_AOUT def_bool y +config ARCH_SUPPORTS_KGDB + def_bool y + # Use the generic interrupt handling code in kernel/irq/: config GENERIC_HARDIRQS bool Index: linux-kgdb.q/arch/x86/kernel/Makefile =================================================================== --- linux-kgdb.q.orig/arch/x86/kernel/Makefile +++ linux-kgdb.q/arch/x86/kernel/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_MODULES) += module_$(BITS) obj-$(CONFIG_ACPI_SRAT) += srat_32.o obj-$(CONFIG_EFI) += efi.o efi_$(BITS).o efi_stub_$(BITS).o obj-$(CONFIG_DOUBLEFAULT) += doublefault_32.o +obj-$(CONFIG_KGDB) += kgdb.o obj-$(CONFIG_VM86) += vm86_32.o obj-$(CONFIG_EARLY_PRINTK) += early_printk.o Index: linux-kgdb.q/arch/x86/kernel/kgdb.c =================================================================== --- /dev/null +++ linux-kgdb.q/arch/x86/kernel/kgdb.c @@ -0,0 +1,550 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +/* + * Copyright (C) 2004 Amit S. Kale <[EMAIL PROTECTED]> + * Copyright (C) 2000-2001 VERITAS Software Corporation. + * Copyright (C) 2002 Andi Kleen, SuSE Labs + * Copyright (C) 2004 LinSysSoft Technologies Pvt. Ltd. + * Copyright (C) 2007 MontaVista Software, Inc. + * Copyright (C) 2007-2008 Jason Wessel, Wind River Systems, Inc. + */ +/**************************************************************************** + * Contributor: Lake Stevens Instrument Division$ + * Written by: Glenn Engel $ + * Updated by: Amit Kale<[EMAIL PROTECTED]> + * Updated by: Tom Rini <[EMAIL PROTECTED]> + * Updated by: Jason Wessel <[EMAIL PROTECTED]> + * Modified for 386 by Jim Kingdon, Cygnus Support. + * Origianl kgdb, compatibility with 2.1.xx kernel by + * David Grothe <[EMAIL PROTECTED]> + * Integrated into 2.2.5 kernel by Tigran Aivazian <[EMAIL PROTECTED]> + * X86_64 changes from Andi Kleen's patch merged by Jim Houston + */ +#include <linux/spinlock.h> +#include <linux/kdebug.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/kgdb.h> +#include <linux/init.h> +#include <linux/smp.h> + +#include <asm/apicdef.h> +#include <asm/system.h> + +#ifdef CONFIG_X86_32 +# include <mach_ipi.h> +#else +# include <asm/mach_apic.h> +#endif + +/* + * Put the error code here just in case the user cares: + */ +static int gdb_x86errcode; + +/* + * Likewise, the vector number here (since GDB only gets the signal + * number through the usual means, and that's not very specific): + */ +static int gdb_x86vector = -1; + +void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + gdb_regs[GDB_AX] = regs->ax; + gdb_regs[GDB_BX] = regs->bx; + gdb_regs[GDB_CX] = regs->cx; + gdb_regs[GDB_DX] = regs->dx; + gdb_regs[GDB_SI] = regs->si; + gdb_regs[GDB_DI] = regs->di; + gdb_regs[GDB_BP] = regs->bp; + gdb_regs[GDB_PS] = regs->flags; + gdb_regs[GDB_PC] = regs->ip; +#ifdef CONFIG_X86_32 + gdb_regs[GDB_DS] = regs->ds; + gdb_regs[GDB_ES] = regs->es; + gdb_regs[GDB_CS] = regs->cs; + gdb_regs[GDB_SS] = __KERNEL_DS; + gdb_regs[GDB_FS] = 0xFFFF; + gdb_regs[GDB_GS] = 0xFFFF; +#else + gdb_regs[GDB_R8] = regs->r8; + gdb_regs[GDB_R9] = regs->r9; + gdb_regs[GDB_R10] = regs->r10; + gdb_regs[GDB_R11] = regs->r11; + gdb_regs[GDB_R12] = regs->r12; + gdb_regs[GDB_R13] = regs->r13; + gdb_regs[GDB_R14] = regs->r14; + gdb_regs[GDB_R15] = regs->r15; +#endif + gdb_regs[GDB_SP] = regs->sp; +} + +void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *p) +{ + gdb_regs[GDB_AX] = 0; + gdb_regs[GDB_BX] = 0; + gdb_regs[GDB_CX] = 0; + gdb_regs[GDB_DX] = 0; + gdb_regs[GDB_SI] = 0; + gdb_regs[GDB_DI] = 0; + gdb_regs[GDB_BP] = *(unsigned long *)p->thread.sp; +#ifdef CONFIG_X86_32 + gdb_regs[GDB_DS] = __KERNEL_DS; + gdb_regs[GDB_ES] = __KERNEL_DS; + gdb_regs[GDB_PS] = 0; + gdb_regs[GDB_CS] = __KERNEL_CS; + gdb_regs[GDB_PC] = p->thread.ip; + gdb_regs[GDB_SS] = __KERNEL_DS; + gdb_regs[GDB_FS] = 0xFFFF; + gdb_regs[GDB_GS] = 0xFFFF; +#else + gdb_regs[GDB_PS] = *(unsigned long *)(p->thread.sp + 8); + gdb_regs[GDB_PC] = 0; + gdb_regs[GDB_R8] = 0; + gdb_regs[GDB_R9] = 0; + gdb_regs[GDB_R10] = 0; + gdb_regs[GDB_R11] = 0; + gdb_regs[GDB_R12] = 0; + gdb_regs[GDB_R13] = 0; + gdb_regs[GDB_R14] = 0; + gdb_regs[GDB_R15] = 0; +#endif + gdb_regs[GDB_SP] = p->thread.sp; +} + +void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + regs->ax = gdb_regs[GDB_AX]; + regs->bx = gdb_regs[GDB_BX]; + regs->cx = gdb_regs[GDB_CX]; + regs->dx = gdb_regs[GDB_DX]; + regs->si = gdb_regs[GDB_SI]; + regs->di = gdb_regs[GDB_DI]; + regs->bp = gdb_regs[GDB_BP]; + regs->flags = gdb_regs[GDB_PS]; + regs->ip = gdb_regs[GDB_PC]; +#ifdef CONFIG_X86_32 + regs->ds = gdb_regs[GDB_DS]; + regs->es = gdb_regs[GDB_ES]; + regs->cs = gdb_regs[GDB_CS]; +#else + regs->r8 = gdb_regs[GDB_R8]; + regs->r9 = gdb_regs[GDB_R9]; + regs->r10 = gdb_regs[GDB_R10]; + regs->r11 = gdb_regs[GDB_R11]; + regs->r12 = gdb_regs[GDB_R12]; + regs->r13 = gdb_regs[GDB_R13]; + regs->r14 = gdb_regs[GDB_R14]; + regs->r15 = gdb_regs[GDB_R15]; +#endif +} + +static struct hw_breakpoint { + unsigned enabled; + unsigned type; + unsigned len; + unsigned long addr; +} breakinfo[4] = { + { .enabled = 0 }, + { .enabled = 0 }, + { .enabled = 0 }, + { .enabled = 0 }, +}; + +static void kgdb_correct_hw_break(void) +{ + unsigned long dr7; + int correctit = 0; + int breakbit; + int breakno; + + get_debugreg(dr7, 7); + for (breakno = 0; breakno < 4; breakno++) { + breakbit = 2 << (breakno << 1); + if (!(dr7 & breakbit) && breakinfo[breakno].enabled) { + correctit = 1; + dr7 |= breakbit; + dr7 &= ~(0xf0000 << (breakno << 2)); + dr7 |= ((breakinfo[breakno].len << 2) | + breakinfo[breakno].type) << + ((breakno << 2) + 16); + switch (breakno) { + case 0: + set_debugreg(breakinfo[0].addr, 0); + break; + + case 1: + set_debugreg(breakinfo[1].addr, 1); + break; + + case 2: + set_debugreg(breakinfo[2].addr, 2); + break; + + case 3: + set_debugreg(breakinfo[3].addr, 3); + break; + } + } else if ((dr7 & breakbit) && !breakinfo[breakno].enabled) { + correctit = 1; + dr7 &= ~breakbit; + dr7 &= ~(0xf0000 << (breakno << 2)); + } + } + if (correctit) + set_debugreg(dr7, 7); +} + +static int +kgdb_remove_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype) +{ + int i; + + for (i = 0; i < 4; i++) + if (breakinfo[i].addr == addr && breakinfo[i].enabled) + break; + if (i == 4) + return -1; + + breakinfo[i].enabled = 0; + + return 0; +} + +static void kgdb_remove_all_hw_break(void) +{ + int i; + + for (i = 0; i < 4; i++) + memset(&breakinfo[i], 0, sizeof(struct hw_breakpoint)); +} + +static int +kgdb_set_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype) +{ + unsigned type; + int i; + + for (i = 0; i < 4; i++) + if (!breakinfo[i].enabled) + break; + if (i == 4) + return -1; + + switch (bptype) { + case BP_HARDWARE_BREAKPOINT: + type = 0; + len = 1; + break; + case BP_WRITE_WATCHPOINT: + type = 1; + break; + case BP_ACCESS_WATCHPOINT: + type = 3; + break; + default: + return -1; + } + + if (len == 1 || len == 2 || len == 4) + breakinfo[i].len = len - 1; + else + return -1; + + breakinfo[i].enabled = 1; + breakinfo[i].addr = addr; + breakinfo[i].type = type; + + return 0; +} + +void kgdb_disable_hw_debug(struct pt_regs *regs) +{ + /* Disable hardware debugging while we are in kgdb: */ + set_debugreg(0UL, 7); +} + +void kgdb_post_master_code(struct pt_regs *regs, int e_vector, int err_code) +{ + /* Master processor is completely in the debugger */ + gdb_x86vector = e_vector; + gdb_x86errcode = err_code; +} + +#ifdef CONFIG_SMP +void kgdb_roundup_cpus(unsigned long flags) +{ + send_IPI_allbutself(APIC_DM_NMI); +} +#endif + +int kgdb_arch_handle_exception(int e_vector, int signo, int err_code, + char *remcomInBuffer, char *remcomOutBuffer, + struct pt_regs *linux_regs) +{ + unsigned long addr; + unsigned long dr6; + char *ptr; + int newPC; + + switch (remcomInBuffer[0]) { + case 'c': + case 's': + /* try to read optional parameter, pc unchanged if no parm */ + ptr = &remcomInBuffer[1]; + if (kgdb_hex2long(&ptr, &addr)) + linux_regs->ip = addr; + newPC = linux_regs->ip; + + /* clear the trace bit */ + linux_regs->flags &= ~TF_MASK; + atomic_set(&cpu_doing_single_step, -1); + + /* set the trace bit if we're stepping */ + if (remcomInBuffer[0] == 's') { + linux_regs->flags |= TF_MASK; + debugger_step = 1; + if (kgdb_contthread) { + atomic_set(&cpu_doing_single_step, + raw_smp_processor_id()); + } + } + + get_debugreg(dr6, 6); + if (!(dr6 & 0x4000)) { + int breakno; + + for (breakno = 0; breakno < 4; breakno++) { + if (dr6 & (1 << breakno) && + breakinfo[breakno].type == 0) { + /* Set restore flag: */ + linux_regs->flags |= X86_EFLAGS_RF; + break; + } + } + } + set_debugreg(0UL, 6); + kgdb_correct_hw_break(); + + return 0; + } + + /* this means that we do not want to exit from the handler: */ + return -1; +} + +#ifdef CONFIG_X86_64 + +static struct pt_regs *in_interrupt_stack(unsigned long rsp, int cpu) +{ + struct pt_regs *regs = NULL; + unsigned long end = (unsigned long)cpu_pda(cpu)->irqstackptr; + + if (rsp <= end && rsp >= end - IRQSTACKSIZE + 8) + regs = *(((struct pt_regs **)end) - 1); + + return regs; +} + +static struct pt_regs *in_exception_stack(unsigned long rsp, int cpu) +{ + struct tss_struct *init_tss = &__get_cpu_var(init_tss); + struct pt_regs *regs; + int i; + + for (i = 0; i < N_EXCEPTION_STACKS; i++) + if (rsp >= init_tss[cpu].x86_tss.ist[i] && + rsp <= init_tss[cpu].x86_tss.ist[i] + EXCEPTION_STKSZ) { + regs = (void *) init_tss[cpu].x86_tss.ist[i] +\ + EXCEPTION_STKSZ; + return regs - 1; + } + + return NULL; +} + +void kgdb_shadowinfo(struct pt_regs *regs, char *buffer, unsigned threadid) +{ + static char intr_desc[] = "Stack at interrupt entrypoint"; + static char exc_desc[] = "Stack at exception entrypoint"; + int cpu = raw_smp_processor_id(); + struct pt_regs *stregs; + + stregs = in_interrupt_stack(regs->sp, cpu); + if (stregs) { + kgdb_mem2hex(intr_desc, buffer, strlen(intr_desc)); + } else { + stregs = in_exception_stack(regs->sp, cpu); + if (stregs) + kgdb_mem2hex(exc_desc, buffer, strlen(exc_desc)); + } +} + +struct task_struct *kgdb_get_shadow_thread(struct pt_regs *regs, int threadid) +{ + int cpu = raw_smp_processor_id(); + struct pt_regs *stregs; + + stregs = in_interrupt_stack(regs->sp, cpu); + if (stregs) { + return current; + } else { + stregs = in_exception_stack(regs->sp, cpu); + if (stregs) + return current; + } + + return NULL; +} + +struct pt_regs *kgdb_shadow_regs(struct pt_regs *regs, int threadid) +{ + int cpu = raw_smp_processor_id(); + struct pt_regs *stregs; + + stregs = in_interrupt_stack(regs->sp, cpu); + if (stregs) { + return stregs; + } else { + stregs = in_exception_stack(regs->sp, cpu); + if (stregs) + return stregs; + } + + return NULL; +} + +#endif /* CONFIG_X86_64 */ + +static inline int +single_step_cont(struct pt_regs *regs, struct die_args *args) +{ + /* + * Single step exception from kernel space to user space so + * eat the exception and continue the process: + */ + printk(KERN_ERR "KGDB: trap/step from kernel to user space, " + "resuming...\n"); + kgdb_arch_handle_exception(args->trapnr, args->signr, + args->err, "c", "", regs); + + return NOTIFY_STOP; +} + +static int +kgdb_notify(struct notifier_block *self, unsigned long cmd, void *ptr) +{ + struct die_args *args = ptr; + struct pt_regs *regs = args->regs; + + switch (cmd) { + case DIE_NMI: + if (atomic_read(&debugger_active)) { + /* KGDB CPU roundup */ + kgdb_nmicallback(raw_smp_processor_id(), regs); + return NOTIFY_STOP; + } + return NOTIFY_DONE; + + case DIE_NMI_IPI: + if (atomic_read(&debugger_active)) { + /* KGDB CPU roundup: */ + if (kgdb_nmicallback(raw_smp_processor_id(), regs)) + return NOTIFY_DONE; + return NOTIFY_STOP; + } + return NOTIFY_DONE; + + case DIE_NMIWATCHDOG: + if (atomic_read(&debugger_active)) { + /* KGDB CPU roundup: */ + kgdb_nmicallback(raw_smp_processor_id(), regs); + return NOTIFY_STOP; + } + /* Enter debugger: */ + break; + + case DIE_DEBUG: + if (atomic_read(&cpu_doing_single_step) == + raw_smp_processor_id() && + user_mode(regs)) + return single_step_cont(regs, args); + /* fall through */ + default: + if (user_mode(regs)) + return NOTIFY_DONE; + } + + if (kgdb_handle_exception(args->trapnr, args->signr, args->err, regs)) + return NOTIFY_DONE; + + return NOTIFY_STOP; +} + +static struct notifier_block kgdb_notifier = { + .notifier_call = kgdb_notify, + + /* + * Lowest-prio notifier priority, we want to be notified last: + */ + .priority = -INT_MAX, +}; + +int kgdb_arch_init(void) +{ + register_die_notifier(&kgdb_notifier); + return 0; +} + +void kgdb_arch_uninit(void) +{ + unregister_die_notifier(&kgdb_notifier); +} + +/* + * Skip an int3 exception when it occurs after a breakpoint has been + * removed. Backtrack eip by 1 since the int3 would have caused it to + * increment by 1. + */ +int kgdb_skipexception(int exception, struct pt_regs *regs) +{ + if (exception == 3 && kgdb_isremovedbreak(regs->ip - 1)) { + regs->ip -= 1; + return 1; + } + return 0; +} + +unsigned long kgdb_arch_pc(int exception, struct pt_regs *regs) +{ + if (exception == 3) + return instruction_pointer(regs) - 1; + return instruction_pointer(regs); +} + +struct kgdb_arch arch_kgdb_ops = { + /* Breakpoint instruction: */ + .gdb_bpt_instr = { 0xcc }, + .flags = KGDB_HW_BREAKPOINT, +#ifndef CONFIG_X86_32 + .shadowth = 1, +#endif + .set_hw_breakpoint = kgdb_set_hw_break, + .remove_hw_breakpoint = kgdb_remove_hw_break, + .remove_all_hw_break = kgdb_remove_all_hw_break, + .correct_hw_break = kgdb_correct_hw_break, +}; Index: linux-kgdb.q/include/asm-x86/kgdb.h =================================================================== --- /dev/null +++ linux-kgdb.q/include/asm-x86/kgdb.h @@ -0,0 +1,87 @@ +#ifdef __KERNEL__ +#ifndef _ASM_KGDB_H_ +#define _ASM_KGDB_H_ + +/* + * Copyright (C) 2001-2004 Amit S. Kale + * Copyright (C) 2008 Wind River Systems, Inc. + */ + +#include <asm-generic/kgdb.h> + +/* + * BUFMAX defines the maximum number of characters in inbound/outbound + * buffers at least NUMREGBYTES*2 are needed for register packets + * Longer buffer is needed to list all threads + */ +#define BUFMAX 1024 + +/* + * Note that this register image is in a different order than + * the register image that Linux produces at interrupt time. + * + * Linux's register image is defined by struct pt_regs in ptrace.h. + * Just why GDB uses a different order is a historical mystery. + */ +#ifdef CONFIG_X86_32 +enum regnames { + GDB_AX, /* 0 */ + GDB_CX, /* 1 */ + GDB_DX, /* 2 */ + GDB_BX, /* 3 */ + GDB_SP, /* 4 */ + GDB_BP, /* 5 */ + GDB_SI, /* 6 */ + GDB_DI, /* 7 */ + GDB_PC, /* 8 also known as eip */ + GDB_PS, /* 9 also known as eflags */ + GDB_CS, /* 10 */ + GDB_SS, /* 11 */ + GDB_DS, /* 12 */ + GDB_ES, /* 13 */ + GDB_FS, /* 14 */ + GDB_GS, /* 15 */ +}; +#else /* ! CONFIG_X86_32 */ +enum regnames { + GDB_AX, /* 0 */ + GDB_DX, /* 1 */ + GDB_CX, /* 2 */ + GDB_BX, /* 3 */ + GDB_SI, /* 4 */ + GDB_DI, /* 5 */ + GDB_BP, /* 6 */ + GDB_SP, /* 7 */ + GDB_R8, /* 8 */ + GDB_R9, /* 9 */ + GDB_R10, /* 10 */ + GDB_R11, /* 11 */ + GDB_R12, /* 12 */ + GDB_R13, /* 13 */ + GDB_R14, /* 14 */ + GDB_R15, /* 15 */ + GDB_PC, /* 16 */ + GDB_PS, /* 17 */ +}; +#endif /* CONFIG_X86_32 */ + +/* + * Number of bytes of registers: + */ +#ifdef CONFIG_X86_32 +# define NUMREGBYTES 64 +#else +# define NUMREGBYTES ((GDB_PS+1)*8) +#endif + +#ifndef __ASSEMBLY__ +static inline void arch_kgdb_breakpoint(void) +{ + asm(" int $3"); +} +# define BREAK_INSTR_SIZE 1 +# define CACHE_FLUSH_IS_SAFE 1 +#endif + +#endif /* _ASM_KGDB_H_ */ +#endif /* __KERNEL__ */ -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/