This is the mail archive of the archer@sourceware.org mailing list for the Archer project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

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,
+};
+
+


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]