Files
xen/tools/libxc/xc_ptrace.c
T
Keir Fraser 5cc436c1d2 libxc: eliminate static variables, use xentoollog; API change
This patch eliminate the global variables in libxenctrl (used for
logging and error reporting).

Instead the information which was in the global variables is now in a
new xc_interface* opaque structure, which xc_interface open returns
instead of the raw file descriptor; furthermore, logging is done via
xentoollog.

There are three new parameters to xc_interface_open to control the
logging, but existing callers can just pass "0" for all three to get
the old behaviour.

All libxc callers have been adjusted accordingly.

Also update QEMU_TAG for corresponding qemu change.

Signed-off-by: Ian Jackson <ian.jackson@eu.citrix.com>
2010-05-28 09:30:19 +01:00

476 lines
12 KiB
C

#include <sys/ptrace.h>
#include <sys/wait.h>
#include <time.h>
#include "xc_private.h"
#include "xg_private.h"
#include "xc_ptrace.h"
#ifdef DEBUG
static char *ptrace_names[] = {
"PTRACE_TRACEME",
"PTRACE_PEEKTEXT",
"PTRACE_PEEKDATA",
"PTRACE_PEEKUSER",
"PTRACE_POKETEXT",
"PTRACE_POKEDATA",
"PTRACE_POKEUSER",
"PTRACE_CONT",
"PTRACE_KILL",
"PTRACE_SINGLESTEP",
"PTRACE_INVALID",
"PTRACE_INVALID",
"PTRACE_GETREGS",
"PTRACE_SETREGS",
"PTRACE_GETFPREGS",
"PTRACE_SETFPREGS",
"PTRACE_ATTACH",
"PTRACE_DETACH",
"PTRACE_GETFPXREGS",
"PTRACE_SETFPXREGS",
"PTRACE_INVALID",
"PTRACE_INVALID",
"PTRACE_INVALID",
"PTRACE_INVALID",
"PTRACE_SYSCALL",
};
#endif
static int current_domid = -1;
static int current_isfile;
static int current_is_hvm;
static uint64_t online_cpumap;
static uint64_t regs_valid;
static unsigned int nr_vcpu_ids;
static vcpu_guest_context_any_t *ctxt;
#define FOREACH_CPU(cpumap, i) for ( cpumap = online_cpumap; (i = xc_ffs64(cpumap)); cpumap &= ~(1 << (index - 1)) )
static int
fetch_regs(xc_interface *xch, int cpu, int *online)
{
xc_vcpuinfo_t info;
int retval = 0;
if (online)
*online = 0;
if ( !(regs_valid & (1 << cpu)) )
{
retval = xc_vcpu_getcontext(xch, current_domid,
cpu, &ctxt[cpu]);
if ( retval )
goto done;
regs_valid |= (1 << cpu);
}
if ( online == NULL )
goto done;
retval = xc_vcpu_getinfo(xch, current_domid, cpu, &info);
*online = info.online;
done:
return retval;
}
static struct thr_ev_handlers {
thr_ev_handler_t td_create;
thr_ev_handler_t td_death;
} handlers;
void
xc_register_event_handler(thr_ev_handler_t h,
td_event_e e)
{
switch (e) {
case TD_CREATE:
handlers.td_create = h;
break;
case TD_DEATH:
handlers.td_death = h;
break;
default:
abort(); /* XXX */
}
}
static inline int
paging_enabled(vcpu_guest_context_any_t *v)
{
unsigned long cr0 = v->c.ctrlreg[0];
return (cr0 & X86_CR0_PE) && (cr0 & X86_CR0_PG);
}
vcpu_guest_context_any_t *xc_ptrace_get_vcpu_ctxt(xc_interface *xch,
unsigned int nr_cpus)
{
if (nr_cpus > nr_vcpu_ids) {
vcpu_guest_context_any_t *new;
new = realloc(ctxt, nr_cpus * sizeof(*ctxt));
if (!new)
return NULL;
ctxt = new;
nr_vcpu_ids = nr_cpus;
}
return ctxt;
}
/*
* Fetch registers for all online cpus and set the cpumap
* to indicate which cpus are online
*
*/
static int
get_online_cpumap(xc_interface *xch, struct xen_domctl_getdomaininfo *d,
uint64_t *cpumap)
{
int i, online;
if (!xc_ptrace_get_vcpu_ctxt(xch, d->max_vcpu_id + 1))
return -ENOMEM;
*cpumap = 0;
for (i = 0; i <= d->max_vcpu_id; i++) {
fetch_regs(xch, i, &online);
if (online)
*cpumap |= (1 << i);
}
return (*cpumap == 0) ? -1 : 0;
}
/*
* Notify GDB of any vcpus that have come online or gone offline
* update online_cpumap
*
*/
static void
online_vcpus_changed(xc_interface *xch, uint64_t cpumap)
{
uint64_t changed_cpumap = cpumap ^ online_cpumap;
int index;
while ( (index = xc_ffs64(changed_cpumap)) ) {
if ( cpumap & (1 << (index - 1)) )
{
if (handlers.td_create) handlers.td_create(index - 1);
} else {
IPRINTF("thread death: %d\n", index - 1);
if (handlers.td_death) handlers.td_death(index - 1);
}
changed_cpumap &= ~(1 << (index - 1));
}
online_cpumap = cpumap;
}
static void *
map_domain_va(
xc_interface *xch,
int cpu,
void *guest_va,
int perm)
{
unsigned long va = (unsigned long)guest_va;
unsigned long mfn;
void *map;
/* cross page boundary */
if ( (va & ~PAGE_MASK) + sizeof(long) > PAGE_SIZE )
return NULL;
mfn = xc_translate_foreign_address(xch, current_domid, cpu, va);
if ( mfn == 0 )
return NULL;
map = xc_map_foreign_range(xch, current_domid, PAGE_SIZE,
perm, mfn);
if (map == NULL)
return NULL;
/*
* Due to the use of API fallback code in libxc, errno may
* be clobberred during successful operations. Since the caller
* of xc_ptrace is depending on errno for return status, clear
* errno here.
*/
errno = 0;
return map + (va & ~PAGE_MASK);
}
static void
unmap_domain_va(void *guest_va)
{
munmap((void *)((unsigned long)guest_va & PAGE_MASK), PAGE_SIZE);
}
int control_c_pressed_flag = 0;
static int
__xc_waitdomain(
xc_interface *xch,
int domain,
int *status,
int options)
{
DECLARE_DOMCTL;
int retval;
struct timespec ts;
uint64_t cpumap;
ts.tv_sec = 0;
ts.tv_nsec = 10*1000*1000;
domctl.cmd = XEN_DOMCTL_getdomaininfo;
domctl.domain = domain;
retry:
retval = do_domctl(xch, &domctl);
if ( retval || (domctl.domain != domain) )
{
IPRINTF("getdomaininfo failed\n");
goto done;
}
*status = domctl.u.getdomaininfo.flags;
if ( options & WNOHANG )
goto done;
if (control_c_pressed_flag) {
xc_domain_pause(xch, domain);
control_c_pressed_flag = 0;
goto done;
}
if ( !(domctl.u.getdomaininfo.flags & XEN_DOMINF_paused) )
{
nanosleep(&ts,NULL);
goto retry;
}
done:
if (get_online_cpumap(xch, &domctl.u.getdomaininfo, &cpumap))
IPRINTF("get_online_cpumap failed\n");
if (online_cpumap != cpumap)
online_vcpus_changed(xch, cpumap);
return retval;
}
long
xc_ptrace(
xc_interface *xch,
enum __ptrace_request request,
uint32_t domid_tid,
long eaddr,
long edata)
{
DECLARE_DOMCTL;
struct gdb_regs pt;
long retval = 0;
unsigned long *guest_va;
uint64_t cpumap;
int cpu, index;
void *addr = (char *)eaddr;
void *data = (char *)edata;
cpu = (request != PTRACE_ATTACH) ? domid_tid : 0;
switch ( request )
{
case PTRACE_PEEKTEXT:
case PTRACE_PEEKDATA:
if (current_isfile)
guest_va = (unsigned long *)map_domain_va_core(
xch, current_domid, cpu, addr);
else
guest_va = (unsigned long *)map_domain_va(
xch, cpu, addr, PROT_READ);
if ( guest_va == NULL )
goto out_error;
retval = *guest_va;
if (!current_isfile)
unmap_domain_va(guest_va);
break;
case PTRACE_POKETEXT:
case PTRACE_POKEDATA:
/* XXX assume that all CPUs have the same address space */
if (current_isfile)
guest_va = (unsigned long *)map_domain_va_core(
xch, current_domid, cpu, addr);
else
guest_va = (unsigned long *)map_domain_va(
xch, cpu, addr, PROT_READ|PROT_WRITE);
if ( guest_va == NULL )
goto out_error;
*guest_va = edata;
if (!current_isfile)
unmap_domain_va(guest_va);
break;
case PTRACE_GETREGS:
if (!current_isfile && fetch_regs(xch, cpu, NULL))
goto out_error;
SET_PT_REGS(pt, ctxt[cpu].c.user_regs);
memcpy(data, &pt, sizeof(struct gdb_regs));
break;
case PTRACE_GETFPREGS:
if (!current_isfile && fetch_regs(xch, cpu, NULL))
goto out_error;
memcpy(data, &ctxt[cpu].c.fpu_ctxt, sizeof (elf_fpregset_t));
break;
case PTRACE_GETFPXREGS:
if (!current_isfile && fetch_regs(xch, cpu, NULL))
goto out_error;
memcpy(data, &ctxt[cpu].c.fpu_ctxt, sizeof(ctxt[cpu].c.fpu_ctxt));
break;
case PTRACE_SETREGS:
if (current_isfile)
goto out_unsupported; /* XXX not yet supported */
SET_XC_REGS(((struct gdb_regs *)data), ctxt[cpu].c.user_regs);
if ((retval = xc_vcpu_setcontext(xch, current_domid, cpu,
&ctxt[cpu])))
goto out_error_domctl;
break;
case PTRACE_SINGLESTEP:
if (current_isfile)
goto out_unsupported; /* XXX not yet supported */
/* XXX we can still have problems if the user switches threads
* during single-stepping - but that just seems retarded
*/
/* Try to enalbe Monitor Trap Flag for HVM, and fall back to TF
* if no MTF support
*/
if ( !current_is_hvm ||
xc_domain_debug_control(xch,
current_domid,
XEN_DOMCTL_DEBUG_OP_SINGLE_STEP_ON,
cpu) )
{
ctxt[cpu].c.user_regs.eflags |= PSL_T;
if ((retval = xc_vcpu_setcontext(xch, current_domid, cpu,
&ctxt[cpu])))
goto out_error_domctl;
}
/* FALLTHROUGH */
case PTRACE_CONT:
case PTRACE_DETACH:
if (current_isfile)
goto out_unsupported; /* XXX not yet supported */
if ( request != PTRACE_SINGLESTEP )
{
FOREACH_CPU(cpumap, index) {
cpu = index - 1;
if ( !current_is_hvm ||
xc_domain_debug_control(xch,
current_domid,
XEN_DOMCTL_DEBUG_OP_SINGLE_STEP_OFF,
cpu) )
{
if (fetch_regs(xch, cpu, NULL))
goto out_error;
/* Clear trace flag */
if ( ctxt[cpu].c.user_regs.eflags & PSL_T )
{
ctxt[cpu].c.user_regs.eflags &= ~PSL_T;
if ((retval = xc_vcpu_setcontext(xch, current_domid,
cpu, &ctxt[cpu])))
goto out_error_domctl;
}
}
}
}
if ( request == PTRACE_DETACH )
{
if ((retval = xc_domain_setdebugging(xch, current_domid, 0)))
goto out_error_domctl;
}
regs_valid = 0;
if ((retval = xc_domain_unpause(xch, current_domid > 0 ?
current_domid : -current_domid)))
goto out_error_domctl;
break;
case PTRACE_ATTACH:
current_domid = domid_tid;
current_isfile = (int)edata;
if (current_isfile)
break;
domctl.cmd = XEN_DOMCTL_getdomaininfo;
domctl.domain = current_domid;
retval = do_domctl(xch, &domctl);
if ( retval || (domctl.domain != current_domid) )
goto out_error_domctl;
if ( domctl.u.getdomaininfo.flags & XEN_DOMINF_paused )
IPRINTF("domain currently paused\n");
else if ((retval = xc_domain_pause(xch, current_domid)))
goto out_error_domctl;
current_is_hvm = !!(domctl.u.getdomaininfo.flags&XEN_DOMINF_hvm_guest);
if ((retval = xc_domain_setdebugging(xch, current_domid, 1)))
goto out_error_domctl;
if (get_online_cpumap(xch, &domctl.u.getdomaininfo, &cpumap))
IPRINTF("get_online_cpumap failed\n");
if (online_cpumap != cpumap)
online_vcpus_changed(xch, cpumap);
break;
case PTRACE_TRACEME:
IPRINTF("PTRACE_TRACEME is an invalid request under Xen\n");
goto out_error;
default:
goto out_unsupported; /* XXX not yet supported */
}
return retval;
out_error_domctl:
PERROR("domctl failed");
out_error:
errno = EINVAL;
return retval;
out_unsupported:
#ifdef DEBUG
IPRINTF("unsupported xc_ptrace request %s\n", ptrace_names[request]);
#endif
errno = ENOSYS;
return -1;
}
int
xc_waitdomain(
xc_interface *xch,
int domain,
int *status,
int options)
{
if (current_isfile)
return xc_waitdomain_core(xch, domain, status, options);
return __xc_waitdomain(xch, domain, status, options);
}
/*
* Local variables:
* mode: C
* c-set-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/