From 61f2a440299cdf5ff809cd330a6400f6a5cdf910 Mon Sep 17 00:00:00 2001 From: Keir Fraser Date: Thu, 15 Oct 2009 09:36:40 +0100 Subject: gdbsx: a gdbserver stub for xen. It should be run on dom0 on gdbsx enabled hypervisor. For details, please see tools/debugger/gdbsx/README Signed-off-by: Mukesh Rathor Signed-off-by: Keir Fraser --- tools/debugger/gdbsx/Makefile | 28 ++ tools/debugger/gdbsx/README | 143 +++++++ tools/debugger/gdbsx/Rules.mk | 9 + tools/debugger/gdbsx/gx/Makefile | 20 + tools/debugger/gdbsx/gx/gx.h | 40 ++ tools/debugger/gdbsx/gx/gx_comm.c | 328 +++++++++++++++ tools/debugger/gdbsx/gx/gx_local.c | 146 +++++++ tools/debugger/gdbsx/gx/gx_main.c | 696 +++++++++++++++++++++++++++++++ tools/debugger/gdbsx/gx/gx_utils.c | 145 +++++++ tools/debugger/gdbsx/gx/xg_dummy.c | 122 ++++++ tools/debugger/gdbsx/xg/Makefile | 36 ++ tools/debugger/gdbsx/xg/xg_main.c | 806 ++++++++++++++++++++++++++++++++++++ tools/debugger/gdbsx/xg/xg_public.h | 110 +++++ 13 files changed, 2629 insertions(+) create mode 100644 tools/debugger/gdbsx/Makefile create mode 100644 tools/debugger/gdbsx/README create mode 100644 tools/debugger/gdbsx/Rules.mk create mode 100644 tools/debugger/gdbsx/gx/Makefile create mode 100644 tools/debugger/gdbsx/gx/gx.h create mode 100644 tools/debugger/gdbsx/gx/gx_comm.c create mode 100644 tools/debugger/gdbsx/gx/gx_local.c create mode 100644 tools/debugger/gdbsx/gx/gx_main.c create mode 100644 tools/debugger/gdbsx/gx/gx_utils.c create mode 100644 tools/debugger/gdbsx/gx/xg_dummy.c create mode 100644 tools/debugger/gdbsx/xg/Makefile create mode 100644 tools/debugger/gdbsx/xg/xg_main.c create mode 100644 tools/debugger/gdbsx/xg/xg_public.h (limited to 'tools/debugger') diff --git a/tools/debugger/gdbsx/Makefile b/tools/debugger/gdbsx/Makefile new file mode 100644 index 0000000000..4b25454e03 --- /dev/null +++ b/tools/debugger/gdbsx/Makefile @@ -0,0 +1,28 @@ +XEN_ROOT = ../../.. +include ./Rules.mk + +sbindir=/usr/sbin + +.PHONY: all +all: + $(MAKE) -C gx + $(MAKE) -C xg + $(MAKE) gdbsx + +.PHONY: clean +clean: + rm -f xg_all.a gx_all.a gdbsx + set -e; for d in xg gx; do $(MAKE) -C $$d clean; done + +.PHONY: install +install: all + [ -d $(DESTDIR)$(sbindir) ] || $(INSTALL_DIR) $(DESTDIR)$(sbindir) + $(INSTALL_PROG) gdbsx $(DESTDIR)$(sbindir)/gdbsx + +gdbsx: gx/gx_all.a xg/xg_all.a + $(CC) -o $@ $^ + +xg/xg_all.a: + $(MAKE) -C xg +gx/gx_all.a: + $(MAKE) -C gx diff --git a/tools/debugger/gdbsx/README b/tools/debugger/gdbsx/README new file mode 100644 index 0000000000..ac01add93c --- /dev/null +++ b/tools/debugger/gdbsx/README @@ -0,0 +1,143 @@ + + gdbsx: gdbserver for xen + + +Welcome to gdbsx. gdbsx is a gdbserver program to debug guest kernels and +kernel modules. It runs on dom0 running on xen hypervisor and allows debug +of 32 or 64bit PV or HVM elf guest binaries. It can also be run standalone, +without remote gdb, to dump context of any/all VCPUs of any guest. + +It is divided in two parts, gx and xg. The former interacts with remote gdb, +while latter interacts with xen and exports public APIs that can be used to +create a plug in for any other debugger or binary type. + + +USAGE: + - boot with gdbsx enabled hypervisor (eg, on OVM: xen-64bit-debug.gz) + - copy gdbsx binary to the dom0 (assume hostname is "dom0"), then: + + USAGE 1: + - dom0> gdbsx -c 1 64 : displays VCPU contexts for 64bit guest with domid 1 + + USAGE 2: + - dom0> gdbsx -a 2 64 9999 + connects to a 64bit guest with domid 2 and waits for gdb connection + - now, connect to the above gdbsx from a remote system or dom0 as: + bash> gdb ./vmlinux (exact matching vmlinux of guest kernel) + (gdb) target remote dom0:9999 + + - Additionally, to debug loadable kernel modules, please do following: + (gdb) p init_mm.pgd[3] + $1 = {pgd = 0x1b874f027} + (gdb) monitor pgd3 0x1b874f027 (Make sure value is in HEX) + pgd3val set to: 0x1b874f027 + + - use gdb as normal, breakpoints, single step, etc... + - when need to break into gdb, instead of ctrl-c, just do "xm pause " + on dom0 to pause the guest. this will break into gdb right away. + - detach/quit from gdb (leave gdbsx alone) to gracefully exit. + - if ctrl-c or core-dumped, make sure to do xm unpause if guest still paused. + + - multiple vcpus: + o gdb>set scheduler-locking on : for single step of correct vcpu. + + o since gdb is not kernel debugger, vcpus are emulated via threads + Thus, gdb>info threads : will show all vcpus. Then, switch thread + to get to another vcpu, etc... Remember, gdb has it's own [thread] + id, off by 1. + + - See below for some useful gdb macros. Please email me if you've more. + + +NOTES: + - For now, it is not possible to run gdbsx on a guest and gdb inside + the same guest at the same time. + - To report problems, please run gdbsx with -d and collect output. + - VCPU offlining is not supported. Thus [0-NUMVCPUs] are all assumed active. + +TIPS: + - make sure firewall is disabled on dom0 if running gdb on a different host. + - Must be at least gdb version 6.5-16.x to debug el5 kernels. + +BUILD: (if you don't have access to binary): + - first compile the hypervisor: xen> make gdbsx=y + To have both kdb and gdbsx, xen> make kdb=y gdbsx=y + (NOTE: kdb is not required for gdbsx) + - install the hypervisor and reboot + - now go to, tools/debugger/gdbsx and do make + On 32bit system, a 32bit binary will be built with support for both 32 + and 64bit guests. On 64bit system, a 64bit binary will be built with + support for both. + + +Mukesh Rathor +Oracle Corporation, +Redwood Shores, CA USA +mukesh[dot]rathor[at]oracle[dot]com + + +------------------------------------------------------------------------------ + +USEFUL gdb macros: + +# Courtesy Zhigang W (http://10.182.120.78/tech/vt/ovm/debug/gdbinit.macros): + +define ps + dont-repeat + set $tasks = (struct list_head *)init_task->tasks + set $offset = (unsigned long)&init_task->tasks - (unsigned long)&init_task + set $task = $tasks + set $task_entry = (struct task_struct *)((unsigned long)$task - $offset) + printf "Pointer PID Command\n" + printf "%-14p%-9d%s\n", $task_entry, $task_entry->pid, $task_entry->comm + set $task = $task->next + while $task != $tasks + set $task_entry = (struct task_struct *)((unsigned long)$task - $offset) + if ($task_entry->pid) != 0 + printf "%-14p%-9d%s\n", $task_entry, $task_entry->pid, $task_entry->comm + end + set $task = $task->next + end +end + +document ps +Report a snapshot of the current processes. +end + + +define lsmod + dont-repeat + # 4 for 32bit kernels. 8 for 64bit kernels. + set $sz = sizeof(long) + set $mod = (struct list_head *)modules + printf "modptr address name\n" + while 1 + set $mod_entry = (struct module *)((unsigned long)$mod - $sz) + if ($sz == 4) + printf "%08lx %08lx %s\n", $mod_entry, \ + $mod_entry->module_core, $mod_entry->name + else + printf "%016lx %016lx %s\n", $mod_entry, \ + $mod_entry->module_core, $mod_entry->name + end + set $mod = $mod->next + if ($mod == &modules) + loop_break + end + end +end + +document lsmod +Show the list of modules loaded in the Linux kernel. +end + +define log + dont-repeat + printf "%s", log_buf +end + +document log +Dump system message buffer. +end + +------------------------------------------------------------------------------ diff --git a/tools/debugger/gdbsx/Rules.mk b/tools/debugger/gdbsx/Rules.mk new file mode 100644 index 0000000000..d729323cb5 --- /dev/null +++ b/tools/debugger/gdbsx/Rules.mk @@ -0,0 +1,9 @@ +include $(XEN_ROOT)/tools/Rules.mk + +CFLAGS += -Werror -Wmissing-prototypes +# (gcc 4.3x and later) -Wconversion -Wno-sign-conversion + +# just in case have to debug gdbsx, keep life simple. +TMPFLAGS := $(CFLAGS) +CFLAGS := $(filter-out -O% -DNDEBUG -fomit-frame-pointer, $(TMPFLAGS)) +CFLAGS += -O0 diff --git a/tools/debugger/gdbsx/gx/Makefile b/tools/debugger/gdbsx/gx/Makefile new file mode 100644 index 0000000000..c9a48c0ccf --- /dev/null +++ b/tools/debugger/gdbsx/gx/Makefile @@ -0,0 +1,20 @@ +XEN_ROOT = ../../../.. +include ../Rules.mk + +GX_OBJS := gx_comm.o gx_main.o gx_utils.o gx_local.o +GX_HDRS := $(wildcard *.h) + +.PHONY: all +all: gx_all.a + +.PHONY: clean +clean: + rm -rf gx_all.a *.o + + +#%.o: %.c $(GX_HDRS) Makefile +# $(CC) -c $(CFLAGS) -o $@ $< + +gx_all.a: $(GX_OBJS) Makefile $(GX_HDRS) + ar cr $@ $(GX_OBJS) # problem with ld using -m32 + diff --git a/tools/debugger/gdbsx/gx/gx.h b/tools/debugger/gdbsx/gx/gx.h new file mode 100644 index 0000000000..47594c3696 --- /dev/null +++ b/tools/debugger/gdbsx/gx/gx.h @@ -0,0 +1,40 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +typedef uint16_t domid_t; +typedef unsigned char uchar; + +#include "../xg/xg_public.h" + +int gx_remote_open (char *commstr); +void gx_remote_close(void); +int gx_getpkt (char *buf); +void gx_write_ok(char *buf); +void gx_write_err(char *buf); +void gx_convert_int_to_ascii (char *from, char *to, int n); +void gx_convert_ascii_to_int (char *from, char *to, int n); +int gx_putpkt (char *buf); +void gx_decode_m_packet(char *, uint64_t *, int *); +char *gx_decode_M_packet(char *, uint64_t *, int *); +void gx_decode_zZ_packet(char *, uint64_t *); +void gx_reply_ok(char *); +void gx_reply_error(char *); +int gx_fromhex(int); +int gx_tohex(int); +int gx_local_cmd(domid_t domid, vcpuid_t vcpuid); +void gxprt(const char *fmt, ...); diff --git a/tools/debugger/gdbsx/gx/gx_comm.c b/tools/debugger/gdbsx/gx/gx_comm.c new file mode 100644 index 0000000000..f991952239 --- /dev/null +++ b/tools/debugger/gdbsx/gx/gx_comm.c @@ -0,0 +1,328 @@ +/* Remote utility routines for the remote server for GDB. + Copyright (C) 2008 + Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +/* This module handles communication with remote gdb. courtesy + * of gdbserver remote-utils.c */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gx.h" + + +extern int gx_remote_dbg; + +static int remote_fd; + + +/* Returns: 0 success. -1 failure */ +static int +do_tcp(char *port_str) +{ + int port; + struct sockaddr_in sockaddr; + socklen_t tmp; + int sock_fd; + + port = atoi(port_str); + + sock_fd = socket(PF_INET, SOCK_STREAM, 0); + if (sock_fd < 0) { + gxprt("ERROR: failed socket open. errno:%d\n", errno); + return -1; + } + + /* Allow rapid reuse of this port. */ + tmp = 1; + setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&tmp,sizeof(tmp)); + + sockaddr.sin_family = PF_INET; + sockaddr.sin_port = htons (port); + sockaddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock_fd, (struct sockaddr *) &sockaddr, sizeof (sockaddr)) + || listen (sock_fd, 1)) { + gxprt("ERROR: can't bind address. errno:%d\n", errno); + close(sock_fd); + return -1; + } + printf("Listening on port %d\n", port); + + tmp = sizeof(sockaddr); + remote_fd = accept(sock_fd, (struct sockaddr *) &sockaddr, &tmp); + if (remote_fd == -1) { + gxprt("ERROR: accept failed. errno:%d\n", errno); + close(sock_fd); + return -1; + } + + /* Enable TCP keep alive process. */ + tmp = 1; + setsockopt(sock_fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&tmp,sizeof(tmp)); + + /* Tell TCP not to delay small packets. This greatly speeds up + * interactive response. */ + tmp = 1; + setsockopt(remote_fd, IPPROTO_TCP, TCP_NODELAY, + (char *)&tmp, sizeof(tmp)); + + close(sock_fd); /* No longer need this */ + + signal(SIGPIPE, SIG_IGN); /* If we don't do this, then gdbserver simply + * exits when the remote side dies. */ + + /* Convert IP address to string */ + printf("Remote debugging from host %s\n", inet_ntoa(sockaddr.sin_addr)); + + return 0; +} + +/* + * Open a connection for remote gdb on the given port number + * Returns: 0 for success. -1 for failure + */ +int +gx_remote_open(char *portnum_str) +{ + int save_fcntl_flags; + + if (do_tcp(portnum_str) == -1) { + close(remote_fd); + return -1; + } + +#if defined(F_SETFL) && defined (FASYNC) + save_fcntl_flags = fcntl(remote_fd, F_GETFL, 0); + fcntl(remote_fd, F_SETFL, save_fcntl_flags | FASYNC); +#if defined (F_SETOWN) + fcntl (remote_fd, F_SETOWN, getpid ()); +#endif +#endif + return 0; +} + +void +gx_remote_close(void) +{ + close(remote_fd); +} + + +/* Returns next char from remote gdb. -1 if error. */ +static int +readchar(void) +{ + static char buf[BUFSIZ]; + static int bufcnt = 0; + static char *bufp; + uint64_t ll; + + if (bufcnt-- > 0) + return *bufp++ & 0x7f; + + bufcnt = read(remote_fd, buf, sizeof (buf)); + ll = *(uint64_t *)buf; + if (bufcnt <= 0) { + if (bufcnt == 0) + gxprt("readchar: Got EOF\n"); + else + perror ("readchar"); + return -1; + } + bufp = buf; + bufcnt--; + return *bufp++ & 0x7f; +} + +/* Read a packet from the remote machine, with error checking, + * and store it in buf. + * Returns: length of packet, or negative int if error. + */ +int +gx_getpkt (char *buf) +{ + char *bp; + unsigned char csum, c1, c2; + int c; + + while (1) { + csum = 0; + + while (1) { + c = readchar(); + if (c == '$') + break; + + if (gx_remote_dbg) + gxprt("[getpkt: discarding char '%c']\n", c); + if (c < 0) + return -1; + } + + bp = buf; + while (1) { + c = readchar (); + if (c < 0) + return -1; + if (c == '#') + break; + *bp++ = c; + csum += c; + } + *bp = 0; + + c1 = gx_fromhex(readchar()); + c2 = gx_fromhex(readchar()); + + if (csum == (c1 << 4) + c2) + break; + + gxprt("Bad checksum, sentsum=0x%x, csum=0x%x, buf=%s\n", + (c1 << 4) + c2, csum, buf); + write(remote_fd, "-", 1); + } + if (gx_remote_dbg) { + gxprt("getpkt (\"%s\"); [sending ack] \n", buf); + } + + write(remote_fd, "+", 1); + + if (gx_remote_dbg) { + gxprt("[sent ack]\n"); + } + return bp - buf; +} + +void +gx_reply_ok(char *buf) +{ + buf[0] = 'O'; + buf[1] = 'K'; + buf[2] = '\0'; +} + +/* ENN error */ +void +gx_reply_error(char *buf) +{ + buf[0] = 'E'; + buf[1] = '0'; + buf[2] = '1'; + buf[3] = '\0'; +} + +/* + * Send a packet to the remote machine, with error checking. + * The data of the packet is in buf. + * Returns: >= 0 on success, -1 otherwise. + */ +int +gx_putpkt (char *buf) +{ + int i; + unsigned char csum = 0; + char *buf2; + char buf3[1]; + int cnt = strlen (buf); + char *p; + + buf2 = malloc(8192); + + /* Copy the packet into buffer buf2, encapsulating it + * and giving it a checksum. */ + + p = buf2; + *p++ = '$'; + + for (i = 0; i < cnt; i++) { + csum += buf[i]; + *p++ = buf[i]; + } + *p++ = '#'; + *p++ = gx_tohex((csum >> 4) & 0xf); + *p++ = gx_tohex(csum & 0xf); + + *p = '\0'; + + /* Send it over and over until we get a positive ack. */ + + do { + int cc; + + if (write(remote_fd, buf2, p - buf2) != p - buf2) { + perror("putpkt(write)"); + return -1; + } + if (gx_remote_dbg) + gxprt("putpkt (\"%s\"); [looking for ack]\n", buf2); + + cc = read(remote_fd, buf3, 1); + if (gx_remote_dbg) + gxprt("[received '%c' (0x%x)]\n", buf3[0], buf3[0]); + + if (cc <= 0) { + if (cc == 0) + gxprt("putpkt(read): Got EOF\n"); + else + gxprt("putpkt(read)"); + free(buf2); + return -1; + } + /* Check for an input interrupt while we're here. */ + if (buf3[0] == '\003') + gxprt("WARN: need to send SIGINT in putpkt\n"); + + } while (buf3[0] != '+'); + + free(buf2); + return 1; /* Success! */ +} + diff --git a/tools/debugger/gdbsx/gx/gx_local.c b/tools/debugger/gdbsx/gx/gx_local.c new file mode 100644 index 0000000000..c8f0e72a1b --- /dev/null +++ b/tools/debugger/gdbsx/gx/gx_local.c @@ -0,0 +1,146 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + + +/* This file to impelement functions that run interactively and don't + * involve remote gdb. Eg, print vcpu context and exit. */ + +#include +#include +#include +#include + +#include "gx.h" + +extern vcpuid_t max_vcpuid; +extern int guest_bitness; + +static void +prnt_32regs(struct xg_gdb_regs32 *r32p) +{ + printf("eip:%08x esp:%08x flags:%08x\n", r32p->eip, r32p->esp, + r32p->eflags); + printf("eax:%08x ebx:%08x ecx:%08x edx:%08x\n", r32p->eax, + r32p->ebx, r32p->ecx, r32p->edx); + printf("esi:%08x edi:%08x ebp:%08x\n", r32p->esi, r32p->edi, + r32p->ebp); + printf("cs:%x ds:%x fs:%x gs:%x\n", r32p->cs, r32p->ds, r32p->fs, + r32p->gs); + printf("\n"); +} + +static void +prnt_64regs(struct xg_gdb_regs64 *r64p) +{ + printf("rip:"XGF64" rsp:"XGF64" flags:"XGF64"\n", r64p->rip, r64p->rsp, + r64p->rflags); + printf("rax:"XGF64" rbx:"XGF64" rcx:"XGF64"\n", r64p->rax, r64p->rbx, + r64p->rcx); + printf("rdx:"XGF64" rsi:"XGF64" rdi:"XGF64"\n", r64p->rdx, r64p->rsi, + r64p->rdi); + printf("r08:"XGF64" r09:"XGF64" r10:"XGF64"\n", r64p->r8, r64p->r9, + r64p->r10); + printf("r11:"XGF64" r12:"XGF64" r13:"XGF64"\n", r64p->r11, r64p->r12, + r64p->r13); + printf("r14:"XGF64" r15:"XGF64" rbp:"XGF64"\n", r64p->r14, r64p->r15, + r64p->rbp); + printf("cs:"XGF64" ds:"XGF64" fs:"XGF64" gs:"XGF64"\n", r64p->cs, + r64p->ds, r64p->fs, r64p->gs); + printf("\n"); +} + + +static void +prnt_call_trace32(uint32_t ip, uint32_t sp) +{ + int stack_max=10; /* try to print upto 10 entries if possible */ + uint32_t loopmax=0, val; + + printf("Call Trace:\n"); + printf(" [%08x]\n", ip); + + while(stack_max > 0) { + if (xg_read_mem((uint64_t)sp, (char *)&val, sizeof(val),0) != 0) + return; + if (val > 0x0c000000) { /* kernel addr */ + printf(" [%08x]\n", val); + --stack_max; + } + sp += sizeof(sp); + if (++loopmax > 10000) /* don't go forever */ + break; + } +} + +static void +prnt_call_trace64(uint64_t ip, uint64_t sp) +{ + int stack_max=10; /* try to print upto 10 entries if possible */ + uint64_t loopmax=0, val; + + printf("Call Trace:\n"); + printf(" ["XGF64"]\n", ip); + + while(stack_max > 0) { + if (xg_read_mem(sp, (char *)&val, sizeof(val),0) != 0) + return; + if (val > 0xffffffff80000000UL) { /* kernel addr */ + printf(" ["XGF64"]\n", val); + --stack_max; + } + sp += sizeof(sp); + if (++loopmax > 10000) /* don't go forever */ + break; + } +} + +static int +prnt_vcpu_context(vcpuid_t vcpuid) +{ + union xg_gdb_regs gregs; + int rc; + + printf("\n--> VCPU:%d\n", vcpuid); + rc = xg_regs_read(XG_GPRS, vcpuid, &gregs, guest_bitness); + if (rc) { + gxprt("ERROR: failed to read regs. errno:%d\n", errno); + return 1; + } + if (guest_bitness==32) { + prnt_32regs(&gregs.gregs_32); + prnt_call_trace32(gregs.gregs_32.eip, gregs.gregs_32.esp); + } else { + prnt_64regs(&gregs.gregs_64); + prnt_call_trace64(gregs.gregs_64.rip, gregs.gregs_64.rsp); + } + return 0; +} + +/* vcpuid is already checked to be <= max_vcpuid */ +int +gx_local_cmd(domid_t domid, vcpuid_t vcpuid) +{ + printf("===> Context for DOMID:%d\n", domid); + if (vcpuid == -1) { + int i; + for (i=0; i <= max_vcpuid; i++) + prnt_vcpu_context(i); + } else + prnt_vcpu_context(vcpuid); + return 0; +} diff --git a/tools/debugger/gdbsx/gx/gx_main.c b/tools/debugger/gdbsx/gx/gx_main.c new file mode 100644 index 0000000000..f20f6ef0d7 --- /dev/null +++ b/tools/debugger/gdbsx/gx/gx_main.c @@ -0,0 +1,696 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +/* 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: "m 0,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 */ + + /* nothing else supported right now */ + remote_buf[0] = '\0'; + + 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)) == 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)) == 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; +} diff --git a/tools/debugger/gdbsx/gx/gx_utils.c b/tools/debugger/gdbsx/gx/gx_utils.c new file mode 100644 index 0000000000..e87ffcb730 --- /dev/null +++ b/tools/debugger/gdbsx/gx/gx_utils.c @@ -0,0 +1,145 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +#include +#include +#include + +#include "gx.h" + + +void +gxprt(const char *fmt, ...) +{ + char buf[2048]; + va_list args; + + va_start(args, fmt); + (void)vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + fprintf(stderr, "%s", buf); + fflush(stderr); +} + +int +gx_fromhex(int a) +{ + if (a >= '0' && a <= '9') + return a - '0'; + else if (a >= 'a' && a <= 'f') + return a - 'a' + 10; + else + gxprt("Reply contains invalid hex digit"); + return 0; +} + +int +gx_tohex(int nib) +{ + if (nib < 10) + return '0' + nib; + else + return 'a' + nib - 10; +} + + +void +gx_convert_int_to_ascii(char *from, char *to, int n) +{ + int nib; + int ch; + while (n--) { + ch = *from++; + nib = ((ch & 0xf0) >> 4) & 0x0f; + *to++ = gx_tohex(nib); + nib = ch & 0x0f; + *to++ = gx_tohex(nib); + } + *to = 0; +} + +/* input: "70676433206431" output: "pgd3 d1" n == 7 */ +void +gx_convert_ascii_to_int(char *from, char *to, int n) +{ + int nib1, nib2; + while (n--) { + nib1 = gx_fromhex(*from++); + nib2 = gx_fromhex(*from++); + *to++ = (((nib1 & 0x0f) << 4) & 0xf0) | (nib2 & 0x0f); + } + *to = 0; +} + +void +gx_decode_zZ_packet(char *from, uint64_t *mem_addr_ptr) +{ + int i = 0; + char ch; + *mem_addr_ptr = 0; + + while ((ch=from[i++]) != ',') { + *mem_addr_ptr = *mem_addr_ptr << 4; + *mem_addr_ptr |= gx_fromhex(ch) & 0x0f; + } +} + +/* Eg: mc0267d3a,1\0 : from points to char after 'm' */ +void +gx_decode_m_packet(char *from, uint64_t *mem_addr_ptr, int *len_ptr) +{ + int i = 0, j = 0; + char ch; + *mem_addr_ptr = *len_ptr = 0; + + while ((ch=from[i++]) != ',') { + *mem_addr_ptr = *mem_addr_ptr << 4; + *mem_addr_ptr |= gx_fromhex(ch) & 0x0f; + } + for (j = 0; j < 4; j++) { + if ((ch=from[i++]) == 0) + break; + *len_ptr = *len_ptr << 4; + *len_ptr |= gx_fromhex(ch) & 0x0f; + } +} + +/* + * Decode M pkt as in: Mc0267d3a,1:cc\0 where c0267d3a is the guest addr + * from points to char after 'M' + * Returns: address of byte after ":", ie, addr of cc in buf + */ +char * +gx_decode_M_packet(char *from, uint64_t *mem_addr_ptr, int *len_ptr) +{ + int i = 0; + char ch; + + *mem_addr_ptr = *len_ptr = 0; + + while ((ch=from[i++]) != ',') { + *mem_addr_ptr = *mem_addr_ptr << 4; + *mem_addr_ptr |= gx_fromhex(ch) & 0x0f; + } + while ((ch = from[i++]) != ':') { + *len_ptr = *len_ptr << 4; + *len_ptr |= gx_fromhex(ch) & 0x0f; + } + return(&from[i]); +} + diff --git a/tools/debugger/gdbsx/gx/xg_dummy.c b/tools/debugger/gdbsx/gx/xg_dummy.c new file mode 100644 index 0000000000..b82899f5e8 --- /dev/null +++ b/tools/debugger/gdbsx/gx/xg_dummy.c @@ -0,0 +1,122 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ +#include +#include +#include +#include +#include + +#include "gx.h" +#include "../xg/xg_public.h" + +int xgtrc_on = 1; + +/* This file is NOT part of gdbsx binary, but a dummy so gdbsx bin can be built + * and used to learn/test "gdb <-> gdbserver" protocol */ + +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); +} + +void +xgprt(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); +} + +int +xg_init() +{ + return 0; +} + +void +xg_deinit() +{ + /* reset debugging info in guest */ + /* unpause the guest */ +} + +int +xg_attach(domid_t domid) +{ + return 2; +} + +int +xg_step(vcpuid_t which_vcpu, int guest_bitness) +{ + return 0; +} + +int +xg_resume(int guest_bitness) +{ + return 0; +} + +int +xg_regs_read(regstype_t which_regs, vcpuid_t which_vcpu, + struct xg_gdb_regs *regsp, int guest_bitness) +{ + return 0; +} + +int +xg_regs_write(regstype_t which_regs, vcpuid_t which_vcpu, + struct xg_gdb_regs *regsp, int guest_bitness) +{ + return 0; +} + +int +xg_read_mem(uint64_t guestva, char *tobuf, int len) +{ + return 0; +} + +int +xg_write_mem(uint64_t guestva, char *frombuf, int len) +{ + return 0; +} + +int +xg_wait_domain(vcpuid_t *vcpu_p, int guest_bitness) +{ + return 0; +} + diff --git a/tools/debugger/gdbsx/xg/Makefile b/tools/debugger/gdbsx/xg/Makefile new file mode 100644 index 0000000000..4ea695987e --- /dev/null +++ b/tools/debugger/gdbsx/xg/Makefile @@ -0,0 +1,36 @@ +XEN_ROOT = ../../../.. +include ../Rules.mk + +XG_HDRS := xg_public.h +XG_OBJS := xg_main.o + +CFLAGS += $(INCLUDES) -I. -I../../../include + + +.PHONY: all +all: build + +.PHONY: build +build: xen-headers xg_all.a $(XG_HDRS) $(XG_OBJS) Makefile +# build: mk-symlinks xg_all.a $(XG_HDRS) $(XG_OBJS) Makefile +# build: mk-symlinks xg_all.a + +xg_all.a: $(XG_OBJS) Makefile $(XG_HDRS) + ar cr $@ $(XG_OBJS) # problems using -m32 in ld +# $(LD) -b elf32-i386 $(LDFLAGS) -r -o $@ $^ +# $(CC) -m32 -c -o $@ $^ + +xen-headers: + $(MAKE) -C ../../../check + $(MAKE) -C ../../../include + +# xg_main.o: xg_main.c Makefile $(XG_HDRS) +#$(CC) -c $(CFLAGS) -o $@ $< + +# %.o: %.c $(XG_HDRS) Makefile -- doesn't work as it won't overwrite Rules.mk +#%.o: %.c -- doesn't recompile when .c changed + +.PHONY: clean +clean: + rm -rf xen xg_all.a $(XG_OBJS) + diff --git a/tools/debugger/gdbsx/xg/xg_main.c b/tools/debugger/gdbsx/xg/xg_main.c new file mode 100644 index 0000000000..9cd9fc3a6d --- /dev/null +++ b/tools/debugger/gdbsx/xg/xg_main.c @@ -0,0 +1,806 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xg_public.h" +#include +#include +#include +#include +#include + +#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("/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 : 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, (ulong)&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, (ulong)&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); + + 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->rflags = cp->rflags; + + rp->cs = (uint64_t)cp->cs; + rp->ss = (uint64_t)cp->ss; + rp->es = (uint64_t)cp->es; + rp->ds = (uint64_t)cp->ds; + rp->fs = (uint64_t)cp->fs; + rp->gs = (uint64_t)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->rflags = 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->rflags; + + 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; + + 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 */ + + _domctl_hcall(XEN_DOMCTL_gdbsx_guestmemio, tobuf, 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 %d bytes. errno:%d rc:%d\n", + iop->remain, errno, rc); + return iop->remain; +} + diff --git a/tools/debugger/gdbsx/xg/xg_public.h b/tools/debugger/gdbsx/xg/xg_public.h new file mode 100644 index 0000000000..6236d08fef --- /dev/null +++ b/tools/debugger/gdbsx/xg/xg_public.h @@ -0,0 +1,110 @@ +/* + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +#define XGERR(...) \ + do {(xgprt(__FUNCTION__,__VA_ARGS__));} while (0) +#define XGTRC(...) \ + do {(xgtrc_on) ? (xgtrc(__FUNCTION__,__VA_ARGS__)):0;} while (0) +#define XGTRC1(...) \ + do {(xgtrc_on==2) ? (xgtrc(__FUNCTION__,__VA_ARGS__)):0;} while (0) + +#if defined(__x86_64__) + #define XGFM64 "%lx" + #define XGF64 "%016lx" +#else + #define XGFM64 "%llx" + #define XGF64 "%016llx" +#endif + + +typedef enum { + XG_GPRS=1, /* general purpose user regs */ + XG_FPRS=2, /* floating point user regs */ +} regstype_t; + + +typedef uint32_t vcpuid_t; + +extern int xgtrc_on; + +/* what gdb wants to receive during register read, or sends during write. + * this from : regformats/reg-i386-linux.dat in gdbserver */ +struct xg_gdb_regs32 { + uint32_t eax; + uint32_t ecx; + uint32_t edx; + uint32_t ebx; + uint32_t esp; + uint32_t ebp; + uint32_t esi; + uint32_t edi; + uint32_t eip; + uint32_t eflags; + uint32_t cs; + uint32_t ss; + uint32_t ds; + uint32_t es; + uint32_t fs; + uint32_t gs; +}; + +/* this from: regformats/reg-x86-64.dat in gdbserver */ +struct xg_gdb_regs64 { + uint64_t rax; + uint64_t rbx; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; + uint64_t rbp; + uint64_t rsp; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + uint64_t rip; + uint64_t rflags; + uint64_t cs; + uint64_t ss; + uint64_t ds; + uint64_t es; + uint64_t fs; + uint64_t gs; +}; + +union xg_gdb_regs { + struct xg_gdb_regs32 gregs_32; + struct xg_gdb_regs64 gregs_64; +}; + + +int xg_init(void); +int xg_attach(int, int); +void xg_detach_deinit(void); +int xg_step(vcpuid_t, int); +vcpuid_t xg_resume_n_wait(int); +int xg_regs_read(regstype_t, vcpuid_t, union xg_gdb_regs *, int); +int xg_regs_write(regstype_t, vcpuid_t, union xg_gdb_regs *, int); +int xg_read_mem(uint64_t, char *, int, uint64_t); +int xg_write_mem(uint64_t, char *, int, uint64_t); +void xgprt(const char *fn, const char *fmt, ...); +void xgtrc(const char *fn, const char *fmt, ...); -- cgit v1.2.3