/* * 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; }
/* * 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; }
/* * 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; }
/* * 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; }
/* Returns: 1 : domain is paused. 0 otherwise */ static int _domain_is_paused(void) { memset(&domctl.u, 0, sizeof(domctl.u)); if (_domctl_hcall(XEN_DOMCTL_getdomaininfo, NULL, 0)) { XGERR("ERROR: Unable to get domain paused info:%d\n", _dom_id); return 0; } return (domctl.u.getdomaininfo.flags & XEN_DOMINF_paused); }
/* Returns: 0 : success */ static int _unpause_domain(void) { memset(&domctl.u, 0, sizeof(domctl.u)); if (_domctl_hcall(XEN_DOMCTL_unpausedomain, NULL, 0)) { XGERR("Unable to unpause domain:%d errno:%d\n", _dom_id, errno); return -1; } return 0; }
/* check if domain is alive and well * returns : 0 if domain is not alive and well */ static int _domain_ok(struct xen_domctl_getdomaininfo *domp) { int rc = 0; if (domp->flags & XEN_DOMINF_dying) XGERR("Invalid domain (state dying)...\n"); else rc = 1; return rc; }
/* * check if any one of the vcpus is in a breakpoint * Returns: vcpuid : if a vcpu found in a bp * -1 : otherwise */ static vcpuid_t _vcpu_in_bp(void) { memset(&domctl.u, 0, sizeof(domctl.u)); if (_domctl_hcall(XEN_DOMCTL_gdbsx_domstatus, NULL, 0)) { XGERR("ERROR: Unable to check vcpu bp status:%d errno:%d\n", _dom_id, errno); return -1; } return domctl.u.gdbsx_domstatus.vcpu_id; }
/* Detach from guest for debugger exit */ void xg_detach_deinit(void) { memset(&domctl.u, 0, sizeof(domctl.u)); domctl.u.setdebugging.enable = 0; if (_domctl_hcall(XEN_DOMCTL_setdebugging, NULL, 0)) { XGERR("Unable to reset domain debug mode: errno:%d\n", errno); } if (_domain_is_paused()) _unpause_domain(); close(_dom0_fd); }
/* get vcpu context from xen and return it in *ctxtp * RETURNS: 0 for success */ static int _get_vcpu_ctxt(vcpuid_t vcpu_id, union vcpu_guest_context_any *anycp) { int sz = sizeof(union vcpu_guest_context_any); memset(anycp, 0, sz); domctl.u.vcpucontext.vcpu = (uint16_t)vcpu_id; set_xen_guest_handle(domctl.u.vcpucontext.ctxt, &anycp->ctxt); if (_domctl_hcall(XEN_DOMCTL_getvcpucontext, anycp, sz)) { XGERR("Failed hcall to get vcpu ctxt. errno:%d\n", errno); return 1; } return 0; }
/* Do the given DOMCTL hcall action(pause or unpause) on all but the given vcpu * Returns: 0 success */ static int _allbutone_vcpu(uint32_t hcall, vcpuid_t which_vcpu) { int i; for (i=0; i <= _max_vcpu_id; i++) { if (i == which_vcpu) continue; memset(&domctl.u, 0, sizeof(domctl.u)); domctl.u.gdbsx_pauseunp_vcpu.vcpu = i; if (_domctl_hcall(hcall, NULL, 0)) { XGERR("Unable to do:%d vcpu:%d errno:%d\n", hcall, i, errno); return 1; } } return 0; }
/* * Precondition: domctl global struct must be filled * Returns : 0 Success, failure otherwise with errno set */ static int _domctl_hcall(uint32_t cmd, /* which domctl hypercall */ void *domctlarg, /* arg/buf to domctl to pin in mem */ int sz) /* size of *domctlarg */ { privcmd_hypercall_t hypercall; int rc; if (domctlarg && sz && mlock(domctlarg, sz)) { XGERR("Unable to pin domctl arg. p:%p sz:%d errno:%d\n", domctlarg, sz, errno); return 1; } domctl.cmd = cmd; hypercall.op = __HYPERVISOR_domctl; hypercall.arg[0] = (unsigned long)&domctl; rc = ioctl(_dom0_fd, IOCTL_PRIVCMD_HYPERCALL, &hypercall); if (domctlarg && sz) munlock(domctlarg, sz); return rc; }