mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-27 14:43:58 +08:00
67ddb4052d
Create generic kernel debugger hooks in the MN10300 arch and make gdbstub use them. This is a preparation for KGDB support. Signed-off-by: David Howells <dhowells@redhat.com>
1925 lines
45 KiB
C
1925 lines
45 KiB
C
/* MN10300 GDB stub
|
|
*
|
|
* Originally written by Glenn Engel, Lake Stevens Instrument Division
|
|
*
|
|
* Contributed by HP Systems
|
|
*
|
|
* Modified for SPARC by Stu Grossman, Cygnus Support.
|
|
*
|
|
* Modified for Linux/MIPS (and MIPS in general) by Andreas Busse
|
|
* Send complaints, suggestions etc. to <andy@waldorf-gmbh.de>
|
|
*
|
|
* Copyright (C) 1995 Andreas Busse
|
|
*
|
|
* Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
|
|
* Modified for Linux/mn10300 by David Howells <dhowells@redhat.com>
|
|
*/
|
|
|
|
/*
|
|
* 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 a BREAK instruction.
|
|
*
|
|
*
|
|
* 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)
|
|
*
|
|
* bBB..BB Set baud rate to BB..BB OK or BNN, then sets
|
|
* baud rate
|
|
*
|
|
* All commands and responses are sent with a packet which includes a
|
|
* checksum. A packet consists of
|
|
*
|
|
* $<packet info>#<checksum>.
|
|
*
|
|
* where
|
|
* <packet info> :: <characters representing the command or response>
|
|
* <checksum> :: < two hex digits computed as modulo 256 sum of <packetinfo>>
|
|
*
|
|
* 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
|
|
*
|
|
*
|
|
* ==============
|
|
* MORE EXAMPLES:
|
|
* ==============
|
|
*
|
|
* For reference -- the following are the steps that one
|
|
* company took (RidgeRun Inc) to get remote gdb debugging
|
|
* going. In this scenario the host machine was a PC and the
|
|
* target platform was a Galileo EVB64120A MIPS evaluation
|
|
* board.
|
|
*
|
|
* Step 1:
|
|
* First download gdb-5.0.tar.gz from the internet.
|
|
* and then build/install the package.
|
|
*
|
|
* Example:
|
|
* $ tar zxf gdb-5.0.tar.gz
|
|
* $ cd gdb-5.0
|
|
* $ ./configure --target=am33_2.0-linux-gnu
|
|
* $ make
|
|
* $ install
|
|
* am33_2.0-linux-gnu-gdb
|
|
*
|
|
* Step 2:
|
|
* Configure linux for remote debugging and build it.
|
|
*
|
|
* Example:
|
|
* $ cd ~/linux
|
|
* $ make menuconfig <go to "Kernel Hacking" and turn on remote debugging>
|
|
* $ make dep; make vmlinux
|
|
*
|
|
* Step 3:
|
|
* Download the kernel to the remote target and start
|
|
* the kernel running. It will promptly halt and wait
|
|
* for the host gdb session to connect. It does this
|
|
* since the "Kernel Hacking" option has defined
|
|
* CONFIG_REMOTE_DEBUG which in turn enables your calls
|
|
* to:
|
|
* set_debug_traps();
|
|
* breakpoint();
|
|
*
|
|
* Step 4:
|
|
* Start the gdb session on the host.
|
|
*
|
|
* Example:
|
|
* $ am33_2.0-linux-gnu-gdb vmlinux
|
|
* (gdb) set remotebaud 115200
|
|
* (gdb) target remote /dev/ttyS1
|
|
* ...at this point you are connected to
|
|
* the remote target and can use gdb
|
|
* in the normal fasion. Setting
|
|
* breakpoints, single stepping,
|
|
* printing variables, etc.
|
|
*
|
|
*/
|
|
|
|
#include <linux/string.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/console.h>
|
|
#include <linux/init.h>
|
|
#include <linux/bug.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
#include <asm/system.h>
|
|
#include <asm/gdb-stub.h>
|
|
#include <asm/exceptions.h>
|
|
#include <asm/debugger.h>
|
|
#include <asm/serial-regs.h>
|
|
#include <asm/busctl-regs.h>
|
|
#include <unit/leds.h>
|
|
#include <unit/serial.h>
|
|
|
|
/* define to use F7F7 rather than FF which is subverted by JTAG debugger */
|
|
#undef GDBSTUB_USE_F7F7_AS_BREAKPOINT
|
|
|
|
/*
|
|
* BUFMAX defines the maximum number of characters in inbound/outbound buffers
|
|
* at least NUMREGBYTES*2 are needed for register packets
|
|
*/
|
|
#define BUFMAX 2048
|
|
|
|
static const char gdbstub_banner[] =
|
|
"Linux/MN10300 GDB Stub (c) RedHat 2007\n";
|
|
|
|
u8 gdbstub_rx_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
|
|
u32 gdbstub_rx_inp;
|
|
u32 gdbstub_rx_outp;
|
|
u8 gdbstub_busy;
|
|
u8 gdbstub_rx_overflow;
|
|
u8 gdbstub_rx_unget;
|
|
|
|
static u8 gdbstub_flush_caches;
|
|
static char input_buffer[BUFMAX];
|
|
static char output_buffer[BUFMAX];
|
|
static char trans_buffer[BUFMAX];
|
|
|
|
struct gdbstub_bkpt {
|
|
u8 *addr; /* address of breakpoint */
|
|
u8 len; /* size of breakpoint */
|
|
u8 origbytes[7]; /* original bytes */
|
|
};
|
|
|
|
static struct gdbstub_bkpt gdbstub_bkpts[256];
|
|
|
|
/*
|
|
* local prototypes
|
|
*/
|
|
static void getpacket(char *buffer);
|
|
static int putpacket(char *buffer);
|
|
static int computeSignal(enum exception_code excep);
|
|
static int hex(unsigned char ch);
|
|
static int hexToInt(char **ptr, int *intValue);
|
|
static unsigned char *mem2hex(const void *mem, char *buf, int count,
|
|
int may_fault);
|
|
static const char *hex2mem(const char *buf, void *_mem, int count,
|
|
int may_fault);
|
|
|
|
/*
|
|
* Convert ch from a hex digit to an int
|
|
*/
|
|
static int hex(unsigned 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;
|
|
}
|
|
|
|
#ifdef CONFIG_GDBSTUB_DEBUGGING
|
|
|
|
void debug_to_serial(const char *p, int n)
|
|
{
|
|
__debug_to_serial(p, n);
|
|
/* gdbstub_console_write(NULL, p, n); */
|
|
}
|
|
|
|
void gdbstub_printk(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int len;
|
|
|
|
/* Emit the output into the temporary buffer */
|
|
va_start(args, fmt);
|
|
len = vsnprintf(trans_buffer, sizeof(trans_buffer), fmt, args);
|
|
va_end(args);
|
|
debug_to_serial(trans_buffer, len);
|
|
}
|
|
|
|
#endif
|
|
|
|
static inline char *gdbstub_strcpy(char *dst, const char *src)
|
|
{
|
|
int loop = 0;
|
|
while ((dst[loop] = src[loop]))
|
|
loop++;
|
|
return dst;
|
|
}
|
|
|
|
/*
|
|
* scan for the sequence $<data>#<checksum>
|
|
*/
|
|
static void getpacket(char *buffer)
|
|
{
|
|
unsigned char checksum;
|
|
unsigned char xmitcsum;
|
|
unsigned char ch;
|
|
int count, i, ret, error;
|
|
|
|
for (;;) {
|
|
/*
|
|
* wait around for the start character,
|
|
* ignore all other characters
|
|
*/
|
|
do {
|
|
gdbstub_io_rx_char(&ch, 0);
|
|
} while (ch != '$');
|
|
|
|
checksum = 0;
|
|
xmitcsum = -1;
|
|
count = 0;
|
|
error = 0;
|
|
|
|
/*
|
|
* now, read until a # or end of buffer is found
|
|
*/
|
|
while (count < BUFMAX) {
|
|
ret = gdbstub_io_rx_char(&ch, 0);
|
|
if (ret < 0)
|
|
error = ret;
|
|
|
|
if (ch == '#')
|
|
break;
|
|
checksum += ch;
|
|
buffer[count] = ch;
|
|
count++;
|
|
}
|
|
|
|
if (error == -EIO) {
|
|
gdbstub_proto("### GDB Rx Error - Skipping packet"
|
|
" ###\n");
|
|
gdbstub_proto("### GDB Tx NAK\n");
|
|
gdbstub_io_tx_char('-');
|
|
continue;
|
|
}
|
|
|
|
if (count >= BUFMAX || error)
|
|
continue;
|
|
|
|
buffer[count] = 0;
|
|
|
|
/* read the checksum */
|
|
ret = gdbstub_io_rx_char(&ch, 0);
|
|
if (ret < 0)
|
|
error = ret;
|
|
xmitcsum = hex(ch) << 4;
|
|
|
|
ret = gdbstub_io_rx_char(&ch, 0);
|
|
if (ret < 0)
|
|
error = ret;
|
|
xmitcsum |= hex(ch);
|
|
|
|
if (error) {
|
|
if (error == -EIO)
|
|
gdbstub_io("### GDB Rx Error -"
|
|
" Skipping packet\n");
|
|
gdbstub_io("### GDB Tx NAK\n");
|
|
gdbstub_io_tx_char('-');
|
|
continue;
|
|
}
|
|
|
|
/* check the checksum */
|
|
if (checksum != xmitcsum) {
|
|
gdbstub_io("### GDB Tx NAK\n");
|
|
gdbstub_io_tx_char('-'); /* failed checksum */
|
|
continue;
|
|
}
|
|
|
|
gdbstub_proto("### GDB Rx '$%s#%02x' ###\n", buffer, checksum);
|
|
gdbstub_io("### GDB Tx ACK\n");
|
|
gdbstub_io_tx_char('+'); /* successful transfer */
|
|
|
|
/*
|
|
* if a sequence char is present,
|
|
* reply the sequence ID
|
|
*/
|
|
if (buffer[2] == ':') {
|
|
gdbstub_io_tx_char(buffer[0]);
|
|
gdbstub_io_tx_char(buffer[1]);
|
|
|
|
/*
|
|
* remove sequence chars from buffer
|
|
*/
|
|
count = 0;
|
|
while (buffer[count])
|
|
count++;
|
|
for (i = 3; i <= count; i++)
|
|
buffer[i - 3] = buffer[i];
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* send the packet in buffer.
|
|
* - return 0 if successfully ACK'd
|
|
* - return 1 if abandoned due to new incoming packet
|
|
*/
|
|
static int putpacket(char *buffer)
|
|
{
|
|
unsigned char checksum;
|
|
unsigned char ch;
|
|
int count;
|
|
|
|
/*
|
|
* $<packet info>#<checksum>.
|
|
*/
|
|
gdbstub_proto("### GDB Tx $'%s'#?? ###\n", buffer);
|
|
|
|
do {
|
|
gdbstub_io_tx_char('$');
|
|
checksum = 0;
|
|
count = 0;
|
|
|
|
while ((ch = buffer[count]) != 0) {
|
|
gdbstub_io_tx_char(ch);
|
|
checksum += ch;
|
|
count += 1;
|
|
}
|
|
|
|
gdbstub_io_tx_char('#');
|
|
gdbstub_io_tx_char(hex_asc_hi(checksum));
|
|
gdbstub_io_tx_char(hex_asc_lo(checksum));
|
|
|
|
} while (gdbstub_io_rx_char(&ch, 0),
|
|
ch == '-' && (gdbstub_io("### GDB Rx NAK\n"), 0),
|
|
ch != '-' && ch != '+' &&
|
|
(gdbstub_io("### GDB Rx ??? %02x\n", ch), 0),
|
|
ch != '+' && ch != '$');
|
|
|
|
if (ch == '+') {
|
|
gdbstub_io("### GDB Rx ACK\n");
|
|
return 0;
|
|
}
|
|
|
|
gdbstub_io("### GDB Tx Abandoned\n");
|
|
gdbstub_rx_unget = ch;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* While we find nice hex chars, build an int.
|
|
* Return number of chars processed.
|
|
*/
|
|
static int hexToInt(char **ptr, int *intValue)
|
|
{
|
|
int numChars = 0;
|
|
int hexValue;
|
|
|
|
*intValue = 0;
|
|
|
|
while (**ptr) {
|
|
hexValue = hex(**ptr);
|
|
if (hexValue < 0)
|
|
break;
|
|
|
|
*intValue = (*intValue << 4) | hexValue;
|
|
numChars++;
|
|
|
|
(*ptr)++;
|
|
}
|
|
|
|
return (numChars);
|
|
}
|
|
|
|
#ifdef CONFIG_GDBSTUB_ALLOW_SINGLE_STEP
|
|
/*
|
|
* We single-step by setting breakpoints. When an exception
|
|
* is handled, we need to restore the instructions hoisted
|
|
* when the breakpoints were set.
|
|
*
|
|
* This is where we save the original instructions.
|
|
*/
|
|
static struct gdb_bp_save {
|
|
u8 *addr;
|
|
u8 opcode[2];
|
|
} step_bp[2];
|
|
|
|
static const unsigned char gdbstub_insn_sizes[256] =
|
|
{
|
|
/* 1 2 3 4 5 6 7 8 9 a b c d e f */
|
|
1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, /* 0 */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1 */
|
|
2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, /* 2 */
|
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, /* 3 */
|
|
1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, /* 4 */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, /* 5 */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */
|
|
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 8 */
|
|
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 9 */
|
|
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* a */
|
|
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* b */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2, /* c */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */
|
|
0, 2, 2, 2, 2, 2, 2, 4, 0, 3, 0, 4, 0, 6, 7, 1 /* f */
|
|
};
|
|
|
|
static int __gdbstub_mark_bp(u8 *addr, int ix)
|
|
{
|
|
/* vmalloc area */
|
|
if (((u8 *) VMALLOC_START <= addr) && (addr < (u8 *) VMALLOC_END))
|
|
goto okay;
|
|
/* SRAM, SDRAM */
|
|
if (((u8 *) 0x80000000UL <= addr) && (addr < (u8 *) 0xa0000000UL))
|
|
goto okay;
|
|
return 0;
|
|
|
|
okay:
|
|
if (gdbstub_read_byte(addr + 0, &step_bp[ix].opcode[0]) < 0 ||
|
|
gdbstub_read_byte(addr + 1, &step_bp[ix].opcode[1]) < 0)
|
|
return 0;
|
|
|
|
step_bp[ix].addr = addr;
|
|
return 1;
|
|
}
|
|
|
|
static inline void __gdbstub_restore_bp(void)
|
|
{
|
|
#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT
|
|
if (step_bp[0].addr) {
|
|
gdbstub_write_byte(step_bp[0].opcode[0], step_bp[0].addr + 0);
|
|
gdbstub_write_byte(step_bp[0].opcode[1], step_bp[0].addr + 1);
|
|
}
|
|
if (step_bp[1].addr) {
|
|
gdbstub_write_byte(step_bp[1].opcode[0], step_bp[1].addr + 0);
|
|
gdbstub_write_byte(step_bp[1].opcode[1], step_bp[1].addr + 1);
|
|
}
|
|
#else
|
|
if (step_bp[0].addr)
|
|
gdbstub_write_byte(step_bp[0].opcode[0], step_bp[0].addr + 0);
|
|
if (step_bp[1].addr)
|
|
gdbstub_write_byte(step_bp[1].opcode[0], step_bp[1].addr + 0);
|
|
#endif
|
|
|
|
gdbstub_flush_caches = 1;
|
|
|
|
step_bp[0].addr = NULL;
|
|
step_bp[0].opcode[0] = 0;
|
|
step_bp[0].opcode[1] = 0;
|
|
step_bp[1].addr = NULL;
|
|
step_bp[1].opcode[0] = 0;
|
|
step_bp[1].opcode[1] = 0;
|
|
}
|
|
|
|
/*
|
|
* emulate single stepping by means of breakpoint instructions
|
|
*/
|
|
static int gdbstub_single_step(struct pt_regs *regs)
|
|
{
|
|
unsigned size;
|
|
uint32_t x;
|
|
uint8_t cur, *pc, *sp;
|
|
|
|
step_bp[0].addr = NULL;
|
|
step_bp[0].opcode[0] = 0;
|
|
step_bp[0].opcode[1] = 0;
|
|
step_bp[1].addr = NULL;
|
|
step_bp[1].opcode[0] = 0;
|
|
step_bp[1].opcode[1] = 0;
|
|
x = 0;
|
|
|
|
pc = (u8 *) regs->pc;
|
|
sp = (u8 *) (regs + 1);
|
|
if (gdbstub_read_byte(pc, &cur) < 0)
|
|
return -EFAULT;
|
|
|
|
gdbstub_bkpt("Single Step from %p { %02x }\n", pc, cur);
|
|
|
|
gdbstub_flush_caches = 1;
|
|
|
|
size = gdbstub_insn_sizes[cur];
|
|
if (size > 0) {
|
|
if (!__gdbstub_mark_bp(pc + size, 0))
|
|
goto fault;
|
|
} else {
|
|
switch (cur) {
|
|
/* Bxx (d8,PC) */
|
|
case 0xc0 ... 0xca:
|
|
if (gdbstub_read_byte(pc + 1, (u8 *) &x) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp(pc + 2, 0))
|
|
goto fault;
|
|
if ((x < 0 || x > 2) &&
|
|
!__gdbstub_mark_bp(pc + (s8) x, 1))
|
|
goto fault;
|
|
break;
|
|
|
|
/* LXX (d8,PC) */
|
|
case 0xd0 ... 0xda:
|
|
if (!__gdbstub_mark_bp(pc + 1, 0))
|
|
goto fault;
|
|
if (regs->pc != regs->lar &&
|
|
!__gdbstub_mark_bp((u8 *) regs->lar, 1))
|
|
goto fault;
|
|
break;
|
|
|
|
/* SETLB - loads the next for bytes into the LIR
|
|
* register */
|
|
case 0xdb:
|
|
if (!__gdbstub_mark_bp(pc + 1, 0))
|
|
goto fault;
|
|
break;
|
|
|
|
/* JMP (d16,PC) or CALL (d16,PC) */
|
|
case 0xcc:
|
|
case 0xcd:
|
|
if (gdbstub_read_byte(pc + 1, ((u8 *) &x) + 0) < 0 ||
|
|
gdbstub_read_byte(pc + 2, ((u8 *) &x) + 1) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp(pc + (s16) x, 0))
|
|
goto fault;
|
|
break;
|
|
|
|
/* JMP (d32,PC) or CALL (d32,PC) */
|
|
case 0xdc:
|
|
case 0xdd:
|
|
if (gdbstub_read_byte(pc + 1, ((u8 *) &x) + 0) < 0 ||
|
|
gdbstub_read_byte(pc + 2, ((u8 *) &x) + 1) < 0 ||
|
|
gdbstub_read_byte(pc + 3, ((u8 *) &x) + 2) < 0 ||
|
|
gdbstub_read_byte(pc + 4, ((u8 *) &x) + 3) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp(pc + (s32) x, 0))
|
|
goto fault;
|
|
break;
|
|
|
|
/* RETF */
|
|
case 0xde:
|
|
if (!__gdbstub_mark_bp((u8 *) regs->mdr, 0))
|
|
goto fault;
|
|
break;
|
|
|
|
/* RET */
|
|
case 0xdf:
|
|
if (gdbstub_read_byte(pc + 2, (u8 *) &x) < 0)
|
|
goto fault;
|
|
sp += (s8)x;
|
|
if (gdbstub_read_byte(sp + 0, ((u8 *) &x) + 0) < 0 ||
|
|
gdbstub_read_byte(sp + 1, ((u8 *) &x) + 1) < 0 ||
|
|
gdbstub_read_byte(sp + 2, ((u8 *) &x) + 2) < 0 ||
|
|
gdbstub_read_byte(sp + 3, ((u8 *) &x) + 3) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp((u8 *) x, 0))
|
|
goto fault;
|
|
break;
|
|
|
|
case 0xf0:
|
|
if (gdbstub_read_byte(pc + 1, &cur) < 0)
|
|
goto fault;
|
|
|
|
if (cur >= 0xf0 && cur <= 0xf7) {
|
|
/* JMP (An) / CALLS (An) */
|
|
switch (cur & 3) {
|
|
case 0: x = regs->a0; break;
|
|
case 1: x = regs->a1; break;
|
|
case 2: x = regs->a2; break;
|
|
case 3: x = regs->a3; break;
|
|
}
|
|
if (!__gdbstub_mark_bp((u8 *) x, 0))
|
|
goto fault;
|
|
} else if (cur == 0xfc) {
|
|
/* RETS */
|
|
if (gdbstub_read_byte(
|
|
sp + 0, ((u8 *) &x) + 0) < 0 ||
|
|
gdbstub_read_byte(
|
|
sp + 1, ((u8 *) &x) + 1) < 0 ||
|
|
gdbstub_read_byte(
|
|
sp + 2, ((u8 *) &x) + 2) < 0 ||
|
|
gdbstub_read_byte(
|
|
sp + 3, ((u8 *) &x) + 3) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp((u8 *) x, 0))
|
|
goto fault;
|
|
} else if (cur == 0xfd) {
|
|
/* RTI */
|
|
if (gdbstub_read_byte(
|
|
sp + 4, ((u8 *) &x) + 0) < 0 ||
|
|
gdbstub_read_byte(
|
|
sp + 5, ((u8 *) &x) + 1) < 0 ||
|
|
gdbstub_read_byte(
|
|
sp + 6, ((u8 *) &x) + 2) < 0 ||
|
|
gdbstub_read_byte(
|
|
sp + 7, ((u8 *) &x) + 3) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp((u8 *) x, 0))
|
|
goto fault;
|
|
} else {
|
|
if (!__gdbstub_mark_bp(pc + 2, 0))
|
|
goto fault;
|
|
}
|
|
|
|
break;
|
|
|
|
/* potential 3-byte conditional branches */
|
|
case 0xf8:
|
|
if (gdbstub_read_byte(pc + 1, &cur) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp(pc + 3, 0))
|
|
goto fault;
|
|
|
|
if (cur >= 0xe8 && cur <= 0xeb) {
|
|
if (gdbstub_read_byte(
|
|
pc + 2, ((u8 *) &x) + 0) < 0)
|
|
goto fault;
|
|
if ((x < 0 || x > 3) &&
|
|
!__gdbstub_mark_bp(pc + (s8) x, 1))
|
|
goto fault;
|
|
}
|
|
break;
|
|
|
|
case 0xfa:
|
|
if (gdbstub_read_byte(pc + 1, &cur) < 0)
|
|
goto fault;
|
|
|
|
if (cur == 0xff) {
|
|
/* CALLS (d16,PC) */
|
|
if (gdbstub_read_byte(
|
|
pc + 2, ((u8 *) &x) + 0) < 0 ||
|
|
gdbstub_read_byte(
|
|
pc + 3, ((u8 *) &x) + 1) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp(pc + (s16) x, 0))
|
|
goto fault;
|
|
} else {
|
|
if (!__gdbstub_mark_bp(pc + 4, 0))
|
|
goto fault;
|
|
}
|
|
break;
|
|
|
|
case 0xfc:
|
|
if (gdbstub_read_byte(pc + 1, &cur) < 0)
|
|
goto fault;
|
|
if (cur == 0xff) {
|
|
/* CALLS (d32,PC) */
|
|
if (gdbstub_read_byte(
|
|
pc + 2, ((u8 *) &x) + 0) < 0 ||
|
|
gdbstub_read_byte(
|
|
pc + 3, ((u8 *) &x) + 1) < 0 ||
|
|
gdbstub_read_byte(
|
|
pc + 4, ((u8 *) &x) + 2) < 0 ||
|
|
gdbstub_read_byte(
|
|
pc + 5, ((u8 *) &x) + 3) < 0)
|
|
goto fault;
|
|
if (!__gdbstub_mark_bp(
|
|
pc + (s32) x, 0))
|
|
goto fault;
|
|
} else {
|
|
if (!__gdbstub_mark_bp(
|
|
pc + 6, 0))
|
|
goto fault;
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
gdbstub_bkpt("Step: %02x at %p; %02x at %p\n",
|
|
step_bp[0].opcode[0], step_bp[0].addr,
|
|
step_bp[1].opcode[0], step_bp[1].addr);
|
|
|
|
if (step_bp[0].addr) {
|
|
#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT
|
|
if (gdbstub_write_byte(0xF7, step_bp[0].addr + 0) < 0 ||
|
|
gdbstub_write_byte(0xF7, step_bp[0].addr + 1) < 0)
|
|
goto fault;
|
|
#else
|
|
if (gdbstub_write_byte(0xFF, step_bp[0].addr + 0) < 0)
|
|
goto fault;
|
|
#endif
|
|
}
|
|
|
|
if (step_bp[1].addr) {
|
|
#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT
|
|
if (gdbstub_write_byte(0xF7, step_bp[1].addr + 0) < 0 ||
|
|
gdbstub_write_byte(0xF7, step_bp[1].addr + 1) < 0)
|
|
goto fault;
|
|
#else
|
|
if (gdbstub_write_byte(0xFF, step_bp[1].addr + 0) < 0)
|
|
goto fault;
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
|
|
fault:
|
|
/* uh-oh - silly address alert, try and restore things */
|
|
__gdbstub_restore_bp();
|
|
return -EFAULT;
|
|
}
|
|
#endif /* CONFIG_GDBSTUB_ALLOW_SINGLE_STEP */
|
|
|
|
#ifdef CONFIG_GDBSTUB_CONSOLE
|
|
|
|
void gdbstub_console_write(struct console *con, const char *p, unsigned n)
|
|
{
|
|
static const char gdbstub_cr[] = { 0x0d };
|
|
char outbuf[26];
|
|
int qty;
|
|
u8 busy;
|
|
|
|
busy = gdbstub_busy;
|
|
gdbstub_busy = 1;
|
|
|
|
outbuf[0] = 'O';
|
|
|
|
while (n > 0) {
|
|
qty = 1;
|
|
|
|
while (n > 0 && qty < 20) {
|
|
mem2hex(p, outbuf + qty, 2, 0);
|
|
qty += 2;
|
|
if (*p == 0x0a) {
|
|
mem2hex(gdbstub_cr, outbuf + qty, 2, 0);
|
|
qty += 2;
|
|
}
|
|
p++;
|
|
n--;
|
|
}
|
|
|
|
outbuf[qty] = 0;
|
|
putpacket(outbuf);
|
|
}
|
|
|
|
gdbstub_busy = busy;
|
|
}
|
|
|
|
static kdev_t gdbstub_console_dev(struct console *con)
|
|
{
|
|
return MKDEV(1, 3); /* /dev/null */
|
|
}
|
|
|
|
static struct console gdbstub_console = {
|
|
.name = "gdb",
|
|
.write = gdbstub_console_write,
|
|
.device = gdbstub_console_dev,
|
|
.flags = CON_PRINTBUFFER,
|
|
.index = -1,
|
|
};
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Convert the memory pointed to by mem into hex, placing result in buf.
|
|
* - if successful, return a pointer to the last char put in buf (NUL)
|
|
* - in case of mem fault, return NULL
|
|
* may_fault is non-zero if we are reading from arbitrary memory, but is
|
|
* currently not used.
|
|
*/
|
|
static
|
|
unsigned char *mem2hex(const void *_mem, char *buf, int count, int may_fault)
|
|
{
|
|
const u8 *mem = _mem;
|
|
u8 ch[4];
|
|
|
|
if ((u32) mem & 1 && count >= 1) {
|
|
if (gdbstub_read_byte(mem, ch) != 0)
|
|
return 0;
|
|
buf = pack_hex_byte(buf, ch[0]);
|
|
mem++;
|
|
count--;
|
|
}
|
|
|
|
if ((u32) mem & 3 && count >= 2) {
|
|
if (gdbstub_read_word(mem, ch) != 0)
|
|
return 0;
|
|
buf = pack_hex_byte(buf, ch[0]);
|
|
buf = pack_hex_byte(buf, ch[1]);
|
|
mem += 2;
|
|
count -= 2;
|
|
}
|
|
|
|
while (count >= 4) {
|
|
if (gdbstub_read_dword(mem, ch) != 0)
|
|
return 0;
|
|
buf = pack_hex_byte(buf, ch[0]);
|
|
buf = pack_hex_byte(buf, ch[1]);
|
|
buf = pack_hex_byte(buf, ch[2]);
|
|
buf = pack_hex_byte(buf, ch[3]);
|
|
mem += 4;
|
|
count -= 4;
|
|
}
|
|
|
|
if (count >= 2) {
|
|
if (gdbstub_read_word(mem, ch) != 0)
|
|
return 0;
|
|
buf = pack_hex_byte(buf, ch[0]);
|
|
buf = pack_hex_byte(buf, ch[1]);
|
|
mem += 2;
|
|
count -= 2;
|
|
}
|
|
|
|
if (count >= 1) {
|
|
if (gdbstub_read_byte(mem, ch) != 0)
|
|
return 0;
|
|
buf = pack_hex_byte(buf, ch[0]);
|
|
}
|
|
|
|
*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
|
|
* may_fault is non-zero if we are reading from arbitrary memory, but is
|
|
* currently not used.
|
|
*/
|
|
static
|
|
const char *hex2mem(const char *buf, void *_mem, int count, int may_fault)
|
|
{
|
|
u8 *mem = _mem;
|
|
union {
|
|
u32 val;
|
|
u8 b[4];
|
|
} ch;
|
|
|
|
if ((u32) mem & 1 && count >= 1) {
|
|
ch.b[0] = hex(*buf++) << 4;
|
|
ch.b[0] |= hex(*buf++);
|
|
if (gdbstub_write_byte(ch.val, mem) != 0)
|
|
return 0;
|
|
mem++;
|
|
count--;
|
|
}
|
|
|
|
if ((u32) mem & 3 && count >= 2) {
|
|
ch.b[0] = hex(*buf++) << 4;
|
|
ch.b[0] |= hex(*buf++);
|
|
ch.b[1] = hex(*buf++) << 4;
|
|
ch.b[1] |= hex(*buf++);
|
|
if (gdbstub_write_word(ch.val, mem) != 0)
|
|
return 0;
|
|
mem += 2;
|
|
count -= 2;
|
|
}
|
|
|
|
while (count >= 4) {
|
|
ch.b[0] = hex(*buf++) << 4;
|
|
ch.b[0] |= hex(*buf++);
|
|
ch.b[1] = hex(*buf++) << 4;
|
|
ch.b[1] |= hex(*buf++);
|
|
ch.b[2] = hex(*buf++) << 4;
|
|
ch.b[2] |= hex(*buf++);
|
|
ch.b[3] = hex(*buf++) << 4;
|
|
ch.b[3] |= hex(*buf++);
|
|
if (gdbstub_write_dword(ch.val, mem) != 0)
|
|
return 0;
|
|
mem += 4;
|
|
count -= 4;
|
|
}
|
|
|
|
if (count >= 2) {
|
|
ch.b[0] = hex(*buf++) << 4;
|
|
ch.b[0] |= hex(*buf++);
|
|
ch.b[1] = hex(*buf++) << 4;
|
|
ch.b[1] |= hex(*buf++);
|
|
if (gdbstub_write_word(ch.val, mem) != 0)
|
|
return 0;
|
|
mem += 2;
|
|
count -= 2;
|
|
}
|
|
|
|
if (count >= 1) {
|
|
ch.b[0] = hex(*buf++) << 4;
|
|
ch.b[0] |= hex(*buf++);
|
|
if (gdbstub_write_byte(ch.val, mem) != 0)
|
|
return 0;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* This table contains the mapping between MN10300 exception codes, and
|
|
* signals, which are primarily what GDB understands. It also indicates
|
|
* which hardware traps we need to commandeer when initializing the stub.
|
|
*/
|
|
static const struct excep_to_sig_map {
|
|
enum exception_code excep; /* MN10300 exception code */
|
|
unsigned char signo; /* Signal that we map this into */
|
|
} excep_to_sig_map[] = {
|
|
{ EXCEP_ITLBMISS, SIGSEGV },
|
|
{ EXCEP_DTLBMISS, SIGSEGV },
|
|
{ EXCEP_TRAP, SIGTRAP },
|
|
{ EXCEP_ISTEP, SIGTRAP },
|
|
{ EXCEP_IBREAK, SIGTRAP },
|
|
{ EXCEP_OBREAK, SIGTRAP },
|
|
{ EXCEP_UNIMPINS, SIGILL },
|
|
{ EXCEP_UNIMPEXINS, SIGILL },
|
|
{ EXCEP_MEMERR, SIGSEGV },
|
|
{ EXCEP_MISALIGN, SIGSEGV },
|
|
{ EXCEP_BUSERROR, SIGBUS },
|
|
{ EXCEP_ILLINSACC, SIGSEGV },
|
|
{ EXCEP_ILLDATACC, SIGSEGV },
|
|
{ EXCEP_IOINSACC, SIGSEGV },
|
|
{ EXCEP_PRIVINSACC, SIGSEGV },
|
|
{ EXCEP_PRIVDATACC, SIGSEGV },
|
|
{ EXCEP_FPU_DISABLED, SIGFPE },
|
|
{ EXCEP_FPU_UNIMPINS, SIGFPE },
|
|
{ EXCEP_FPU_OPERATION, SIGFPE },
|
|
{ EXCEP_WDT, SIGALRM },
|
|
{ EXCEP_NMI, SIGQUIT },
|
|
{ EXCEP_IRQ_LEVEL0, SIGINT },
|
|
{ EXCEP_IRQ_LEVEL1, SIGINT },
|
|
{ EXCEP_IRQ_LEVEL2, SIGINT },
|
|
{ EXCEP_IRQ_LEVEL3, SIGINT },
|
|
{ EXCEP_IRQ_LEVEL4, SIGINT },
|
|
{ EXCEP_IRQ_LEVEL5, SIGINT },
|
|
{ EXCEP_IRQ_LEVEL6, SIGINT },
|
|
{ 0, 0}
|
|
};
|
|
|
|
/*
|
|
* convert the MN10300 exception code into a UNIX signal number
|
|
*/
|
|
static int computeSignal(enum exception_code excep)
|
|
{
|
|
const struct excep_to_sig_map *map;
|
|
|
|
for (map = excep_to_sig_map; map->signo; map++)
|
|
if (map->excep == excep)
|
|
return map->signo;
|
|
|
|
return SIGHUP; /* default for things we don't know about */
|
|
}
|
|
|
|
static u32 gdbstub_fpcr, gdbstub_fpufs_array[32];
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void gdbstub_store_fpu(void)
|
|
{
|
|
#ifdef CONFIG_FPU
|
|
|
|
asm volatile(
|
|
"or %2,epsw\n"
|
|
#ifdef CONFIG_MN10300_PROC_MN103E010
|
|
"nop\n"
|
|
"nop\n"
|
|
#endif
|
|
"mov %1, a1\n"
|
|
"fmov fs0, (a1+)\n"
|
|
"fmov fs1, (a1+)\n"
|
|
"fmov fs2, (a1+)\n"
|
|
"fmov fs3, (a1+)\n"
|
|
"fmov fs4, (a1+)\n"
|
|
"fmov fs5, (a1+)\n"
|
|
"fmov fs6, (a1+)\n"
|
|
"fmov fs7, (a1+)\n"
|
|
"fmov fs8, (a1+)\n"
|
|
"fmov fs9, (a1+)\n"
|
|
"fmov fs10, (a1+)\n"
|
|
"fmov fs11, (a1+)\n"
|
|
"fmov fs12, (a1+)\n"
|
|
"fmov fs13, (a1+)\n"
|
|
"fmov fs14, (a1+)\n"
|
|
"fmov fs15, (a1+)\n"
|
|
"fmov fs16, (a1+)\n"
|
|
"fmov fs17, (a1+)\n"
|
|
"fmov fs18, (a1+)\n"
|
|
"fmov fs19, (a1+)\n"
|
|
"fmov fs20, (a1+)\n"
|
|
"fmov fs21, (a1+)\n"
|
|
"fmov fs22, (a1+)\n"
|
|
"fmov fs23, (a1+)\n"
|
|
"fmov fs24, (a1+)\n"
|
|
"fmov fs25, (a1+)\n"
|
|
"fmov fs26, (a1+)\n"
|
|
"fmov fs27, (a1+)\n"
|
|
"fmov fs28, (a1+)\n"
|
|
"fmov fs29, (a1+)\n"
|
|
"fmov fs30, (a1+)\n"
|
|
"fmov fs31, (a1+)\n"
|
|
"fmov fpcr, %0\n"
|
|
: "=d"(gdbstub_fpcr)
|
|
: "g" (&gdbstub_fpufs_array), "i"(EPSW_FE)
|
|
: "a1"
|
|
);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static void gdbstub_load_fpu(void)
|
|
{
|
|
#ifdef CONFIG_FPU
|
|
|
|
asm volatile(
|
|
"or %1,epsw\n"
|
|
#ifdef CONFIG_MN10300_PROC_MN103E010
|
|
"nop\n"
|
|
"nop\n"
|
|
#endif
|
|
"mov %0, a1\n"
|
|
"fmov (a1+), fs0\n"
|
|
"fmov (a1+), fs1\n"
|
|
"fmov (a1+), fs2\n"
|
|
"fmov (a1+), fs3\n"
|
|
"fmov (a1+), fs4\n"
|
|
"fmov (a1+), fs5\n"
|
|
"fmov (a1+), fs6\n"
|
|
"fmov (a1+), fs7\n"
|
|
"fmov (a1+), fs8\n"
|
|
"fmov (a1+), fs9\n"
|
|
"fmov (a1+), fs10\n"
|
|
"fmov (a1+), fs11\n"
|
|
"fmov (a1+), fs12\n"
|
|
"fmov (a1+), fs13\n"
|
|
"fmov (a1+), fs14\n"
|
|
"fmov (a1+), fs15\n"
|
|
"fmov (a1+), fs16\n"
|
|
"fmov (a1+), fs17\n"
|
|
"fmov (a1+), fs18\n"
|
|
"fmov (a1+), fs19\n"
|
|
"fmov (a1+), fs20\n"
|
|
"fmov (a1+), fs21\n"
|
|
"fmov (a1+), fs22\n"
|
|
"fmov (a1+), fs23\n"
|
|
"fmov (a1+), fs24\n"
|
|
"fmov (a1+), fs25\n"
|
|
"fmov (a1+), fs26\n"
|
|
"fmov (a1+), fs27\n"
|
|
"fmov (a1+), fs28\n"
|
|
"fmov (a1+), fs29\n"
|
|
"fmov (a1+), fs30\n"
|
|
"fmov (a1+), fs31\n"
|
|
"fmov %2, fpcr\n"
|
|
:
|
|
: "g" (&gdbstub_fpufs_array), "i"(EPSW_FE), "d"(gdbstub_fpcr)
|
|
: "a1"
|
|
);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* set a software breakpoint
|
|
*/
|
|
int gdbstub_set_breakpoint(u8 *addr, int len)
|
|
{
|
|
int bkpt, loop, xloop;
|
|
|
|
#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT
|
|
len = (len + 1) & ~1;
|
|
#endif
|
|
|
|
gdbstub_bkpt("setbkpt(%p,%d)\n", addr, len);
|
|
|
|
for (bkpt = 255; bkpt >= 0; bkpt--)
|
|
if (!gdbstub_bkpts[bkpt].addr)
|
|
break;
|
|
if (bkpt < 0)
|
|
return -ENOSPC;
|
|
|
|
for (loop = 0; loop < len; loop++)
|
|
if (gdbstub_read_byte(&addr[loop],
|
|
&gdbstub_bkpts[bkpt].origbytes[loop]
|
|
) < 0)
|
|
return -EFAULT;
|
|
|
|
gdbstub_flush_caches = 1;
|
|
|
|
#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT
|
|
for (loop = 0; loop < len; loop++)
|
|
if (gdbstub_write_byte(0xF7, &addr[loop]) < 0)
|
|
goto restore;
|
|
#else
|
|
for (loop = 0; loop < len; loop++)
|
|
if (gdbstub_write_byte(0xFF, &addr[loop]) < 0)
|
|
goto restore;
|
|
#endif
|
|
|
|
gdbstub_bkpts[bkpt].addr = addr;
|
|
gdbstub_bkpts[bkpt].len = len;
|
|
|
|
gdbstub_bkpt("Set BKPT[%02x]: %p-%p {%02x%02x%02x%02x%02x%02x%02x}\n",
|
|
bkpt,
|
|
gdbstub_bkpts[bkpt].addr,
|
|
gdbstub_bkpts[bkpt].addr + gdbstub_bkpts[bkpt].len - 1,
|
|
gdbstub_bkpts[bkpt].origbytes[0],
|
|
gdbstub_bkpts[bkpt].origbytes[1],
|
|
gdbstub_bkpts[bkpt].origbytes[2],
|
|
gdbstub_bkpts[bkpt].origbytes[3],
|
|
gdbstub_bkpts[bkpt].origbytes[4],
|
|
gdbstub_bkpts[bkpt].origbytes[5],
|
|
gdbstub_bkpts[bkpt].origbytes[6]
|
|
);
|
|
|
|
return 0;
|
|
|
|
restore:
|
|
for (xloop = 0; xloop < loop; xloop++)
|
|
gdbstub_write_byte(gdbstub_bkpts[bkpt].origbytes[xloop],
|
|
addr + xloop);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/*
|
|
* clear a software breakpoint
|
|
*/
|
|
int gdbstub_clear_breakpoint(u8 *addr, int len)
|
|
{
|
|
int bkpt, loop;
|
|
|
|
#ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT
|
|
len = (len + 1) & ~1;
|
|
#endif
|
|
|
|
gdbstub_bkpt("clearbkpt(%p,%d)\n", addr, len);
|
|
|
|
for (bkpt = 255; bkpt >= 0; bkpt--)
|
|
if (gdbstub_bkpts[bkpt].addr == addr &&
|
|
gdbstub_bkpts[bkpt].len == len)
|
|
break;
|
|
if (bkpt < 0)
|
|
return -ENOENT;
|
|
|
|
gdbstub_bkpts[bkpt].addr = NULL;
|
|
|
|
gdbstub_flush_caches = 1;
|
|
|
|
for (loop = 0; loop < len; loop++)
|
|
if (gdbstub_write_byte(gdbstub_bkpts[bkpt].origbytes[loop],
|
|
addr + loop) < 0)
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function does all command processing for interfacing to gdb
|
|
* - returns 0 if the exception should be skipped, -ERROR otherwise.
|
|
*/
|
|
static int gdbstub(struct pt_regs *regs, enum exception_code excep)
|
|
{
|
|
unsigned long *stack;
|
|
unsigned long epsw, mdr;
|
|
uint32_t zero, ssp;
|
|
uint8_t broke;
|
|
char *ptr;
|
|
int sigval;
|
|
int addr;
|
|
int length;
|
|
int loop;
|
|
|
|
if (excep == EXCEP_FPU_DISABLED)
|
|
return -ENOTSUPP;
|
|
|
|
gdbstub_flush_caches = 0;
|
|
|
|
mn10300_set_gdbleds(1);
|
|
|
|
asm volatile("mov mdr,%0" : "=d"(mdr));
|
|
local_save_flags(epsw);
|
|
arch_local_change_intr_mask_level(
|
|
NUM2EPSW_IM(CONFIG_DEBUGGER_IRQ_LEVEL + 1));
|
|
|
|
gdbstub_store_fpu();
|
|
|
|
#ifdef CONFIG_GDBSTUB_IMMEDIATE
|
|
/* skip the initial pause loop */
|
|
if (regs->pc == (unsigned long) __gdbstub_pause)
|
|
regs->pc = (unsigned long) start_kernel;
|
|
#endif
|
|
|
|
/* if we were single stepping, restore the opcodes hoisted for the
|
|
* breakpoint[s] */
|
|
broke = 0;
|
|
#ifdef CONFIG_GDBSTUB_ALLOW_SINGLE_STEP
|
|
if ((step_bp[0].addr && step_bp[0].addr == (u8 *) regs->pc) ||
|
|
(step_bp[1].addr && step_bp[1].addr == (u8 *) regs->pc))
|
|
broke = 1;
|
|
|
|
__gdbstub_restore_bp();
|
|
#endif
|
|
|
|
if (gdbstub_rx_unget) {
|
|
sigval = SIGINT;
|
|
if (gdbstub_rx_unget != 3)
|
|
goto packet_waiting;
|
|
gdbstub_rx_unget = 0;
|
|
}
|
|
|
|
stack = (unsigned long *) regs->sp;
|
|
sigval = broke ? SIGTRAP : computeSignal(excep);
|
|
|
|
/* send information about a BUG() */
|
|
if (!user_mode(regs) && excep == EXCEP_SYSCALL15) {
|
|
const struct bug_entry *bug;
|
|
|
|
bug = find_bug(regs->pc);
|
|
if (bug)
|
|
goto found_bug;
|
|
length = snprintf(trans_buffer, sizeof(trans_buffer),
|
|
"BUG() at address %lx\n", regs->pc);
|
|
goto send_bug_pkt;
|
|
|
|
found_bug:
|
|
length = snprintf(trans_buffer, sizeof(trans_buffer),
|
|
"BUG() at address %lx (%s:%d)\n",
|
|
regs->pc, bug->file, bug->line);
|
|
|
|
send_bug_pkt:
|
|
ptr = output_buffer;
|
|
*ptr++ = 'O';
|
|
ptr = mem2hex(trans_buffer, ptr, length, 0);
|
|
*ptr = 0;
|
|
putpacket(output_buffer);
|
|
|
|
regs->pc -= 2;
|
|
sigval = SIGABRT;
|
|
} else if (regs->pc == (unsigned long) __gdbstub_bug_trap) {
|
|
regs->pc = regs->mdr;
|
|
sigval = SIGABRT;
|
|
}
|
|
|
|
/*
|
|
* send a message to the debugger's user saying what happened if it may
|
|
* not be clear cut (we can't map exceptions onto signals properly)
|
|
*/
|
|
if (sigval != SIGINT && sigval != SIGTRAP && sigval != SIGILL) {
|
|
static const char title[] = "Excep ", tbcberr[] = "BCBERR ";
|
|
static const char crlf[] = "\r\n";
|
|
char hx;
|
|
u32 bcberr = BCBERR;
|
|
|
|
ptr = output_buffer;
|
|
*ptr++ = 'O';
|
|
ptr = mem2hex(title, ptr, sizeof(title) - 1, 0);
|
|
|
|
hx = hex_asc_hi(excep >> 8);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_lo(excep >> 8);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_hi(excep);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_lo(excep);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
|
|
ptr = mem2hex(crlf, ptr, sizeof(crlf) - 1, 0);
|
|
*ptr = 0;
|
|
putpacket(output_buffer); /* send it off... */
|
|
|
|
/* BCBERR */
|
|
ptr = output_buffer;
|
|
*ptr++ = 'O';
|
|
ptr = mem2hex(tbcberr, ptr, sizeof(tbcberr) - 1, 0);
|
|
|
|
hx = hex_asc_hi(bcberr >> 24);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_lo(bcberr >> 24);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_hi(bcberr >> 16);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_lo(bcberr >> 16);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_hi(bcberr >> 8);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_lo(bcberr >> 8);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_hi(bcberr);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
hx = hex_asc_lo(bcberr);
|
|
ptr = pack_hex_byte(ptr, hx);
|
|
|
|
ptr = mem2hex(crlf, ptr, sizeof(crlf) - 1, 0);
|
|
*ptr = 0;
|
|
putpacket(output_buffer); /* send it off... */
|
|
}
|
|
|
|
/*
|
|
* tell the debugger that an exception has occurred
|
|
*/
|
|
ptr = output_buffer;
|
|
|
|
/*
|
|
* Send trap type (converted to signal)
|
|
*/
|
|
*ptr++ = 'T';
|
|
ptr = pack_hex_byte(ptr, sigval);
|
|
|
|
/*
|
|
* Send Error PC
|
|
*/
|
|
ptr = pack_hex_byte(ptr, GDB_REGID_PC);
|
|
*ptr++ = ':';
|
|
ptr = mem2hex(®s->pc, ptr, 4, 0);
|
|
*ptr++ = ';';
|
|
|
|
/*
|
|
* Send frame pointer
|
|
*/
|
|
ptr = pack_hex_byte(ptr, GDB_REGID_FP);
|
|
*ptr++ = ':';
|
|
ptr = mem2hex(®s->a3, ptr, 4, 0);
|
|
*ptr++ = ';';
|
|
|
|
/*
|
|
* Send stack pointer
|
|
*/
|
|
ssp = (unsigned long) (regs + 1);
|
|
ptr = pack_hex_byte(ptr, GDB_REGID_SP);
|
|
*ptr++ = ':';
|
|
ptr = mem2hex(&ssp, ptr, 4, 0);
|
|
*ptr++ = ';';
|
|
|
|
*ptr++ = 0;
|
|
putpacket(output_buffer); /* send it off... */
|
|
|
|
packet_waiting:
|
|
/*
|
|
* Wait for input from remote GDB
|
|
*/
|
|
while (1) {
|
|
output_buffer[0] = 0;
|
|
getpacket(input_buffer);
|
|
|
|
switch (input_buffer[0]) {
|
|
/* request repeat of last signal number */
|
|
case '?':
|
|
output_buffer[0] = 'S';
|
|
output_buffer[1] = hex_asc_hi(sigval);
|
|
output_buffer[2] = hex_asc_lo(sigval);
|
|
output_buffer[3] = 0;
|
|
break;
|
|
|
|
case 'd':
|
|
/* toggle debug flag */
|
|
break;
|
|
|
|
/*
|
|
* Return the value of the CPU registers
|
|
*/
|
|
case 'g':
|
|
zero = 0;
|
|
ssp = (u32) (regs + 1);
|
|
ptr = output_buffer;
|
|
ptr = mem2hex(®s->d0, ptr, 4, 0);
|
|
ptr = mem2hex(®s->d1, ptr, 4, 0);
|
|
ptr = mem2hex(®s->d2, ptr, 4, 0);
|
|
ptr = mem2hex(®s->d3, ptr, 4, 0);
|
|
ptr = mem2hex(®s->a0, ptr, 4, 0);
|
|
ptr = mem2hex(®s->a1, ptr, 4, 0);
|
|
ptr = mem2hex(®s->a2, ptr, 4, 0);
|
|
ptr = mem2hex(®s->a3, ptr, 4, 0);
|
|
|
|
ptr = mem2hex(&ssp, ptr, 4, 0); /* 8 */
|
|
ptr = mem2hex(®s->pc, ptr, 4, 0);
|
|
ptr = mem2hex(®s->mdr, ptr, 4, 0);
|
|
ptr = mem2hex(®s->epsw, ptr, 4, 0);
|
|
ptr = mem2hex(®s->lir, ptr, 4, 0);
|
|
ptr = mem2hex(®s->lar, ptr, 4, 0);
|
|
ptr = mem2hex(®s->mdrq, ptr, 4, 0);
|
|
|
|
ptr = mem2hex(®s->e0, ptr, 4, 0); /* 15 */
|
|
ptr = mem2hex(®s->e1, ptr, 4, 0);
|
|
ptr = mem2hex(®s->e2, ptr, 4, 0);
|
|
ptr = mem2hex(®s->e3, ptr, 4, 0);
|
|
ptr = mem2hex(®s->e4, ptr, 4, 0);
|
|
ptr = mem2hex(®s->e5, ptr, 4, 0);
|
|
ptr = mem2hex(®s->e6, ptr, 4, 0);
|
|
ptr = mem2hex(®s->e7, ptr, 4, 0);
|
|
|
|
ptr = mem2hex(&ssp, ptr, 4, 0);
|
|
ptr = mem2hex(®s, ptr, 4, 0);
|
|
ptr = mem2hex(®s->sp, ptr, 4, 0);
|
|
ptr = mem2hex(®s->mcrh, ptr, 4, 0); /* 26 */
|
|
ptr = mem2hex(®s->mcrl, ptr, 4, 0);
|
|
ptr = mem2hex(®s->mcvf, ptr, 4, 0);
|
|
|
|
ptr = mem2hex(&gdbstub_fpcr, ptr, 4, 0); /* 29 - FPCR */
|
|
ptr = mem2hex(&zero, ptr, 4, 0);
|
|
ptr = mem2hex(&zero, ptr, 4, 0);
|
|
for (loop = 0; loop < 32; loop++)
|
|
ptr = mem2hex(&gdbstub_fpufs_array[loop],
|
|
ptr, 4, 0); /* 32 - FS0-31 */
|
|
|
|
break;
|
|
|
|
/*
|
|
* set the value of the CPU registers - return OK
|
|
*/
|
|
case 'G':
|
|
{
|
|
const char *ptr;
|
|
|
|
ptr = &input_buffer[1];
|
|
ptr = hex2mem(ptr, ®s->d0, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->d1, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->d2, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->d3, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->a0, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->a1, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->a2, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->a3, 4, 0);
|
|
|
|
ptr = hex2mem(ptr, &ssp, 4, 0); /* 8 */
|
|
ptr = hex2mem(ptr, ®s->pc, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->mdr, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->epsw, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->lir, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->lar, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->mdrq, 4, 0);
|
|
|
|
ptr = hex2mem(ptr, ®s->e0, 4, 0); /* 15 */
|
|
ptr = hex2mem(ptr, ®s->e1, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->e2, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->e3, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->e4, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->e5, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->e6, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->e7, 4, 0);
|
|
|
|
ptr = hex2mem(ptr, &ssp, 4, 0);
|
|
ptr = hex2mem(ptr, &zero, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->sp, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->mcrh, 4, 0); /* 26 */
|
|
ptr = hex2mem(ptr, ®s->mcrl, 4, 0);
|
|
ptr = hex2mem(ptr, ®s->mcvf, 4, 0);
|
|
|
|
ptr = hex2mem(ptr, &zero, 4, 0); /* 29 - FPCR */
|
|
ptr = hex2mem(ptr, &zero, 4, 0);
|
|
ptr = hex2mem(ptr, &zero, 4, 0);
|
|
for (loop = 0; loop < 32; loop++) /* 32 - FS0-31 */
|
|
ptr = hex2mem(ptr, &zero, 4, 0);
|
|
|
|
#if 0
|
|
/*
|
|
* See if the stack pointer has moved. If so, then copy
|
|
* the saved locals and ins to the new location.
|
|
*/
|
|
unsigned long *newsp = (unsigned long *) registers[SP];
|
|
if (sp != newsp)
|
|
sp = memcpy(newsp, sp, 16 * 4);
|
|
#endif
|
|
|
|
gdbstub_strcpy(output_buffer, "OK");
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* mAA..AA,LLLL Read LLLL bytes at address AA..AA
|
|
*/
|
|
case 'm':
|
|
ptr = &input_buffer[1];
|
|
|
|
if (hexToInt(&ptr, &addr) &&
|
|
*ptr++ == ',' &&
|
|
hexToInt(&ptr, &length)
|
|
) {
|
|
if (mem2hex((char *) addr, output_buffer,
|
|
length, 1))
|
|
break;
|
|
gdbstub_strcpy(output_buffer, "E03");
|
|
} else {
|
|
gdbstub_strcpy(output_buffer, "E01");
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* MAA..AA,LLLL: Write LLLL bytes at address AA.AA
|
|
* return OK
|
|
*/
|
|
case 'M':
|
|
ptr = &input_buffer[1];
|
|
|
|
if (hexToInt(&ptr, &addr) &&
|
|
*ptr++ == ',' &&
|
|
hexToInt(&ptr, &length) &&
|
|
*ptr++ == ':'
|
|
) {
|
|
if (hex2mem(ptr, (char *) addr, length, 1))
|
|
gdbstub_strcpy(output_buffer, "OK");
|
|
else
|
|
gdbstub_strcpy(output_buffer, "E03");
|
|
|
|
gdbstub_flush_caches = 1;
|
|
} else {
|
|
gdbstub_strcpy(output_buffer, "E02");
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* cAA..AA Continue at address AA..AA(optional)
|
|
*/
|
|
case 'c':
|
|
/* try to read optional parameter, pc unchanged if no
|
|
* parm */
|
|
|
|
ptr = &input_buffer[1];
|
|
if (hexToInt(&ptr, &addr))
|
|
regs->pc = addr;
|
|
goto done;
|
|
|
|
/*
|
|
* kill the program
|
|
*/
|
|
case 'k' :
|
|
goto done; /* just continue */
|
|
|
|
/*
|
|
* Reset the whole machine (FIXME: system dependent)
|
|
*/
|
|
case 'r':
|
|
break;
|
|
|
|
/*
|
|
* Step to next instruction
|
|
*/
|
|
case 's':
|
|
/* Using the T flag doesn't seem to perform single
|
|
* stepping (it seems to wind up being caught by the
|
|
* JTAG unit), so we have to use breakpoints and
|
|
* continue instead.
|
|
*/
|
|
#ifdef CONFIG_GDBSTUB_ALLOW_SINGLE_STEP
|
|
if (gdbstub_single_step(regs) < 0)
|
|
/* ignore any fault error for now */
|
|
gdbstub_printk("unable to set single-step"
|
|
" bp\n");
|
|
goto done;
|
|
#else
|
|
gdbstub_strcpy(output_buffer, "E01");
|
|
break;
|
|
#endif
|
|
|
|
/*
|
|
* Set baud rate (bBB)
|
|
*/
|
|
case 'b':
|
|
do {
|
|
int baudrate;
|
|
|
|
ptr = &input_buffer[1];
|
|
if (!hexToInt(&ptr, &baudrate)) {
|
|
gdbstub_strcpy(output_buffer, "B01");
|
|
break;
|
|
}
|
|
|
|
if (baudrate) {
|
|
/* ACK before changing speed */
|
|
putpacket("OK");
|
|
gdbstub_io_set_baud(baudrate);
|
|
}
|
|
} while (0);
|
|
break;
|
|
|
|
/*
|
|
* Set breakpoint
|
|
*/
|
|
case 'Z':
|
|
ptr = &input_buffer[1];
|
|
|
|
if (!hexToInt(&ptr, &loop) || *ptr++ != ',' ||
|
|
!hexToInt(&ptr, &addr) || *ptr++ != ',' ||
|
|
!hexToInt(&ptr, &length)
|
|
) {
|
|
gdbstub_strcpy(output_buffer, "E01");
|
|
break;
|
|
}
|
|
|
|
/* only support software breakpoints */
|
|
gdbstub_strcpy(output_buffer, "E03");
|
|
if (loop != 0 ||
|
|
length < 1 ||
|
|
length > 7 ||
|
|
(unsigned long) addr < 4096)
|
|
break;
|
|
|
|
if (gdbstub_set_breakpoint((u8 *) addr, length) < 0)
|
|
break;
|
|
|
|
gdbstub_strcpy(output_buffer, "OK");
|
|
break;
|
|
|
|
/*
|
|
* Clear breakpoint
|
|
*/
|
|
case 'z':
|
|
ptr = &input_buffer[1];
|
|
|
|
if (!hexToInt(&ptr, &loop) || *ptr++ != ',' ||
|
|
!hexToInt(&ptr, &addr) || *ptr++ != ',' ||
|
|
!hexToInt(&ptr, &length)
|
|
) {
|
|
gdbstub_strcpy(output_buffer, "E01");
|
|
break;
|
|
}
|
|
|
|
/* only support software breakpoints */
|
|
gdbstub_strcpy(output_buffer, "E03");
|
|
if (loop != 0 ||
|
|
length < 1 ||
|
|
length > 7 ||
|
|
(unsigned long) addr < 4096)
|
|
break;
|
|
|
|
if (gdbstub_clear_breakpoint((u8 *) addr, length) < 0)
|
|
break;
|
|
|
|
gdbstub_strcpy(output_buffer, "OK");
|
|
break;
|
|
|
|
default:
|
|
gdbstub_proto("### GDB Unsupported Cmd '%s'\n",
|
|
input_buffer);
|
|
break;
|
|
}
|
|
|
|
/* reply to the request */
|
|
putpacket(output_buffer);
|
|
}
|
|
|
|
done:
|
|
/*
|
|
* Need to flush the instruction cache here, as we may
|
|
* have deposited a breakpoint, and the icache probably
|
|
* has no way of knowing that a data ref to some location
|
|
* may have changed something that is in the instruction
|
|
* cache.
|
|
* NB: We flush both caches, just to be sure...
|
|
*/
|
|
if (gdbstub_flush_caches)
|
|
debugger_local_cache_flushinv();
|
|
|
|
gdbstub_load_fpu();
|
|
mn10300_set_gdbleds(0);
|
|
if (excep == EXCEP_NMI)
|
|
NMICR = NMICR_NMIF;
|
|
|
|
touch_softlockup_watchdog();
|
|
|
|
local_irq_restore(epsw);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Determine if we hit a debugger special breakpoint that needs skipping over
|
|
* automatically.
|
|
*/
|
|
int at_debugger_breakpoint(struct pt_regs *regs)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* handle event interception
|
|
*/
|
|
asmlinkage int debugger_intercept(enum exception_code excep,
|
|
int signo, int si_code, struct pt_regs *regs)
|
|
{
|
|
static u8 notfirst = 1;
|
|
int ret;
|
|
|
|
if (gdbstub_busy)
|
|
gdbstub_printk("--> gdbstub reentered itself\n");
|
|
gdbstub_busy = 1;
|
|
|
|
if (notfirst) {
|
|
unsigned long mdr;
|
|
asm("mov mdr,%0" : "=d"(mdr));
|
|
|
|
gdbstub_entry(
|
|
"--> debugger_intercept(%p,%04x) [MDR=%lx PC=%lx]\n",
|
|
regs, excep, mdr, regs->pc);
|
|
|
|
gdbstub_entry(
|
|
"PC: %08lx EPSW: %08lx SSP: %08lx mode: %s\n",
|
|
regs->pc, regs->epsw, (unsigned long) &ret,
|
|
user_mode(regs) ? "User" : "Super");
|
|
gdbstub_entry(
|
|
"d0: %08lx d1: %08lx d2: %08lx d3: %08lx\n",
|
|
regs->d0, regs->d1, regs->d2, regs->d3);
|
|
gdbstub_entry(
|
|
"a0: %08lx a1: %08lx a2: %08lx a3: %08lx\n",
|
|
regs->a0, regs->a1, regs->a2, regs->a3);
|
|
gdbstub_entry(
|
|
"e0: %08lx e1: %08lx e2: %08lx e3: %08lx\n",
|
|
regs->e0, regs->e1, regs->e2, regs->e3);
|
|
gdbstub_entry(
|
|
"e4: %08lx e5: %08lx e6: %08lx e7: %08lx\n",
|
|
regs->e4, regs->e5, regs->e6, regs->e7);
|
|
gdbstub_entry(
|
|
"lar: %08lx lir: %08lx mdr: %08lx usp: %08lx\n",
|
|
regs->lar, regs->lir, regs->mdr, regs->sp);
|
|
gdbstub_entry(
|
|
"cvf: %08lx crl: %08lx crh: %08lx drq: %08lx\n",
|
|
regs->mcvf, regs->mcrl, regs->mcrh, regs->mdrq);
|
|
gdbstub_entry(
|
|
"threadinfo=%p task=%p)\n",
|
|
current_thread_info(), current);
|
|
} else {
|
|
notfirst = 1;
|
|
}
|
|
|
|
ret = gdbstub(regs, excep);
|
|
|
|
gdbstub_entry("<-- debugger_intercept()\n");
|
|
gdbstub_busy = 0;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* handle the GDB stub itself causing an exception
|
|
*/
|
|
asmlinkage void gdbstub_exception(struct pt_regs *regs,
|
|
enum exception_code excep)
|
|
{
|
|
unsigned long mdr;
|
|
|
|
asm("mov mdr,%0" : "=d"(mdr));
|
|
gdbstub_entry("--> gdbstub exception({%p},%04x) [MDR=%lx]\n",
|
|
regs, excep, mdr);
|
|
|
|
while ((unsigned long) regs == 0xffffffff) {}
|
|
|
|
/* handle guarded memory accesses where we know it might fault */
|
|
if (regs->pc == (unsigned) gdbstub_read_byte_guard) {
|
|
regs->pc = (unsigned) gdbstub_read_byte_cont;
|
|
goto fault;
|
|
}
|
|
|
|
if (regs->pc == (unsigned) gdbstub_read_word_guard) {
|
|
regs->pc = (unsigned) gdbstub_read_word_cont;
|
|
goto fault;
|
|
}
|
|
|
|
if (regs->pc == (unsigned) gdbstub_read_dword_guard) {
|
|
regs->pc = (unsigned) gdbstub_read_dword_cont;
|
|
goto fault;
|
|
}
|
|
|
|
if (regs->pc == (unsigned) gdbstub_write_byte_guard) {
|
|
regs->pc = (unsigned) gdbstub_write_byte_cont;
|
|
goto fault;
|
|
}
|
|
|
|
if (regs->pc == (unsigned) gdbstub_write_word_guard) {
|
|
regs->pc = (unsigned) gdbstub_write_word_cont;
|
|
goto fault;
|
|
}
|
|
|
|
if (regs->pc == (unsigned) gdbstub_write_dword_guard) {
|
|
regs->pc = (unsigned) gdbstub_write_dword_cont;
|
|
goto fault;
|
|
}
|
|
|
|
gdbstub_printk("\n### GDB stub caused an exception ###\n");
|
|
|
|
/* something went horribly wrong */
|
|
console_verbose();
|
|
show_registers(regs);
|
|
|
|
panic("GDB Stub caused an unexpected exception - can't continue\n");
|
|
|
|
/* we caught an attempt by the stub to access silly memory */
|
|
fault:
|
|
gdbstub_entry("<-- gdbstub exception() = EFAULT\n");
|
|
regs->d0 = -EFAULT;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* send an exit message to GDB
|
|
*/
|
|
void gdbstub_exit(int status)
|
|
{
|
|
unsigned char checksum;
|
|
unsigned char ch;
|
|
int count;
|
|
|
|
gdbstub_busy = 1;
|
|
output_buffer[0] = 'W';
|
|
output_buffer[1] = hex_asc_hi(status);
|
|
output_buffer[2] = hex_asc_lo(status);
|
|
output_buffer[3] = 0;
|
|
|
|
gdbstub_io_tx_char('$');
|
|
checksum = 0;
|
|
count = 0;
|
|
|
|
while ((ch = output_buffer[count]) != 0) {
|
|
gdbstub_io_tx_char(ch);
|
|
checksum += ch;
|
|
count += 1;
|
|
}
|
|
|
|
gdbstub_io_tx_char('#');
|
|
gdbstub_io_tx_char(hex_asc_hi(checksum));
|
|
gdbstub_io_tx_char(hex_asc_lo(checksum));
|
|
|
|
/* make sure the output is flushed, or else RedBoot might clobber it */
|
|
gdbstub_io_tx_flush();
|
|
|
|
gdbstub_busy = 0;
|
|
}
|
|
|
|
/*
|
|
* initialise the GDB stub
|
|
*/
|
|
asmlinkage void __init gdbstub_init(void)
|
|
{
|
|
#ifdef CONFIG_GDBSTUB_IMMEDIATE
|
|
unsigned char ch;
|
|
int ret;
|
|
#endif
|
|
|
|
gdbstub_busy = 1;
|
|
|
|
printk(KERN_INFO "%s", gdbstub_banner);
|
|
|
|
gdbstub_io_init();
|
|
|
|
gdbstub_entry("--> gdbstub_init\n");
|
|
|
|
/* try to talk to GDB (or anyone insane enough to want to type GDB
|
|
* protocol by hand) */
|
|
gdbstub_io("### GDB Tx ACK\n");
|
|
gdbstub_io_tx_char('+'); /* 'hello world' */
|
|
|
|
#ifdef CONFIG_GDBSTUB_IMMEDIATE
|
|
gdbstub_printk("GDB Stub waiting for packet\n");
|
|
|
|
/* in case GDB is started before us, ACK any packets that are already
|
|
* sitting there (presumably "$?#xx")
|
|
*/
|
|
do { gdbstub_io_rx_char(&ch, 0); } while (ch != '$');
|
|
do { gdbstub_io_rx_char(&ch, 0); } while (ch != '#');
|
|
/* eat first csum byte */
|
|
do { ret = gdbstub_io_rx_char(&ch, 0); } while (ret != 0);
|
|
/* eat second csum byte */
|
|
do { ret = gdbstub_io_rx_char(&ch, 0); } while (ret != 0);
|
|
|
|
gdbstub_io("### GDB Tx NAK\n");
|
|
gdbstub_io_tx_char('-'); /* NAK it */
|
|
|
|
#else
|
|
printk("GDB Stub ready\n");
|
|
#endif
|
|
|
|
gdbstub_busy = 0;
|
|
gdbstub_entry("<-- gdbstub_init\n");
|
|
}
|
|
|
|
/*
|
|
* register the console at a more appropriate time
|
|
*/
|
|
#ifdef CONFIG_GDBSTUB_CONSOLE
|
|
static int __init gdbstub_postinit(void)
|
|
{
|
|
printk(KERN_NOTICE "registering console\n");
|
|
register_console(&gdbstub_console);
|
|
return 0;
|
|
}
|
|
|
|
__initcall(gdbstub_postinit);
|
|
#endif
|
|
|
|
/*
|
|
* handle character reception on GDB serial port
|
|
* - jump into the GDB stub if BREAK is detected on the serial line
|
|
*/
|
|
asmlinkage void gdbstub_rx_irq(struct pt_regs *regs, enum exception_code excep)
|
|
{
|
|
char ch;
|
|
int ret;
|
|
|
|
gdbstub_entry("--> gdbstub_rx_irq\n");
|
|
|
|
do {
|
|
ret = gdbstub_io_rx_char(&ch, 1);
|
|
if (ret != -EIO && ret != -EAGAIN) {
|
|
if (ret != -EINTR)
|
|
gdbstub_rx_unget = ch;
|
|
gdbstub(regs, excep);
|
|
}
|
|
} while (ret != -EAGAIN);
|
|
|
|
gdbstub_entry("<-- gdbstub_rx_irq\n");
|
|
}
|