mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 18:50:05 +01:00
gdb 8.0 fixed bounds checking for 'g' packet (commit
9dc193c3be85aafa60ceff57d3b0430af607b4ce "Check for truncated
registers in process_g_packet"). This revealed that gdbsx did
not properly formatted 'g' packet - segment registers and eflags are
expected to be 32-bit fields in the response (according to
gdb/features/i386/64bit-core.xml in gdb sources). Specific error is:
Truncated register 26 in remote 'g' packet
instead of silently truncating part of register.
Additionally, it looks like segment registers of 64bit guests were never
reported correctly, because of type mismatch.
Signed-off-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
Acked-by: Wei Liu <wei.liu2@citrix.com>
837 lines
23 KiB
C
837 lines
23 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* This is the main module to interface with xen. This module exports APIs that
|
|
* allow for creating any remote debugger plugin. The APIs are:
|
|
*
|
|
* xg_init() : initialize
|
|
* xg_attach(): attach to the given guest, preparing it for debug
|
|
* xg_detach_deinit(): exit debugging
|
|
*
|
|
* xg_step() : single step the the given vcpu
|
|
* xg_resume_n_wait(): resume the target guest and wait for any debug event
|
|
* xg_regs_read(): read context of given vcpu
|
|
* xg_regs_write(): write context of given vcpu
|
|
* xg_read_mem(): read memory of guest at given VA
|
|
* xg_write_mem(): write memory of guest at given VA
|
|
*
|
|
* XGERR(): generic print error utility
|
|
* XGTRC(): generic trace utility
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <sys/mman.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <assert.h>
|
|
|
|
#include "xg_public.h"
|
|
#include <xen/version.h>
|
|
#include <xen/domctl.h>
|
|
#include <xen/sys/privcmd.h>
|
|
#include <xen/foreign/x86_32.h>
|
|
#include <xen/foreign/x86_64.h>
|
|
|
|
#define XGMIN(x,y) (((x)<(y))?(x):(y))
|
|
|
|
#define X86_EFLAGS_TF 0x00000100 /* Trap Flag */
|
|
|
|
|
|
/*
|
|
* Contexts returned by xen: (gdbsx : dom 0 : hypervisor)
|
|
*
|
|
* 32 : 32 : 32 => 64bit context never returned. can't run 64bit guests
|
|
* 32 : 32 : 64 => 32bit ctxt for 32bit PV guest. 64bit ctxt for 64 PV guests.
|
|
* HVM always 64bit ctxt.
|
|
* 32 : 64 : 64 => N/A
|
|
* 64 : 64 : 64 => Same as 32:32:64 (CONFIG_COMPAT is almost always defined)
|
|
* 64 : 64 : 64 => !CONFIG_COMPAT : not supported.
|
|
*/
|
|
typedef union vcpu_guest_context_any {
|
|
vcpu_guest_context_x86_64_t ctxt64;
|
|
vcpu_guest_context_x86_32_t ctxt32;
|
|
vcpu_guest_context_t ctxt;
|
|
} vcpu_guest_context_any_t;
|
|
|
|
|
|
int xgtrc_on = 0;
|
|
struct xen_domctl domctl; /* just use a global domctl */
|
|
|
|
static int _hvm_guest; /* hvm guest? 32bit HVMs have 64bit context */
|
|
static domid_t _dom_id; /* guest domid */
|
|
static int _max_vcpu_id; /* thus max_vcpu_id+1 VCPUs */
|
|
static int _dom0_fd; /* fd of /dev/privcmd */
|
|
static int _32bit_hyp; /* hyp is 32bit */
|
|
|
|
|
|
/* print trace info with function name pre-pended */
|
|
void
|
|
xgtrc(const char *fn, const char *fmt, ...)
|
|
{
|
|
char buf[2048];
|
|
va_list args;
|
|
|
|
fprintf(stderr, "%s:", fn);
|
|
va_start(args, fmt);
|
|
(void)vsnprintf(buf, sizeof(buf), fmt, args);
|
|
va_end(args);
|
|
fprintf(stderr, "%s", buf);
|
|
fflush (stderr);
|
|
}
|
|
|
|
/* print error msg with function name pre-pended */
|
|
void
|
|
xgprt(const char *fn, const char *fmt, ...)
|
|
{
|
|
char buf[2048];
|
|
va_list args;
|
|
|
|
fprintf(stderr, "ERROR:%s:", fn);
|
|
va_start(args, fmt);
|
|
(void)vsnprintf(buf, sizeof(buf), fmt, args);
|
|
va_end(args);
|
|
fprintf (stderr, "%s", buf);
|
|
fflush (stderr);
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns: 0 success
|
|
* -1 failure, errno set.
|
|
*/
|
|
int
|
|
xg_init()
|
|
{
|
|
int flags, saved_errno;
|
|
|
|
XGTRC("E\n");
|
|
if ((_dom0_fd=open("/dev/xen/privcmd", O_RDWR)) == -1) {
|
|
if ((_dom0_fd=open("/proc/xen/privcmd", O_RDWR)) == -1) {
|
|
perror("Failed to open /dev/xen/privcmd or /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;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
return _max_vcpu_id;
|
|
}
|
|
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
static void
|
|
_cp_32ctxt_to_32gdb(struct cpu_user_regs_x86_32 *cp, struct xg_gdb_regs32 *rp)
|
|
{
|
|
memset(rp, 0, sizeof(struct xg_gdb_regs32));
|
|
rp->ebx = cp->ebx;
|
|
rp->ecx = cp->ecx;
|
|
rp->edx = cp->edx;
|
|
rp->esi = cp->esi;
|
|
rp->edi = cp->edi;
|
|
rp->ebp = cp->ebp;
|
|
rp->eax = cp->eax;
|
|
rp->eip = cp->eip;
|
|
rp->cs = cp->cs;
|
|
rp->eflags = cp->eflags;
|
|
rp->esp = cp->esp;
|
|
rp->ss = cp->ss;
|
|
rp->es = cp->es;
|
|
rp->ds = cp->ds;
|
|
rp->fs = cp->fs;
|
|
rp->gs = cp->gs;
|
|
}
|
|
|
|
static void
|
|
_cp_64ctxt_to_32gdb(struct cpu_user_regs_x86_64 *cp, struct xg_gdb_regs32 *rp)
|
|
{
|
|
memset(rp, 0, sizeof(struct xg_gdb_regs32));
|
|
rp->ebx = cp->rbx;
|
|
rp->ecx = cp->rcx;
|
|
rp->edx = cp->rdx;
|
|
rp->esi = cp->rsi;
|
|
rp->edi = cp->rdi;
|
|
rp->ebp = cp->rbp;
|
|
rp->eax = cp->rax;
|
|
rp->eip = cp->rip;
|
|
rp->cs = cp->cs;
|
|
rp->eflags = cp->rflags;
|
|
rp->esp = cp->rsp;
|
|
rp->ss = cp->ss;
|
|
rp->es = cp->es;
|
|
rp->ds = cp->ds;
|
|
rp->fs = cp->fs;
|
|
rp->gs = cp->gs;
|
|
}
|
|
|
|
static void
|
|
_cp_64ctxt_to_64gdb(struct cpu_user_regs_x86_64 *cp, struct xg_gdb_regs64 *rp)
|
|
{
|
|
memset(rp, 0, sizeof(struct xg_gdb_regs64));
|
|
rp->r8 = cp->r8;
|
|
rp->r9 = cp->r9;
|
|
rp->r10 = cp->r10;
|
|
rp->r11 = cp->r11;
|
|
rp->r12 = cp->r12;
|
|
rp->r13 = cp->r13;
|
|
rp->r14 = cp->r14;
|
|
rp->r15 = cp->r15;
|
|
rp->rbx = cp->rbx;
|
|
rp->rcx = cp->rcx;
|
|
rp->rdx = cp->rdx;
|
|
rp->rsi = cp->rsi;
|
|
rp->rdi = cp->rdi;
|
|
rp->rbp = cp->rbp;
|
|
rp->rax = cp->rax;
|
|
rp->rip = cp->rip;
|
|
rp->rsp = cp->rsp;
|
|
rp->eflags = cp->rflags;
|
|
|
|
rp->cs = cp->cs;
|
|
rp->ss = cp->ss;
|
|
rp->es = cp->es;
|
|
rp->ds = cp->ds;
|
|
rp->fs = cp->fs;
|
|
rp->gs = cp->gs;
|
|
#if 0
|
|
printf("cp:%llx bp:%llx rip:%llx\n", rp->rsp, rp->rbp, rp->rip);
|
|
printf("rax:%llx rbx:%llx\n", rp->rax, rp->rbx);
|
|
printf("cs:%04x ss:%04x ds:%04x\n", (int)rp->cs, (int)rp->ss,
|
|
(int)rp->ds);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
_cp_32gdb_to_32ctxt(struct xg_gdb_regs32 *rp, struct cpu_user_regs_x86_32 *cp)
|
|
{
|
|
cp->ebx = rp->ebx;
|
|
cp->ecx = rp->ecx;
|
|
cp->edx = rp->edx;
|
|
cp->esi = rp->esi;
|
|
cp->edi = rp->edi;
|
|
cp->ebp = rp->ebp;
|
|
cp->eax = rp->eax;
|
|
cp->eip = rp->eip;
|
|
cp->esp = rp->esp;
|
|
cp->cs = rp->cs;
|
|
cp->ss = rp->ss;
|
|
cp->es = rp->es;
|
|
cp->ds = rp->ds;
|
|
cp->fs = rp->fs;
|
|
cp->gs = rp->gs;
|
|
cp->eflags = rp->eflags;
|
|
}
|
|
|
|
static void
|
|
_cp_32gdb_to_64ctxt(struct xg_gdb_regs32 *rp, struct cpu_user_regs_x86_64 *cp)
|
|
{
|
|
cp->rbx = rp->ebx;
|
|
cp->rcx = rp->ecx;
|
|
cp->rdx = rp->edx;
|
|
cp->rsi = rp->esi;
|
|
cp->rdi = rp->edi;
|
|
cp->rbp = rp->ebp;
|
|
cp->rax = rp->eax;
|
|
cp->rip = rp->eip;
|
|
cp->rsp = rp->esp;
|
|
cp->cs = rp->cs;
|
|
cp->ss = rp->ss;
|
|
cp->es = rp->es;
|
|
cp->ds = rp->ds;
|
|
cp->fs = rp->fs;
|
|
cp->gs = rp->gs;
|
|
cp->eflags = rp->eflags;
|
|
}
|
|
|
|
static void
|
|
_cp_64gdb_to_64ctxt(struct xg_gdb_regs64 *rp, struct cpu_user_regs_x86_64 *cp)
|
|
{
|
|
cp->r8 = rp->r8;
|
|
cp->r9 = rp->r9;
|
|
cp->r10 = rp->r10;
|
|
cp->r11 = rp->r11;
|
|
cp->r12 = rp->r12;
|
|
cp->r13 = rp->r13;
|
|
cp->r14 = rp->r14;
|
|
cp->r15 = rp->r15;
|
|
cp->rbx = rp->rbx;
|
|
cp->rcx = rp->rcx;
|
|
cp->rdx = rp->rdx;
|
|
cp->rsi = rp->rsi;
|
|
cp->rdi = rp->rdi;
|
|
cp->rbp = rp->rbp;
|
|
cp->rax = rp->rax;
|
|
cp->rip = rp->rip;
|
|
cp->rsp = rp->rsp;
|
|
cp->rflags = rp->eflags;
|
|
|
|
cp->cs = (uint16_t)rp->cs;
|
|
cp->ss = (uint16_t)rp->ss;
|
|
cp->es = (uint16_t)rp->es;
|
|
cp->ds = (uint16_t)rp->ds;
|
|
cp->fs = (uint16_t)rp->fs;
|
|
cp->gs = (uint16_t)rp->gs;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* mode: C
|
|
* c-file-style: "BSD"
|
|
* c-basic-offset: 4
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*/
|