diff options
Diffstat (limited to 'tools/debugger/gdbsx/gx/gx_main.c')
-rw-r--r-- | tools/debugger/gdbsx/gx/gx_main.c | 696 |
1 files changed, 696 insertions, 0 deletions
diff --git a/tools/debugger/gdbsx/gx/gx_main.c b/tools/debugger/gdbsx/gx/gx_main.c new file mode 100644 index 0000000000..f20f6ef0d7 --- /dev/null +++ b/tools/debugger/gdbsx/gx/gx_main.c @@ -0,0 +1,696 @@ +/* + * Copyright (C) 2009, Mukesh Rathor, Oracle Corp. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +/* This module is the main module for gdbsx implementation. gdbsx is a remote + * gdbserver stub for xen. It facilitates debugging of xen guests. It also + * prints vcpu contexts locally without remote gdb. */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <signal.h> + +#include "gx.h" + + +enum target_signal { + TARGET_SIGNAL_INT = 2, + TARGET_SIGNAL_TRAP = 5 +}; + + +/* At present, we don't support offlining VCPUs, or dynamic adding/removal + * of them. As such, max_vcpu means active [0 - max_vcpuid] vcpus */ +vcpuid_t max_vcpuid; /* so max_vcpuid+1 vcpus overall */ +int guest_bitness; /* 32 or 64 */ + +const char host_name[] = ""; + +int gx_remote_dbg; /* enable debug trace output for debugging */ +uint64_t pgd3val; /* value of init_mm.pgd[3] set by monitor gdb cmd */ + +static vcpuid_t current_vcpu; + +/* + * write regs received from remote gdb to guest + */ +static void +gx_write_guest_regs(char *rbuf) +{ + union xg_gdb_regs gregs; + int rc; + char *savrbuf = rbuf; + int regsz = (guest_bitness == 32) ? sizeof(gregs.gregs_32) : + sizeof(gregs.gregs_64); + rbuf++; + if (strlen(rbuf) != 2*regsz) { + gxprt("ERROR: wrong sized register pkt received...\n" + "Expected:%d got:%d\n", 2*regsz, strlen(rbuf)); + } + gx_convert_ascii_to_int(rbuf, (char *)&gregs, regsz); + + rc = xg_regs_write(XG_GPRS, current_vcpu, &gregs, guest_bitness); + if (rc) { + gxprt("ERROR: failed to write regs. errno:%d\n", errno); + savrbuf[0] ='\0'; + gx_reply_error(savrbuf); + } else { + gx_reply_ok(savrbuf); + } +} + +/* + * read guest regs and send to remote gdb + */ +static void +gx_read_guest_regs(char *rbuf) +{ + union xg_gdb_regs gregs; + int rc; + + rc = xg_regs_read(XG_GPRS, current_vcpu, &gregs, guest_bitness); + if (rc) { + gxprt("ERROR: failed to read regs. errno:%d\n", errno); + rbuf[0] ='\0'; + } else { + int sz = (guest_bitness == 32) ? sizeof(gregs.gregs_32) : + sizeof(gregs.gregs_64); + gx_convert_int_to_ascii((char *)&gregs, rbuf, sz); + } +} + +/* remote_buf: 'qRcmd,pgd3 c0ae9018\0' (c0ae9018 may also be 0xc0ae9018) */ +static void +_do_qRcmd_req(char *remote_buf) +{ + char buf[64], buf1[64]; + char *p = remote_buf + 6; + int len = strlen(p)/2; /* because "70" is one char "p" */ + + gx_convert_ascii_to_int(p, buf, len); + XGTRC("remote_buf:%s buf:%s\n", remote_buf, buf); + + if (strncmp(buf, "pgd3 ", 5) == 0) { + char *endp; + + pgd3val = strtoull(buf+5, &endp, 16); + XGTRC("buf+5:%s pgd3val:0x%llx\n", buf+5, pgd3val); + + if (*endp == '\0' && pgd3val > 0) { + sprintf(buf1, "pgd3val set to: "XGF64"\n", pgd3val); + } else { + sprintf(buf1, "Invalid pgd3val "XGF64"\n", pgd3val); + pgd3val = 0; + } + } else { + sprintf(buf1, "Bad monitor command\n"); + } + gx_convert_int_to_ascii(buf1, remote_buf, strlen(buf1)); + return; +} + +/* qSupported qC qfThreadInfo qsThreadInfo qThreadExtraInfo,1 etc.. */ +static void +process_q_request(char *remote_buf) +{ + /* send a list of tids: "m 0,1,2,3l" */ + if (strcmp("qfThreadInfo", remote_buf) == 0) { + vcpuid_t vid = 0; + char *p = remote_buf; + + sprintf(p, "m %x", vid); /* puts null char at the end */ + p = p + strlen(p); + for (vid=1; vid <= max_vcpuid; vid++) { + sprintf(p, ",%x", vid); + p = p + strlen(p); + } + sprintf(p, "l"); /* puts null char at the end */ + return; + } + /* qSymbol works for init_mm, and not init_mm.pgd, hence we can't use + * it at this time. instead use "monitor" in gdb */ + if (strncmp("qRcmd,", remote_buf, 6) == 0) { + _do_qRcmd_req(remote_buf); + return; + } + + /* TBD : qThreadExtraInfo : send extra banner info */ + + /* nothing else supported right now */ + remote_buf[0] = '\0'; + + return; +} + +/* + * Set current thread/vcpu to : -1 all threads, 0 any thread, or given tid/vcpu + * Even tho, 0 is a valid vcpu for us, it's OK as vcpu 0 is any vcpu + * Eg. Hc-1\0 Hc0\0 etc... + */ +static void +process_H_request(char *remote_buf) +{ + char ch1 = remote_buf[1]; + + if (ch1 == 'c' || ch1 == 'g' || ch1 == 's') { + vcpuid_t vcpu; + + /* we keep vcpu_id (which gdb thinks is tid) and + * gdb_id the same for simplicity */ + + vcpu = strtoul(&remote_buf[2], NULL, 16); + if (vcpu == -1) { + vcpu = 0; + } + /* it doesn't matter to us: g, c, or s */ + current_vcpu = vcpu; + gx_reply_ok(remote_buf); + } else { + /* Silently ignore so gdb can extend the protocol + * without compatibility headaches */ + remote_buf[0] = '\0'; + } +} + +/* read guest memory to send to remote gdb user */ +static void +process_m_request(char *remote_buf) +{ + uint64_t addr; + int len, remain; + char *xbuf; + + gx_decode_m_packet(&remote_buf[1], &addr, &len); + + if ((xbuf=malloc(len)) == NULL) { + gx_reply_error(remote_buf); + return; + } + if ((remain=xg_read_mem(addr, xbuf, len, pgd3val)) != 0) { + XGTRC("Failed read mem. addr:0x%llx len:%d remn:%d errno:%d\n", + addr, len, remain, errno); + gx_reply_error(remote_buf); + free(xbuf); + return; + } + gx_convert_int_to_ascii(xbuf, remote_buf, len); + free(xbuf); + return; +} + +/* write guest memory */ +static void +process_M_request(char *remote_buf) +{ + uint64_t addr; + int len, remain; + char *xbuf, *data_strtp; /* where guest data actually starts */ + + data_strtp = gx_decode_M_packet(&remote_buf[1], &addr, &len); + + if ((xbuf=malloc(len)) == NULL) { + gx_reply_error(remote_buf); + return; + } + gx_convert_ascii_to_int(data_strtp, xbuf, len); + + if ((remain=xg_write_mem(addr, xbuf, len, pgd3val)) != 0) { + gxprt("Failed write mem. addr:0x%llx len:%d rem:%d errno:%d\n", + addr, len, remain, errno); + gx_reply_error(remote_buf); + } else { + gx_reply_ok(remote_buf); + } + free(xbuf); + return; +} + +/* Eg.: "vCont;c" "vCont;s:5" */ +static void +process_v_cont_request(char *bufp) +{ + char *savbufp = bufp; + + bufp = bufp + 5; /* address of semicolon */ + + if (*bufp == '\0' || *bufp != ';') + goto errout; + bufp++; + if (*bufp == 'S' || *bufp == 'C') /* we don't support signalling */ + goto errout; +#if 0 + if (*bufp == 'c') { + if (*(bufp+1) != '\0') + goto errout; /* don't tolerate bad pkt */ + xg_resume(guest_bitness); /* continue domain */ + + } else if (*bufp == 's') { + + /* we don't support : step vcpuid. user must switch to the + * thread/vcpu and then do step */ + bufp++; + if (*bufp != '\0') + goto errout; + xg_step(current_vcpu, guest_bitness); + } +#endif + return; + + errout: + savbufp[0] = '\0'; + gxprt("WARN: Bad v pkt: %s\n", savbufp); + return; +} + +static void +process_v_request(char *remote_buf) +{ + if (strncmp(remote_buf, "vCont;", 6) == 0) { + process_v_cont_request(remote_buf); /* valid request */ + return; + } + if (strncmp(remote_buf, "vCont?", 6) == 0) { + /* tell remote gdb what we support : c and s */ + /* strcpy(remote_buf, "vCont;c;s"); */ + remote_buf[0] = '\0'; + return; + } + /* failed to understand the v packet */ + remote_buf[0] = '\0'; + return; +} + +/* TBD: add watchpoint in future */ +static int +watchpoint_stop(void) +{ + return 0; +} + +#if 0 +static char * +copy_mini_context32(char *rbuf, union xg_gdb_regs32 *regsp) +{ + *rbuf++ = gx_tohex((EBP_IDX >> 4) & 0xf); + *rbuf++ = gx_tohex(EBP_IDX & 0xf); + *rbuf++ = ':'; + rbuf = gx_convert_int_to_ascii(regsp->ebp, rbuf, 4); + + *rbuf++ = gx_tohex((ESP_IDX >> 4) & 0xf); + *rbuf++ = gx_tohex(ESP_IDX & 0xf); + *rbuf++ = ':'; + rbuf = gx_convert_int_to_ascii(regsp->esp, rbuf, 4); + + *rbuf++ = gx_tohex((EIP_IDX >> 4) & 0xf); + *rbuf++ = gx_tohex(EIP_IDX & 0xf); + *rbuf++ = ':'; + rbuf = gx_convert_int_to_ascii(regsp->eip, rbuf, 4); + + return rbuf; +} + +static char * +copy_mini_context64(char *rbuf, union xg_gdb_regs64 *regsp) +{ + *rbuf++ = gx_tohex((RBP_IDX >> 4) & 0xf); + *rbuf++ = gx_tohex(RBP_IDX & 0xf); + *rbuf++ = ':'; + rbuf = gx_convert_int_to_ascii(regsp->ebp, rbuf, 4); + + *rbuf++ = gx_tohex((RSP_IDX >> 4) & 0xf); + *rbuf++ = gx_tohex(RSP_IDX & 0xf); + *rbuf++ = ':'; + rbuf = gx_convert_int_to_ascii(regsp->esp, rbuf, 4); + + *rbuf++ = gx_tohex((RIP_IDX >> 4) & 0xf); + *rbuf++ = gx_tohex(RIP_IDX & 0xf); + *rbuf++ = ':'; + rbuf = gx_convert_int_to_ascii(regsp->eip, rbuf, 4); + + return rbuf; +} + +static char * +copy_mini_context(char *rbuf) +{ + union xg_gdb_regs regs; + + if (xg_regs_read(XG_GPRS, 0, ®s, guest_bitness)) { + gxprt("WARN: Unable to get read regs. errno:%d\n", errno); + return; + } + if (guest_bitness == 32) + rbuf = copy_mini_context32(rbuf, ®s.u.gregs_32); + else + rbuf = copy_mini_context64(rbuf, ®s.u.gregs_64); + return rbuf; +} + +#endif + +/* + * prepare reply for remote gdb as to why we stopped + */ +static void +prepare_stop_reply(enum target_signal sig, char *buf, vcpuid_t vcpu) +{ + int nib; + + *buf++ = 'T'; /* we stopped because of a trap (SIGTRAP) */ + + nib = ((sig & 0xf0) >> 4); + *buf++ = gx_tohex(nib); + nib = sig & 0x0f; + *buf++ = gx_tohex(nib); + + /* TBD: check if we stopped because of watchpoint */ + if (watchpoint_stop()) { + strncpy(buf, "watch:", 6); + buf += 6; + /* TBD: **/ + } + sprintf(buf, "thread:%x;", vcpu); + buf += strlen(buf); + *buf++ = '\0'; +} +/* + * Indicate the reason the guest halted + */ +static void +process_reas_request(char *remote_buf, vcpuid_t vcpu) +{ + prepare_stop_reply(TARGET_SIGNAL_TRAP, remote_buf, vcpu); +} + +/* continue request */ +static void +process_c_request(char *remote_buf) +{ + enum target_signal sig; + + if ((current_vcpu=xg_resume_n_wait(guest_bitness)) == -1) { + current_vcpu = 0; /* default vcpu */ + sig = TARGET_SIGNAL_INT; + } else + sig = TARGET_SIGNAL_TRAP; + + prepare_stop_reply(sig, remote_buf, current_vcpu); +} + +#if 0 +/* insert a bp: Z#,addr,len : where # is 0 for software bp, 1 for hardware bp, + * 2 is a write watchpoint, 3 is read watchpoint, 4 access watchpt + * We ignore len, it should always be 1. + * Eg: Z0,c0267d3a,1 + */ +static void +process_Z_request(char *rbuf) +{ + char ch1 = rbuf[1]; + uint64_t gva; + + if (ch1 != '0') { + gx_reply_error(rbuf); + return; + } + gx_decode_zZ_packet(&rbuf[3], &gva); + if (xg_set_bp(gva, ch1)) + gx_reply_error(rbuf); + else + gx_reply_ok(rbuf); +} + +/* remove a bp */ +static void +process_z_request(char *rbuf) +{ + char ch1 = rbuf[1]; + uint64_t gva; + + if (ch1 != '0') { + gx_reply_error(rbuf); + return; + } + gx_decode_zZ_packet(&rbuf[3], &gva); + if (xg_rm_bp(gva, ch1)) + gx_reply_error(rbuf); + else + gx_reply_ok(rbuf); +} +#endif + +static int +process_remote_request(char *remote_buf) /* buffer received from remote gdb */ +{ + char ch; + int rc=0, i=0; + + XGTRC("E:%s curvcpu:%d\n", remote_buf, current_vcpu); + + ch = remote_buf[i++]; + switch(ch) + { + case 'q': + process_q_request(remote_buf); + break; + + case 'd': /* print debug trace output */ + gx_remote_dbg = !gx_remote_dbg; + printf("WARN: received d pkt:%s\n", remote_buf); + remote_buf[0] = '\0'; + break; + + case 'D': + gx_reply_ok(remote_buf); + rc = 1; + break; + + case '?': + process_reas_request(remote_buf, 0); + break; + + case 'H': + process_H_request(remote_buf); + break; + + /* send general registers to remote gdb */ + case 'g': + assert(current_vcpu != -1); + gx_read_guest_regs(remote_buf); + break; + + /* receive general regs from remote gdb */ + case 'G': + assert(current_vcpu != -1); + gx_write_guest_regs(remote_buf); + break; + + /* read guest memory and send to remote gdb */ + case 'm': + process_m_request(remote_buf); + break; + + case 'M': + process_M_request(remote_buf); + break; + + case 'C': + printf("WARN: C pkt: %s\n", remote_buf); + remote_buf[0] = '\0'; + break; + + case 'S': + printf("WARN: S pkt:%s\n", remote_buf); + remote_buf[0] = '\0'; + break; + + case 'c': + process_c_request(remote_buf); /* continue request */ + break; + + case 's': /* single step */ + if (xg_step(current_vcpu, guest_bitness) != 0) { + remote_buf[0] = '\0'; + } else { + prepare_stop_reply(TARGET_SIGNAL_TRAP, remote_buf, + current_vcpu); + } + break; + +#if 0 + case 'Z': + process_Z_request(remote_buf); /* insert a bp */ + break; + + case 'z': + process_z_request(remote_buf); /* remove a bp */ + break; +#endif + case 'k': /* kill inferior */ + printf("WARN: k pkt:%s\n", remote_buf); + remote_buf[0] = '\0'; + break; + + case 'T': /* find out if thread is alive */ + gx_reply_ok(remote_buf); /* no vcpu offling supported yet */ + break; + + case 'R': /* TBD: restart gdbserver program */ + /* Restarting the inferior is only supported in the + * extended protocol. */ + remote_buf[0] = '\0'; + break; + + case 'v': + process_v_request(remote_buf); + break; + + default: + /* It is a request we don't understand. Respond with an + * empty packet so that gdb knows that we don't support this + * request. */ + remote_buf[0] = '\0'; + break; + + } /* end of switch(ch) */ + + XGTRC("X:%s curvcpu:%d\n", remote_buf, current_vcpu); + return rc; +} + +static void +gdbsx_usage_exit(void) +{ + printf ("Usage 1: gdbsx -a domid <32|64> PORT [-d]\n" + " PORT to listen for a TCP connection.\n" + " Eg. gdbsx -a 3 32 9999\n\n"); + printf("Usage 2: gdbsx -c domid <32|64> [vcpu#] [-d]\n"); + printf(" to dump vcpu context(s) for given domid\n\n"); + exit(1); +} + +static void +check_usage_n_stuff(int argc, char **argv, domid_t *domid_p, vcpuid_t *vp) +{ + char *arg_end; + + if (strcmp(argv[argc-1], "-d")==0) { + xgtrc_on = 1; /* debug trace on */ + argc--; + } + if (argc < 4 || (strcmp(argv[1], "-h") == 0) || + (strcmp(argv[1], "-a")==0 && argc < 5)) { + gdbsx_usage_exit(); + } + if (argc > 5 || + (*domid_p=strtoul(argv[2], &arg_end, 10)) == 0 || + *arg_end != '\0' || + *domid_p == 0 || + (guest_bitness=strtoul(argv[3], &arg_end, 10)) == 0 || + *arg_end != '\0' || + (guest_bitness != 32 && guest_bitness != 64)) { + + gdbsx_usage_exit(); + } + *vp = -1; /* assume all VCPUs */ + if (strcmp(argv[1], "-c")==0 && argc >= 5) { + *vp = strtoul(argv[4], &arg_end, 10); + if (*arg_end != '\0') { + gdbsx_usage_exit(); + } + } +} + +static void +initialize(char **rbufpp) +{ +#define BUFSIZE 4096 + + /* allocate buffer used to communicate back and forth with remote gdb */ + /* size should be big enough to hold all registers + extra */ + if ((*rbufpp=malloc(BUFSIZE)) == NULL) { + gxprt("ERROR: can't malloc %d bytes. errno:%d\n", + BUFSIZE, errno); + exit(3); + } + signal(SIGIO, SIG_IGN); /* default action is TERM */ +} + + +int +main(int argc, char *argv[]) +{ + char *remote_buf; + domid_t domid = 0; + vcpuid_t vcpuid; + int exit_rc = 0; + + check_usage_n_stuff(argc, argv, &domid, &vcpuid); + + if (xg_init() == -1) { + gxprt("ERROR: failed to initialize errno:%d\n", errno); + exit(1); + } + if ((max_vcpuid=xg_attach(domid, guest_bitness)) == -1) { + gxprt("ERROR: failed to attach to domain:%d errno:%d\n", + domid, errno); + exit(1); + } + if (strcmp(argv[1], "-c")==0) { + if (vcpuid != -1 && vcpuid > max_vcpuid) { /* just got set */ + printf("gdbsx: Invalid VCPU id:%d\n", vcpuid); + xg_detach_deinit(); + gdbsx_usage_exit(); + } + exit_rc = gx_local_cmd(domid, vcpuid); + xg_detach_deinit(); + return exit_rc; /* EXIT */ + } + + initialize(&remote_buf); + + /* we have the guest paused at this point, ready for debug. wait for + * connection from remote gdb */ + if (gx_remote_open(argv[4]) == -1) { + xg_detach_deinit(); + return 1; + } + + /* we've a gdb connection at this point, process requests */ + while(gx_getpkt(remote_buf) > 0) { + if ((exit_rc=process_remote_request(remote_buf))) + break; + if (gx_putpkt(remote_buf) == -1) { + exit_rc = 1; + break; + } + } + /* unpause and let the guest continue */ + gxprt("Detaching from guest\n"); + xg_detach_deinit(); + + if (exit_rc == 0) { + gxprt("Exiting.. Remote side has terminated connection\n"); + } + gx_remote_close(); + return exit_rc; +} |