From 1fddef4b1ba3bf14d36472475019a4a6acd4d976 Mon Sep 17 00:00:00 2001 From: bellard Date: Sun, 17 Apr 2005 19:16:13 +0000 Subject: [PATCH] gdb support for user mode (Paul Brook) git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1367 c046a42c-6fe2-441c-8c8c-71466251a162 --- Makefile.target | 3 + exec.c | 12 ++-- gdbstub.c | 152 +++++++++++++++++++++++++++++++++++++---- gdbstub.h | 11 +++ linux-user/main.c | 69 ++++++++++++++++++- linux-user/qemu.h | 1 + linux-user/signal.c | 6 ++ target-arm/cpu.h | 7 ++ target-arm/op.c | 6 ++ target-arm/translate.c | 14 +++- target-i386/cpu.h | 2 + target-ppc/cpu.h | 2 + target-sparc/cpu.h | 2 + vl.h | 7 +- 14 files changed, 265 insertions(+), 29 deletions(-) create mode 100644 gdbstub.h 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 +#include +#include +#include +#include +#include + +#include "qemu.h" +#else #include "vl.h" +#endif #include #include @@ -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 */