gdbstub: manage CPUs as threads (Jan Kiszka)

This patch enhances QEMU's built-in debugger for SMP guest debugging.
Using the thread support of the gdb remote protocol, each VCPU is mapped
on a pseudo thread and exposed to the gdb frontend. This way you can
easy switch the focus of gdb between the VCPUs and observe their states.
On breakpoint hit, the focus is automatically adjusted just as for
normal multi-threaded application under gdb control.

Furthermore, the patch propagates breakpoint and watchpoint insertions
or removals to all CPUs, not just the current one as it was the case so
far. Without this, SMP guest debugging was practically unfeasible.

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>



git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5743 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
aliguori 2008-11-18 20:30:24 +00:00
parent bfa50bc263
commit 880a757838
3 changed files with 196 additions and 82 deletions

276
gdbstub.c
View File

@ -69,7 +69,9 @@ enum RSState {
RS_SYSCALL,
};
typedef struct GDBState {
CPUState *env; /* current CPU */
CPUState *c_cpu; /* current CPU for step/continue ops */
CPUState *g_cpu; /* current CPU for other ops */
CPUState *query_cpu; /* for q{f|s}ThreadInfo */
enum RSState state; /* parsing state */
char line_buf[MAX_PACKET_LENGTH];
int line_buf_index;
@ -90,6 +92,8 @@ typedef struct GDBState {
*/
static int sstep_flags = SSTEP_ENABLE|SSTEP_NOIRQ|SSTEP_NOTIMER;
static GDBState *gdbserver_state;
/* This is an ugly hack to cope with both new and old gdb.
If gdb sends qXfer:features:read then assume we're talking to a newish
gdb that understands target descriptions. */
@ -99,9 +103,6 @@ static int gdb_has_xml;
/* XXX: This is not thread safe. Do we care? */
static int gdbserver_fd = -1;
/* XXX: remove this hack. */
static GDBState gdbserver_state;
static int get_char(GDBState *s)
{
uint8_t ch;
@ -126,8 +127,6 @@ static int get_char(GDBState *s)
}
#endif
/* GDB stub state for use by semihosting syscalls. */
static GDBState *gdb_syscall_state;
static gdb_syscall_complete_cb gdb_current_syscall_cb;
enum {
@ -141,8 +140,8 @@ enum {
int use_gdb_syscalls(void)
{
if (gdb_syscall_mode == GDB_SYS_UNKNOWN) {
gdb_syscall_mode = (gdb_syscall_state ? GDB_SYS_ENABLED
: GDB_SYS_DISABLED);
gdb_syscall_mode = (gdbserver_state ? GDB_SYS_ENABLED
: GDB_SYS_DISABLED);
}
return gdb_syscall_mode == GDB_SYS_ENABLED;
}
@ -1031,7 +1030,7 @@ static int memtox(char *buf, const char *mem, int len)
return p - buf;
}
const char *get_feature_xml(CPUState *env, const char *p, const char **newp)
const char *get_feature_xml(const char *p, const char **newp)
{
extern const char *const xml_builtin[][2];
size_t len;
@ -1057,7 +1056,7 @@ const char *get_feature_xml(CPUState *env, const char *p, const char **newp)
"<xi:include href=\"%s\"/>",
GDB_CORE_XML);
for (r = env->gdb_regs; r; r = r->next) {
for (r = first_cpu->gdb_regs; r; r = r->next) {
strcat(target_xml, "<xi:include href=\"");
strcat(target_xml, r->xml);
strcat(target_xml, "\"/>");
@ -1160,55 +1159,84 @@ static const int xlat_gdb_type[] = {
};
#endif
static int gdb_breakpoint_insert(CPUState *env, target_ulong addr,
target_ulong len, int type)
static int gdb_breakpoint_insert(target_ulong addr, target_ulong len, int type)
{
CPUState *env;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
return cpu_breakpoint_insert(env, addr, BP_GDB, NULL);
for (env = first_cpu; env != NULL; env = env->next_cpu) {
err = cpu_breakpoint_insert(env, addr, BP_GDB, NULL);
if (err)
break;
}
return err;
#ifndef CONFIG_USER_ONLY
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
return cpu_watchpoint_insert(env, addr, len, xlat_gdb_type[type],
NULL);
for (env = first_cpu; env != NULL; env = env->next_cpu) {
err = cpu_watchpoint_insert(env, addr, len, xlat_gdb_type[type],
NULL);
if (err)
break;
}
return err;
#endif
default:
return -ENOSYS;
}
}
static int gdb_breakpoint_remove(CPUState *env, target_ulong addr,
target_ulong len, int type)
static int gdb_breakpoint_remove(target_ulong addr, target_ulong len, int type)
{
CPUState *env;
int err = 0;
switch (type) {
case GDB_BREAKPOINT_SW:
case GDB_BREAKPOINT_HW:
return cpu_breakpoint_remove(env, addr, BP_GDB);
for (env = first_cpu; env != NULL; env = env->next_cpu) {
err = cpu_breakpoint_remove(env, addr, BP_GDB);
if (err)
break;
}
return err;
#ifndef CONFIG_USER_ONLY
case GDB_WATCHPOINT_WRITE:
case GDB_WATCHPOINT_READ:
case GDB_WATCHPOINT_ACCESS:
return cpu_watchpoint_remove(env, addr, len, xlat_gdb_type[type]);
for (env = first_cpu; env != NULL; env = env->next_cpu) {
err = cpu_watchpoint_remove(env, addr, len, xlat_gdb_type[type]);
if (err)
break;
}
return err;
#endif
default:
return -ENOSYS;
}
}
static void gdb_breakpoint_remove_all(CPUState *env)
static void gdb_breakpoint_remove_all(void)
{
cpu_breakpoint_remove_all(env, BP_GDB);
CPUState *env;
for (env = first_cpu; env != NULL; env = env->next_cpu) {
cpu_breakpoint_remove_all(env, BP_GDB);
#ifndef CONFIG_USER_ONLY
cpu_watchpoint_remove_all(env, BP_GDB);
cpu_watchpoint_remove_all(env, BP_GDB);
#endif
}
}
static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
static int gdb_handle_packet(GDBState *s, const char *line_buf)
{
CPUState *env;
const char *p;
int ch, reg_size, type, res;
int ch, reg_size, type, res, thread;
char buf[MAX_PACKET_LENGTH];
uint8_t mem_buf[MAX_PACKET_LENGTH];
uint8_t *registers;
@ -1222,32 +1250,33 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
switch(ch) {
case '?':
/* TODO: Make this return the correct value for user-mode. */
snprintf(buf, sizeof(buf), "S%02x", SIGTRAP);
snprintf(buf, sizeof(buf), "T%02xthread:%02x;", SIGTRAP,
s->c_cpu->cpu_index+1);
put_packet(s, buf);
/* Remove all the breakpoints when this query is issued,
* because gdb is doing and initial connect and the state
* should be cleaned up.
*/
gdb_breakpoint_remove_all(env);
gdb_breakpoint_remove_all();
break;
case 'c':
if (*p != '\0') {
addr = strtoull(p, (char **)&p, 16);
#if defined(TARGET_I386)
env->eip = addr;
s->c_cpu->eip = addr;
#elif defined (TARGET_PPC)
env->nip = addr;
s->c_cpu->nip = addr;
#elif defined (TARGET_SPARC)
env->pc = addr;
env->npc = addr + 4;
s->c_cpu->pc = addr;
s->c_cpu->npc = addr + 4;
#elif defined (TARGET_ARM)
env->regs[15] = addr;
s->c_cpu->regs[15] = addr;
#elif defined (TARGET_SH4)
env->pc = addr;
s->c_cpu->pc = addr;
#elif defined (TARGET_MIPS)
env->active_tc.PC = addr;
s->c_cpu->active_tc.PC = addr;
#elif defined (TARGET_CRIS)
env->pc = addr;
s->c_cpu->pc = addr;
#endif
}
gdb_continue(s);
@ -1262,7 +1291,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
exit(0);
case 'D':
/* Detach packet */
gdb_breakpoint_remove_all(env);
gdb_breakpoint_remove_all();
gdb_continue(s);
put_packet(s, "OK");
break;
@ -1270,23 +1299,23 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
if (*p != '\0') {
addr = strtoull(p, (char **)&p, 16);
#if defined(TARGET_I386)
env->eip = addr;
s->c_cpu->eip = addr;
#elif defined (TARGET_PPC)
env->nip = addr;
s->c_cpu->nip = addr;
#elif defined (TARGET_SPARC)
env->pc = addr;
env->npc = addr + 4;
s->c_cpu->pc = addr;
s->c_cpu->npc = addr + 4;
#elif defined (TARGET_ARM)
env->regs[15] = addr;
s->c_cpu->regs[15] = addr;
#elif defined (TARGET_SH4)
env->pc = addr;
s->c_cpu->pc = addr;
#elif defined (TARGET_MIPS)
env->active_tc.PC = addr;
s->c_cpu->active_tc.PC = addr;
#elif defined (TARGET_CRIS)
env->pc = addr;
s->c_cpu->pc = addr;
#endif
}
cpu_single_step(env, sstep_flags);
cpu_single_step(s->c_cpu, sstep_flags);
gdb_continue(s);
return RS_IDLE;
case 'F':
@ -1305,7 +1334,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
p++;
type = *p;
if (gdb_current_syscall_cb)
gdb_current_syscall_cb(s->env, ret, err);
gdb_current_syscall_cb(s->c_cpu, ret, err);
if (type == 'C') {
put_packet(s, "T02");
} else {
@ -1316,7 +1345,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
case 'g':
len = 0;
for (addr = 0; addr < num_g_regs; addr++) {
reg_size = gdb_read_register(env, mem_buf + len, addr);
reg_size = gdb_read_register(s->g_cpu, mem_buf + len, addr);
len += reg_size;
}
memtohex(buf, mem_buf, len);
@ -1327,7 +1356,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
len = strlen(p) / 2;
hextomem((uint8_t *)registers, p, len);
for (addr = 0; addr < num_g_regs && len > 0; addr++) {
reg_size = gdb_write_register(env, registers, addr);
reg_size = gdb_write_register(s->g_cpu, registers, addr);
len -= reg_size;
registers += reg_size;
}
@ -1338,7 +1367,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
if (*p == ',')
p++;
len = strtoull(p, NULL, 16);
if (cpu_memory_rw_debug(env, addr, mem_buf, len, 0) != 0) {
if (cpu_memory_rw_debug(s->g_cpu, addr, mem_buf, len, 0) != 0) {
put_packet (s, "E14");
} else {
memtohex(buf, mem_buf, len);
@ -1353,7 +1382,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
if (*p == ':')
p++;
hextomem(mem_buf, p, len);
if (cpu_memory_rw_debug(env, addr, mem_buf, len, 1) != 0)
if (cpu_memory_rw_debug(s->g_cpu, addr, mem_buf, len, 1) != 0)
put_packet(s, "E14");
else
put_packet(s, "OK");
@ -1365,7 +1394,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
if (!gdb_has_xml)
goto unknown_command;
addr = strtoull(p, (char **)&p, 16);
reg_size = gdb_read_register(env, mem_buf, addr);
reg_size = gdb_read_register(s->g_cpu, mem_buf, addr);
if (reg_size) {
memtohex(buf, mem_buf, reg_size);
put_packet(s, buf);
@ -1381,7 +1410,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
p++;
reg_size = strlen(p) / 2;
hextomem(mem_buf, p, reg_size);
gdb_write_register(env, mem_buf, addr);
gdb_write_register(s->g_cpu, mem_buf, addr);
put_packet(s, "OK");
break;
case 'Z':
@ -1394,9 +1423,9 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
p++;
len = strtoull(p, (char **)&p, 16);
if (ch == 'Z')
res = gdb_breakpoint_insert(env, addr, len, type);
res = gdb_breakpoint_insert(addr, len, type);
else
res = gdb_breakpoint_remove(env, addr, len, type);
res = gdb_breakpoint_remove(addr, len, type);
if (res >= 0)
put_packet(s, "OK");
else if (res == -ENOSYS)
@ -1404,6 +1433,45 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
else
put_packet(s, "E22");
break;
case 'H':
type = *p++;
thread = strtoull(p, (char **)&p, 16);
if (thread == -1 || thread == 0) {
put_packet(s, "OK");
break;
}
for (env = first_cpu; env != NULL; env = env->next_cpu)
if (env->cpu_index + 1 == thread)
break;
if (env == NULL) {
put_packet(s, "E22");
break;
}
switch (type) {
case 'c':
s->c_cpu = env;
put_packet(s, "OK");
break;
case 'g':
s->g_cpu = env;
put_packet(s, "OK");
break;
default:
put_packet(s, "E22");
break;
}
break;
case 'T':
thread = strtoull(p, (char **)&p, 16);
#ifndef CONFIG_USER_ONLY
if (thread > 0 && thread < smp_cpus + 1)
#else
if (thread == 1)
#endif
put_packet(s, "OK");
else
put_packet(s, "E22");
break;
case 'q':
case 'Q':
/* parse any 'q' packets here */
@ -1429,10 +1497,39 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
sstep_flags = type;
put_packet(s, "OK");
break;
} else if (strcmp(p,"C") == 0) {
/* "Current thread" remains vague in the spec, so always return
* the first CPU (gdb returns the first thread). */
put_packet(s, "QC1");
break;
} else if (strcmp(p,"fThreadInfo") == 0) {
s->query_cpu = first_cpu;
goto report_cpuinfo;
} else if (strcmp(p,"sThreadInfo") == 0) {
report_cpuinfo:
if (s->query_cpu) {
snprintf(buf, sizeof(buf), "m%x", s->query_cpu->cpu_index+1);
put_packet(s, buf);
s->query_cpu = s->query_cpu->next_cpu;
} else
put_packet(s, "l");
break;
} else if (strncmp(p,"ThreadExtraInfo,", 16) == 0) {
thread = strtoull(p+16, (char **)&p, 16);
for (env = first_cpu; env != NULL; env = env->next_cpu)
if (env->cpu_index + 1 == thread) {
len = snprintf((char *)mem_buf, sizeof(mem_buf),
"CPU#%d [%s]", env->cpu_index,
env->halted ? "halted " : "running");
memtohex(buf, mem_buf, len);
put_packet(s, buf);
break;
}
break;
}
#ifdef CONFIG_LINUX_USER
else if (strncmp(p, "Offsets", 7) == 0) {
TaskState *ts = env->opaque;
TaskState *ts = s->c_cpu->opaque;
snprintf(buf, sizeof(buf),
"Text=" TARGET_ABI_FMT_lx ";Data=" TARGET_ABI_FMT_lx
@ -1459,7 +1556,7 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
gdb_has_xml = 1;
p += 19;
xml = get_feature_xml(env, p, &p);
xml = get_feature_xml(p, &p);
if (!xml) {
snprintf(buf, sizeof(buf), "E00");
put_packet(s, buf);
@ -1507,10 +1604,17 @@ static int gdb_handle_packet(GDBState *s, CPUState *env, const char *line_buf)
extern void tb_flush(CPUState *env);
void gdb_set_stop_cpu(CPUState *env)
{
gdbserver_state->c_cpu = env;
gdbserver_state->g_cpu = env;
}
#ifndef CONFIG_USER_ONLY
static void gdb_vm_stopped(void *opaque, int reason)
{
GDBState *s = opaque;
GDBState *s = gdbserver_state;
CPUState *env = s->c_cpu;
char buf[256];
const char *type;
int ret;
@ -1519,11 +1623,11 @@ static void gdb_vm_stopped(void *opaque, int reason)
return;
/* disable single step if it was enable */
cpu_single_step(s->env, 0);
cpu_single_step(env, 0);
if (reason == EXCP_DEBUG) {
if (s->env->watchpoint_hit) {
switch (s->env->watchpoint_hit->flags & BP_MEM_ACCESS) {
if (env->watchpoint_hit) {
switch (env->watchpoint_hit->flags & BP_MEM_ACCESS) {
case BP_MEM_READ:
type = "r";
break;
@ -1534,20 +1638,22 @@ static void gdb_vm_stopped(void *opaque, int reason)
type = "";
break;
}
snprintf(buf, sizeof(buf), "T%02x%swatch:" TARGET_FMT_lx ";",
SIGTRAP, type, s->env->watchpoint_hit->vaddr);
snprintf(buf, sizeof(buf),
"T%02xthread:%02x;%swatch:" TARGET_FMT_lx ";",
SIGTRAP, env->cpu_index+1, type,
env->watchpoint_hit->vaddr);
put_packet(s, buf);
s->env->watchpoint_hit = NULL;
env->watchpoint_hit = NULL;
return;
}
tb_flush(s->env);
tb_flush(env);
ret = SIGTRAP;
} else if (reason == EXCP_INTERRUPT) {
ret = SIGINT;
} else {
ret = 0;
}
snprintf(buf, sizeof(buf), "S%02x", ret);
snprintf(buf, sizeof(buf), "T%02xthread:%02x;", ret, env->cpu_index+1);
put_packet(s, buf);
}
#endif
@ -1566,7 +1672,7 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...)
uint64_t i64;
GDBState *s;
s = gdb_syscall_state;
s = gdbserver_state;
if (!s)
return;
gdb_current_syscall_cb = cb;
@ -1611,15 +1717,14 @@ void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...)
va_end(va);
put_packet(s, buf);
#ifdef CONFIG_USER_ONLY
gdb_handlesig(s->env, 0);
gdb_handlesig(s->c_cpu, 0);
#else
cpu_interrupt(s->env, CPU_INTERRUPT_EXIT);
cpu_interrupt(s->c_cpu, CPU_INTERRUPT_EXIT);
#endif
}
static void gdb_read_byte(GDBState *s, int ch)
{
CPUState *env = s->env;
int i, csum;
uint8_t reply;
@ -1685,7 +1790,7 @@ static void gdb_read_byte(GDBState *s, int ch)
} else {
reply = '+';
put_buffer(s, &reply, 1);
s->state = gdb_handle_packet(s, env, s->line_buf);
s->state = gdb_handle_packet(s, s->line_buf);
}
break;
default:
@ -1702,7 +1807,7 @@ gdb_handlesig (CPUState *env, int sig)
char buf[256];
int n;
s = &gdbserver_state;
s = gdbserver_state;
if (gdbserver_fd < 0 || s->fd < 0)
return sig;
@ -1750,7 +1855,7 @@ void gdb_exit(CPUState *env, int code)
GDBState *s;
char buf[4];
s = &gdbserver_state;
s = gdbserver_state;
if (gdbserver_fd < 0 || s->fd < 0)
return;
@ -1759,7 +1864,7 @@ void gdb_exit(CPUState *env, int code)
}
static void gdb_accept(void *opaque)
static void gdb_accept(void)
{
GDBState *s;
struct sockaddr_in sockaddr;
@ -1781,13 +1886,20 @@ static void gdb_accept(void *opaque)
val = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
s = &gdbserver_state;
s = qemu_mallocz(sizeof(GDBState));
if (!s) {
errno = ENOMEM;
perror("accept");
return;
}
memset (s, 0, sizeof (GDBState));
s->env = first_cpu; /* XXX: allow to change CPU */
s->c_cpu = first_cpu;
s->g_cpu = first_cpu;
s->fd = fd;
gdb_has_xml = 0;
gdb_syscall_state = s;
gdbserver_state = s;
fcntl(fd, F_SETFL, O_NONBLOCK);
}
@ -1829,7 +1941,7 @@ int gdbserver_start(int port)
if (gdbserver_fd < 0)
return -1;
/* accept connections */
gdb_accept (NULL);
gdb_accept();
return 0;
}
#else
@ -1842,11 +1954,10 @@ static int gdb_chr_can_receive(void *opaque)
static void gdb_chr_receive(void *opaque, const uint8_t *buf, int size)
{
GDBState *s = opaque;
int i;
for (i = 0; i < size; i++) {
gdb_read_byte(s, buf[i]);
gdb_read_byte(gdbserver_state, buf[i]);
}
}
@ -1855,7 +1966,6 @@ static void gdb_chr_event(void *opaque, int event)
switch (event) {
case CHR_EVENT_RESET:
vm_stop(EXCP_INTERRUPT);
gdb_syscall_state = opaque;
gdb_has_xml = 0;
break;
default:
@ -1890,11 +2000,13 @@ int gdbserver_start(const char *port)
if (!s) {
return -1;
}
s->env = first_cpu; /* XXX: allow to change CPU */
s->c_cpu = first_cpu;
s->g_cpu = first_cpu;
s->chr = chr;
gdbserver_state = s;
qemu_chr_add_handlers(chr, gdb_chr_can_receive, gdb_chr_receive,
gdb_chr_event, s);
qemu_add_vm_stop_handler(gdb_vm_stopped, s);
gdb_chr_event, NULL);
qemu_add_vm_stop_handler(gdb_vm_stopped, NULL);
return 0;
}
#endif

View File

@ -8,6 +8,7 @@ typedef void (*gdb_syscall_complete_cb)(CPUState *env,
void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...);
int use_gdb_syscalls(void);
void gdb_set_stop_cpu(CPUState *env);
#ifdef CONFIG_USER_ONLY
int gdb_handlesig (CPUState *, int);
void gdb_exit(CPUState *, int);

1
vl.c
View File

@ -3720,6 +3720,7 @@ static int main_loop(void)
ret = EXCP_INTERRUPT;
}
if (unlikely(ret == EXCP_DEBUG)) {
gdb_set_stop_cpu(cur_cpu);
vm_stop(EXCP_DEBUG);
}
/* If all cpus are halted then wait until the next IRQ */