This is the mail archive of the
archer@sourceware.org
mailing list for the Archer project.
linux kernel gdb stub for userspace processes, prototype version 3
- From: "Frank Ch. Eigler" <fche at redhat dot com>
- To: systemtap at sourceware dot org, utrace-devel at redhat dot com, archer at sourceware dot org
- Date: Tue, 7 Jul 2009 00:26:39 -0400
- Subject: linux kernel gdb stub for userspace processes, prototype version 3
Hi -
Further to http://sourceware.org/ml/systemtap/2009-q2/msg00969.html, I
attach another snapshot of my gdb-stub in linux-kernel prototype. It's
working a lot better. Usage is as before:
% PROCESS &
[1] 21175
% gdb PROCESS
(gdb) target remote /proc/21175/gdb
(gdb) # whatever strikes your fancy
Known limitations:
- http://sourceware.org/ml/gdb/2009-07/msg00036.html
(occasional "Remote failure reply: E....." error)
- only for single-threaded programs
- x86-64 and x86 only
- floating poing registers not yet done
- checkpatch.pl not yet satisfied
This patch should apply to recent utrace-patched kernels.
- FChE
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 3326bbf..0afb05a 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -77,6 +77,7 @@
#include <linux/audit.h>
#include <linux/poll.h>
#include <linux/nsproxy.h>
+#include <linux/utrace.h>
#include <linux/oom.h>
#include <linux/elf.h>
#include <linux/pid_namespace.h>
@@ -2542,6 +2543,9 @@ static const struct pid_entry tgid_base_stuff[] = {
#ifdef CONFIG_TASK_IO_ACCOUNTING
INF("io", S_IRUGO, proc_tgid_io_accounting),
#endif
+#ifdef CONFIG_UTRACE_GDB
+ REG("gdb", S_IRUSR|S_IWUSR, proc_gdb_operations),
+#endif
};
static int proc_tgid_base_readdir(struct file * filp,
diff --git a/include/linux/utrace.h b/include/linux/utrace.h
index f877ec6..f33a5da 100644
--- a/include/linux/utrace.h
+++ b/include/linux/utrace.h
@@ -689,4 +689,8 @@ static inline __must_check int utrace_barrier_pid(struct pid *pid,
#endif /* CONFIG_UTRACE */
+#ifdef CONFIG_UTRACE_GDB
+extern const struct file_operations proc_gdb_operations;
+#endif
+
#endif /* linux/utrace.h */
diff --git a/init/Kconfig b/init/Kconfig
index a6987df..7ffa60d 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1158,6 +1158,14 @@ menuconfig UTRACE
kernel interface exported to kernel modules, to track events in
user threads, extract and change user thread state.
+config UTRACE_GDB
+ bool "/proc/<pid>/gdb file for gdb remote connection"
+ select UTRACE
+ default y
+ help
+ Enable the utrace-based /proc/<pid>/gdb process debugging
+ interface, for connection using the gdb remote protocol.
+
source "block/Kconfig"
config PREEMPT_NOTIFIERS
diff --git a/kernel/Makefile b/kernel/Makefile
index a79634e..21457e6 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_RESOURCE_COUNTERS) += res_counter.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
obj-$(CONFIG_UTRACE) += utrace.o
+obj-$(CONFIG_UTRACE_GDB) += utrace-gdb.o
obj-$(CONFIG_AUDIT) += audit.o auditfilter.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
obj-$(CONFIG_AUDIT_TREE) += audit_tree.o
diff --git a/kernel/utrace-gdb.c b/kernel/utrace-gdb.c
new file mode 100644
index 0000000..3835761
--- /dev/null
+++ b/kernel/utrace-gdb.c
@@ -0,0 +1,1148 @@
+/*
+ * utrace-based gdb remote protocol server for user processes
+ *
+ * Copyright (C) 2009 Red Hat, Inc. All rights reserved.
+ *
+ * This copyrighted material is made available to anyone wishing to use,
+ * modify, copy, or redistribute it subject to the terms and conditions
+ * of the GNU General Public License v.2.
+ *
+ * Red Hat Author: Frank Ch. Eigler
+ */
+
+/* #define DEBUG 1 */
+
+#include <asm/syscall.h>
+#include <asm/signal.h>
+#include <linux/ptrace.h>
+#include <linux/err.h>
+#include <linux/pid.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/proc_fs.h>
+#include <linux/ctype.h>
+#include <linux/regset.h>
+#include <linux/utrace.h>
+#include <linux/tracehook.h>
+
+
+
+/** struct gdb_connection - Tracks one active gdb-process session.
+ */
+
+#define GDB_BUFMAX 4096
+
+
+struct gdb_connection {
+ pid_t target;
+ struct utrace_engine *engine;
+
+ /* changed under output_mutex */
+ int at_quiesce_do;
+ unsigned char stopcode[GDB_BUFMAX]; // set <=> at_quiesce_do = UTRACE_STOP
+ int skip_signals;
+ int stop_signals;
+ /* XXX: per-thread later */
+
+ char output_buf[GDB_BUFMAX];
+ size_t output_buf_size;
+ loff_t output_buf_read;
+ struct mutex output_mutex;
+ wait_queue_head_t output_wait;
+
+ char input_buf[GDB_BUFMAX];
+ size_t input_buf_size;
+ struct mutex input_mutex;
+ wait_queue_head_t input_wait;
+
+ struct list_head link;
+};
+
+
+static LIST_HEAD(gdb_connections);
+static DEFINE_MUTEX(gdb_connections_mutex);
+static const struct utrace_engine_ops gdb_utrace_ops;
+
+
+
+/* ------------------------------------------------------------------------ */
+
+static unsigned byteme (unsigned char hex1, unsigned char hex2)
+{
+ return (isdigit(hex1) ? hex1-'0' : tolower(hex1)-'a'+10) * 16 +
+ (isdigit(hex2) ? hex2-'0' : tolower(hex2)-'a'+10);
+}
+
+
+
+/* Begin a new packet. Add the $, and remember where we put it.
+ * Return the offset for later checksum addition via
+ * push_output_packet_end. */
+static size_t push_output_packet_start (struct gdb_connection *p)
+{
+ size_t start = p->output_buf_size;
+
+ BUG_ON (p->output_buf_size + 1 >= GDB_BUFMAX);
+ p->output_buf[p->output_buf_size++] = '$';
+ return start;
+}
+
+
+/* Add a character to the output queue. Assumes output_mutex held. */
+static void push_output (struct gdb_connection *p, unsigned char c)
+{
+ /* We know some space must exist; we check for this in
+ proc_gdb_write() for example. */
+ BUG_ON (p->output_buf_size >= GDB_BUFMAX);
+ p->output_buf[p->output_buf_size++] = c;
+}
+
+
+static char hex[] = "0123456789ABCDEF";
+
+/* Add a byte (hexified) to the output queue. Assumes output_mutex held. */
+static void push_output_hex (struct gdb_connection *p, unsigned char c)
+{
+ /* We know some space must exist; we check for this in
+ proc_gdb_write() for example. */
+ BUG_ON (p->output_buf_size >= GDB_BUFMAX);
+ p->output_buf[p->output_buf_size++] = hex[(c & 0xf0) >> 4];
+ p->output_buf[p->output_buf_size++] = hex[(c & 0x0f) >> 0];
+}
+
+
+/* Finish the last packet. Starting after the given '$' offset, compute
+ * the checksum and append it. */
+static void push_output_packet_end (struct gdb_connection *p, size_t start)
+{
+ unsigned char checksum = 0;
+ int i;
+
+ BUG_ON (p->output_buf_size + 3 >= GDB_BUFMAX);
+ BUG_ON (p->output_buf[start] != '$');
+
+ for (i=start+1; i<p->output_buf_size; i++)
+ checksum += p->output_buf[i];
+
+ p->output_buf[p->output_buf_size++] = '#';
+ p->output_buf[p->output_buf_size++] = hex[(checksum & 0xf0) >> 4];
+ p->output_buf[p->output_buf_size++] = hex[(checksum & 0x0f) >> 0];
+}
+
+
+/* Add a complete packet payload to the output queue. */
+static void push_output_packet (struct gdb_connection *p, const char *s)
+{
+ size_t ss = strlen(s);
+ size_t start;
+ int i;
+
+ start = push_output_packet_start(p);
+ for (i=0; i<ss; i++)
+ push_output(p, s[i]);
+ push_output_packet_end(p, start);
+}
+
+
+
+/* ------------------------------------------------------------------------ */
+
+/* utrace callbacks */
+
+
+u32 gdb_utrace_report_quiesce(enum utrace_resume_action action,
+ struct utrace_engine *engine,
+ struct task_struct *task,
+ unsigned long event)
+{
+ struct gdb_connection *p = engine->data;
+ pr_debug ("report_quiesce %d event 0x%lx 0x%x->0x%x\n", task->pid,
+ event, action, p->at_quiesce_do);
+
+ return p->at_quiesce_do;
+}
+
+
+u32 gdb_utrace_report_clone(enum utrace_resume_action action,
+ struct utrace_engine *engine,
+ struct task_struct *parent,
+ unsigned long clone_flags,
+ struct task_struct *child)
+{
+ pr_debug ("report_clone %d->%d\n", parent->pid, child->pid);
+
+ if (clone_flags & CLONE_THREAD) {
+ printk (KERN_WARNING "unsupported multithreading on /proc/%d/gdb.\n",
+ task_pid_nr (parent));
+ }
+ /* XXX: is there anything else to do here? */
+ return UTRACE_RESUME;
+}
+
+
+u32 gdb_utrace_report_exec(enum utrace_resume_action action,
+ struct utrace_engine *engine,
+ struct task_struct *task,
+ const struct linux_binfmt *fmt,
+ const struct linux_binprm *bprm,
+ struct pt_regs *regs)
+{
+ /* XXX: Model an exec as if it were an exit. */
+ struct gdb_connection *p = engine->data;
+
+ pr_debug ("report_exec %d->%s\n", task->pid, task->comm);
+
+ mutex_lock(&p->output_mutex);
+
+ p->at_quiesce_do = UTRACE_STOP;
+ snprintf (p->stopcode, GDB_BUFMAX, "W%02x", 0);
+ push_output_packet (p, p->stopcode);
+
+ mutex_unlock(&p->output_mutex);
+ wake_up(&p->output_wait);
+
+ /* Suspend the exec operation, to ensure that the connected gdb
+ receives the notification packet, and lets us go. */
+ return UTRACE_STOP;
+}
+
+
+u32 gdb_utrace_report_signal(u32 action,
+ struct utrace_engine *engine,
+ struct task_struct *task,
+ struct pt_regs *regs,
+ siginfo_t *info,
+ const struct k_sigaction *orig_ka,
+ struct k_sigaction *return_ka)
+{
+ struct gdb_connection *p = engine->data;
+ u32 ret = action;
+ int kern_p;
+
+ mutex_lock(&p->output_mutex);
+
+ kern_p = (info != SEND_SIG_NOINFO && (is_si_special(info) || SI_FROMKERNEL(info)));
+
+ pr_debug ("report_signal %d (0x%x) kern %d skip %d stop %d\n",
+ task->pid, action, kern_p, p->skip_signals, p->stop_signals);
+
+ /* The target is about to receive a signal. There are several
+ * cases:
+ *
+ * 1) This is an ordinary signal. We UTRACE_STOP to notify gdb.
+ *
+ * 2) This is a SIGTRAP arising from a breakpoint. We UTRACE_STOP.
+ *
+ * 3) This is a signal our code injected to stop the process, in lieu
+ * of UTRACE_INTERRUPT. We UTRACE_STOP | UTRACE_SIGNAL_IGN.
+ *
+ * 4) This is a signal our code injected on behalf of gdb (C/S/I packets).
+ * We UTRACE_RESUME.
+ *
+ * 5) This is a UTRACE_SIGNAL_REPORT or UTRACE_SIGNAL_HANDLER event.
+ * Just let utrace continue, as these signal events of minor internal
+ * interest.
+ */
+
+ if (utrace_signal_action(action) == UTRACE_SIGNAL_REPORT ||
+ utrace_signal_action(action) == UTRACE_SIGNAL_HANDLER) { /* case 5 */
+ /* NB: disregard p->at_quiesce_do */
+ ret = UTRACE_RESUME | utrace_signal_action(action);
+ } else if (p->skip_signals > 0 /*&& kern_p*/) { /* case 4 */
+ p->skip_signals --;
+ p->at_quiesce_do = UTRACE_RESUME;
+ ret = UTRACE_RESUME; /* deliver */
+ } else if (p->stop_signals > 0 /*&& kern_p*/) { /* Case 3 */
+ p->stop_signals --;
+ snprintf (p->stopcode, GDB_BUFMAX, "S%02x", info->si_signo);
+ push_output_packet (p, p->stopcode);
+ p->at_quiesce_do = UTRACE_STOP;
+ ret = UTRACE_STOP | UTRACE_SIGNAL_IGN;
+ } else { /* Cases 1, 2 */
+ snprintf (p->stopcode, GDB_BUFMAX, "S%02x", info->si_signo);
+ push_output_packet (p, p->stopcode);
+ p->at_quiesce_do = UTRACE_STOP;
+ ret = UTRACE_STOP;
+ }
+
+ pr_debug ("action 0x%x\n", ret);
+
+ mutex_unlock(&p->output_mutex);
+ wake_up(&p->output_wait);
+
+ return ret;
+}
+
+
+u32 gdb_utrace_report_exit(enum utrace_resume_action action,
+ struct utrace_engine *engine,
+ struct task_struct *task,
+ long orig_code, long *code)
+{
+ struct gdb_connection *p = engine->data;
+
+ pr_debug ("report_exit %d (%lx)\n", task->pid, (unsigned long) orig_code);
+
+ mutex_lock(&p->output_mutex);
+
+ p->at_quiesce_do = UTRACE_STOP;
+ snprintf (p->stopcode, GDB_BUFMAX,
+ "W%02x", (unsigned)(orig_code & 0xFF));
+ push_output_packet (p, p->stopcode);
+
+ mutex_unlock(&p->output_mutex);
+ wake_up(&p->output_wait);
+
+ /* Suspend the exit operation, to ensure that the connected gdb
+ receives the notification packet, and lets us go. */
+ return UTRACE_STOP;
+}
+
+
+u32 gdb_utrace_report_death(struct utrace_engine *engine,
+ struct task_struct *task,
+ bool group_dead, int signal)
+{
+ struct gdb_connection *p = engine->data;
+
+ pr_debug ("report_death %d (%d)\n", task->pid, signal);
+
+ mutex_lock(&p->output_mutex);
+
+ p->at_quiesce_do = UTRACE_DETACH;
+ snprintf (p->stopcode, GDB_BUFMAX, "X%2x", (unsigned)(signal & 0xFF));
+ push_output_packet (p, p->stopcode);
+
+ p->engine = NULL;
+
+ mutex_unlock(&p->output_mutex);
+ wake_up(&p->output_wait);
+
+ return UTRACE_DETACH;
+}
+
+
+
+static const struct utrace_engine_ops gdb_utrace_ops = {
+ .report_quiesce = gdb_utrace_report_quiesce,
+ .report_signal = gdb_utrace_report_signal,
+ .report_death = gdb_utrace_report_death,
+ .report_exit = gdb_utrace_report_exit,
+ .report_exec = gdb_utrace_report_exec,
+ .report_clone = gdb_utrace_report_clone,
+ /* XXX: syscall trapping is also possible. */
+};
+
+
+
+/* XXX: arch-dependent lookup of gdb remote protocol register
+ * numbering. The register numbers (user-side) & expected sizes come
+ * from gdb's regformats/FOO-linux.dat. The regset (kernel-side)
+ * numbers could come from offsetof/sizeof constructs based upon each
+ * arch's asm/user*.h.
+ */
+
+struct gdb_map_regset {
+ unsigned pos; /* regset offset */
+ unsigned count; /* regset byte count */
+ unsigned rsn; /* regset number */
+ unsigned bytes; /* gdb's view of register width; <= count */
+};
+
+struct gdb_map_regset arch_i386_map_regset[] = {
+ [0]={ /* eax */ 6*4, 4, NT_PRSTATUS, 4, },
+ [1]={ /* ecx */ 1*4, 4, NT_PRSTATUS, 4, },
+ [2]={ /* edx */ 2*4, 4, NT_PRSTATUS, 4, },
+ [3]={ /* ebx */ 0*4, 4, NT_PRSTATUS, 4, },
+ [4]={ /* esp */ 15*4, 4, NT_PRSTATUS, 4, },
+ [5]={ /* ebp */ 5*4, 4, NT_PRSTATUS, 4, },
+ [6]={ /* esi */ 3*4, 4, NT_PRSTATUS, 4, },
+ [7]={ /* edi */ 4*4, 4, NT_PRSTATUS, 4, },
+ [8]={ /* eip */ 12*4, 4, NT_PRSTATUS, 4, },
+ [9]={ /* eflags */ 14*4, 4, NT_PRSTATUS, 4, },
+ [10]={ /* cs */ 13*4, 4, NT_PRSTATUS, 4, },
+ [11]={ /* ss */ 16*4, 4, NT_PRSTATUS, 4, },
+ [12]={ /* ds */ 7*4, 4, NT_PRSTATUS, 4, },
+ [13]={ /* es */ 8*4, 4, NT_PRSTATUS, 4, },
+ [14]={ /* fs */ 9*4, 4, NT_PRSTATUS, 4, },
+ [15]={ /* gs */ 10*4, 4, NT_PRSTATUS, 4, },
+ [16]={ /* st0 */ 0, 0, NT_PRFPREG, 10, },
+ [17]={ /* st1 */ 0, 0, NT_PRFPREG, 10, },
+ [18]={ /* st2 */ 0, 0, NT_PRFPREG, 10, },
+ [19]={ /* st3 */ 0, 0, NT_PRFPREG, 10, },
+ [20]={ /* st4 */ 0, 0, NT_PRFPREG, 10, },
+ [21]={ /* st5 */ 0, 0, NT_PRFPREG, 10, },
+ [22]={ /* st6 */ 0, 0, NT_PRFPREG, 10, },
+ [23]={ /* st7 */ 0, 0, NT_PRFPREG, 10, },
+ [24]={ /* fctrl */ 0, 0, NT_PRFPREG, 4, },
+ [25]={ /* fstat */ 0, 0, NT_PRFPREG, 4, },
+ [26]={ /* ftag */ 0, 0, NT_PRFPREG, 4, },
+ [27]={ /* fiseg */ 0, 0, NT_PRFPREG, 4, },
+ [28]={ /* fioff */ 0, 0, NT_PRFPREG, 4, },
+ [29]={ /* foseg */ 0, 0, NT_PRFPREG, 4, },
+ [30]={ /* fooff */ 0, 0, NT_PRFPREG, 4, },
+ [31]={ /* fop */ 0, 0, NT_PRFPREG, 4, },
+ [32]={ /* xmm0 */ 0, 0, NT_PRFPREG, 16, },
+ [33]={ /* xmm1 */ 0, 0, NT_PRFPREG, 16, },
+ [34]={ /* xmm2 */ 0, 0, NT_PRFPREG, 16, },
+ [35]={ /* xmm3 */ 0, 0, NT_PRFPREG, 16, },
+ [36]={ /* xmm4 */ 0, 0, NT_PRFPREG, 16, },
+ [37]={ /* xmm5 */ 0, 0, NT_PRFPREG, 16, },
+ [38]={ /* xmm6 */ 0, 0, NT_PRFPREG, 16, },
+ [39]={ /* xmm7 */ 0, 0, NT_PRFPREG, 16, },
+ [40]={ /* mxcsr */ 0, 0, NT_PRFPREG, 4, },
+ [41]={ /* orig_eax*/ 0, 0, NT_PRSTATUS, 4, },
+};
+
+
+struct gdb_map_regset arch_x86_64_map_regset[] = {
+ [0]={ /* rax */ 10*8, 8, NT_PRSTATUS, 8, },
+ [1]={ /* rbx */ 5*8, 8, NT_PRSTATUS, 8, },
+ [2]={ /* rcx */ 11*8, 8, NT_PRSTATUS, 8, },
+ [3]={ /* rdx */ 12*8, 8, NT_PRSTATUS, 8, },
+ [4]={ /* rsi */ 13*8, 8, NT_PRSTATUS, 8, },
+ [5]={ /* rdi */ 14*8, 8, NT_PRSTATUS, 8, },
+ [6]={ /* rbp */ 4*8, 8, NT_PRSTATUS, 8, },
+ [7]={ /* rsp */ 19*8, 8, NT_PRSTATUS, 8, },
+ [8]={ /* r8 */ 9*8, 8, NT_PRSTATUS, 8, },
+ [9]={ /* r9 */ 8*8, 8, NT_PRSTATUS, 8, },
+ [10]={ /* r10 */ 7*8, 8, NT_PRSTATUS, 8, },
+ [11]={ /* r11 */ 6*8, 8, NT_PRSTATUS, 8, },
+ [12]={ /* r12 */ 3*8, 8, NT_PRSTATUS, 8, },
+ [13]={ /* r13 */ 2*8, 8, NT_PRSTATUS, 8, },
+ [14]={ /* r14 */ 1*8, 8, NT_PRSTATUS, 8, },
+ [15]={ /* r15 */ 0*8, 8, NT_PRSTATUS, 8, },
+ [16]={ /* rip */ 16*8, 8, NT_PRSTATUS, 8, },
+ [17]={ /* flags */ 18*8, 8, NT_PRSTATUS, 4, },
+ [18]={ /* cs */ 17*8, 8, NT_PRSTATUS, 4, },
+ [19]={ /* ss */ 20*8, 8, NT_PRSTATUS, 4, },
+ [20]={ /* ds */ 23*8, 8, NT_PRSTATUS, 4, },
+ [21]={ /* es */ 24*8, 8, NT_PRSTATUS, 4, },
+ [22]={ /* fs */ 25*8, 8, NT_PRSTATUS, 4, },
+ [23]={ /* gs */ 26*8, 8, NT_PRSTATUS, 4, },
+ [24]={ /* st0 */ 0, 0, NT_PRFPREG, 10, },
+ [25]={ /* st1 */ 0, 0, NT_PRFPREG, 10, },
+ [26]={ /* st2 */ 0, 0, NT_PRFPREG, 10, },
+ [27]={ /* st3 */ 0, 0, NT_PRFPREG, 10, },
+ [28]={ /* st4 */ 0, 0, NT_PRFPREG, 10, },
+ [29]={ /* st5 */ 0, 0, NT_PRFPREG, 10, },
+ [30]={ /* st6 */ 0, 0, NT_PRFPREG, 10, },
+ [31]={ /* st7 */ 0, 0, NT_PRFPREG, 10, },
+ [32]={ /* fctrl */ 0, 0, NT_PRFPREG, 4, },
+ [33]={ /* fstat */ 0, 0, NT_PRFPREG, 4, },
+ [34]={ /* ftag */ 0, 0, NT_PRFPREG, 4, },
+ [35]={ /* fiseg */ 0, 0, NT_PRFPREG, 4, },
+ [36]={ /* fioff */ 0, 0, NT_PRFPREG, 4, },
+ [37]={ /* foseg */ 0, 0, NT_PRFPREG, 4, },
+ [38]={ /* fooff */ 0, 0, NT_PRFPREG, 4, },
+ [39]={ /* fop */ 0, 0, NT_PRFPREG, 4, },
+ [40]={ /* xmm0 */ 0, 0, NT_PRFPREG, 16, },
+ [41]={ /* xmm1 */ 0, 0, NT_PRFPREG, 16, },
+ [42]={ /* xmm2 */ 0, 0, NT_PRFPREG, 16, },
+ [43]={ /* xmm3 */ 0, 0, NT_PRFPREG, 16, },
+ [44]={ /* xmm4 */ 0, 0, NT_PRFPREG, 16, },
+ [45]={ /* xmm5 */ 0, 0, NT_PRFPREG, 16, },
+ [46]={ /* xmm6 */ 0, 0, NT_PRFPREG, 16, },
+ [47]={ /* xmm7 */ 0, 0, NT_PRFPREG, 16, },
+ [48]={ /* xmm8 */ 0, 0, NT_PRFPREG, 16, },
+ [49]={ /* xmm9 */ 0, 0, NT_PRFPREG, 16, },
+ [50]={ /* xmm10 */ 0, 0, NT_PRFPREG, 16, },
+ [51]={ /* xmm11 */ 0, 0, NT_PRFPREG, 16, },
+ [52]={ /* xmm12 */ 0, 0, NT_PRFPREG, 16, },
+ [53]={ /* xmm13 */ 0, 0, NT_PRFPREG, 16, },
+ [54]={ /* xmm14 */ 0, 0, NT_PRFPREG, 16, },
+ [55]={ /* xmm15 */ 0, 0, NT_PRFPREG, 16, },
+ [56]={ /* mxcsr */ 0, 0, NT_PRFPREG, 4, },
+ [57]={ /* orig_rax*/ 15*8, 8, NT_PRSTATUS, 8, },
+};
+
+
+
+static int gdb_remote_register_info(struct gdb_connection *p,
+ struct task_struct *task,
+ unsigned number,
+ unsigned *pos, unsigned *count,
+ unsigned *bytes)
+{
+ const struct user_regset_view *rs = task_user_regset_view(task);
+ int rsn = -1;
+
+ if(rs == 0)
+ return -ENOENT;
+
+ /* pr_debug ("gdb_remote_register_info rs=%p rs->n=%u\n", rs, rs->n); */
+
+#define GMRSIZE (sizeof(struct gdb_map_regset))
+
+ if(rs->e_machine == EM_386) {
+ if (number < sizeof(arch_i386_map_regset)/GMRSIZE) {
+ *pos = arch_i386_map_regset[number].pos;
+ *count = arch_i386_map_regset[number].count;
+ *bytes = arch_i386_map_regset[number].bytes;
+ rsn = arch_i386_map_regset[number].rsn;
+ }
+ } else if(rs->e_machine == EM_X86_64) {
+ if (number < sizeof(arch_x86_64_map_regset)/GMRSIZE) {
+ *pos = arch_x86_64_map_regset[number].pos;
+ *count = arch_x86_64_map_regset[number].count;
+ *bytes = arch_x86_64_map_regset[number].bytes;
+ rsn = arch_x86_64_map_regset[number].rsn;
+ }
+ } /* else ... rsn stays -1. */
+
+#undef GMRSIZE
+
+ /* Now map to the per-architecture regset index, based on the
+ elf core_note_type we found. */
+ if (rsn >= 0) {
+ unsigned j;
+ for(j=0; j<rs->n; j++) {
+ if(rs->regsets[j].core_note_type == rsn)
+ return j;
+ }
+ }
+
+ /* Invalid machines, register numbers, rsns, or unset rsns all
+ * fall through here.
+ */
+ return -ENOENT;
+}
+
+
+
+/* Process an entire, checksum-confirmed $command# at the front of
+ * p->input_buf[]. The input and output mutexes are being held.
+ */
+static void handle_gdb_command_packet (struct gdb_connection *p, struct task_struct *task)
+{
+ unsigned long arg1, arg2, arg3;
+ size_t op_start;
+ int rc = 0;
+ int i, j;
+
+ pr_debug ("gdb packet code %c\n", p->input_buf[1]);
+
+ switch (p->input_buf[1]) {
+ case '?':
+ if (p->at_quiesce_do != UTRACE_STOP) {
+ /* shouldn't happen */
+ send_sig(SIGTRAP, task, 1);
+#if 0
+ rc = utrace_control (task, p->engine, UTRACE_INTERRUPT);
+ if (rc == -EINPROGRESS)
+ rc = utrace_barrier(task, p->engine);
+#endif
+
+ /* Note that we don't enqueue a reply packet here,
+ but make gdb wait for a response from the
+ utrace report_FOO callbacks. */
+ p->skip_signals ++;
+ } else {
+ push_output_packet (p, p->stopcode);
+ }
+ break;
+
+ case 'i': /* [ADDR[,NNN]] */
+ case 's': /* [ADDR] */
+ /* XXX: if !arch_has_single_step() ... then what? */
+ case 'c': /* [ADDR] */
+ rc = sscanf(& p->input_buf[2], "%lx,%lx", &arg1, &arg2);
+ if (rc >= 1) { /* Have a PC? */
+ /* XXX: set it */
+ }
+ if (rc >= 2) { /* ,NNN present */
+ /* XXX: disregard it. */
+ }
+ /* XXX: args ignored */
+ p->stopcode[0]='\0';
+ p->at_quiesce_do =
+ ((p->input_buf[1]=='c' || !arch_has_single_step())
+ ? UTRACE_RESUME : UTRACE_SINGLESTEP);
+ if (p->at_quiesce_do == UTRACE_SINGLESTEP)
+ p->stop_signals ++;
+ utrace_control (task, p->engine, p->at_quiesce_do);
+ break;
+ case 'C': /* SIG[;ADDR] */
+ case 'S': /* SIG[;ADDR] */
+ /* XXX: if !arch_has_single_step() ... then what? */
+ case 'I': /* SIG[;ADDR[,NNN?]] */
+ rc = sscanf(& p->input_buf[2], "%lx;%lx,%lx", &arg1, &arg2, &arg3);
+ if (rc >= 1) { /* SIG present */
+ send_sig ((int)arg1, task, 1);
+ }
+ if (rc >= 2) { /* ;ADDR present */
+ /* XXX: not done */
+ }
+ if (rc >= 3) { /* ,NNN present */
+ /* XXX: disregard it. */
+ }
+ p->skip_signals ++;
+ p->stopcode[0]='\0';
+ p->at_quiesce_do =
+ ((p->input_buf[1]=='C' || !arch_has_single_step())
+ ? UTRACE_RESUME : UTRACE_SINGLESTEP);
+ if (p->at_quiesce_do == UTRACE_SINGLESTEP)
+ p->stop_signals ++;
+ utrace_control (task, p->engine, p->at_quiesce_do);
+ /* Response will come at next report_signal. */
+ break;
+ case 'D':
+ push_output_packet (p, "OK");
+ /* NB: the .release fop callback performs actual utrace detach. */
+ break;
+ case 'g':
+ op_start = push_output_packet_start(p);
+ /* GDB_BUFMAX stands for some random large number,
+ * known to be larger than the number of gdb indexed
+ * registers. */
+ for (i=0; i<GDB_BUFMAX; i++) {
+ unsigned rs_count;
+ unsigned rs_pos;
+ unsigned bytes;
+ const struct user_regset_view* rsv;
+ const struct user_regset* rs;
+ unsigned char reg_contents[16]; /* maximum reg. width */
+
+ int rsn = gdb_remote_register_info(p, task, i,
+ &rs_pos, &rs_count,
+ &bytes);
+
+ if (rsn < 0)
+ break;
+
+ /* If we want to extract register data, make sure
+ we're fetching at least that much. */
+ BUG_ON (rs_count > 0 && rs_count < bytes);
+ /* Assert reg_contents size is right. */
+ BUG_ON(sizeof(reg_contents) < bytes ||
+ sizeof(reg_contents) < rs_count);
+
+ if (rs_count) { /* real register */
+ rsv = task_user_regset_view(task);
+ BUG_ON(rsn >= rsv->n);
+ rs = & rsv->regsets[rsn];
+
+ /* Extract the register value into reg_contents[]. */
+ rc = (rs->get) (task, rs, rs_pos, rs_count,
+ reg_contents, NULL);
+ if (rc)
+ break;
+ } else { /* dummy value */
+ memset (reg_contents, 0, sizeof(reg_contents));
+ }
+
+ /* Hex-dump it. */
+ /* pr_debug ("gdb register %d => rsn %d p%u c%u b%u (",
+ i, rsn, rs_pos, rs_count, bytes); */
+ /* XXX: endianness adjust for count != bytes */
+ for(j=0; j<bytes; j++) {
+ /* pr_debug("%02x", reg_contents[j]);*/
+ push_output_hex(p, reg_contents[j]);
+ }
+ /* pr_debug(")\n"); */
+
+ }
+ push_output_packet_end(p, op_start);
+ break;
+ case 'G':
+ i = 0;
+ op_start = 2; /* use as input pointer, past $G in command */
+ while(p->input_buf[op_start] != '#' &&
+ op_start < p->input_buf_size) {
+ unsigned rs_count;
+ unsigned rs_pos;
+ unsigned bytes;
+ const struct user_regset_view* rsv;
+ const struct user_regset* rs;
+ unsigned char reg_contents[16]; /* maximum reg. width */
+
+ int rsn = gdb_remote_register_info(p, task, i,
+ &rs_pos, &rs_count,
+ &bytes);
+ /* pr_debug ("gdb register %d => rsn %d p%u c%u b%u\n",
+ i, rsn, rs_pos, rs_count, bytes); */
+
+ if (rsn < 0)
+ break;
+
+ /* If we want to extract register data, make sure
+ we're fetching at least that much. */
+ BUG_ON(rs_count > 0 && rs_count < bytes);
+ /* Assert reg_contents size is right. */
+ BUG_ON(sizeof(reg_contents) < bytes ||
+ sizeof(reg_contents) < rs_count);
+
+ /* Remaining packet too short? */
+ if ((op_start + 2*bytes + 3) < p->input_buf_size)
+ break;
+
+ /* 0-fill the register copy. XXX initialize
+ * it from rs->get() instead?
+ */
+ memset (reg_contents, 0, sizeof(reg_contents));
+
+ /* Hex-unconvert all the bytes. */
+ /* XXX: endianness adjust for count != bytes */
+ for(j=0; j<bytes; j++)
+ reg_contents[j]=byteme(p->input_buf[op_start+2*j],
+ p->input_buf[op_start+2*j+1]);
+ op_start += 2*bytes;
+
+ if (rs_count) { /* real register */
+ BUG_ON(rs_count > sizeof(reg_contents));
+ rsv = task_user_regset_view(task);
+ BUG_ON(rsn >= rsv->n);
+ rs = & rsv->regsets[rsn];
+
+ /* Set the register value from reg_contents[]. */
+ rc = (rs->set) (task, rs, rs_pos, rs_count,
+ reg_contents, NULL);
+ if (rc)
+ break;
+ } else { /* dummy register */
+ ;
+ }
+ }
+ if (p->input_buf[op_start] == '#' && rc == 0)
+ push_output_packet (p, "OK");
+ else
+ push_output_packet (p, "E01");
+ break;
+ case 'p': /* REG */
+ break;
+ case 'P': /* REG=VAL */
+ break;
+ case 'm': /* ADDR,LENGTH */
+ rc = sscanf(& p->input_buf[2], "%lx,%lx", &arg1, &arg2);
+ if (rc != 2)
+ push_output_packet(p, "E01");
+ else {
+ size_t o = push_output_packet_start (p);
+ while (arg2 > 0) {
+ unsigned char value;
+
+ /* Simply stop looping if requested
+ length was too large. gdb will
+ probably retry from this point
+ on. */
+ if (p->output_buf_size + 5 > GDB_BUFMAX)
+ break;
+
+ rc = access_process_vm(task, arg1, &value, 1, 0);
+ if (rc != 1)
+ break; /* EFAULT */
+ else
+ push_output_hex (p, value);
+
+ arg1++;
+ arg2--;
+ }
+ push_output_packet_end (p, o);
+ }
+ break;
+ case 'M': /* ADDR,LENGTH:XX */
+ /* `i' will index p->input_buf to consume XX hex bytes. */
+ rc = sscanf(& p->input_buf[2], "%lx,%lx:%n",
+ &arg1, &arg2, &i);
+ op_start = i + 2; /* Skip the leading $M also. */
+ if (rc < 2) {
+ push_output_packet(p, "E01");
+ break;
+ }
+ while (arg2 > 0) {
+ unsigned char value;
+
+ /* Check that enough input bytes left for
+ * these two hex chars, plus the #XX checksum.
+ */
+ if (i+4 >= p->input_buf_size)
+ break;
+
+ value = byteme(p->input_buf[i],
+ p->input_buf[i+1]);
+ rc = access_process_vm(task, arg1, &value, 1, 1);
+ if (rc != 1)
+ break; /* EFAULT */
+
+ i += 2;
+ arg1++;
+ arg2--;
+ }
+ if (arg2 != 0)
+ push_output_packet(p, "E02");
+ else
+ push_output_packet(p, "OK");
+ break;
+ default:
+ push_output_packet (p, "");
+ }
+}
+
+
+
+
+/* ------------------------------------------------------------------------ */
+
+/* gdb control callbacks */
+
+#define get_proc_task(inode) get_pid_task(PROC_I((inode))->pid, PIDTYPE_PID)
+
+static int proc_gdb_open(struct inode *inode, struct file *filp)
+{
+ struct task_struct *task = get_proc_task(inode);
+ int ret = -EBUSY;
+ struct gdb_connection *p;
+ struct list_head *l;
+
+ pr_debug ("opened /proc/%d/gdb\n", task->pid);
+
+ /* Reject kernel threads. */
+ if (task->flags & PF_KTHREAD) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Reject if connection is for other than tg-leader thread. */
+ if (task_pid_nr(task) != task_tgid_nr(task)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock (& gdb_connections_mutex);
+
+ /* Reject if a connection exists for the thread group
+ * leader.
+ */
+ list_for_each(l, &gdb_connections) {
+ p = list_entry (l, struct gdb_connection, link);
+ if (p->target == task_tgid_nr(task)) {
+ ret = -EBUSY;
+ goto out_mutex;
+ }
+ }
+ /* (Don't unlock yet, to defeat a race of two concurrent opens.) */
+
+ p = kzalloc(sizeof (struct gdb_connection), GFP_KERNEL);
+ if (!p) {
+ ret = -ENOMEM;
+ goto out_mutex;
+ }
+
+ /* Send initial ping to gdb. */
+ push_output_packet (p, "");
+
+ mutex_init(& p->output_mutex);
+ init_waitqueue_head(& p->output_wait);
+
+ mutex_init(& p->input_mutex);
+ init_waitqueue_head(& p->input_wait);
+
+ p->target = task->tgid;
+
+ /* NB: During attach, we don't want to bother the target.
+ Soon though a send_sig will interrupt it. */
+ p->at_quiesce_do = UTRACE_RESUME;
+
+ p->engine = utrace_attach_task(task,
+ UTRACE_ATTACH_CREATE |
+ UTRACE_ATTACH_EXCLUSIVE,
+ &gdb_utrace_ops,
+ p);
+ if (IS_ERR(p->engine) || p->engine==NULL) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ ret = utrace_set_events(task, p->engine,
+ UTRACE_EVENT_SIGNAL_ALL|
+ UTRACE_EVENT(QUIESCE)|
+ UTRACE_EVENT(DEATH)|
+ UTRACE_EVENT(EXIT)|
+ UTRACE_EVENT(EXEC)|
+ UTRACE_EVENT(CLONE));
+ pr_debug ("utrace_set_events sent, ret=%d\n", ret);
+ if (!ret)
+ ;
+
+ filp->private_data = p;
+
+ INIT_LIST_HEAD(& p->link);
+ list_add(&gdb_connections, &p->link);
+
+ p->stop_signals ++;
+ send_sig(SIGTRAP, task, 1);
+#if 0
+ ret = utrace_control(task, p->engine, UTRACE_INTERRUPT);
+ if (ret == -EINPROGRESS)
+ ret = utrace_barrier(task, p->engine);
+#endif
+
+ goto out_mutex;
+
+out_free:
+ kfree(p);
+out_mutex:
+ mutex_unlock (& gdb_connections_mutex);
+out:
+ return ret;
+}
+
+
+static int proc_gdb_release(struct inode *inode, struct file *filp)
+{
+ struct task_struct *task = get_proc_task(inode);
+ struct gdb_connection *p = filp->private_data;
+ int ret = 0;
+
+ mutex_lock (& gdb_connections_mutex);
+
+ if (task == NULL) {
+ /* The thread is already gone; report_death was already called. */
+ pr_debug ("gdb %d releasing old\n", p->target);
+ } else {
+ pr_debug ("gdb %d releasing current\n", p->target);
+
+ ret = utrace_set_events(task, p->engine, 0);
+ if (ret == -EINPROGRESS)
+ ret = utrace_barrier(task, p->engine);
+ /* No more callbacks will be received! */
+
+ ret = utrace_control(task, p->engine, UTRACE_DETACH); /* => RESUME */
+ if (ret == -EINPROGRESS)
+ ret = utrace_barrier(task, p->engine);
+
+ utrace_engine_put (p->engine);
+ }
+
+ list_del(&p->link);
+ kfree(p);
+
+ mutex_unlock (& gdb_connections_mutex);
+
+ return ret;
+}
+
+
+
+static int proc_gdb_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ /* XXX: GDB usually thinks that a file name for "target
+ * remote" implies a serial port with tty-ish ioctl's
+ * available. We pretend to accept them all. */
+ return 0;
+}
+
+
+
+static ssize_t proc_gdb_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct gdb_connection *p = filp->private_data;
+ struct task_struct *task;
+ int rc = 0;
+ size_t len;
+
+ task = find_task_by_vpid (p->target);
+ if (!task)
+ return -EINVAL;
+
+ if ((p->output_buf_size <= p->output_buf_read) &&
+ filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+again:
+ rc = wait_event_interruptible (p->output_wait,
+ (p->output_buf_size > p->output_buf_read));
+ if (rc)
+ goto out;
+
+ mutex_lock(&p->output_mutex);
+
+ if(p->output_buf_size <= p->output_buf_read) {
+ mutex_unlock(&p->output_mutex);
+ goto again;
+ }
+
+ len = min (count, (size_t)(p->output_buf_size - p->output_buf_read));
+ if (copy_to_user (buf, & p->output_buf[p->output_buf_read], len)) {
+ rc = -EFAULT;
+ goto out_unlock;
+ }
+
+ pr_debug ("sent %u bytes (%ld left) data (%.*s)\n",
+ (unsigned)len,
+ ((long)p->output_buf_size-(long)p->output_buf_read)-len,
+ (int)len, & p->output_buf[p->output_buf_read]);
+
+ p->output_buf_read += len;
+ rc = len;
+
+ /* If whole packet is consumed, reset for next one. */
+ BUG_ON (p->output_buf_read > p->output_buf_size);
+ if (p->output_buf_read == p->output_buf_size) {
+ p->output_buf_read = 0;
+ p->output_buf_size = 0;
+ }
+
+out_unlock:
+ mutex_unlock(&p->output_mutex);
+
+out:
+ return rc;
+}
+
+
+static ssize_t proc_gdb_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct gdb_connection *p = filp->private_data;
+ size_t last_input_buf_size;
+ struct task_struct *task;
+ size_t len;
+ int ret = 0;
+
+ task = find_task_by_vpid (p->target);
+ if (!task)
+ return -EINVAL;
+
+again:
+ ret = wait_event_interruptible (p->input_wait,
+ (p->input_buf_size < GDB_BUFMAX));
+ if (ret)
+ goto out;
+
+ mutex_lock(&p->input_mutex);
+ if (p->input_buf_size == GDB_BUFMAX) {
+ mutex_unlock(&p->input_mutex);
+ goto again;
+ }
+ mutex_lock(&p->output_mutex);
+
+ /* We now know there is some room in the input buffer. Upon
+ entry, the input_buf will either be empty, or contain a
+ partial gdb request packet. */
+
+ /* Copy the data. */
+ len = min (count, (size_t)(GDB_BUFMAX - p->input_buf_size));
+ if (copy_from_user (& p->input_buf[p->input_buf_size], buf, len)) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ /* pr_debug ("received data %.*s\n", (int)len, & p->input_buf[p->input_buf_size]); */
+
+ p->input_buf_size += len;
+ ret = len;
+
+ /* Process any packets in the buffer to restore the incoming
+ invariant. (Normal GDB will not send more than one packet
+ before waiting for a response.) */
+
+ /* We iterate until we can no longer shrink the input buffer. Usually
+ we will not iterate more than once, since there may be one +/-
+ ack byte and/or one gdb packet. */
+ last_input_buf_size = 0;
+ while (p->input_buf_size
+ && p->input_buf_size != last_input_buf_size) {
+ last_input_buf_size = p->input_buf_size;
+
+ if (p->input_buf[0] == '+') {
+ /* This must have been an ack to our
+ * previously output packet.
+ * Consume the input.
+ */
+ memmove (&p->input_buf[0], &p->input_buf[1], --p->input_buf_size);
+ } else if (p->input_buf[0] == '-') {
+ /* Whoops, a nak. Unfortunately, we don't
+ * handle transmission errors by
+ * retransmitting the last output_buf; it's
+ * already gone. OTOH we should not encounter
+ * transmission errors on a reliable channel
+ * such as a read syscall.
+ * Consume the input.
+ */
+ printk(KERN_WARNING "Unexpected NAK received"
+ "on /proc/%d/gdb connection.\n", task_pid_nr(task));
+ memmove (&p->input_buf[0], &p->input_buf[1], --p->input_buf_size);
+ } else if (p->input_buf[0] == 3) { /* ^C == INTR */
+ /* NB: don't overwrite 'ret'. */
+ pr_debug ("received gdb interrupt\n");
+ p->stop_signals ++;
+ send_sig(SIGTRAP, task, 1);
+#if 0
+ int rc = utrace_control(task, p->engine, UTRACE_INTERRUPT);
+ if (rc == -EINPROGRESS)
+ rc = utrace_barrier(task, p->engine);
+#endif
+ /* p->at_quiesce_do will be set in report_signal(SIGNAL_REPORT) */
+ /* NB: this packet does not generate an +/- ack.
+ Consume the input. */
+ memmove (&p->input_buf[0], &p->input_buf[1], --p->input_buf_size);
+ } else if (p->input_buf[0] == '$') { /* command packet */
+ int j;
+ unsigned char checksum = 0;
+ for (j=1; j<p->input_buf_size-2; j++) {
+ if (p->input_buf[j] == '#') {
+ unsigned char checksum2;
+ checksum2 = byteme (p->input_buf[j+1],
+ p->input_buf[j+2]);
+ pr_debug ("received gdb packet %.*s\n",
+ j+3, & p->input_buf[0]);
+ if (checksum == checksum2) {
+ push_output (p, '+');
+ handle_gdb_command_packet (p, task);
+ } else {
+ push_output (p, '-');
+ }
+ /* Consume the whole packet. */
+ p->input_buf_size -= (j+3);
+ memmove(&p->input_buf[0], &p->input_buf[j+3],
+ p->input_buf_size);
+ break;
+ } else {
+ checksum += p->input_buf[j];
+ }
+ } /* End searching for end of packet */
+
+ /* We may not have found the #<hex><hex>
+ * checksum. If so, leave the partial packet
+ * in input_buf. Since input_buf_size will
+ * not have decreased, the while() loop above
+ * will detect a fixpoint and exit.
+ *
+ * Alternately, there could be another gdb packet
+ * just behind the one we just consumed. In this
+ * we'll iterate one more time in this loop.
+ */
+ } else { /* junk character */
+ printk(KERN_WARNING "Unexpected character (%x) received"
+ " on /proc/%d/gdb connection.\n",
+ (int) p->input_buf[0], task_pid_nr(task));
+ /* Consume the input. */
+ memmove (&p->input_buf[0], &p->input_buf[1], --p->input_buf_size);
+ }
+ }
+
+out_unlock:
+ wake_up(&p->input_wait); /* Probably have more room in input_buf. */
+ wake_up(&p->output_wait); /* Probably have data in output_buf. */
+
+ mutex_unlock(&p->output_mutex);
+ mutex_unlock(&p->input_mutex);
+out:
+ return ret;
+}
+
+
+const struct file_operations proc_gdb_operations = {
+ .open = proc_gdb_open,
+ .read = proc_gdb_read,
+ .write = proc_gdb_write,
+ .release = proc_gdb_release,
+ .ioctl = proc_gdb_ioctl,
+};
+
+