/* * 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, see . */ /* 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 #include #include #include #include #include #include #include #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: "m0,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 */ remote_buf[0] = '\0'; /* nothing else supported for now */ 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+1)) == 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+1)) == 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; }