diff -u linux/arch/x86_64/kernel/gdbstub.c-GDB linux/arch/x86_64/kernel/gdbstub.c --- linux/arch/x86_64/kernel/gdbstub.c-GDB 2003-06-10 14:39:32.000000000 +0200 +++ linux/arch/x86_64/kernel/gdbstub.c 2003-06-10 14:39:56.000000000 +0200 @@ -0,0 +1,1342 @@ +/* + * + * 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. + * + */ + +/* + TODo-x86-64: + - Do an NMI on beginning on SMP systems to stop everybody in case + the nmi watchdog doesn't run. + - Fix gdb console + - Support access to interrupt/exception stacks of other cpus + (need more magic thread ids) + - Support access to XMM/segment registers (needs protocol extension?) + - need "expect single step trap" flag to catch spurious TFs from + user space. + + (longer term:) + - Use early_console serial driver directly to be independent +*/ + +/* + * Copyright (C) 2000-2001 VERITAS Software Corporation. + * Copyright (C) 2002 Andi Kleen, SuSE Labs + */ +/**************************************************************************** + * Header: remcom.c,v 1.34 91/03/09 12:29:49 glenne Exp $ + * + * Module name: remcom.c $ + * Revision: 1.34 $ + * Date: 91/03/09 12:29:49 $ + * Contributor: Lake Stevens Instrument Division$ + * + * Description: low level support for gdb debugger. $ + * + * Considerations: only works on target hardware $ + * + * Written by: Glenn Engel $ + * Updated by: Amit Kale + * ModuleState: Experimental $ + * + * NOTES: See Below $ + * + * Modified for 386 by Jim Kingdon, Cygnus Support. + * Original kgdb, compatibility with 2.1.xx kernel by David Grothe + * Integrated into 2.2.5 kernel by Tigran Aivazian + * thread support, + * support for multiple processors, + * support for ia-32(x86) hardware debugging, + * Console support, + * handling nmi watchdog + * Amit S. Kale ( akale@veritas.com ) + * Hacked for x86-64 by Andi Kleen. + * This works via the x86-64 notify_die() call chain interface. + * Rewrote the thread handling and added support for interrupt/exception threads + * + * To enable debugger support, two things need to happen. One, a + * call to set_debug_traps() is necessary in order to allow any breakpoints + * or error conditions to be properly intercepted and reported to gdb. + * Two, a breakpoint needs to be generated to begin communication. This + * is most easily accomplished by a call to breakpoint(). Breakpoint() + * simulates a breakpoint by executing an int 3. + * + ************* + * + * The following gdb commands are supported: + * + * command function Return value + * + * g return the value of the CPU registers hex data or ENN + * G set the value of the CPU registers OK or ENN + * + * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN + * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN + * + * c Resume at current address SNN ( signal NN) + * cAA..AA Continue at address AA..AA SNN + * + * s Step one instruction SNN + * sAA..AA Step one instruction from AA..AA SNN + * + * k kill + * + * ? What was the last sigval ? SNN (signal NN) + * + * All commands and responses are sent with a packet which includes a + * checksum. A packet consists of + * + * $#. + * + * where + * :: + * :: < two hex digits computed as modulo 256 sum of > + * + * When a packet is received, it is first acknowledged with either '+' or '-'. + * '+' indicates a successful transfer. '-' indicates a failed transfer. + * + * Example: + * + * Host: Reply: + * $m0,10#2a +$00010203040506070809101112131415#42 + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include /* for linux pt_regs struct */ +#include +#include +#include +#ifdef CONFIG_GDB_CONSOLE +#include +#endif +#include +#include + +#define Dearly_printk(x...) + +/************************************************************************ + * + * external low-level support routines + */ +typedef void (*Function)(void); /* pointer to a function */ + +/* Thread reference */ +typedef unsigned char threadref[8]; + +extern int nmi_watchdog_disabled; + +extern int putDebugChar(int); /* write a single character */ +extern int getDebugChar(void); /* read and return a single char */ + +/************************************************************************/ +/* 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 + +static char initialized; /* boolean flag. != 0 means we've been initialized */ + + +static struct task_struct *interrupted_tasks[NR_CPUS]; +static struct pt_regs *interrupted_regs[NR_CPUS]; + + +static const char hexchars[]="0123456789abcdef"; + +/* Number of bytes of registers. */ +#define NUMREGBYTES (_LASTREG*8) +/* + * 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. + * + * Could add XMM and segment registers here. + */ +enum regnames {_RAX, + _RDX, + _RCX, + _RBX, + _RSI, + _RDI, + _RBP, + _RSP, + _R8, + _R9, + _R10, + _R11, + _R12, + _R13, + _R14, + _R15, + _PC, + _PS, + _LASTREG=_PS }; + + + + +/*************************** ASSEMBLY CODE MACROS *************************/ +/* */ + +#define BREAKPOINT() asm(" int $3"); + +/* Put the error code here just in case the user cares. */ +int gdb_x8664errcode; +/* Likewise, the vector number here (since GDB only gets the signal + number through the usual means, and that's not very specific). */ +int gdb_x8664vector = -1; + +#if KGDB_MAX_NO_CPUS != 8 +#error change the definition of slavecpulocks +#endif + +static spinlock_t slavecpulocks[KGDB_MAX_NO_CPUS] = { SPIN_LOCK_UNLOCKED, + SPIN_LOCK_UNLOCKED, SPIN_LOCK_UNLOCKED, SPIN_LOCK_UNLOCKED, + SPIN_LOCK_UNLOCKED, SPIN_LOCK_UNLOCKED, SPIN_LOCK_UNLOCKED, + SPIN_LOCK_UNLOCKED }; +volatile int procindebug[KGDB_MAX_NO_CPUS]; + +volatile unsigned kgdb_lock = 0; + +static void kgdb_usercode (void) +{ +} + +int hex(char ch) +{ + if ((ch >= 'a') && (ch <= 'f')) return (ch-'a'+10); + if ((ch >= '0') && (ch <= '9')) return (ch-'0'); + if ((ch >= 'A') && (ch <= 'F')) return (ch-'A'+10); + return (-1); +} + + +/* scan for the sequence $# */ +void getpacket(char * buffer) +{ + unsigned char checksum; + unsigned char xmitcsum; + int i; + int count; + char ch; + + do { + /* wait around for the start character, ignore all other characters */ + while ((ch = (getDebugChar() & 0x7f)) != '$'); + checksum = 0; + xmitcsum = -1; + + count = 0; + + /* now, read until a # or end of buffer is found */ + while (count < BUFMAX) { + ch = getDebugChar() & 0x7f; + if (ch == '#') break; + checksum = checksum + ch; + buffer[count] = ch; + count = count + 1; + } + buffer[count] = 0; + + if (ch == '#') { + xmitcsum = hex(getDebugChar() & 0x7f) << 4; + xmitcsum += hex(getDebugChar() & 0x7f); + + if (checksum != xmitcsum) putDebugChar('-'); /* failed checksum */ + else { + putDebugChar('+'); /* successful transfer */ + /* if a sequence char is present, reply the sequence ID */ + if (buffer[2] == ':') { + putDebugChar( buffer[0] ); + putDebugChar( buffer[1] ); + /* remove sequence chars from buffer */ + count = strlen(buffer); + for (i=3; i <= count; i++) buffer[i-3] = buffer[i]; + } + } + } + } while (checksum != xmitcsum); + +} + +/* send the packet in buffer. */ + + +void putpacket(char * buffer) +{ + unsigned char checksum; + int count; + char ch; + + /* $#. */ + do { + putDebugChar('$'); + checksum = 0; + count = 0; + + while ((ch=buffer[count])) { + if (! putDebugChar(ch)) return; + checksum += ch; + count += 1; + } + + putDebugChar('#'); + putDebugChar(hexchars[checksum >> 4]); + putDebugChar(hexchars[checksum % 16]); + + } while ((getDebugChar() & 0x7f) != '+'); + +} + +static char remcomInBuffer[BUFMAX]; +static char remcomOutBuffer[BUFMAX]; +static short error; + +static void regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + gdb_regs[_RAX] = regs->rax; + gdb_regs[_RBX] = regs->rbx; + gdb_regs[_RCX] = regs->rcx; + gdb_regs[_RDX] = regs->rdx; + gdb_regs[_RSI] = regs->rsi; + gdb_regs[_RDI] = regs->rdi; + gdb_regs[_RBP] = regs->rbp; + gdb_regs[ _PS] = regs->eflags; + gdb_regs[ _PC] = regs->rip; + gdb_regs[ _R8] = regs->r8; + gdb_regs[ _R9] = regs->r9; + gdb_regs[_R10] = regs->r10; + gdb_regs[_R11] = regs->r11; + gdb_regs[_R12] = regs->r12; + gdb_regs[_R13] = regs->r13; + gdb_regs[_R14] = regs->r14; + gdb_regs[_R15] = regs->r15; + gdb_regs[_RSP] = regs->rsp; +} /* regs_to_gdb_regs */ + +static void gdb_regs_to_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + regs->rax = gdb_regs[_RAX] ; + regs->rbx = gdb_regs[_RBX] ; + regs->rcx = gdb_regs[_RCX] ; + regs->rdx = gdb_regs[_RDX] ; + regs->rsi = gdb_regs[_RSI] ; + regs->rdi = gdb_regs[_RDI] ; + regs->rbp = gdb_regs[_RBP] ; + regs->eflags= gdb_regs[ _PS] ; + regs->rip = gdb_regs[ _PC] ; + regs->r8 = gdb_regs[ _R8] ; + regs->r9 = gdb_regs[ _R9] ; + regs->r10 = gdb_regs[ _R10] ; + regs->r11 = gdb_regs[ _R11] ; + regs->r12 = gdb_regs[ _R12] ; + regs->r13 = gdb_regs[ _R13] ; + regs->r14 = gdb_regs[ _R14] ; + regs->r15 = gdb_regs[ _R15] ; +#if 0 /* can't change these */ + regs->rsp = gdb_regs[_RSP] ; + regs->ss = gdb_regs[ _SS] ; + regs->fs = gdb_regs[ _FS] ; + regs->gs = gdb_regs[ _GS] ; +#endif + +} /* gdb_regs_to_regs */ + +/* convert the memory pointed to by mem into hex, placing result in buf */ +/* return a pointer to the last char put in buf (null) */ +char* mem2hex( char* mem, + char* buf, + int count) +{ + int i; + unsigned char ch; + + for (i=0;i> 4]; + *buf++ = hexchars[ch % 16]; + } + *buf = 0; + return(buf); +} + +/* convert the hex array pointed to by buf into binary to be placed in mem */ +/* return a pointer to the character AFTER the last byte written */ +char* hex2mem( char* buf, + char* mem, + int count) +{ + int i; + unsigned char ch; + + for (i=0;i=0) + { + *intValue = (*intValue <<4) | hexValue; + numChars ++; + } + else + break; + + (*ptr)++; + } + + return (numChars); +} + + +/* 16bit encoded as hex */ +static pid_t hex_to_pid(unsigned char *buf) +{ + pid_t pid = simple_strtoul(buf,NULL,16); + return pid; +} + +static int pid_to_hex(pid_t pid, unsigned char *buf) +{ + return sprintf(buf,"%x",pid); +} + +/* everything is stopped so we can access the thread list safely. + but check if someone grabbed it for write */ +static inline int thread_list_busy(void) +{ + return 0; +#ifdef CONFIG_SMP + return (tasklist_lock.lock <= 0x1000000); +#else + return 0; /* no checking possible */ +#endif +} + +static struct task_struct *lookup_thread(pid_t pid) +{ + struct task_struct *t = &init_task; + if (thread_list_busy()) + return NULL; + int it = 0; + do { + pid_t tpid; + if (__get_user(tpid, &t->pid)) + return NULL; + if (pid == tpid) + return t; + if (__get_user(t, &t->next_task)) + return NULL; + if ((unsigned long)t < __PAGE_OFFSET) + return NULL; + ++it; + } while (t != &init_task); + Dearly_printk("lookup_thread hit end (%d)\n",it); + return NULL; +} + +/* null terminate */ +static void encode_thread(pid_t pid, unsigned char *p) +{ + static const char stat_nam[] = { 'R', 'S', 'D', 'Z', 'T', 'W' }; + char buf[128], *s; + struct task_struct *t; + int cpu = hard_smp_processor_id(); + + if (pid == 0x9001) + sprintf(buf, "%-4d cpu %d interrupt stack thread\n", pid, cpu); + else if (pid == 0x9002) + sprintf(buf, "%-4d cpu %d exception stack thread\n", pid, cpu); + else if ((t = lookup_thread(pid)) == NULL) + sprintf(buf, "%-4d unknown task\n", pid); + else + sprintf(buf,"%-4d %30s %c\n", pid, t->comm, + t->state && ffz(~t->state)+1 < sizeof(stat_nam)/sizeof(char) ? + stat_nam[ffz(~t->state) + 1] : ' '); + for (s = buf; *s; ++s) { + *p++ = hexchars[(*s >> 4) & 0xf]; + *p++ = hexchars[*s & 0xf]; + } + *p = 0; +} + +static struct pt_regs *in_interrupt_stack(unsigned long rsp, int cpu) +{ + struct pt_regs *regs; + unsigned long end = (unsigned long) cpu_pda[cpu].irqstackptr; + if (rsp <= end && + rsp >= end - IRQSTACKSIZE + 8) { + if (__get_user(regs, ((struct pt_regs **)end)-1)) + return NULL; + return regs; + } + return NULL; +} + +static struct pt_regs *in_exception_stack(unsigned long rsp, int cpu) +{ + int i; + for (i = 0; i < N_EXCEPTION_STACKS; i++) + if (rsp >= init_tss[cpu].ist[i] && + rsp <= init_tss[cpu].ist[i] + EXCEPTION_STKSZ) { + struct pt_regs *r = (void *)init_tss[cpu].ist[i] + EXCEPTION_STKSZ; + return r-1; + } + return NULL; +} + +struct hw_breakpoint { + unsigned enabled; + unsigned type; + unsigned len; + unsigned long addr; +} breakinfo[4] = { { enabled:0 }, { enabled:0 }, { enabled:0 }, { enabled:0 }}; + +void correct_hw_break( void ) +{ + int breakno; + int correctit; + int breakbit; + unsigned long dr7; + + asm volatile ( + "movq %%db7, %0\n" + : "=r" (dr7) + : ); + do + { + unsigned long addr0, addr1, addr2, addr3; + asm volatile ( + "movq %%db0, %0\n" + "movq %%db1, %1\n" + "movq %%db2, %2\n" + "movq %%db3, %3\n" + : "=r" (addr0), "=r" (addr1), "=r" (addr2), + "=r" (addr3) : ); + } while (0); + correctit = 0; + for (breakno = 0; breakno < 3; 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) << 16) << + (breakno << 2); + switch (breakno) { + case 0: + asm volatile ("movq %0, %%dr0\n" + : + : "r" (breakinfo[breakno].addr) ); + break; + + case 1: + asm volatile ("movq %0, %%dr1\n" + : + : "r" (breakinfo[breakno].addr) ); + break; + + case 2: + asm volatile ("movq %0, %%dr2\n" + : + : "r" (breakinfo[breakno].addr) ); + break; + + case 3: + asm volatile ("movq %0, %%dr3\n" + : + : "r" (breakinfo[breakno].addr) ); + break; + } + } else if ((dr7 & breakbit) && !breakinfo[breakno].enabled){ + correctit = 1; + dr7 &= ~breakbit; + dr7 &= ~(0xf0000 << (breakno << 2)); + } + } + if (correctit) { + asm volatile ( "movq %0, %%db7\n" + : + : "r" (dr7)); + } +} + +int remove_hw_break( + unsigned breakno) +{ + if (!breakinfo[breakno].enabled) { + return -1; + } + breakinfo[breakno].enabled = 0; + return 0; +} + +int set_hw_break( + unsigned breakno, + unsigned type, + unsigned len, + unsigned addr) +{ + if (breakinfo[breakno].enabled) { + return -1; + } + breakinfo[breakno].enabled = 1; + breakinfo[breakno].type = type; + breakinfo[breakno].len = len; + breakinfo[breakno].addr = addr; + return 0; +} + +extern void thread_return(void); + +static struct pt_regs *task_regs(struct task_struct *t) +{ + int k; + + for (k = 0; k < NR_CPUS; k++) { + if (t == interrupted_tasks[k]) { + Dearly_printk("task_regs: cpu %d, regs %p\n", + k, interrupted_regs[k]); + return interrupted_regs[k]; + } + } + + extern void ret_from_fork(void); + if (t->thread.rip != (unsigned long)ret_from_fork) { + static struct pt_regs regs; + struct save_context_frame *frame = (void *)t->thread.rsp; + memset(®s, 0, sizeof(struct pt_regs)); + Dearly_printk("task_regs: rsp %lx\n", t->thread.rsp); +#define C(r) __get_user(regs.r, &frame->r) + C(rbx); + C(rcx); + C(rdx); + C(rbp); + C(rdi); + C(rsi); + C(r8); + C(r9); + C(r10); + C(r11); + C(r12); + C(r13); + C(r14); + C(r15); +#undef C + regs.rax = -1UL; + regs.rsp = (unsigned long)(frame + 1); + regs.cs = __KERNEL_CS; + regs.ss = __KERNEL_DS; + regs.rip = (unsigned long)thread_return; + regs.orig_rax = -1; + regs.eflags = 0; + return ®s; + } + /* rip == ret_from_fork could happen, but there is no sensible + way to get registers there */ + + Dearly_printk("no registers for task %s\n", t->comm); + + return NULL; +} + +void gdb_wait(struct pt_regs *regs) +{ + unsigned long flags; + int processor; + + local_irq_save(flags); + processor = hard_smp_processor_id(); + procindebug[processor] = 1; + + /* XXX: if the CPU was in interrupt stack you cannot find the + per process backtrace currently */ + interrupted_tasks[processor] = current; + interrupted_regs[processor] = regs; + + /* Wait till master processor goes completely into the debugger */ + while (!procindebug[kgdb_lock - 1]) { + int i = 10; /* an arbitrary number */ + + while (--i) + asm volatile ("rep ; nop": : : "memory"); + + } + + /* Wait till master processor is done with debugging */ + spin_lock(slavecpulocks + processor); + correct_hw_break(); + + interrupted_regs[processor] = NULL; + + /* Signal the master processor that we are done */ + procindebug[processor] = 0; + spin_unlock(slavecpulocks + processor); + local_irq_restore(flags); +} + +void +printexceptioninfo( + int exceptionNo, + int errorcode, + char *buffer) +{ + unsigned long dr6; + int i; + switch (exceptionNo) { + case 1: /* debug exception */ + break; + case 3: /* breakpoint */ + sprintf(buffer, "Software breakpoint"); + return; + default: + sprintf(buffer, "Details not available"); + return; + } + asm volatile ("movq %%db6, %0\n" + : "=r" (dr6) + : ); + if (dr6 & 0x4000) { + sprintf(buffer, "Single step"); + return; + } + for (i = 0; i < 4; ++i) { + if (dr6 & (1 << i)) { + sprintf(buffer, "Hardware breakpoint %d", i); + return; + } + } + sprintf(buffer, "Unknown trap"); + return; +} + +/* + * This function does all command processing for interfacing to gdb. + */ +int handle_exception(int exceptionVector, + int signo, + int err_code, + struct pt_regs *linux_regs) +{ + struct task_struct *usethread = NULL; + long addr, length; + long breakno, breaktype; + char *ptr; + long newPC; + unsigned long flags; + long gdb_regs[NUMREGBYTES/8]; + int i; + long dr6; + unsigned procid; + int old_nmi_state; + struct pt_regs *origregs = linux_regs; + unsigned long oldgs; + int exit_code = 0; + struct task_struct *cur_thread = NULL; /* for walking the task list */ + + /* should do an broadcast NMI to stop all cpus */ + + Dearly_printk("gdbstub: ex:%d sig:%x err:%d rip:%lx rsp:%lx\n",exceptionVector,signo,err_code, linux_regs->rip, linux_regs->rsp); + +#define regs (*linux_regs) + + /* + * If the entry is not from the kernel then return to the Linux + * trap handler and let it process the interrupt normally. + */ + if (3 & linux_regs->cs) { + return(0); + } + + old_nmi_state = nmi_watchdog_disabled; + nmi_watchdog_disabled = 1; + + { + struct x8664_pda *pda = cpu_pda + hard_smp_processor_id(); + rdmsrl(MSR_GS_BASE, oldgs); + if (oldgs != (unsigned long)pda) { + /* can happen when you e.g. set a breakpoint on + system_call. the stub needs correct gs to run. */ + wrmsrl(MSR_GS_BASE, pda); + } + } + + /* Hold kgdb_lock */ + procid = hard_smp_processor_id(); + while (cmpxchg(&kgdb_lock, 0, (procid + 1)) != 0) { + int i = 25; /* an arbitrary number */ + + while (--i) + asm volatile ("rep ; nop": : : "memory"); + } + local_irq_save(flags); + + /* Disable hardware debugging while we are in kgdb */ + __asm__("movq %0,%%db7" + : /* no output */ + : "r" (0UL)); + + for (i = 0; i < smp_num_cpus; i++) { + spin_lock(&slavecpulocks[i]); + } + + /* spin_lock code is good enough as a barrier so we don't + * need one here */ + procindebug[procid] = 1; + + interrupted_tasks[procid] = current; + interrupted_regs[procid] = linux_regs; + + /* Master processor is completely in the debugger */ + + gdb_x8664vector = exceptionVector; + gdb_x8664errcode = err_code ; + + /* NULL usethread means current interrupt/exception stack */ + { + struct pt_regs *nr; + nr = in_interrupt_stack(regs.rsp,procid); + if (nr) { + struct pt_regs *nr2 = in_exception_stack(nr->rsp, procid); + if (!nr2) + nr2 = nr; + interrupted_regs[procid] = nr2; + } else if ((nr = in_exception_stack(regs.rsp, procid))) { + struct pt_regs *nr2 = in_interrupt_stack(nr->rsp, procid); + if (!nr2) + nr2 = nr; + interrupted_regs[procid] = nr2; + } else { + usethread = NULL; + } + } + + /* reply to host that an exception has occurred */ + remcomOutBuffer[0] = 'S'; + remcomOutBuffer[1] = hexchars[signo >> 4]; + remcomOutBuffer[2] = hexchars[signo % 16]; + remcomOutBuffer[3] = 0; + + putpacket(remcomOutBuffer); + + while (1==1) { + error = 0; + remcomOutBuffer[0] = 0; + getpacket(remcomInBuffer); + + //Dearly_printk("command %x\n", remcomInBuffer[0]); + + switch (remcomInBuffer[0]) { + case '?' : + remcomOutBuffer[0] = 'S'; + remcomOutBuffer[1] = hexchars[signo >> 4]; + remcomOutBuffer[2] = hexchars[signo % 16]; + remcomOutBuffer[3] = 0; + break; + case 'g' : /* return the value of the CPU registers */ + /* RED-PEN: need to handle exception/interrupt stacks + of other CPUs */ + Dearly_printk("usethread %p\n", usethread); + if (!usethread) { + regs_to_gdb_regs(gdb_regs, ®s); + } else { + struct pt_regs *nr; + char dummy; + memset(gdb_regs, 0, NUMREGBYTES); + nr = task_regs(usethread); + if (nr) { + if (!__get_user(dummy, (char *)nr)) + regs_to_gdb_regs(gdb_regs, nr); + } else { + gdb_regs[_PC] = (unsigned long)kgdb_usercode; + } + } + mem2hex((char*) gdb_regs, remcomOutBuffer, NUMREGBYTES); + break; + case 'G' : /* set the value of the CPU registers - return OK */ + hex2mem(&remcomInBuffer[1], (char*) gdb_regs, NUMREGBYTES); + if (!usethread || usethread == current) { + gdb_regs_to_regs(gdb_regs, ®s) ; + strcpy(remcomOutBuffer,"OK"); + } else { + strcpy(remcomOutBuffer,"E00"); + } + break; + + /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ + case 'm' : + /* TRY TO READ %x,%x. IF SUCCEED, SET PTR = 0 */ + ptr = &remcomInBuffer[1]; + if (hexToLong(&ptr,&addr)) + if (*(ptr++) == ',') + if (hexToLong(&ptr,&length)) + { + ptr = 0; + mem2hex((char*) addr, remcomOutBuffer, length); + } + + if (ptr) + { + strcpy(remcomOutBuffer,"E01"); + } + break; + + /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */ + case 'M' : + /* TRY TO READ '%x,%x:'. IF SUCCEED, SET PTR = 0 */ + ptr = &remcomInBuffer[1]; + if (hexToLong(&ptr,&addr)) + if (*(ptr++) == ',') + if (hexToLong(&ptr,&length)) + if (*(ptr++) == ':') + { + hex2mem(ptr, (char*) addr, length); + + strcpy(remcomOutBuffer,"OK"); + + ptr = 0; + } + if (ptr) + { + strcpy(remcomOutBuffer,"E02"); + } + break; + + /* cAA..AA Continue at address AA..AA(optional) */ + /* sAA..AA Step one instruction from AA..AA(optional) */ + case 'c' : + case 's' : + + /* try to read optional parameter, pc unchanged if no parm */ + ptr = &remcomInBuffer[1]; + if (hexToLong(&ptr,&addr)) + { + regs.rip = addr; + } + + newPC = regs.rip ; + + /* clear the trace bit */ + regs.eflags &= ~0x100UL; + + /* set the trace bit if we're stepping */ + if (remcomInBuffer[0] == 's') regs.eflags |= 0x100; + + asm volatile ("movq %%db6, %0\n" + : "=r" (dr6) + : ); + if (!(dr6 & 0x4000)) { + for (breakno = 0; breakno < 4; ++breakno) { + if (dr6 & (1 << breakno)) { + if (breakinfo[breakno].type == 0) { + /* Set restore flag */ + regs.eflags |= 0x10000; + break; + } + } + } + } + correct_hw_break(); + asm volatile ( + "movq %0, %%db6\n" + : + : "r" (0UL) ); + + procindebug[smp_processor_id()] = 0; + + /* Signal the slave processors to quit from + * the debugger */ + for (i = 0; i < smp_num_cpus; i++) { + spin_unlock(&slavecpulocks[i]); + } + + /* Wait till all the processors have quit + * from the debugger */ + + for (i = 0; i < smp_num_cpus; i++) { + while (procindebug[i]) { + int j = 10; /* an arbitrary number */ + + while (--j) { + asm volatile ("rep ; nop" + : : : "memory"); + } + } + } + + exit_code = 1; + /* FALL THROUGH */ + case 'k': + /* Release kgdb_lock */ + asm volatile ("movb $0,%0" : "=m" (kgdb_lock) : : "memory"); + + local_irq_restore(flags); + + nmi_watchdog_disabled = old_nmi_state; + wrmsrl(MSR_GS_BASE, oldgs); + return exit_code; + + /* query */ + case 'q' : + switch (remcomInBuffer[1]) { + case 'C': { /* current thread id */ + pid_t pid; + int len; + remcomOutBuffer[0] = 'q'; + remcomOutBuffer[1] = 'C'; + if (in_interrupt_stack(regs.rsp,procid)) + pid = 0x9001; + else if (in_exception_stack(regs.rsp,procid)) + pid = 0x9002; + else + pid = current->pid; + len = pid_to_hex(pid, remcomOutBuffer+2); + remcomOutBuffer[1+len] = 0; + break; + } + case 's': + case 'f': { /* list threads */ + int i; + char *p; + + remcomOutBuffer[0] = 'm'; + if (thread_list_busy() || + (remcomInBuffer[1] == 's' && !cur_thread)) { + strcpy(remcomOutBuffer, "l"); + break; + } + if (remcomInBuffer[1] == 'f') + cur_thread = &init_task; + p = remcomOutBuffer + 1; + if (cur_thread == &init_task) { + if (remcomInBuffer[1] != 'f') { + strcpy(remcomOutBuffer,"l"); + break; + } + /* dump pseudo tasks */ + p += pid_to_hex(0x9001, p); + *p++ = ','; + p += pid_to_hex(0x9002, p); + *p++ = ','; + } + for (i = 0; i < 32; i++) { + pid_t pid; + struct task_struct *next; + + if (__get_user(pid, &cur_thread->pid)) { + cur_thread = NULL; + break; + } + p += pid_to_hex(pid, p); + *p++ = ','; + if (__get_user(next, &cur_thread->next_task)) { + cur_thread = NULL; + break; + } + cur_thread = next; + if (cur_thread == &init_task) { + cur_thread = NULL; + break; + } + } + *--p = 0; + break; + } + + case 'q': { /* extra thread info */ + pid_t pid; + if (remcomInBuffer[2] != ',') + break; + pid = hex_to_pid(remcomInBuffer + 3); + encode_thread(pid, remcomOutBuffer); + break; + } + case 'E': + /* Print exception info */ + printexceptioninfo(exceptionVector, err_code, remcomOutBuffer); + break; + } + break; + + /* task related */ + case 'H' : + switch (remcomInBuffer[1]) { + pid_t pid; + case 'g': + pid = hex_to_pid(remcomInBuffer + 2); + + Dearly_printk("pid %u(%x)\n", pid, pid); + + /* nested exception stacks are not handled + correctly, but they should be rather + unlikely. */ + switch (pid) { + struct pt_regs *nr; + struct task_struct *thread; + + /* interrupt stack */ + case 0x9001: + Dearly_printk("int stack\n"); + + if (in_interrupt_stack(origregs->rsp,procid)) + linux_regs = origregs; + else if ((nr = in_exception_stack(origregs->rsp, procid)) != NULL) + linux_regs = nr; + else + goto bad; + usethread = NULL; + break; + /* exception stack */ + case 0x9002: + Dearly_printk("excpt stack\n"); + + nr = in_interrupt_stack(origregs->rsp,procid); + if (nr && in_exception_stack(nr->rsp, procid)) + linux_regs = nr; + else if (in_exception_stack(origregs->rsp,procid)) + linux_regs = origregs; + else + goto bad; + usethread = NULL; + break; + + case 0: /* any */ + case -1: /* all */ + case 0xffff: /* paranoia */ + usethread = NULL; + break; + + default: + thread = lookup_thread(pid); + if (!thread) { + bad: + remcomOutBuffer[0] = 'E'; + remcomOutBuffer[1] = '\0'; + goto reply; + } + usethread = thread; + Dearly_printk("thread %p\n", thread); + break; + } + /* FALL THROUGH */ + case 'c': + remcomOutBuffer[0] = 'O'; + remcomOutBuffer[1] = 'K'; + remcomOutBuffer[2] = '\0'; + break; + } + break; + + /* Query thread status */ + case 'T': { + pid_t pid; + ptr = &remcomInBuffer[1]; + pid = hex_to_pid(remcomInBuffer + 1); + if (pid == 0x9001 || pid == 0x9002 || lookup_thread(pid)) { + remcomOutBuffer[0] = 'O'; + remcomOutBuffer[1] = 'K'; + remcomOutBuffer[2] = '\0'; + } else { + remcomOutBuffer[0] = 'E'; + remcomOutBuffer[1] = '\0'; + } + break; + } + + case 'Y': + ptr = &remcomInBuffer[1]; + hexToLong(&ptr, &breakno); + ptr++; + hexToLong(&ptr, &breaktype); + ptr++; + hexToLong(&ptr, &length); + ptr++; + hexToLong(&ptr, &addr); + if (set_hw_break(breakno & 0x3, breaktype & 0x3 , length & 0x3, addr) + == 0) { + strcpy(remcomOutBuffer, "OK"); + } else { + strcpy(remcomOutBuffer, "ERROR"); + } + break; + + /* Remove hardware breakpoint */ + case 'y': + ptr = &remcomInBuffer[1]; + hexToLong(&ptr, &breakno); + if (remove_hw_break(breakno & 0x3) == 0) { + strcpy(remcomOutBuffer, "OK"); + } else { + strcpy(remcomOutBuffer, "ERROR"); + } + break; + + } /* switch */ + + reply: + /* reply to the request */ + putpacket(remcomOutBuffer); + } +} + +#undef regs +static int kgdb_notify(struct notifier_block *self, unsigned long cmd, void *ptr) +{ + struct die_args *d = ptr; + +// Dearly_printk("notify %s %ld\n", d->str, cmd); + + if (user_mode(d->regs)) + return NOTIFY_DONE; + switch (cmd) { + case DIE_NMI: + if (kgdb_lock) { + int cpu = hard_smp_processor_id(); + + if (!procindebug[cpu] && kgdb_lock != (cpu + 1)) { + gdb_wait(d->regs); + } + return NOTIFY_BAD; + } + break; + } + + if (handle_exception(d->trapnr, d->signr, d->err, d->regs)) + return NOTIFY_BAD; /* skip */ + + return NOTIFY_DONE; +} + +static struct notifier_block kgdb_notifier = { + .notifier_call = kgdb_notify, + .priority = 0, +}; + +/* this function is used to set up exception handlers for tracing and + breakpoints */ +void set_debug_traps(void) +{ + notifier_chain_register(&die_chain, &kgdb_notifier); + + /* + * In case GDB is started before us, ack any packets (presumably + * "$?#xx") sitting there. */ + putDebugChar ('+'); + + initialized = 1; +} + +/* This function will generate a breakpoint exception. It is used at the + beginning of a program to sync up with a debugger and can be used + otherwise as a quick means to stop program execution and "break" into + the debugger. */ + +void breakpoint(void) +{ + if (initialized) + BREAKPOINT(); +} + +#ifdef CONFIG_GDB_CONSOLE +char gdbconbuf[BUFMAX]; + +void gdb_console_write(struct console *co, const char *s, + unsigned count) +{ + int i; + int wcount; + char *bufptr; + + if (!gdb_initialized) { + return; + } + gdbconbuf[0] = 'O'; + bufptr = gdbconbuf + 1; + while (count > 0) { + if ((count << 1) > (BUFMAX - 2)) { + wcount = (BUFMAX - 2) >> 1; + } else { + wcount = count; + } + count -= wcount; + for (i = 0; i < wcount; i++) { + *bufptr++ = hexchars[(s[i] >> 4) & 0xf]; + *bufptr++ = hexchars[s[i] & 0xf]; + } + *bufptr = '\0'; + s += wcount; + + putpacket(gdbconbuf); + + } +} +#endif +static int __init kgdb_opt_gdb(char *str) +{ + gdb_enter = 1; + return 1; +} +static int __init kgdb_opt_gdbttyS(char *str) +{ + gdb_ttyS = simple_strtoul(str,NULL,10); + return 1; +} +static int __init kgdb_opt_gdbbaud(char *str) +{ + gdb_baud = simple_strtoul(str,NULL,10); + return 1; +} + +/* + * Sequence of these lines has to be maintained because gdb option is a prefix + * of the other two options + */ + +__setup("gdbttyS=", kgdb_opt_gdbttyS); +__setup("gdbbaud=", kgdb_opt_gdbbaud); +__setup("gdb", kgdb_opt_gdb); diff -u linux/arch/x86_64/kernel/Makefile-GDB linux/arch/x86_64/kernel/Makefile --- linux/arch/x86_64/kernel/Makefile-GDB 2003-06-10 14:39:32.000000000 +0200 +++ linux/arch/x86_64/kernel/Makefile 2003-06-10 14:39:39.000000000 +0200 @@ -27,6 +27,7 @@ obj-y += pci-pc.o pci-irq.o endif +obj-$(CONFIG_X86_REMOTE_DEBUG) += gdbstub.o obj-$(CONFIG_MTRR) += mtrr.o obj-$(CONFIG_X86_MSR) += msr.o obj-$(CONFIG_X86_CPUID) += cpuid.o diff -u linux/arch/x86_64/config.in-GDB linux/arch/x86_64/config.in --- linux/arch/x86_64/config.in-GDB 2003-06-10 14:39:32.000000000 +0200 +++ linux/arch/x86_64/config.in 2003-06-10 14:39:39.000000000 +0200 @@ -240,6 +240,12 @@ bool ' Spinlock debugging' CONFIG_DEBUG_SPINLOCK bool ' Additional run-time checks' CONFIG_CHECKING bool ' Debug __init statements' CONFIG_INIT_DEBUG + bool 'KGDB: Remote (serial) kernel debugging with gdb' CONFIG_X86_REMOTE_DEBUG + if [ "$CONFIG_X86_REMOTE_DEBUG" != "n" ]; then + #bool 'KGDB: Thread analysis' CONFIG_KGDB_THREAD + bool 'KGDB: Console messages through gdb' CONFIG_GDB_CONSOLE + #bool 'KGDB: Enable kernel asserts' CONFIG_KERNEL_ASSERTS + fi bool ' IOMMU debugging' CONFIG_IOMMU_DEBUG bool ' IOMMU leak tracing' CONFIG_IOMMU_LEAK bool ' Probalistic stack overflow check' CONFIG_DEBUG_STACKOVERFLOW diff -u linux/include/asm-x86_64/ioctls.h-GDB linux/include/asm-x86_64/ioctls.h --- linux/include/asm-x86_64/ioctls.h-GDB 2003-06-10 14:39:32.000000000 +0200 +++ linux/include/asm-x86_64/ioctls.h 2003-06-10 14:39:39.000000000 +0200 @@ -51,6 +51,8 @@ #define TIOCSPTLCK _IOW('T',0x31, int) /* Lock/unlock Pty */ #define TIOCGDEV _IOR('T',0x32, unsigned int) +#define TIOCGDB 0x547F /* enable GDB stub mode on this tty */ + #define FIONCLEX 0x5450 /* these numbers need to be adjusted. */ #define FIOCLEX 0x5451 #define FIOASYNC 0x5452