724 lines
18 KiB
C
724 lines
18 KiB
C
/*
|
|
* Copyright (C) 1996-1998 by the Board of Trustees
|
|
* of Leland Stanford Junior University.
|
|
*
|
|
* This file is part of the SimOS distribution.
|
|
* See LICENSE file for terms of the license.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) yajin 2008 <yajinzhou@gmail.com >
|
|
*
|
|
* This file is part of the virtualmips distribution.
|
|
* See LICENSE file for terms of the license.
|
|
*
|
|
*/
|
|
|
|
/*GDB interface for virtualmips based on simos*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <memory.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/signal.h>
|
|
#include <sys/types.h>
|
|
#include <assert.h>
|
|
|
|
#include "vm.h"
|
|
#include "mips.h"
|
|
#include "gdb_interface.h"
|
|
#include "utils.h"
|
|
#include "debug.h"
|
|
|
|
#define GDB_NUM_REGS 72 //general cpu register and cp0 reegister
|
|
#define VCONT "vCont"
|
|
#define QSYMBOL "qSymbol"
|
|
#define QC "qC"
|
|
#define QOFFSETS "qOffsets"
|
|
|
|
int delete_breakpoint (vm_instance_t * vm, m_uint32_t addr, int len)
|
|
{
|
|
virtual_breakpoint_t *tmp, *prev = 0;
|
|
|
|
tmp = vm->breakpoint_head;
|
|
|
|
// addr = SIGN_EXTEND(32,addr);
|
|
while (tmp) {
|
|
if (tmp->addr == addr) {
|
|
/*find it */
|
|
if (prev)
|
|
prev->next = tmp->next;
|
|
|
|
if (vm->breakpoint_head == tmp)
|
|
vm->breakpoint_head = tmp->next;
|
|
|
|
if (vm->breakpoint_tail == tmp)
|
|
vm->breakpoint_tail = prev;
|
|
|
|
if (tmp->save)
|
|
free (tmp->save);
|
|
free (tmp);
|
|
|
|
goto exit;
|
|
}
|
|
|
|
prev = tmp;
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
exit:
|
|
return SUCCESS;
|
|
}
|
|
|
|
virtual_breakpoint_t *alloc_breakpoint (vm_instance_t * vm, m_uint32_t addr,
|
|
char *save, int len)
|
|
{
|
|
virtual_breakpoint_t *tmp;
|
|
|
|
tmp = malloc (sizeof (virtual_breakpoint_t));
|
|
if (!tmp)
|
|
return NULL;
|
|
|
|
memset (tmp, 0, sizeof (*tmp));
|
|
tmp->addr = (addr);
|
|
|
|
if (save) {
|
|
tmp->save = malloc (strlen (save) + 1);
|
|
strcpy (tmp->save, save);
|
|
}
|
|
//tmp->cpuno = cpuno;
|
|
tmp->len = len;
|
|
tmp->next = 0;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
int insert_breakpoint (vm_instance_t * vm, virtual_breakpoint_t * new)
|
|
{
|
|
virtual_breakpoint_t *tmp;
|
|
|
|
if (vm->breakpoint_head == 0) {
|
|
vm->breakpoint_tail = vm->breakpoint_head = new;
|
|
} else {
|
|
tmp = vm->breakpoint_head;
|
|
|
|
while (tmp) {
|
|
if (tmp->addr == new->addr) {
|
|
/*the same one */
|
|
if (new->save)
|
|
free (new->save);
|
|
free (new);
|
|
goto exit;
|
|
}
|
|
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
vm->breakpoint_tail->next = new;
|
|
vm->breakpoint_tail = new;
|
|
}
|
|
|
|
exit:
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int fromhex (int a)
|
|
{
|
|
if (a >= '0' && a <= '9')
|
|
return a - '0';
|
|
else if (a >= 'a' && a <= 'f')
|
|
return a - 'a' + 10;
|
|
else {
|
|
fprintf (stderr, "Request contains invalid hex digit : %s %d\n",
|
|
__FILE__, __LINE__);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Convert number NIB to a hex digit. */
|
|
|
|
static int tohex (int nib)
|
|
{
|
|
if (nib < 10)
|
|
return '0' + nib;
|
|
else
|
|
return 'a' + nib - 10;
|
|
}
|
|
|
|
#if 0
|
|
static int bin2hex (const char *bin, char *hex, int count)
|
|
{
|
|
int i;
|
|
/* May use a length, or a nul-terminated string as input. */
|
|
if (count == 0)
|
|
count = strlen (bin);
|
|
|
|
for (i = 0; i < count; i++) {
|
|
*hex++ = tohex ((*bin >> 4) & 0xf);
|
|
*hex++ = tohex (*bin++ & 0xf);
|
|
}
|
|
*hex = 0;
|
|
return i;
|
|
}
|
|
|
|
#endif
|
|
|
|
static void convert_int_to_ascii (char *from, char *to, int n)
|
|
{
|
|
int nib;
|
|
char ch;
|
|
while (n--) {
|
|
ch = *from++;
|
|
nib = ((ch & 0xf0) >> 4) & 0x0f;
|
|
*to++ = tohex (nib);
|
|
nib = ch & 0x0f;
|
|
*to++ = tohex (nib);
|
|
}
|
|
*to++ = 0;
|
|
}
|
|
|
|
/*****************************************************************
|
|
* readchar
|
|
* Returns next char from remote GDB. -1 if error.
|
|
*****************************************************************/
|
|
static int readchar (int fd)
|
|
{
|
|
static char buf[BUFSIZ];
|
|
static int bufcnt = 0;
|
|
static char *bufp;
|
|
|
|
if (bufcnt-- > 0)
|
|
return *bufp++ & 0x7f;
|
|
|
|
bufcnt = read (fd, buf, BUFSIZ);
|
|
if (bufcnt <= 0) {
|
|
printf ("Simdebug readchar");
|
|
return -1;
|
|
}
|
|
|
|
bufp = buf;
|
|
bufcnt--;
|
|
return *bufp++ & 0x7f;
|
|
}
|
|
|
|
/*****************************************************************
|
|
* getpkt
|
|
*
|
|
* Read a packet from the remote machine, with error checking,
|
|
* and store it in BUF. Returns length of packet, or negative if error.
|
|
*****************************************************************/
|
|
static int getpkt (int fd, char *buf)
|
|
{
|
|
char *bp;
|
|
unsigned char csum, c1, c2;
|
|
int c;
|
|
|
|
bp = buf;
|
|
while (1) {
|
|
csum = 0;
|
|
|
|
while (1) {
|
|
c = readchar (fd);
|
|
if (c == '$')
|
|
break;
|
|
if (c < 0)
|
|
goto errout;
|
|
}
|
|
|
|
bp = buf;
|
|
while (1) {
|
|
c = readchar (fd);
|
|
if (c < 0)
|
|
goto errout;
|
|
|
|
if (c == '#')
|
|
break;
|
|
|
|
*bp++ = c;
|
|
csum += c;
|
|
}
|
|
*bp = 0;
|
|
|
|
c = readchar (fd);
|
|
if (c < 0)
|
|
goto errout;
|
|
c1 = fromhex (c);
|
|
|
|
c = readchar (fd);
|
|
if (c < 0)
|
|
goto errout;
|
|
c2 = fromhex (c);
|
|
|
|
if (csum == (c1 << 4) + c2)
|
|
break;
|
|
|
|
if (write (fd, "-", 1) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (write (fd, "+", 1) < 0)
|
|
return -1;
|
|
return bp - buf;
|
|
|
|
errout:
|
|
return -1;
|
|
}
|
|
|
|
static void write_ok (char *buf)
|
|
{
|
|
buf[0] = 'O';
|
|
buf[1] = 'K';
|
|
buf[2] = '\0';
|
|
}
|
|
|
|
static void write_enn (char *buf)
|
|
{
|
|
buf[0] = 'E';
|
|
#if 0
|
|
buf[1] = 'N';
|
|
buf[2] = 'N';
|
|
#endif
|
|
|
|
buf[1] = '0';
|
|
buf[2] = '1';
|
|
buf[3] = '\0';
|
|
}
|
|
|
|
/* Send a packet to the remote machine, with error checking.
|
|
The data of the packet is in BUF. Returns >= 0 on success, -1 otherwise. */
|
|
|
|
static int putpkt (int fd, char *buf)
|
|
{
|
|
//printf("putpkt\n");
|
|
int i;
|
|
unsigned char csum = 0;
|
|
char buf2[2000];
|
|
char buf3[1];
|
|
int cnt = strlen (buf);
|
|
char *p;
|
|
|
|
/* Copy the packet into buffer BUF2, encapsulating it
|
|
* and giving it a checksum. */
|
|
|
|
p = buf2;
|
|
*p++ = '$';
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
csum += buf[i];
|
|
*p++ = buf[i];
|
|
}
|
|
|
|
*p++ = '#';
|
|
*p++ = tohex ((csum >> 4) & 0xf);
|
|
*p++ = tohex (csum & 0xf);
|
|
|
|
/* Send it over and over[A until we get a positive ack. */
|
|
|
|
do {
|
|
int cc;
|
|
|
|
if (write (fd, buf2, p - buf2) != p - buf2) {
|
|
printf ("putpkt(write)\n");
|
|
goto errout;
|
|
}
|
|
|
|
cc = read (fd, buf3, 1);
|
|
if (cc <= 0) {
|
|
printf ("putpkt(read)\n");
|
|
goto errout;
|
|
}
|
|
}
|
|
while (buf3[0] != '+');
|
|
|
|
return 1; /* Success! */
|
|
|
|
errout:
|
|
return -1;
|
|
}
|
|
|
|
static void attach_gdb (vm_instance_t * vm)
|
|
{
|
|
struct sockaddr_in sockaddr1;
|
|
unsigned int tmp;
|
|
int gdb_fd;
|
|
|
|
if (!vm->gdb_debug_from_poll)
|
|
printf ("Waiting for gdb on port %d.\n", vm->gdb_port);
|
|
|
|
tmp = sizeof (sockaddr1);
|
|
gdb_fd =
|
|
accept (vm->gdb_listen_sock, (struct sockaddr *) &sockaddr1, &tmp);
|
|
vm->gdb_interact_sock = gdb_fd;
|
|
if (gdb_fd == -1) {
|
|
printf ("Debugger accept failed\n");
|
|
return;
|
|
}
|
|
|
|
if (!vm->gdb_debug_from_poll)
|
|
printf ("GDB attached. Enjoy yourself!\n");
|
|
|
|
/* Tell TCP not to delay small packets. This greatly speeds up
|
|
* interactive response. */
|
|
|
|
tmp = 1;
|
|
setsockopt (gdb_fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &tmp,
|
|
sizeof (tmp));
|
|
|
|
tmp = 1;
|
|
setsockopt (gdb_fd, IPPROTO_TCP, TCP_NODELAY, (char *) &tmp,
|
|
sizeof (tmp));
|
|
|
|
// int save_fcntl_flags;
|
|
#if defined(F_SETFL) && defined (FASYNC)
|
|
save_fcntl_flags = fcntl (gdb_fd, F_GETFL, 0);
|
|
fcntl (gdb_fd, F_SETFL, save_fcntl_flags | FASYNC);
|
|
#if defined (F_SETOWN)
|
|
fcntl (gdb_fd, F_SETOWN, getpid ());
|
|
#endif
|
|
#endif
|
|
|
|
signal (SIGIO, SIG_IGN);
|
|
|
|
// return gdb_fd;
|
|
}
|
|
|
|
#define MAX_XFER (8*1024)
|
|
#define WORD_SZ 4
|
|
#define BYTE_SZ 1
|
|
|
|
int debug_read_memory (cpu_mips_t * cpu, m_uint32_t vaddr, unsigned int len,
|
|
char *buf)
|
|
{
|
|
//void *haddr=NULL;
|
|
//m_uint32_t exc;
|
|
//m_uint32_t data;
|
|
//m_uint8_t has_set_value=FALSE;
|
|
|
|
char readout_buf[MAX_XFER];
|
|
if (len > MAX_XFER) {
|
|
fprintf (stderr, "read_memory: too many bytes requested");
|
|
}
|
|
assert (len <= MAX_XFER);
|
|
|
|
int reallen = len;
|
|
char *cur = readout_buf;
|
|
int i = 0;
|
|
|
|
//We read one byte once to avoid align problem
|
|
for (i = 0; i < (reallen / BYTE_SZ); i++) {
|
|
cpu->mips_mts_gdb_lb (cpu, vaddr, cur);
|
|
|
|
cur += BYTE_SZ;
|
|
vaddr += BYTE_SZ;
|
|
}
|
|
|
|
convert_int_to_ascii (readout_buf, buf, len);
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
int forced_inline cpu_hit_breakpoint (vm_instance_t * vm, m_uint32_t vaddr)
|
|
{
|
|
virtual_breakpoint_t *tmp = vm->breakpoint_head;
|
|
static char cmd_buf[BUFSIZ + 1];
|
|
|
|
while (tmp) {
|
|
if (tmp->addr == vaddr) {
|
|
sprintf (cmd_buf, "S%02x", SIGTRAP);
|
|
if (putpkt (vm->gdb_interact_sock, cmd_buf) < 0) {
|
|
/* the connection was terminated prematurely. Reset */
|
|
close (vm->gdb_interact_sock);
|
|
vm->gdb_interact_sock = -1;
|
|
vm->mipsy_debug_mode = 0; //we do not debug anymore
|
|
printf ("Remote debugger connection lost, continuing...\n");
|
|
return FAILURE;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
tmp = tmp->next;
|
|
}
|
|
return FAILURE;
|
|
}
|
|
|
|
/* Convert hex digit A to a number. */
|
|
|
|
Simdebug_result Simdebug_run (vm_instance_t * vm, int sig)
|
|
{
|
|
static char cmd_buf[BUFSIZ + 1];
|
|
char *b;
|
|
int i = 0;
|
|
Simdebug_result ret = SD_CONTINUE;
|
|
|
|
if (vm->gdb_interact_sock <= 0) {
|
|
attach_gdb (vm);
|
|
if (vm->gdb_interact_sock < 0) {
|
|
return SD_CONTINUE; /* could not get debugger */
|
|
}
|
|
vm->gdb_debug_from_poll = 0; /* whether or not we entered from poll doesn't
|
|
* matter this time, but we'd better reset it
|
|
* before next time this function runs
|
|
*/
|
|
} else {
|
|
/* reestablish an old connection. We might get here either
|
|
* for internal reasons (hitting a breakpoint) or for external
|
|
* reasons (gdb sent us a ^C). If external, we need to swallow
|
|
* the break packet. Then in either case, let GDB know why
|
|
* we stopped.
|
|
*/
|
|
|
|
if (vm->gdb_debug_from_poll) {
|
|
vm->gdb_debug_from_poll = 0;
|
|
int cc;
|
|
char c;
|
|
|
|
cc = read (vm->gdb_interact_sock, &c, 1);
|
|
|
|
if (cc != 1 || c != '\003') {
|
|
fprintf (stderr, "input_interrupt, cc = %d c = %d\n", cc, c);
|
|
goto errout;
|
|
}
|
|
|
|
sig = SIGINT;
|
|
/* Inform the remote debugger we have entered debugging mode. */
|
|
// sprintf (cmd_buf, "S%02x%02x", sig, debug_cpuno);
|
|
sprintf (cmd_buf, "S%02x", sig);
|
|
if (putpkt (vm->gdb_interact_sock, cmd_buf) < 0)
|
|
goto errout;
|
|
|
|
}
|
|
}
|
|
|
|
while (1) {
|
|
char ch;
|
|
i = 0;
|
|
int pkg_len = 0;
|
|
|
|
if ((pkg_len = getpkt (vm->gdb_interact_sock, cmd_buf)) <= 0)
|
|
goto errout;
|
|
ch = cmd_buf[i++];
|
|
SIM_DEBUG (('g', "gdb command %c \n", ch));
|
|
//printf("---\n gdb command %s\n",cmd_buf);
|
|
|
|
switch (ch) {
|
|
case 'c':
|
|
ret = SD_CONTINUE;
|
|
goto out;
|
|
case 'g': //read register
|
|
{
|
|
m_uint32_t r = 0;
|
|
b = cmd_buf;
|
|
for (i = 0; i < GDB_NUM_REGS; i++) {
|
|
//FIXME: find the mapping from register index to register of cpu and cpu0 of gdb
|
|
if (vm->boot_cpu->reg_get (vm->boot_cpu, i,
|
|
&r) == FAILURE)
|
|
r = 0;
|
|
/*
|
|
* flip the bytes around
|
|
*/
|
|
r = htovm32 (r); //gdb likes the data of target machine format
|
|
convert_int_to_ascii ((char *) &r, b, 4);
|
|
|
|
b += 8;
|
|
}
|
|
*b = 0;
|
|
break;
|
|
}
|
|
|
|
case 'H':
|
|
write_ok (cmd_buf);
|
|
break;
|
|
case 'm':
|
|
{
|
|
m_uint32_t memaddr;
|
|
unsigned int len;
|
|
|
|
if (sscanf (cmd_buf + 1, "%x,%x", &memaddr, &len) != 2) {
|
|
write_enn (cmd_buf);
|
|
} else {
|
|
|
|
if (debug_read_memory (vm->boot_cpu, memaddr, len,
|
|
cmd_buf) != SUCCESS)
|
|
write_enn (cmd_buf);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'p':
|
|
{
|
|
m_uint32_t r, index = 0;
|
|
b = cmd_buf;
|
|
if (sscanf (cmd_buf + 1, "%x", &index) != 1)
|
|
write_enn (cmd_buf);
|
|
else {
|
|
// index=vmtoh32(index);
|
|
if (vm->boot_cpu->reg_get (vm->boot_cpu, index,
|
|
&r) == FAILURE)
|
|
r = 0;
|
|
/*
|
|
* flip the bytes around
|
|
*/
|
|
r = htovm32 (r); //gdb likes the data of target machine format
|
|
convert_int_to_ascii ((char *) &r, b, 4);
|
|
//*(r+4)=0;
|
|
//*(r+5)=0;
|
|
//*(r+6)=0;
|
|
//*(r+7)=0;
|
|
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
case 'v':
|
|
{
|
|
char *cmd_tmp;
|
|
|
|
cmd_tmp = cmd_buf;
|
|
|
|
if (strncmp (cmd_buf, VCONT, strlen (VCONT)) != 0)
|
|
write_enn (cmd_buf);
|
|
else {
|
|
cmd_tmp += strlen (VCONT);
|
|
if (';' == cmd_tmp[0]) {
|
|
cmd_tmp++;
|
|
if ('c' == cmd_tmp[0]) {
|
|
ret = SD_CONTINUE;
|
|
goto out;
|
|
} else if ('s' == cmd_tmp[0]) {
|
|
/*single step */
|
|
int cpuno;
|
|
cpuno = 0;
|
|
|
|
ret = cpuno;
|
|
|
|
goto out;
|
|
} else
|
|
write_enn (cmd_buf);
|
|
} else if ('?' == (cmd_tmp)[0]) {
|
|
/*query */
|
|
strcpy (cmd_buf, VCONT ";c");
|
|
} else
|
|
write_enn (cmd_buf);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'q':
|
|
{
|
|
if (strncmp (cmd_buf, QSYMBOL, strlen (QSYMBOL)) == 0)
|
|
write_ok (cmd_buf);
|
|
else if (strncmp (cmd_buf, QC, strlen (QC)) == 0) {
|
|
strcpy (cmd_buf, "QC0");
|
|
} else if (strncmp (cmd_buf, QOFFSETS,
|
|
strlen (QOFFSETS)) == 0) {
|
|
strcpy (cmd_buf, "Text=0;Data=0;Bss=0");
|
|
} else {
|
|
write_enn (cmd_buf);
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
case 'S':
|
|
ret = SD_NEXTI_ANYCPU;
|
|
goto out;
|
|
|
|
case 'z':
|
|
{
|
|
m_uint32_t addr;
|
|
int len;
|
|
if ('0' == (cmd_buf + 1)[0]) {
|
|
//memory breakpoint
|
|
if (sscanf (cmd_buf + 3, "%x,%x", &addr, &len) != 2) {
|
|
|
|
write_enn (cmd_buf);
|
|
}
|
|
|
|
else {
|
|
if (delete_breakpoint (vm, addr, len) != SUCCESS)
|
|
write_enn (cmd_buf);
|
|
else {
|
|
|
|
write_ok (cmd_buf);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
write_enn (cmd_buf);
|
|
|
|
}
|
|
break;
|
|
|
|
case 'Z':
|
|
{
|
|
m_uint32_t addr;
|
|
int len;
|
|
if ('0' == (cmd_buf + 1)[0]) {
|
|
//memory breakpoint
|
|
if (sscanf (cmd_buf + 3, "%x,%x", &addr, &len) != 2) {
|
|
|
|
write_enn (cmd_buf);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
virtual_breakpoint_t *tmp =
|
|
alloc_breakpoint (vm, addr, 0, len);
|
|
insert_breakpoint (vm, tmp);
|
|
write_ok (cmd_buf);
|
|
|
|
}
|
|
} else
|
|
write_enn (cmd_buf);
|
|
|
|
}
|
|
break;
|
|
case '?':
|
|
sprintf (cmd_buf, "S%02x", sig);
|
|
break;
|
|
default:
|
|
write_enn (cmd_buf);
|
|
break;
|
|
|
|
}
|
|
//printf("send %s\n",cmd_buf);
|
|
if (putpkt (vm->gdb_interact_sock, cmd_buf) < 0)
|
|
goto errout;
|
|
|
|
}
|
|
out:
|
|
return ret;
|
|
errout:
|
|
/* the connection was terminated prematurely. Reset */
|
|
close (vm->gdb_interact_sock);
|
|
vm->gdb_interact_sock = -1;
|
|
printf ("Remote debugger connection lost, continuing...\n");
|
|
return SD_CONTINUE;
|
|
}
|
|
|
|
void bad_memory_access_gdb (vm_instance_t * vm)
|
|
{
|
|
static char cmd_buf[BUFSIZ + 1];
|
|
|
|
vm->mipsy_break_nexti = MIPS_BREAKANYCPU;
|
|
sprintf (cmd_buf, "S%02x", SIGTRAP);
|
|
if (putpkt (vm->gdb_interact_sock, cmd_buf) < 0) {
|
|
/* the connection was terminated prematurely. Reset */
|
|
close (vm->gdb_interact_sock);
|
|
vm->gdb_interact_sock = -1;
|
|
vm->mipsy_debug_mode = 0; //we do not debug anymore
|
|
printf ("Remote debugger connection lost, continuing...\n");
|
|
}
|
|
}
|