summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2005-04-17 19:16:13 +0000
committerbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2005-04-17 19:16:13 +0000
commit1fddef4b1ba3bf14d36472475019a4a6acd4d976 (patch)
tree4cef1b563a49637d0bd0cacc5ec4c78c32e953de
parent6e4255f6a65091fbe7d17bfda546e2aa1b72f9a6 (diff)
downloadqemu-1fddef4b1ba3bf14d36472475019a4a6acd4d976.tar.gz
gdb support for user mode (Paul Brook)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1367 c046a42c-6fe2-441c-8c8c-71466251a162
-rw-r--r--Makefile.target3
-rw-r--r--exec.c12
-rw-r--r--gdbstub.c152
-rw-r--r--gdbstub.h11
-rw-r--r--linux-user/main.c69
-rw-r--r--linux-user/qemu.h1
-rw-r--r--linux-user/signal.c6
-rw-r--r--target-arm/cpu.h7
-rw-r--r--target-arm/op.c6
-rw-r--r--target-arm/translate.c14
-rw-r--r--target-i386/cpu.h2
-rw-r--r--target-ppc/cpu.h2
-rw-r--r--target-sparc/cpu.h2
-rw-r--r--vl.h7
14 files changed, 265 insertions, 29 deletions
diff --git a/Makefile.target b/Makefile.target
index 16f649637d..789848e531 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -298,6 +298,9 @@ endif
ifeq ($(ARCH),ia64)
OBJS += ia64-syscall.o
endif
+ifdef CONFIG_GDBSTUB
+OBJS+=gdbstub.o
+endif
all: $(PROGS)
diff --git a/exec.c b/exec.c
index 264775e1e5..f923043763 100644
--- a/exec.c
+++ b/exec.c
@@ -1076,7 +1076,7 @@ static void tb_reset_jump_recursive(TranslationBlock *tb)
tb_reset_jump_recursive2(tb, 1);
}
-#if defined(TARGET_I386) || defined(TARGET_PPC) || defined(TARGET_SPARC)
+#if defined(TARGET_HAS_ICE)
static void breakpoint_invalidate(CPUState *env, target_ulong pc)
{
target_ulong phys_addr;
@@ -1090,7 +1090,7 @@ static void breakpoint_invalidate(CPUState *env, target_ulong pc)
breakpoint is reached */
int cpu_breakpoint_insert(CPUState *env, target_ulong pc)
{
-#if defined(TARGET_I386) || defined(TARGET_PPC) || defined(TARGET_SPARC)
+#if defined(TARGET_HAS_ICE)
int i;
for(i = 0; i < env->nb_breakpoints; i++) {
@@ -1112,7 +1112,7 @@ int cpu_breakpoint_insert(CPUState *env, target_ulong pc)
/* remove a breakpoint */
int cpu_breakpoint_remove(CPUState *env, target_ulong pc)
{
-#if defined(TARGET_I386) || defined(TARGET_PPC) || defined(TARGET_SPARC)
+#if defined(TARGET_HAS_ICE)
int i;
for(i = 0; i < env->nb_breakpoints; i++) {
if (env->breakpoints[i] == pc)
@@ -1120,9 +1120,9 @@ int cpu_breakpoint_remove(CPUState *env, target_ulong pc)
}
return -1;
found:
- memmove(&env->breakpoints[i], &env->breakpoints[i + 1],
- (env->nb_breakpoints - (i + 1)) * sizeof(env->breakpoints[0]));
env->nb_breakpoints--;
+ if (i < env->nb_breakpoints)
+ env->breakpoints[i] = env->breakpoints[env->nb_breakpoints];
breakpoint_invalidate(env, pc);
return 0;
@@ -1135,7 +1135,7 @@ int cpu_breakpoint_remove(CPUState *env, target_ulong pc)
CPU loop after each instruction */
void cpu_single_step(CPUState *env, int enabled)
{
-#if defined(TARGET_I386) || defined(TARGET_PPC) || defined(TARGET_SPARC)
+#if defined(TARGET_HAS_ICE)
if (env->singlestep_enabled != enabled) {
env->singlestep_enabled = enabled;
/* must flush all the translated code to avoid inconsistancies */
diff --git a/gdbstub.c b/gdbstub.c
index 1e95c7c08a..5dc93c4570 100644
--- a/gdbstub.c
+++ b/gdbstub.c
@@ -17,7 +17,18 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#ifdef CONFIG_USER_ONLY
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "qemu.h"
+#else
#include "vl.h"
+#endif
#include <sys/socket.h>
#include <netinet/in.h>
@@ -31,9 +42,10 @@ enum RSState {
RS_GETLINE,
RS_CHKSUM1,
RS_CHKSUM2,
+ RS_CONTINUE
};
-
-static int gdbserver_fd;
+/* XXX: This is not thread safe. Do we care? */
+static int gdbserver_fd = -1;
typedef struct GDBState {
enum RSState state;
@@ -43,6 +55,11 @@ typedef struct GDBState {
int line_csum;
} GDBState;
+#ifdef CONFIG_USER_ONLY
+/* XXX: remove this hack. */
+static GDBState gdbserver_state;
+#endif
+
static int get_char(GDBState *s)
{
uint8_t ch;
@@ -330,8 +347,47 @@ static void cpu_gdb_write_registers(CPUState *env, uint8_t *mem_buf, int size)
env->npc = tswapl(registers[69]);
env->fsr = tswapl(registers[70]);
}
-#else
+#elif defined (TARGET_ARM)
+static int cpu_gdb_read_registers(CPUState *env, uint8_t *mem_buf)
+{
+ int i;
+ uint8_t *ptr;
+
+ ptr = mem_buf;
+ /* 16 core integer registers (4 bytes each). */
+ for (i = 0; i < 16; i++)
+ {
+ *(uint32_t *)ptr = tswapl(env->regs[i]);
+ ptr += 4;
+ }
+ /* 8 FPA registers (12 bytes each), FPS (4 bytes).
+ Not yet implemented. */
+ memset (ptr, 0, 8 * 12 + 4);
+ ptr += 8 * 12 + 4;
+ /* CPSR (4 bytes). */
+ *(uint32_t *)ptr = tswapl (env->cpsr);
+ ptr += 4;
+
+ return ptr - mem_buf;
+}
+static void cpu_gdb_write_registers(CPUState *env, uint8_t *mem_buf, int size)
+{
+ int i;
+ uint8_t *ptr;
+
+ ptr = mem_buf;
+ /* Core integer registers. */
+ for (i = 0; i < 16; i++)
+ {
+ env->regs[i] = tswapl(*(uint32_t *)ptr);
+ ptr += 4;
+ }
+ /* Ignore FPA regs and scr. */
+ ptr += 8 * 12 + 4;
+ env->cpsr = tswapl(*(uint32_t *)ptr);
+}
+#else
static int cpu_gdb_read_registers(CPUState *env, uint8_t *mem_buf)
{
return 0;
@@ -343,10 +399,8 @@ static void cpu_gdb_write_registers(CPUState *env, uint8_t *mem_buf, int size)
#endif
-/* port = 0 means default port */
-static int gdb_handle_packet(GDBState *s, const char *line_buf)
+static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
{
- CPUState *env = cpu_single_env;
const char *p;
int ch, reg_size, type;
char buf[4096];
@@ -361,6 +415,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf)
ch = *p++;
switch(ch) {
case '?':
+ /* TODO: Make this return the correct value for user-mode. */
snprintf(buf, sizeof(buf), "S%02x", SIGTRAP);
put_packet(s, buf);
break;
@@ -376,8 +431,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf)
env->npc = addr + 4;
#endif
}
- vm_start();
- break;
+ return RS_CONTINUE;
case 's':
if (*p != '\0') {
addr = strtoul(p, (char **)&p, 16);
@@ -391,8 +445,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf)
#endif
}
cpu_single_step(env, 1);
- vm_start();
- break;
+ return RS_CONTINUE;
case 'g':
reg_size = cpu_gdb_read_registers(env, mem_buf);
memtohex(buf, mem_buf, reg_size);
@@ -472,6 +525,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf)
extern void tb_flush(CPUState *env);
+#ifndef CONFIG_USER_ONLY
static void gdb_vm_stopped(void *opaque, int reason)
{
GDBState *s = opaque;
@@ -490,17 +544,20 @@ static void gdb_vm_stopped(void *opaque, int reason)
snprintf(buf, sizeof(buf), "S%02x", ret);
put_packet(s, buf);
}
+#endif
-static void gdb_read_byte(GDBState *s, int ch)
+static void gdb_read_byte(GDBState *s, CPUState *env, int ch)
{
int i, csum;
char reply[1];
+#ifndef CONFIG_USER_ONLY
if (vm_running) {
/* when the CPU is running, we cannot do anything except stop
it when receiving a char */
vm_stop(EXCP_INTERRUPT);
} else {
+#endif
switch(s->state) {
case RS_IDLE:
if (ch == '$') {
@@ -535,13 +592,67 @@ static void gdb_read_byte(GDBState *s, int ch)
} else {
reply[0] = '+';
put_buffer(s, reply, 1);
- s->state = gdb_handle_packet(s, s->line_buf);
+ s->state = gdb_handle_packet(s, env, s->line_buf);
}
break;
+ case RS_CONTINUE:
+#ifndef CONFIG_USER_ONLY
+ vm_start();
+ s->state = RS_IDLE;
+#endif
+ break;
}
+#ifndef CONFIG_USER_ONLY
}
+#endif
}
+#ifdef CONFIG_USER_ONLY
+int
+gdb_handlesig (CPUState *env, int sig)
+{
+ GDBState *s;
+ char buf[256];
+ int n;
+
+ if (gdbserver_fd < 0)
+ return sig;
+
+ s = &gdbserver_state;
+
+ /* disable single step if it was enabled */
+ cpu_single_step(env, 0);
+ tb_flush(env);
+
+ if (sig != 0)
+ {
+ snprintf(buf, sizeof(buf), "S%02x", sig);
+ put_packet(s, buf);
+ }
+
+ /* TODO: How do we terminate this loop? */
+ sig = 0;
+ s->state = RS_IDLE;
+ while (s->state != RS_CONTINUE)
+ {
+ n = read (s->fd, buf, 256);
+ if (n > 0)
+ {
+ int i;
+
+ for (i = 0; i < n; i++)
+ gdb_read_byte (s, env, buf[i]);
+ }
+ else if (n == 0 || errno != EAGAIN)
+ {
+ /* XXX: Connection closed. Should probably wait for annother
+ connection before continuing. */
+ return sig;
+ }
+ }
+ return sig;
+}
+#else
static int gdb_can_read(void *opaque)
{
return 256;
@@ -559,10 +670,12 @@ static void gdb_read(void *opaque, const uint8_t *buf, int size)
vm_start();
} else {
for(i = 0; i < size; i++)
- gdb_read_byte(s, buf[i]);
+ gdb_read_byte(s, cpu_single_env, buf[i]);
}
}
+#endif
+
static void gdb_accept(void *opaque, const uint8_t *buf, int size)
{
GDBState *s;
@@ -585,15 +698,21 @@ static void gdb_accept(void *opaque, const uint8_t *buf, int size)
val = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
+#ifdef CONFIG_USER_ONLY
+ s = &gdbserver_state;
+ memset (s, 0, sizeof (GDBState));
+#else
s = qemu_mallocz(sizeof(GDBState));
if (!s) {
close(fd);
return;
}
+#endif
s->fd = fd;
fcntl(fd, F_SETFL, O_NONBLOCK);
+#ifndef CONFIG_USER_ONLY
/* stop the VM */
vm_stop(EXCP_INTERRUPT);
@@ -601,6 +720,7 @@ static void gdb_accept(void *opaque, const uint8_t *buf, int size)
qemu_add_fd_read_handler(s->fd, gdb_can_read, gdb_read, s);
/* when the VM is stopped, the following callback is called */
qemu_add_vm_stop_handler(gdb_vm_stopped, s);
+#endif
}
static int gdbserver_open(int port)
@@ -631,7 +751,9 @@ static int gdbserver_open(int port)
perror("listen");
return -1;
}
+#ifndef CONFIG_USER_ONLY
fcntl(fd, F_SETFL, O_NONBLOCK);
+#endif
return fd;
}
@@ -641,6 +763,10 @@ int gdbserver_start(int port)
if (gdbserver_fd < 0)
return -1;
/* accept connections */
+#ifdef CONFIG_USER_ONLY
+ gdb_accept (NULL, NULL, 0);
+#else
qemu_add_fd_read_handler(gdbserver_fd, NULL, gdb_accept, NULL);
+#endif
return 0;
}
diff --git a/gdbstub.h b/gdbstub.h
new file mode 100644
index 0000000000..63b88dadc5
--- /dev/null
+++ b/gdbstub.h
@@ -0,0 +1,11 @@
+#ifndef GDBSTUB_H
+#define GDBSTUB_H
+
+#define DEFAULT_GDBSTUB_PORT 1234
+
+#ifdef CONFIG_USER_ONLY
+int gdb_handlesig (CPUState *, int);
+#endif
+int gdbserver_start(int);
+
+#endif
diff --git a/linux-user/main.c b/linux-user/main.c
index 126a9193ed..0dcffb186f 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -278,6 +278,20 @@ void cpu_loop(CPUX86State *env)
case EXCP_INTERRUPT:
/* just indicate that signals should be handled asap */
break;
+ case EXCP_DEBUG:
+ {
+ int sig;
+
+ sig = gdb_handlesig (env, TARGET_SIGTRAP);
+ if (sig)
+ {
+ info.si_signo = sig;
+ info.si_errno = 0;
+ info.si_code = TARGET_TRAP_BRKPT;
+ queue_signal(info.si_signo, &info);
+ }
+ }
+ break;
default:
pc = env->segs[R_CS].base + env->eip;
fprintf(stderr, "qemu: 0x%08lx: unhandled CPU exception 0x%x - aborting\n",
@@ -379,6 +393,20 @@ void cpu_loop(CPUARMState *env)
queue_signal(info.si_signo, &info);
}
break;
+ case EXCP_DEBUG:
+ {
+ int sig;
+
+ sig = gdb_handlesig (env, TARGET_SIGTRAP);
+ if (sig)
+ {
+ info.si_signo = sig;
+ info.si_errno = 0;
+ info.si_code = TARGET_TRAP_BRKPT;
+ queue_signal(info.si_signo, &info);
+ }
+ }
+ break;
default:
error:
fprintf(stderr, "qemu: unhandled CPU exception 0x%x - aborting\n",
@@ -529,6 +557,20 @@ void cpu_loop (CPUSPARCState *env)
break;
case 0x100: // XXX, why do we get these?
break;
+ case EXCP_DEBUG:
+ {
+ int sig;
+
+ sig = gdb_handlesig (env, TARGET_SIGTRAP);
+ if (sig)
+ {
+ info.si_signo = sig;
+ info.si_errno = 0;
+ info.si_code = TARGET_TRAP_BRKPT;
+ queue_signal(info.si_signo, &info);
+ }
+ }
+ break;
default:
printf ("Unhandled trap: 0x%x\n", trapnr);
cpu_dump_state(env, stderr, fprintf, 0);
@@ -911,8 +953,20 @@ void cpu_loop(CPUPPCState *env)
case EXCP_INTERRUPT:
/* Don't know why this should ever happen... */
break;
- case EXCP_DEBUG:
- break;
+ case EXCP_DEBUG:
+ {
+ int sig;
+
+ sig = gdb_handlesig (env, TARGET_SIGTRAP);
+ if (sig)
+ {
+ info.si_signo = sig;
+ info.si_errno = 0;
+ info.si_code = TARGET_TRAP_BRKPT;
+ queue_signal(info.si_signo, &info);
+ }
+ }
+ break;
default:
fprintf(stderr, "qemu: unhandled CPU exception 0x%x - aborting\n",
trapnr);
@@ -930,10 +984,11 @@ void cpu_loop(CPUPPCState *env)
void usage(void)
{
printf("qemu-" TARGET_ARCH " version " QEMU_VERSION ", Copyright (c) 2003-2004 Fabrice Bellard\n"
- "usage: qemu-" TARGET_ARCH " [-h] [-d opts] [-L path] [-s size] program [arguments...]\n"
+ "usage: qemu-" TARGET_ARCH " [-h] [-g] [-d opts] [-L path] [-s size] program [arguments...]\n"
"Linux CPU emulator (compiled for %s emulation)\n"
"\n"
"-h print this help\n"
+ "-g wait gdb connection to port %d\n"
"-L path set the elf interpreter prefix (default=%s)\n"
"-s size set the stack size in bytes (default=%ld)\n"
"\n"
@@ -944,6 +999,7 @@ void usage(void)
"-d options activate log (logfile=%s)\n"
"-p pagesize set the host page size to 'pagesize'\n",
TARGET_ARCH,
+ DEFAULT_GDBSTUB_PORT,
interp_prefix,
x86_stack_size,
DEBUG_LOGFILE);
@@ -967,6 +1023,7 @@ int main(int argc, char **argv)
CPUState *env;
int optind;
const char *r;
+ int use_gdbstub = 0;
if (argc <= 1)
usage();
@@ -1020,6 +1077,8 @@ int main(int argc, char **argv)
fprintf(stderr, "page size must be a power of two\n");
exit(1);
}
+ } else if (!strcmp(r, "g")) {
+ use_gdbstub = 1;
} else
#ifdef USE_CODE_COPY
if (!strcmp(r, "no-code-copy")) {
@@ -1176,6 +1235,10 @@ int main(int argc, char **argv)
#error unsupported target CPU
#endif
+ if (use_gdbstub) {
+ gdbserver_start (DEFAULT_GDBSTUB_PORT);
+ gdb_handlesig(env, 0);
+ }
cpu_loop(env);
/* never exits */
return 0;
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index c176a58aa4..2a815eb81f 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -9,6 +9,7 @@
#include "cpu.h"
#include "syscall.h"
+#include "gdbstub.h"
/* This struct is used to hold certain information about the image.
* Basically, it replicates in user space what would be certain
diff --git a/linux-user/signal.c b/linux-user/signal.c
index 7a904ad00d..a7c06c9fb8 100644
--- a/linux-user/signal.c
+++ b/linux-user/signal.c
@@ -1675,6 +1675,12 @@ void process_pending_signals(void *cpu_env)
k->first = q->next;
if (!k->first)
k->pending = 0;
+
+ sig = gdb_handlesig (cpu_env, sig);
+ if (!sig) {
+ fprintf (stderr, "Lost signal\n");
+ abort();
+ }
handler = k->sa._sa_handler;
if (handler == TARGET_SIG_DFL) {
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index 1346175134..ef7469d0c3 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -26,6 +26,8 @@
#include "softfloat.h"
+#define TARGET_HAS_ICE 1
+
#define EXCP_UDEF 1 /* undefined instruction */
#define EXCP_SWI 2 /* software interrupt */
#define EXCP_PREFETCH_ABORT 3
@@ -62,6 +64,11 @@ typedef struct CPUARMState {
int user_mode_only;
uint32_t address;
+ /* ICE debug support. */
+ target_ulong breakpoints[MAX_BREAKPOINTS];
+ int nb_breakpoints;
+ int singlestep_enabled;
+
/* in order to avoid passing too many arguments to the memory
write helpers, we store some rarely used information in the CPU
context) */
diff --git a/target-arm/op.c b/target-arm/op.c
index 42ee4f960a..f4cbb6e662 100644
--- a/target-arm/op.c
+++ b/target-arm/op.c
@@ -858,6 +858,12 @@ void OPPROTO op_undef_insn(void)
cpu_loop_exit();
}
+void OPPROTO op_debug(void)
+{
+ env->exception_index = EXCP_DEBUG;
+ cpu_loop_exit();
+}
+
/* VFP support. We follow the convention used for VFP instrunctions:
Single precition routines have a "s" suffix, double precision a
"d" suffix. */
diff --git a/target-arm/translate.c b/target-arm/translate.c
index 39c28e754d..280b68c4cb 100644
--- a/target-arm/translate.c
+++ b/target-arm/translate.c
@@ -2026,6 +2026,17 @@ static inline int gen_intermediate_code_internal(CPUState *env,
dc->pc = pc_start;
lj = -1;
do {
+ if (env->nb_breakpoints > 0) {
+ for(j = 0; j < env->nb_breakpoints; j++) {
+ if (env->breakpoints[j] == dc->pc) {
+ gen_op_movl_T0_im((long)dc->pc);
+ gen_op_movl_reg_TN[0][15]();
+ gen_op_debug();
+ dc->is_jmp = DISAS_JUMP;
+ break;
+ }
+ }
+ }
if (search_pc) {
j = gen_opc_ptr - gen_opc_buf;
if (lj < j) {
@@ -2040,7 +2051,8 @@ static inline int gen_intermediate_code_internal(CPUState *env,
disas_thumb_insn(dc);
else
disas_arm_insn(env, dc);
- } while (!dc->is_jmp && gen_opc_ptr < gen_opc_end &&
+ } while (!dc->is_jmp && gen_opc_ptr < gen_opc_end &&
+ !env->singlestep_enabled &&
(dc->pc - pc_start) < (TARGET_PAGE_SIZE - 32));
switch(dc->is_jmp) {
case DISAS_JUMP_NEXT:
diff --git a/target-i386/cpu.h b/target-i386/cpu.h
index ce65c34306..d0157ce8d2 100644
--- a/target-i386/cpu.h
+++ b/target-i386/cpu.h
@@ -34,6 +34,8 @@
close to the modifying instruction */
#define TARGET_HAS_PRECISE_SMC
+#define TARGET_HAS_ICE 1
+
#include "cpu-defs.h"
#include "softfloat.h"
diff --git a/target-ppc/cpu.h b/target-ppc/cpu.h
index 70ee83507a..12f7c17207 100644
--- a/target-ppc/cpu.h
+++ b/target-ppc/cpu.h
@@ -29,6 +29,8 @@
#include "softfloat.h"
+#define TARGET_HAS_ICE 1
+
/* Instruction types */
enum {
PPC_NONE = 0x0000,
diff --git a/target-sparc/cpu.h b/target-sparc/cpu.h
index 67a6e3e465..b556e23e4a 100644
--- a/target-sparc/cpu.h
+++ b/target-sparc/cpu.h
@@ -17,6 +17,8 @@
#include "softfloat.h"
+#define TARGET_HAS_ICE 1
+
/*#define EXCP_INTERRUPT 0x100*/
/* trap definitions */
diff --git a/vl.h b/vl.h
index 5091ca14e7..0fe94ac6e1 100644
--- a/vl.h
+++ b/vl.h
@@ -71,6 +71,7 @@ static inline char *realpath(const char *path, char *resolved_path)
#else
#include "cpu.h"
+#include "gdbstub.h"
#endif /* !defined(QEMU_TOOL) */
@@ -829,10 +830,4 @@ const char *readline_get_history(unsigned int index);
void readline_start(const char *prompt, int is_password,
ReadLineFunc *readline_func, void *opaque);
-/* gdbstub.c */
-
-#define DEFAULT_GDBSTUB_PORT 1234
-
-int gdbserver_start(int port);
-
#endif /* VL_H */