/* * Returns: bytes remaining to be read. 0 => read all bytes, ie, success. */ int xg_read_mem(uint64_t guestva, char *tobuf, int tobuf_len, uint64_t pgd3val) { struct xen_domctl_gdbsx_memio *iop = &domctl.u.gdbsx_guest_memio; union {uint64_t llbuf8; char buf8[8];} u = {0}; int i, rc; XGTRC("E:gva:%llx tobuf:%lx len:%d\n", guestva, tobuf, tobuf_len); memset(&domctl.u, 0, sizeof(domctl.u)); iop->pgd3val = pgd3val; iop->gva = guestva; iop->uva = (uint64_aligned_t)((unsigned long)tobuf); iop->len = tobuf_len; iop->gwr = 0; /* not writing to guest */ if ( (rc = _domctl_hcall(XEN_DOMCTL_gdbsx_guestmemio, tobuf, tobuf_len)) ) { XGTRC("ERROR: failed to read bytes. errno:%d rc:%d\n", errno, rc); return tobuf_len; } for(i=0; i < XGMIN(8, tobuf_len); u.buf8[i]=tobuf[i], i++); XGTRC("X:remain:%d buf8:0x%llx\n", iop->remain, u.llbuf8); return iop->remain; }
/* * Single step the given vcpu. This is achieved by pausing all but given vcpus, * setting the TF flag, let the domain run and pause, unpause all vcpus, and * clear TF flag on given vcpu. * Returns: 0 success */ int xg_step(vcpuid_t which_vcpu, int guest_bitness) { int rc; XGTRC("E:vcpu:%d\n", (int)which_vcpu); if (_allbutone_vcpu(XEN_DOMCTL_gdbsx_pausevcpu, which_vcpu)) return 1; if ((rc=_change_TF(which_vcpu, guest_bitness, 1))) return rc; XGTRC("unpausing domain\n"); /* now unpause the domain so our vcpu can execute */ if (_unpause_domain()) return 1; /* wait for our vcpu to finish step */ _wait_domain_pause(); _allbutone_vcpu(XEN_DOMCTL_gdbsx_unpausevcpu, which_vcpu); rc = _change_TF(which_vcpu, guest_bitness, 0); return rc; }
/* * Resume the domain if no pending events. If there are pending events, like * another vcpu in a BP, report it. Otherwise, continue, and wait till an * event, like bp or user doing xm pause, occurs. * * Returns: vcpuid : if a vcpu hits a breakpoint or end of step * -1 : either an error (msg printed on terminal), or non-bp * event, like "xm pause domid", to enter debugger */ vcpuid_t xg_resume_n_wait(int guest_bitness) { vcpuid_t vcpu; XGTRC("E:\n"); assert(_domain_is_paused()); if ((vcpu=_vcpu_in_bp()) != -1) { /* another vcpu in breakpoint. return it's id */ return vcpu; } XGTRC("unpausing domain\n"); if (_unpause_domain()) return -1; /* now wait for domain to pause */ _wait_domain_pause(); /* check again if any vcpu in BP, or user thru "xm pause" */ vcpu = _vcpu_in_bp(); XGTRC("X:vcpu:%d\n", vcpu); return vcpu; }
/* * Returns: 0 success * -1 failure, errno set. */ int xg_init() { int flags, saved_errno; XGTRC("E\n"); if ((_dom0_fd=open("/proc/xen/privcmd", O_RDWR)) == -1) { perror("Failed to open /proc/xen/privcmd\n"); return -1; } /* Although we return the file handle as the 'xc handle' the API * does not specify / guarentee that this integer is in fact * a file handle. Thus we must take responsiblity to ensure * it doesn't propagate (ie leak) outside the process (copied comment)*/ if ( (flags=fcntl(_dom0_fd, F_GETFD)) < 0 ) { perror("Could not get file handle flags (F_GETFD)"); goto error; } flags |= FD_CLOEXEC; if (fcntl(_dom0_fd, F_SETFD, flags) < 0) { perror("Could not set file handle flags"); goto error; } XGTRC("X:fd:%d\n", _dom0_fd); return _dom0_fd; error: XGTRC("X:Error: errno:%d\n", errno); saved_errno = errno; close(_dom0_fd); errno = saved_errno; return -1; }
/* * Returns: bytes that could not be written. 0 => wrote all bytes, ie, success. */ int xg_write_mem(uint64_t guestva, char *frombuf, int buflen, uint64_t pgd3val) { struct xen_domctl_gdbsx_memio *iop = &domctl.u.gdbsx_guest_memio; union {uint64_t llbuf8; char buf8[8];} u = {0}; int i, rc; for(i=0; i < XGMIN(8, buflen); u.buf8[i]=frombuf[i], i++); XGTRC("E:gva:%llx frombuf:%lx len:%d buf8:0x%llx\n", guestva, frombuf, buflen, u.llbuf8); memset(&domctl.u, 0, sizeof(domctl.u)); iop->pgd3val = pgd3val; iop->gva = guestva; iop->uva = (uint64_aligned_t)((unsigned long)frombuf); iop->len = buflen; iop->gwr = 1; /* writing to guest */ if ((rc=_domctl_hcall(XEN_DOMCTL_gdbsx_guestmemio, frombuf, buflen))) { XGERR("ERROR: failed to write bytes to %llx. errno:%d rc:%d\n", guestva, errno, rc); return buflen; } return iop->remain; }
/* * write registers for the given vcpu * Returns: 0 success, 1 failure with errno */ int xg_regs_write(regstype_t which_regs, vcpuid_t which_vcpu, union xg_gdb_regs *regsp, int guest_bitness) { union vcpu_guest_context_any anyc; struct cpu_user_regs_x86_32 *cr32p = &anyc.ctxt32.user_regs; struct cpu_user_regs_x86_64 *cr64p = &anyc.ctxt64.user_regs; struct xg_gdb_regs32 *r32p = ®sp->gregs_32; struct xg_gdb_regs64 *r64p = ®sp->gregs_64; int rc, sz = sizeof(anyc); if (which_regs != XG_GPRS) { errno = EINVAL; XGERR("regs got: %d. Expected GPRS:%d\n", which_regs, XG_GPRS); return 1; } if ((rc=_get_vcpu_ctxt(which_vcpu, &anyc))) return rc; if (guest_bitness == 32) { if (_32bit_hyp || !_hvm_guest) _cp_32gdb_to_32ctxt(r32p, cr32p); else _cp_32gdb_to_64ctxt(r32p, cr64p); } else _cp_64gdb_to_64ctxt(r64p, cr64p); /* set vcpu context back */ if ((rc =_domctl_hcall(XEN_DOMCTL_setvcpucontext, &anyc, sz))) { XGERR("Failed hcall to set vcpu ctxt. errno:%d\n", errno); return rc; } XGTRC("X:vcpu:%d bitness:%d rc:%d\n", which_vcpu, guest_bitness, rc); return rc; }
/* * read regs for a particular vcpu. For now only GPRs, no FPRs. * Returns: 0 success, else failure with errno set */ int xg_regs_read(regstype_t which_regs, vcpuid_t which_vcpu, union xg_gdb_regs *regsp, int guest_bitness) { union vcpu_guest_context_any anyc; struct cpu_user_regs_x86_32 *cr32p = &anyc.ctxt32.user_regs; struct cpu_user_regs_x86_64 *cr64p = &anyc.ctxt64.user_regs; struct xg_gdb_regs32 *r32p = ®sp->gregs_32; struct xg_gdb_regs64 *r64p = ®sp->gregs_64; int rc; if (which_regs != XG_GPRS) { errno = EINVAL; XGERR("regs got: %d. Expected GPRS:%d\n", which_regs, XG_GPRS); return 1; } if ((rc=_get_vcpu_ctxt(which_vcpu, &anyc))) return rc; /* 64bit hyp: only 32bit PV returns 32bit context, all others 64bit. * 32bit hyp: all contexts returned are 32bit */ if (guest_bitness == 32) { if (_32bit_hyp || !_hvm_guest) _cp_32ctxt_to_32gdb(cr32p, r32p); else _cp_64ctxt_to_32gdb(cr64p, r32p); } else _cp_64ctxt_to_64gdb(cr64p, r64p); XGTRC("X:vcpu:%d bitness:%d rc:%d\n", which_vcpu, guest_bitness, rc); return rc; }
/* * Change the TF flag for single step. TF = (setit ? 1 : 0); * Returns: 0 Success */ static int _change_TF(vcpuid_t which_vcpu, int guest_bitness, int setit) { union vcpu_guest_context_any anyc; int sz = sizeof(anyc); /* first try the MTF for hvm guest. otherwise do manually */ if (_hvm_guest) { domctl.u.debug_op.vcpu = which_vcpu; domctl.u.debug_op.op = setit ? XEN_DOMCTL_DEBUG_OP_SINGLE_STEP_ON : XEN_DOMCTL_DEBUG_OP_SINGLE_STEP_OFF; if (_domctl_hcall(XEN_DOMCTL_debug_op, NULL, 0) == 0) { XGTRC("vcpu:%d:MTF success setit:%d\n", which_vcpu, setit); return 0; } XGTRC("vcpu:%d:MTF failed. setit:%d\n", which_vcpu, setit); } memset(&anyc, 0, sz); domctl.u.vcpucontext.vcpu = (uint16_t)which_vcpu; set_xen_guest_handle(domctl.u.vcpucontext.ctxt, &anyc.ctxt); if (_domctl_hcall(XEN_DOMCTL_getvcpucontext, &anyc, sz)) { XGERR("Failed hcall to get vcpu ctxt for TF. errno:%d\n",errno); return 1; } if (_32bit_hyp || (guest_bitness == 32 && !_hvm_guest)) { if (setit) anyc.ctxt32.user_regs.eflags |= X86_EFLAGS_TF; else anyc.ctxt32.user_regs.eflags &= ~X86_EFLAGS_TF; } else { if (setit) anyc.ctxt64.user_regs.rflags |= X86_EFLAGS_TF; else anyc.ctxt64.user_regs.rflags &= ~X86_EFLAGS_TF; } if (_domctl_hcall(XEN_DOMCTL_setvcpucontext, &anyc, sz)) { XGERR("Failed hcall to set vcpu ctxt for TF. errno:%d\n",errno); return 1; } return 0; }
/* * Returns : 0 success. * 1 error, with errno set (hopefully :)) */ static int _wait_domain_pause(void) { int dom_paused; struct timespec ts={0, 10*1000*1000}; XGTRC("E:\n"); do { dom_paused = _domain_is_paused(); nanosleep(&ts, NULL); } while(!dom_paused); return 0; }
/* * Attach to the given domid for debugging. * Returns: max vcpu id : Success * -1 : Failure */ int xg_attach(int domid, int guest_bitness) { XGTRC("E:domid:%d\n", domid); _dom_id = domctl.domain = domid; domctl.interface_version = XEN_DOMCTL_INTERFACE_VERSION; if (mlock(&domctl, sizeof(domctl))) { XGERR("Unable to pin domctl in memory. errno:%d\n", errno); return -1; } if (_check_hyp(guest_bitness)) return -1; if (_domctl_hcall(XEN_DOMCTL_pausedomain, NULL, 0)) { XGERR("Unable to pause domain:%d\n", _dom_id); return -1; } memset(&domctl.u, 0, sizeof(domctl.u)); domctl.u.setdebugging.enable = 1; if (_domctl_hcall(XEN_DOMCTL_setdebugging, NULL, 0)) { XGERR("Unable to set domain to debug mode: errno:%d\n", errno); _unpause_domain(); return -1; } memset(&domctl.u, 0, sizeof(domctl.u)); if (_domctl_hcall(XEN_DOMCTL_getdomaininfo, NULL, 0)) { XGERR("Unable to get domain info: domid:%d errno:%d\n", domid, errno); _unpause_domain(); return -1; } if (!_domain_ok(&domctl.u.getdomaininfo)) { _unpause_domain(); return -1; } _max_vcpu_id = domctl.u.getdomaininfo.max_vcpu_id; _hvm_guest = (domctl.u.getdomaininfo.flags & XEN_DOMINF_hvm_guest); _pvh_guest = (domctl.u.getdomaininfo.flags & XEN_DOMINF_pvh_guest); return _max_vcpu_id; }
/* * Make sure we are running on hyp enabled for gdbsx. Also, note whether * its 32bit. Fail if user typed 64bit for guest in case of 32bit hyp. * * RETURNS: 0 : everything OK. */ static int _check_hyp(int guest_bitness) { xen_capabilities_info_t xen_caps = ""; privcmd_hypercall_t hypercall; int rc; /* * Try to unpause an invalid vcpu. If hypervisor supports gdbsx then * this should fail with an error other than ENOSYS. */ domctl.u.gdbsx_pauseunp_vcpu.vcpu = ~0u; (void)_domctl_hcall(XEN_DOMCTL_gdbsx_unpausevcpu, NULL, 0); if (errno == ENOSYS) { XGERR("Hyp is NOT enabled for gdbsx\n"); return -1; } if (mlock(&xen_caps, sizeof(xen_caps))) { XGERR("Unable to pin xen_caps in memory. errno:%d\n", errno); return -1; } memset(&xen_caps, 0, sizeof(xen_caps)); hypercall.op = __HYPERVISOR_xen_version; hypercall.arg[0] = (unsigned long)XENVER_capabilities; hypercall.arg[1] = (unsigned long)&xen_caps; rc = ioctl(_dom0_fd, IOCTL_PRIVCMD_HYPERCALL, &hypercall); munlock(&xen_caps, sizeof(xen_caps)); XGTRC("XENCAPS:%s\n", xen_caps); if (rc != 0) { XGERR("Failed xen_version hcall. errno:%d\n", errno); return -1; } _32bit_hyp = (strstr(xen_caps, "x86_64") == NULL); if (_32bit_hyp && guest_bitness !=32) { XGERR("32bit hyp can only run 32bit guests\n"); return -1; } return 0; }