diff -Nru linux-2.4.6-dist/Makefile linux-2.4.6-kgdb/Makefile --- linux-2.4.6-dist/Makefile Wed Jul 25 13:53:08 2001 +++ linux-2.4.6-kgdb/Makefile Wed Jul 25 15:14:17 2001 @@ -87,8 +87,13 @@ CPPFLAGS := -D__KERNEL__ -I$(HPATH) +ifdef CONFIG_REMOTE_DEBUG +CFLAGS := $(CPPFLAGS) -Wall -Wstrict-prototypes -Wno-trigraphs -O2 -g +else CFLAGS := $(CPPFLAGS) -Wall -Wstrict-prototypes -Wno-trigraphs -O2 \ -fomit-frame-pointer -fno-strict-aliasing +endif + AFLAGS := -D__ASSEMBLY__ $(CPPFLAGS) # diff -Nru linux-2.4.6-dist/arch/i386/config.in linux-2.4.6-kgdb/arch/i386/config.in --- linux-2.4.6-dist/arch/i386/config.in Wed Jul 25 13:53:08 2001 +++ linux-2.4.6-kgdb/arch/i386/config.in Wed Jul 25 14:00:34 2001 @@ -388,4 +388,5 @@ #bool 'Debug kmalloc/kfree' CONFIG_DEBUG_MALLOC bool 'Magic SysRq key' CONFIG_MAGIC_SYSRQ +bool 'Remote kernel debugger (gdb)' CONFIG_REMOTE_DEBUG endmenu diff -Nru linux-2.4.6-dist/arch/i386/kernel/Makefile linux-2.4.6-kgdb/arch/i386/kernel/Makefile --- linux-2.4.6-dist/arch/i386/kernel/Makefile Fri Dec 29 17:35:47 2000 +++ linux-2.4.6-kgdb/arch/i386/kernel/Makefile Wed Jul 25 14:00:34 2001 @@ -40,5 +40,6 @@ obj-$(CONFIG_X86_LOCAL_APIC) += apic.o obj-$(CONFIG_X86_IO_APIC) += io_apic.o mpparse.o obj-$(CONFIG_X86_VISWS_APIC) += visws_apic.o +obj-$(CONFIG_REMOTE_DEBUG) += i386-stub.o include $(TOPDIR)/Rules.make diff -Nru linux-2.4.6-dist/arch/i386/kernel/entry.S linux-2.4.6-kgdb/arch/i386/kernel/entry.S --- linux-2.4.6-dist/arch/i386/kernel/entry.S Wed Jul 25 13:53:08 2001 +++ linux-2.4.6-kgdb/arch/i386/kernel/entry.S Wed Aug 1 18:43:36 2001 @@ -64,6 +64,7 @@ OLDSS = 0x38 CF_MASK = 0x00000001 +TF_MASK = 0x00000100 IF_MASK = 0x00000200 NT_MASK = 0x00004000 VM_MASK = 0x00020000 @@ -317,25 +318,128 @@ addl $4,%esp jmp ret_from_exception +ENTRY(nmi) + pushl %eax + SAVE_ALL + movl %esp,%edx + pushl $0 + pushl %edx + call SYMBOL_NAME(do_nmi) + addl $8,%esp + RESTORE_ALL + +#ifdef CONFIG_REMOTE_DEBUG + +#define DEBUGGER_SAVE_REGISTERS \ + movl %eax, SYMBOL_NAME(kgdb_registers) \ + movl %ecx, SYMBOL_NAME(kgdb_registers)+4 \ + movl %edx, SYMBOL_NAME(kgdb_registers)+8 \ + movl %ebx, SYMBOL_NAME(kgdb_registers)+12 \ + movl %ebp, SYMBOL_NAME(kgdb_registers)+20 \ + movl %esi, SYMBOL_NAME(kgdb_registers)+24 \ + movl %edi, SYMBOL_NAME(kgdb_registers)+28 \ + movw $0, %ax \ + movw %ds, SYMBOL_NAME(kgdb_registers)+48 \ + movw %ax, SYMBOL_NAME(kgdb_registers)+50 \ + movw %es, SYMBOL_NAME(kgdb_registers)+52 \ + movw %ax, SYMBOL_NAME(kgdb_registers)+54 \ + movw %fs, SYMBOL_NAME(kgdb_registers)+56 \ + movw %ax, SYMBOL_NAME(kgdb_registers)+58 \ + movw %gs, SYMBOL_NAME(kgdb_registers)+60 \ + movw %ax, SYMBOL_NAME(kgdb_registers)+62 + + + +/* If we get an breakpoint interrupt in kernel space, we want to be as + * lean as possible. On the other hand, a breakpoint in user space + * requires error_code and all its friends, since we might have to + * stop the process, signal the debugger, and reschedule. So we check + * the low 2 bits of the saved CS register here to figure out if we're + * in kernel space. If they're zero, call the kernel debugger; + * otherwise do a normal int 3. + */ + +ENTRY(int3) + pushl $0 + pushl %eax + movl 12(%esp),%eax + andl $3,%eax + popl %eax + jnz user_int3 + cmpl $0, kgdb_initialized + jz user_int3 + SAVE_ALL + movl %esp,%edx + pushl %edx + pushl $5 /* 5 = SIGTRAP */ + call SYMBOL_NAME(handle_exception) + addl $8,%esp + RESTORE_ALL + +user_int3: + pushl $ SYMBOL_NAME(do_int3) + jmp error_code + +/* Same rational for interrupt 1; the debug (single step) fault. + * If kgdb_stepping isn't set, then it's a spurious trap, so + * ignore it, i.e, if we single stepped through a save_flags(), + * then we'll get a trap later on after restore_flags() + */ + + ENTRY(debug) + pushl %eax + movl 8(%esp),%eax + andl $3,%eax + popl %eax + jnz user_debug + cmpl $0, kgdb_stepping + jz spurious_debug + pushl $0 + SAVE_ALL + movl %esp,%edx + pushl %edx + pushl $5 /* 5 = SIGTRAP */ + call SYMBOL_NAME(handle_exception) + addl $8,%esp + RESTORE_ALL + +spurious_debug: + andl $~TF_MASK, 8(%esp) /* Clear trace flag */ + iret + +user_debug: pushl $0 pushl $ SYMBOL_NAME(do_debug) jmp error_code -ENTRY(nmi) - pushl %eax +/* When the kernel debugger is active, it installs this routine as the + * page fault handler, to trap illegal memory accesses. Since it's + * never used except while the kernel is halted and the debugger is + * active, it's very simple - just hand off to the C routine. + */ + +ENTRY(debugger_fault) SAVE_ALL movl %esp,%edx pushl $0 pushl %edx - call SYMBOL_NAME(do_nmi) + call SYMBOL_NAME(do_debugger_fault) addl $8,%esp RESTORE_ALL +#else ENTRY(int3) pushl $0 pushl $ SYMBOL_NAME(do_int3) jmp error_code + +ENTRY(debug) + pushl $0 + pushl $ SYMBOL_NAME(do_debug) + jmp error_code + +#endif ENTRY(overflow) pushl $0 diff -Nru linux-2.4.6-dist/arch/i386/kernel/i386-stub.c linux-2.4.6-kgdb/arch/i386/kernel/i386-stub.c --- linux-2.4.6-dist/arch/i386/kernel/i386-stub.c Wed Dec 31 19:00:00 1969 +++ linux-2.4.6-kgdb/arch/i386/kernel/i386-stub.c Wed Aug 1 20:30:12 2001 @@ -0,0 +1,622 @@ +/**************************************************************************** + + THIS SOFTWARE IS NOT COPYRIGHTED + + HP offers the following for use in the public domain. HP makes no + warranty with regard to the software or it's performance and the + user accepts the software "AS IS" with all faults. + + HP DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD + TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +****************************************************************************/ + +/**************************************************************************** + * 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 $ + * ModuleState: Experimental $ + * + * NOTES: See Below $ + * + * Modified for 386 by Jim Kingdon, Cygnus Support. + * + * Modified for Linux i386 kernel by Brent Baccala + * + * The basic idea is to attach two machines together via a null modem + * cable between their serial ports. The debugging machine should have + * the linux kernel source tree and vmlinux object file. The machine + * under test should boot the same version of the kernel (possibily + * by copying bzImage to it from the debugging machine). The debugging + * machine runs gdb (unmodified) on the linux kernel and controls the + * machine under test via the serial connection, the gdb remote protocol, + * and this code (in the kernel of the machine under test). + * + * To enable debugger support, two things need to happen. One, any + * breakpoints or error conditions need to be properly intercepted + * and reported to gdb via handle_exception(), which is called from + * int1/int3 handlers (in entry.S), do_page_fault (in fault.c), and + * do_trap() (in traps.c) on any kernel fault if remote debugging has + * been enabled. 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 + * INT3. + * + * The INT 3 (breakpoint) and INT 1 (debug) handlers should install + * as interrupt gates so that interrupts are masked while the handler + * runs. + * + * Because gdb will sometimes write to the stack area to execute function + * calls, this program cannot rely on using the supervisor stack. + * + * CAVEAT: This is currently broken; we use the supervisor stack, + * so the gdb "call" command will trash the kernel. Don't use it. + * + * CAVEAT: No smp support at this time + * + * CAVEAT: Hardware watchpoints could be done, but presently aren't + * supported by either this code or the gdb i386 remote backend. + * + ************* + * TEST PROCEDURE FOR THIS CODE + * + * 1. boot kernel, specifying "kgdb" as a kernel argument + * 2. kernel should halt almost immediately after loading, and send + * an "$S05#84" out the serial port at 9600N8. Should be visible + * with a standard serial program. Tests kernel INT3 handler. + * 3. run "gdb vmlinux" on remote machine from the linux source dir + * 4. "target remote /dev/cua0" (or whatever the serial device is) + * 5. debugger should show a breakpoint in function breakpoint() + * Tests basic debugger/kernel serial interface + * 6. "info reg" - tests debuggers ability to read processor registers + * 7. "where" - shows stack trace and tests memory fault handler + * (the "Cannot access memory" message is normal) + * 8. "break blk_get_queue" + * 9. "cont" - tests debugger's ability to set a breakpoint, restart + * kernel, and get control back a few seconds later + * 10. "next" - tests kernel single step trap, and single steps past a + * save_flags() in blk_get_queue, thus pushing the flags register + * with the TF bit set + * 11. "dis 1" - disable the breakpoint + * 12. "cont" - tests spurious single step trap handling, since the + * saved flags registers will generate a spurious trap when it + * is popped and restored. Kernel should _not_ halt; there should + * be no visible indication of the spurious trap + * 13. wait for kernel to finish booting + * 14. hit CNTL-C on debugger + * 15. kernel should halt and give control to debugger. Tests ability + * to grab control via a serial interrupt. "continue" + * 16. on the machine under test, run ordinary gdb on some ordinary program + * 17. "break main" and "run" (ordinary gdb) + * 18. should breakpoint normally, without a breakpoint on the kernel + * debugger. Tests user space INT 3 handling + * 19. "next" (ordinary gdb) + * 20. should step normally, without affecting the kernel debugger. + * Tests user space single step trap handling + * + ************* + * + * 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 + +/************************************************************************ + * + * external low-level support routines + */ + +extern void putDebugChar(char); /* write a single character */ +extern char getDebugChar(void); /* read and return a single char */ +extern void kgdb_interruptible(int); /* true if recv data triggers breakpt */ + +/************************************************************************/ +/* BUFMAX defines the maximum number of characters in inbound/outbound buffers*/ +/* at least NUMREGBYTES*2 are needed for register packets */ +#define BUFMAX 400 + +char kgdb_initialized = 0; /* TRUE = we've been initialized */ +int kgdb_active = 0; /* TRUE = we're in the exception handler */ +int kgdb_stepping = 0; /* TRUE = we're single-stepping the kernel*/ + +static const char hexchars[]="0123456789abcdef"; + +/* Number of registers. */ +#define NUMREGS 16 + +/* Number of bytes of registers. */ +#define NUMREGBYTES (NUMREGS * 4) + +/* This is the register order that GDB uses - it is _not_ the order + * that Linux uses (struct pt_regs), so we need to be careful + */ + +enum regnames {GDB_EAX, GDB_ECX, GDB_EDX, GDB_EBX, + GDB_ESP, GDB_EBP, GDB_ESI, GDB_EDI, + GDB_PC /* also known as eip */, + GDB_PS /* also known as eflags */, + GDB_CS, GDB_SS, GDB_DS, GDB_ES, GDB_FS, GDB_GS}; + +static int registers[NUMREGS]; + +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); +} + +static char remcomInBuffer[BUFMAX]; +static char remcomOutBuffer[BUFMAX]; + +/* scan for the sequence $# */ + +unsigned char * +getpacket (void) +{ + unsigned char *buffer = &remcomInBuffer[0]; + unsigned char checksum; + unsigned char xmitcsum; + int count; + char ch; + + while (1) + { + /* wait around for the start character, ignore all other characters */ + while ((ch = getDebugChar ()) != '$') + ; + +retry: + checksum = 0; + xmitcsum = -1; + count = 0; + + /* now, read until a # or end of buffer is found */ + while (count < BUFMAX) + { + ch = getDebugChar (); + if (ch == '$') + goto retry; + if (ch == '#') + break; + checksum = checksum + ch; + buffer[count] = ch; + count = count + 1; + } + buffer[count] = 0; + + if (ch == '#') + { + ch = getDebugChar (); + xmitcsum = hex (ch) << 4; + ch = getDebugChar (); + xmitcsum += hex (ch); + + 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]); + + return &buffer[3]; + } + + return &buffer[0]; + } + } + } +} + +/* 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])) { + putDebugChar(ch); + checksum += ch; + count += 1; + } + + putDebugChar('#'); + putDebugChar(hexchars[checksum >> 4]); + putDebugChar(hexchars[checksum % 16]); + + } while (getDebugChar() != '+'); + +} + +/* Get/set a byte in memory, with error indication on page faults + * + * I originally tried using the exception table mechanism for this + * code, but the standard page fault handler is too heavy weight - it + * grabs semaphores, spinlocks, and such. Since the kernel is halted + * when this code runs, the only page faults we should get are our + * own, so I just install my own custom page fault handler, and + * restore the system handler when the kernel restarts. The assembler + * statements are hard coded so I know exactly what the critical + * instructions are. Each is a two-byte move that I just step past in + * the fault handler. + * + * debugger_fault and page_fault are defined in arch/i386/kernel/entry.S + * They are the fault handlers registered in the processor's IDT. They + * save the registers onto the stack and set up a C call frame before + * calling do_page_fault (the normal fault routine) or do_debugger_fault. + */ + +static volatile int mem_err = 0; + +unsigned char get_char (unsigned char *addr) +{ + int val = 0; + + __asm__("movb (%1), %%al" + : "=a" (val) : "r" (addr)); + + return val; +} + +void set_char (unsigned char *addr, unsigned char val) +{ + __asm__("movb %%al, (%1)" + : : "a" (val), "r" (addr)); +} + +asmlinkage void debugger_fault(void); +asmlinkage void page_fault(void); + +asmlinkage void do_debugger_fault(struct pt_regs *regs, long error_code) +{ + mem_err = 1; + regs->eip += 2; +} + +/* convert the memory pointed to by mem into hex, placing result in buf */ +/* return a pointer to the last char put in buf (null) */ +/* If MAY_FAULT is non-zero, then we should set mem_err in response to + a fault; if zero treat a fault like any other fault in the stub. */ +char* mem2hex(char *mem, char *buf, int count, int may_fault) +{ + 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 may_fault) +{ + int i; + unsigned char ch; + + for (i=0;i=0) + { + *intValue = (*intValue <<4) | hexValue; + numChars ++; + } + else + break; + + (*ptr)++; + } + + return (numChars); +} + +/* + * This function does all command procesing for interfacing to gdb. + */ + +void handle_exception(int sigval, struct pt_regs *regs) +{ + int addr, length; + char * ptr; + int newPC; + + if (kgdb_active) { + printk("interrupt while in kgdb, returning\n"); + return; + } + kgdb_active = 1; + + kgdb_interruptible(0); + set_intr_gate(14,&debugger_fault); + /* lock_kernel(); */ + + registers[GDB_EAX] = regs->eax; + registers[GDB_EBX] = regs->ebx; + registers[GDB_ECX] = regs->ecx; + registers[GDB_EDX] = regs->edx; + registers[GDB_EBP] = regs->ebp; + registers[GDB_ESI] = regs->esi; + registers[GDB_EDI] = regs->edi; + registers[GDB_PC] = regs->eip; + registers[GDB_PS] = regs->eflags; + registers[GDB_CS] = regs->xcs; + registers[GDB_DS] = regs->xds; + registers[GDB_ES] = regs->xes; + + /* Kernel doesn't use FS or GS */ + registers[GDB_FS] = 0; + registers[GDB_GS] = 0; + + /* We came in through a trap gate from the same privilege level, so + * the CPU didn't change stacks and push ESP/SP like it would have + * for a trap from user space (different privilege level), so + * regs->esp and regs->xss are invalid. We figure out ESP based + * the address of the register frame (go past the register frame, + * then backup 8 bytes to account for ESP/SS that didn't get pushded), + * and SS we ignore because GDB doesn't use it. + */ + + registers[GDB_ESP] = (int)regs + sizeof(struct pt_regs) - 8; + registers[GDB_SS] = 0; + + /* reply to host that an exception has occurred */ + remcomOutBuffer[0] = 'S'; + remcomOutBuffer[1] = hexchars[sigval >> 4]; + remcomOutBuffer[2] = hexchars[sigval % 16]; + remcomOutBuffer[3] = 0; + + putpacket(remcomOutBuffer); + + kgdb_stepping = 0; + + while (1==1) { + remcomOutBuffer[0] = 0; + ptr = getpacket(); + + switch (*ptr++) { + case '?' : remcomOutBuffer[0] = 'S'; + remcomOutBuffer[1] = hexchars[sigval >> 4]; + remcomOutBuffer[2] = hexchars[sigval % 16]; + remcomOutBuffer[3] = 0; + break; + case 'd' : /* toggle debug flag */ + break; + case 'g' : /* return the value of the CPU registers */ + mem2hex((char*) registers, remcomOutBuffer, NUMREGBYTES, 0); + break; + case 'G' : /* set the value of the CPU registers - return OK */ + hex2mem(ptr, (char*) registers, NUMREGBYTES, 0); + strcpy(remcomOutBuffer,"OK"); + break; + case 'P' : /* set the value of a single CPU register - return OK */ + { + int regno; + + if (hexToInt (&ptr, ®no) && *ptr++ == '=') + if (regno >= 0 && regno < NUMREGS) + { + hex2mem (ptr, (char *)®isters[regno], 4, 0); + strcpy(remcomOutBuffer,"OK"); + break; + } + + strcpy (remcomOutBuffer, "E01"); + break; + } + + /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ + case 'm' : + /* TRY TO READ %x,%x. IF SUCCEED, SET PTR = 0 */ + if (hexToInt(&ptr,&addr)) + if (*(ptr++) == ',') + if (hexToInt(&ptr,&length)) + { + ptr = 0; + mem_err = 0; + mem2hex((char*) addr, remcomOutBuffer, length, 1); + if (mem_err) { + strcpy (remcomOutBuffer, "E03"); + } + } + + 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 */ + if (hexToInt(&ptr,&addr)) + if (*(ptr++) == ',') + if (hexToInt(&ptr,&length)) + if (*(ptr++) == ':') + { + mem_err = 0; + hex2mem(ptr, (char*) addr, length, 1); + + if (mem_err) { + strcpy (remcomOutBuffer, "E03"); + } else { + 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 's' : + kgdb_stepping = 1; + case 'c' : + /* try to read optional parameter, pc unchanged if no parm */ + if (hexToInt(&ptr,&addr)) + registers[ GDB_PC ] = addr; + + newPC = registers[ GDB_PC]; + + /* clear the trace bit */ + registers[ GDB_PS ] &= 0xfffffeff; + + /* set the trace bit if we're stepping */ + if (kgdb_stepping) registers[ GDB_PS ] |= 0x100; + + regs->eax = registers[GDB_EAX]; + regs->ebx = registers[GDB_EBX]; + regs->ecx = registers[GDB_ECX]; + regs->edx = registers[GDB_EDX]; + regs->ebp = registers[GDB_EBP]; + regs->esi = registers[GDB_ESI]; + regs->edi = registers[GDB_EDI]; + regs->eip = registers[GDB_PC]; + regs->eflags = registers[GDB_PS]; + regs->xcs = registers[GDB_CS]; + regs->xds = registers[GDB_DS]; + regs->xes = registers[GDB_ES]; + + /* We didn't restore FS, GS, or SS because we never saved them, + * nor did we restore ESP because we're using the same stack + * ourselves! + */ + + set_intr_gate(14,&page_fault); + kgdb_interruptible(1); + /* unlock_kernel(); */ + kgdb_active = 0; + + return; + + /* kill the program */ + case 'k' : + strcpy(remcomOutBuffer, "OK"); + putpacket(remcomOutBuffer); + machine_restart(NULL); + break; + + } /* switch */ + + /* reply to the request */ + putpacket(remcomOutBuffer); + } +} + +/* this function is used to set up exception handlers for tracing and + breakpoints */ +void set_debug_traps(void) +{ + kgdb_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 (kgdb_initialized) { + asm(" int $3"); + } +} diff -Nru linux-2.4.6-dist/arch/i386/kernel/setup.c linux-2.4.6-kgdb/arch/i386/kernel/setup.c --- linux-2.4.6-dist/arch/i386/kernel/setup.c Fri May 25 20:07:09 2001 +++ linux-2.4.6-kgdb/arch/i386/kernel/setup.c Wed Jul 25 14:00:34 2001 @@ -104,6 +104,12 @@ #include #include #include + +#ifdef CONFIG_REMOTE_DEBUG +extern int serial_kgdb_hook(int); +extern void set_debug_traps(void); +#endif + /* * Machine setup.. */ @@ -1016,6 +1022,19 @@ low_mem_size = ((max_low_pfn << PAGE_SHIFT) + 0xfffff) & ~0xfffff; if (low_mem_size > pci_mem_start) pci_mem_start = low_mem_size; + +#ifdef CONFIG_REMOTE_DEBUG + if (strstr(*cmdline_p, "kgdb") && (serial_kgdb_hook(0) == 0)) { + + /* We can't breakpoint yet because traps haven't been set up, + * but we'll just grab the UART (serial_kgdb_hook did that) + * and initialize. We'll breakpoint in traps.c as soon + * as it's ready. + */ + + set_debug_traps(); + } +#endif #ifdef CONFIG_VT #if defined(CONFIG_VGA_CONSOLE) diff -Nru linux-2.4.6-dist/arch/i386/kernel/traps.c linux-2.4.6-kgdb/arch/i386/kernel/traps.c --- linux-2.4.6-dist/arch/i386/kernel/traps.c Wed Jul 25 13:53:09 2001 +++ linux-2.4.6-kgdb/arch/i386/kernel/traps.c Tue Jul 31 00:48:24 2001 @@ -86,6 +86,17 @@ asmlinkage void spurious_interrupt_bug(void); asmlinkage void machine_check(void); +#ifdef CONFIG_REMOTE_DEBUG + +extern char kgdb_initialized; +extern int kgdb_active; +extern int kgdb_stepping; + +extern void handle_exception(int sigval, struct pt_regs *regs); +extern void breakpoint(void); + +#endif + int kstack_depth_to_print = 24; /* @@ -263,6 +274,16 @@ unsigned long fixup = search_exception_table(regs->eip); if (fixup) regs->eip = fixup; +#ifdef CONFIG_REMOTE_DEBUG + + /* This is not the main entry point for the remote debugger. + * That's in entry.S's int3 (breakpoint) handler. This code + * just drops us into the debugger on the odd kernel fault. + */ + + else if (kgdb_initialized) + handle_exception(signr, regs); +#endif else die(str, regs, error_code); return; @@ -432,6 +453,16 @@ */ int sum, cpu = smp_processor_id(); +#ifdef CONFIG_REMOTE_DEBUG + + /* If GDB is active, then we're sitting in a loop waiting for the + * remote debugger, so the CPU may appear hung. Skip the checks. + */ + + if (kgdb_active) return; + +#endif + sum = apic_timer_irqs[cpu]; if (last_irq_sums[cpu] == sum) { @@ -527,6 +558,25 @@ __asm__ __volatile__("movl %%db6,%0" : "=r" (condition)); +#ifdef CONFIG_REMOTE_DEBUG + + /* If we running GDB on the kernel, then check for a single + * step trap in kernel space and hand it off to the debugger. + * If kgdb_stepping isn't set, then it's a spurious trap, so + * ignore it, i.e, if we single stepped through a save_flags(), + * then we'll get a trap later on after restore_flags() + */ + + if ((condition & DR_STEP) && !(regs->xcs & 3)) { + if (kgdb_stepping) { + handle_exception(5, regs); /* 5 is SIGTRAP */ + return; + } else { + goto clear_TF; + } + } +#endif + /* Mask out spurious debug traps due to lazy DR7 setting */ if (condition & (DR_TRAP0|DR_TRAP1|DR_TRAP2|DR_TRAP3)) { if (!tsk->thread.debugreg[7]) @@ -817,21 +867,42 @@ * Pentium F0 0F bugfix can have resulted in the mapped * IDT being write-protected. */ + +/* Interrupt gate - masks off interrupts and can't be called from user space, + * only by a hardware interrupt, processor fault, or "INT n" instruction + * in kernel space (but that never happens) + */ + void set_intr_gate(unsigned int n, void *addr) { _set_gate(idt_table+n,14,0,addr); } +/* Trap gate - doesn't mask interrupts and can't be called from user space */ + static void __init set_trap_gate(unsigned int n, void *addr) { _set_gate(idt_table+n,15,0,addr); } +/* System gate - doesn't mask interrupts and can be called from user space, + * via an "INT n" instruction + */ + static void __init set_system_gate(unsigned int n, void *addr) { _set_gate(idt_table+n,15,3,addr); } +/* System interrupt gate - masks ints and can be called from user space */ + +static void __init set_system_intr_gate(unsigned int n, void *addr) +{ + _set_gate(idt_table+n,14,3,addr); +} + +/* Call gate - accessed via a far CALL rather than an exception or INT n */ + static void __init set_call_gate(void *a, void *addr) { _set_gate(a,12,3,addr); @@ -965,10 +1036,18 @@ EISA_bus = 1; #endif + /* Interrupts 1 and 3 are interrupt gates in case they were + * generated within the kernel, in which case we want + * interrupts masked off while the kernel debugger runs. + * Additionally, interrupt 3 could be called from user space + * by an "INT 3" inserted by a user space debugger, so it gets + * a system interrupt gate. + */ + set_trap_gate(0,÷_error); - set_trap_gate(1,&debug); + set_intr_gate(1,&debug); set_intr_gate(2,&nmi); - set_system_gate(3,&int3); /* int3-5 can be called from all */ + set_system_intr_gate(3,&int3); /* int3-5 can be called from all */ set_system_gate(4,&overflow); set_system_gate(5,&bounds); set_trap_gate(6,&invalid_op); @@ -1004,5 +1083,12 @@ superio_init(); lithium_init(); cobalt_init(); +#endif + +#ifdef CONFIG_REMOTE_DEBUG + /* Traps are now setup - OK to fire off an initial breakpoint, + * which should halt the kernel and hand control to the debugger. + */ + if (kgdb_initialized) breakpoint(); #endif } diff -Nru linux-2.4.6-dist/arch/i386/mm/fault.c linux-2.4.6-kgdb/arch/i386/mm/fault.c --- linux-2.4.6-dist/arch/i386/mm/fault.c Tue May 15 03:16:51 2001 +++ linux-2.4.6-kgdb/arch/i386/mm/fault.c Wed Aug 1 12:39:33 2001 @@ -25,6 +25,10 @@ extern void die(const char *,struct pt_regs *,long); +#ifdef CONFIG_REMOTE_DEBUG +extern char kgdb_initialized; +#endif + /* * Ugly, ugly, but the goto's result in better assembly.. */ @@ -270,6 +274,13 @@ * Oops. The kernel tried to access some bad page. We'll have to * terminate things with extreme prejudice. */ + +#ifdef CONFIG_REMOTE_DEBUG + if (kgdb_initialized) { + handle_exception(11, regs); /* 11 - SIGSEGV */ + return; + } +#endif bust_spinlocks(); diff -Nru linux-2.4.6-dist/drivers/char/serial.c linux-2.4.6-kgdb/drivers/char/serial.c --- linux-2.4.6-dist/drivers/char/serial.c Sun May 20 15:11:38 2001 +++ linux-2.4.6-kgdb/drivers/char/serial.c Wed Jul 25 14:00:34 2001 @@ -264,6 +264,8 @@ static int IRQ_timeout[NR_IRQS]; #ifdef CONFIG_SERIAL_CONSOLE static struct console sercons; +#endif +#if defined(CONFIG_SERIAL_CONSOLE) || defined(CONFIG_REMOTE_DEBUG) static int lsr_break_flag; #endif #if defined(CONFIG_SERIAL_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) @@ -360,6 +362,17 @@ #define DBG_CNT(s) #endif +/* If kgdb is active is one of the serial ports, kgdb_index holds + * the port's offset in rs_table[]. No normal operations are permitted + * on a port running kgdb. + */ + +static int kgdb_index = -1; + +#ifdef CONFIG_REMOTE_DEBUG +void enable_kgdb_interrupts(void); +#endif + /* * tmp_buf is used as a temporary buffer by serial_write. We need to * lock it in case the copy_from_user blocks while swapping in a page, @@ -3133,7 +3146,7 @@ MOD_INC_USE_COUNT; line = MINOR(tty->device) - tty->driver.minor_start; - if ((line < 0) || (line >= NR_PORTS)) { + if ((line < 0) || (line >= NR_PORTS) || (line == kgdb_index)) { MOD_DEC_USE_COUNT; return -ENODEV; } @@ -5285,6 +5298,12 @@ panic("Couldn't register callout driver\n"); for (i = 0, state = rs_table; i < NR_PORTS; i++,state++) { +#ifdef CONFIG_REMOTE_DEBUG + if (i == kgdb_index) { + enable_kgdb_interrupts(); + continue; + } +#endif state->magic = SSTATE_MAGIC; state->line = i; state->type = PORT_UNKNOWN; @@ -5307,7 +5326,7 @@ autoconfig(state); } for (i = 0, state = rs_table; i < NR_PORTS; i++,state++) { - if (state->type == PORT_UNKNOWN) + if ((state->type == PORT_UNKNOWN) || (i == kgdb_index)) continue; if ( (state->flags & ASYNC_BOOT_AUTOCONF) && (state->flags & ASYNC_AUTO_IRQ) @@ -5564,12 +5583,10 @@ * Serial console driver * ------------------------------------------------------------ */ -#ifdef CONFIG_SERIAL_CONSOLE +#if defined(CONFIG_SERIAL_CONSOLE) || defined(CONFIG_REMOTE_DEBUG) #define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) -static struct async_struct async_sercons; - /* * Wait for transmitter & holding register to empty */ @@ -5595,6 +5612,12 @@ } } +#endif + + +#ifdef CONFIG_SERIAL_CONSOLE + +static struct async_struct async_sercons; /* * Print a string to the serial port trying not to disturb @@ -5821,6 +5844,215 @@ void __init serial_console_init(void) { register_console(&sercons); +} +#endif + +/* + * Hooks for remote GDB driver + * + * GDB has a mode ("target remote") which allows it to debug a program + * via a simple serial protocol, documented in GDB's "remote.c" file. + * In our case, the "program" is the Linux kernel itself, and the + * protocol runs over a standard serial port. Standard configuration + * is to run a null modem cable between two machines, then GDB can be + * run on one and be used to breakpoint, single step, and do all that + * neat source debugger stuff to the kernel on the other machine. + * + * If the "kgdb" option is given to the kernel, serial_kgdb_hook(index) is + * called which sets up for remote GDB on the "index"th serial line. + * The index is squirreled away in kgdb_index, to make sure the normal + * serial code doesn't try to init the UART or open the device. + * A special interrupt handler is installed, and the putDebugChar() and + * getDebugChar() functions are exported, along with kgdb_interruptible(), + * which tells the system's state: + * + * kgdb_interruptible(1) - system running, + * CNTL-C from the remote triggers a breakpoint + * kgdb_interruptible(0) - system halted, + * characters from the remote go to getDebugChar() + */ + +#ifdef CONFIG_REMOTE_DEBUG + +void breakpoint(void); + +static struct async_struct async_kgdb; +static int kgdb_interrupts_active = 0; + +static void rs_interrupt_kgdb(int irq, void *dev_id, struct pt_regs *regs) +{ + while (serial_in(&async_kgdb, UART_LSR) & UART_LSR_DR) { + if (serial_in(&async_kgdb, UART_RX) == '\003') { + breakpoint(); + return; + } + } +} + +int serial_kgdb_hook(int index) +{ + struct async_struct *info; + struct serial_state *state; + unsigned cval; + int baud = 9600; + int bits = 8; + int parity = 'n'; + int cflag = CREAD | HUPCL | CLOCAL; + int quot = 0; + + kgdb_index = index; + + /* + * Now construct a cflag setting. + */ + switch(baud) { + case 1200: + cflag |= B1200; + break; + case 2400: + cflag |= B2400; + break; + case 4800: + cflag |= B4800; + break; + case 19200: + cflag |= B19200; + break; + case 38400: + cflag |= B38400; + break; + case 57600: + cflag |= B57600; + break; + case 115200: + cflag |= B115200; + break; + case 9600: + default: + cflag |= B9600; + break; + } + switch(bits) { + case 7: + cflag |= CS7; + break; + default: + case 8: + cflag |= CS8; + break; + } + switch(parity) { + case 'o': case 'O': + cflag |= PARODD; + break; + case 'e': case 'E': + cflag |= PARENB; + break; + } + + /* + * Divisor, bytesize and parity + */ + state = rs_table + index; + info = &async_kgdb; + info->magic = SERIAL_MAGIC; + info->state = state; + info->port = state->port; + info->flags = state->flags; +#ifdef CONFIG_HUB6 + info->hub6 = state->hub6; +#endif + info->io_type = state->io_type; + info->iomem_base = state->iomem_base; + info->iomem_reg_shift = state->iomem_reg_shift; + quot = state->baud_base / baud; + cval = cflag & (CSIZE | CSTOPB); +#if defined(__powerpc__) || defined(__alpha__) + cval >>= 8; +#else /* !__powerpc__ && !__alpha__ */ + cval >>= 4; +#endif /* !__powerpc__ && !__alpha__ */ + if (cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(cflag & PARODD)) + cval |= UART_LCR_EPAR; + + + /* + * Set DTR and RTS high along with OUT1 and OUT2 (to + * enable interrupts) and set speed. + */ + serial_out(info, UART_LCR, cval | UART_LCR_DLAB); /* set DLAB */ + serial_out(info, UART_DLL, quot & 0xff); /* LS of divisor */ + serial_out(info, UART_DLM, quot >> 8); /* MS of divisor */ + serial_out(info, UART_LCR, cval); /* reset DLAB */ + serial_out(info, UART_IER, 0); /* int off for now */ + /* serial_out(info, UART_MCR, UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT1 | UART_MCR_OUT2); */ + serial_out(info, UART_MCR, UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); + + /* + * If we read 0xff from the LSR, there is no UART here. + */ + if (serial_in(info, UART_LSR) == 0xff) { + return -1; + } + return 0; +} + +static void enable_kgdb_interrupts(void) +{ + /* This routine gets called from rs_init(), and sets up the + * IRQ handler and I/O block for the KGDB debugger. We don't + * need this to make KGDB work - we only need it to debug + * asynchronously (hit CTRL-C on the debugger and break into + * the kernel). We didn't do this in serial_kgdb_hook() since + * IRQs hadn't been initialized yet! + */ + + /* Allocate the IRQ (we don't share it) and IO ports */ + + if (request_irq(async_kgdb.state->irq, rs_interrupt_kgdb, 0, + "serial(kgdb)", NULL)) { + printk("Can't get KGDB IRQ - no asynchronous debugging\n"); + } + + /* enable UART recv data interrupt */ + serial_out(&async_kgdb, UART_IER, UART_IER_RDI); + + rs_interrupt_kgdb(async_kgdb.state->irq, NULL, NULL); + + kgdb_interrupts_active = 1; + + request_region(async_kgdb.port, 8, "serial(kgdb)"); +} + +void putDebugChar(char kgdb_char) +{ + wait_for_xmitr(&async_kgdb); + serial_out(&async_kgdb, UART_TX, kgdb_char); + wait_for_xmitr(&async_kgdb); +} + +char getDebugChar(void) +{ + int lsr; + + do { + lsr = serial_in(&async_kgdb, UART_LSR); + } while (!(lsr & UART_LSR_DR)); + + return serial_in(&async_kgdb, UART_RX); +} + +void kgdb_interruptible(int i) +{ + if (i && kgdb_interrupts_active) { + /* KGDB interruptible - enable UART recv data interrupt */ + serial_out(&async_kgdb, UART_IER, UART_IER_RDI); + } else { + /* KGDB not interruptible - disable UART interrupts */ + serial_out(&async_kgdb, UART_IER, 0); + } } #endif