ref: ccef7a8bdca9d58397ab9ebe6af7ccf29edd6dad
parent: 4cabbe15f6cde5fac863ace9468361507b8d945a
author: David Arroyo <david@arroyo.cc>
date: Wed Mar 11 13:47:51 EDT 2026
Add gdbfs(4) gdbfs connects to a gdbserver and presents enough proc(3) files for acid(1) and db(1) to debug a 9 program running under gdb, such as a 9 kernel running in a hypervisor.
--- /dev/null
+++ b/sys/man/4/gdbfs
@@ -1,0 +1,96 @@
+.TH GDBFS 4
+.SH NAME
+gdbfs \- GNU debugger file system
+.SH SYNOPSIS
+.B gdbfs
+[
+.B -Dd
+]
+[
+.B -s
+.I srvname
+]
+[
+.B -m
+.I arch
+]
+[
+.B -p
+.I pid
+]
+[
+.B -t
+.I text
+]
+[
+.I addr
+]
+.SH DESCRIPTION
+.I Gdbfs
+presents a set of
+.IR proc (3)
+files under
+.BI /proc/ pid
+for debugging a remote process through the GNU debugger stub listening at
+.IR addr ,
+or connected to standard input/output if
+.I addr
+is not specified.
+If the
+.B -s
+option is given,
+.I gdbfs
+will post its channel at
+.BI /srv/ srvname
+(see
+.IR srv (3)),
+allowing the session to be shared or reattached later.
+.PP
+If the
+.B -D
+flag is specified, 9P messages will be logged to standard error.
+If the
+.B -d
+flag is specified,
+.IR gdbfs (4)
+will log gdbserver protocol messages exchanged with the remote gdbserver, along with other diagnostic information.
+.PP
+.IR Text ,
+if provided, should be a copy of the binary running on the target, in
+.IR a.out (6)
+format.
+It will be used to determine the architecture of the running target, and will be served at
+.BI /proc/ pid /text.
+If
+.I text
+is not provided, the target architecture must be specified with the
+.B -m
+flag.
+
+.SH EXAMPLES
+On drawterm, use the host unix network stack to connect to the gdbserver listening at
+.BR localhost:1234 ,
+which is a hypervisor running a a 9pc64 guest VM:
+.IP
+.EX
+ bind /mnt/term/net /net
+ gdbfs -t /amd64/9pc64 tcp!localhost!1234
+ acid 1
+.EE
+.PP
+Connect to gdbserver over a serial interface to debug an arm system:
+.IP
+.EX
+ gdbfs -m arm <>/dev/eia0
+.EE
+.SH BUGS
+Gdbfs does not consistently respond to or emit retransmit requests (character '-').
+Use a reliable transport such as TCP or a pipe for best results.
+
+Stubs which emit more than one reply per request may cause gdbfs to become stuck.
+.SH SOURCE
+/sys/src/cmd/gdbfs
+.SH "SEE ALSO"
+.IR acid (1),
+.IR db (1),
+.IR rdbfs (4)
--- /dev/null
+++ b/sys/src/cmd/gdbfs/dat.h
@@ -1,0 +1,36 @@
+int debug;
+void dbg(char *fmt, ...);
+
+enum {+ /* counting the terminating NUL is intentional */
+ Minwrite = sizeof "M0000000000000000,00000000:#00",
+};
+
+enum state {+ Running = 1,
+ Stopped,
+ Shutdown,
+};
+
+struct
+{+ int tid;
+ QLock; /* guards state transitions */
+ int state;
+ int pktlen;
+ int wfd;
+ Biobuf *rb;
+ Channel *c;
+} gdb;
+
+void gdbinit(int rfd, int wfd);
+void gdbshutdown(void);
+
+void gdbreadmem(Req *r);
+void gdbwritemem(Req *r);
+void gdbreadreg(Req *r);
+void gdbwritereg(Req *r);
+void gdbstart(Req *r);
+void gdbwaitstop(Req *r);
+void gdbstartstop(Req *r);
+void gdbstop(Req *r);
--- /dev/null
+++ b/sys/src/cmd/gdbfs/gdb.c
@@ -1,0 +1,655 @@
+/* client for the GDB remote serial protocol, documented here:
+ https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html
+*/
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mach.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "dat.h"
+
+static char Ebadctl[] = "bad process or channel control request";
+static char Erunning[] = "target is running";
+static char hex[] = "0123456789abcdef";
+
+/* /proc/$pid/[k]regs holds a Ureg structure for the target platform.
+ However, its definition will not always match the output of the
+ gdbserver's "g" command, so we need to conversion tables for each
+ machine type */
+
+typedef struct Gdbreg Gdbreg;
+struct Gdbreg
+{+ char *name; /* matches Reglist[].name */
+ int size; /* may not always agree with libmach */
+};
+
+/* registers listed in the order output by gdbserver, no gaps */
+static Gdbreg armregs[] = {+ { "R0", 4 },+ { "R1", 4 },+ { "R2", 4 },+ { "R3", 4 },+ { "R4", 4 },+ { "R5", 4 },+ { "R6", 4 },+ { "R7", 4 },+ { "R8", 4 },+ { "R9", 4 },+ { "R10", 4 },+ { "R11", 4 },+ { "R12", 4 },+ { "R13", 4 },+ { "R14", 4 },+ { "R15", 4 },+ {0},+};
+
+static Gdbreg amd64regs[] = {+ { "AX", 8 },+ { "BX", 8 },+ { "CX", 8 },+ { "DX", 8 },+ { "SI", 8 },+ { "DI", 8 },+ { "BP", 8 },+ { "SP", 8 },+ { "R8", 8 },+ { "R9", 8 },+ { "R10", 8 },+ { "R11", 8 },+ { "R12", 8 },+ { "R13", 8 },+ { "R14", 8 },+ { "R15", 8 },+ { "PC", 8 },+ { "EFLAGS", 4 },+ { "CS", 4},+ { "SS", 4},+ { "DS", 4},+ { "ES", 4},+ { "FS", 4},+ { "GS", 4},+ {0},+};
+
+static Gdbreg *gdbregs[] =
+{+ [MARM] = armregs,
+ [MAMD64] = amd64regs,
+};
+
+static Reglist *
+findreg(char *rname)
+{+ Reglist *r;
+ for(r = mach->reglist; r->rname != nil; r++){+ if(strcmp(r->rname, rname) == 0)
+ return r;
+ }
+ return nil;
+}
+
+uchar *
+bin2ureg(uchar *src)
+{+ uchar *ureg;
+ Gdbreg *greg;
+ Reglist *reg;
+
+ union {+ u16int u16;
+ u32int u32;
+ u64int u64;
+ } v;
+
+ if(mach->mtype > nelem(gdbregs) || gdbregs[mach->mtype] == nil){+ werrstr("no register map for %s", mach->name);+ return nil;
+ }
+ ureg = mallocz(mach->regsize, 1);
+ if(ureg == nil)
+ return nil;
+
+ for(greg = gdbregs[mach->mtype]; greg->name != nil; greg++){+ if((reg = findreg(greg->name)) == nil)
+ continue;
+
+ memmove(&v, src, greg->size);
+ src += greg->size;
+
+ switch(reg->rformat){+ case 'x':
+ memmove(&ureg[reg->roffs], &v.u16, sizeof v.u16);
+ break;
+ case 'X': case 'W': case 'f':
+ memmove(&ureg[reg->roffs], &v.u32, sizeof v.u32);
+ break;
+ case 'Y': case 'F':
+ memmove(&ureg[reg->roffs], &v.u64, sizeof v.u64);
+ break;
+ }
+ }
+ return ureg;
+}
+
+static uintptr
+off2addr(vlong off)
+{+ off <<= 1;
+ off >>= 1;
+ return off;
+}
+
+static int
+badsum(char *p, uint sum)
+{+ while(*p)
+ sum -= *p++;
+ return sum & 0xff;
+}
+
+static long
+hex2bin(char *dst, char *src, long len)
+{+ int i, n, v;
+
+ for(i = n = 0; n < len && src[i]; i++){+ switch(src[i]){+ case '0' ... '9':
+ v = src[i] - '0';
+ break;
+ case 'a' ... 'f':
+ v = src[i] - 'a' + 10;
+ break;
+ case 'A' ... 'F':
+ v = src[i] - 'A' + 10;
+ break;
+ default:
+ /* todo: run-length encoding */
+ werrstr("bad hex digit %c", src[i]);+ return -1;
+ }
+ dst[n] = (dst[n] << 4) + v;
+ n += i & 1;
+ }
+ if(i & 1){+ werrstr("incomplete hex digit");+ return -1;
+ }
+ return n;
+}
+
+static Channel *
+cmdstr(char *str)
+{+ Channel *rc;
+ char *err;
+
+ rc = chancreate(sizeof(char*), 0);
+ if(sendp(gdb.c, rc) < 0){+ werrstr("interrupted");+ free(str);
+ chanfree(rc);
+ return nil;
+ }
+ if(sendp(rc, str) < 0)
+ sysfatal("cmdstr: sendp %s failed", str);+
+ /* ack */
+ if(recv(rc, &err) < 0){+ werrstr("interrupted");+ chanclose(rc);
+ return nil;
+ }
+ if(err != nil){+ werrstr("%s", err);+ free(err);
+ chanfree(rc);
+ return nil;
+ }
+ return rc;
+}
+
+static Channel *
+vcmd(char *fmt, va_list args)
+{+ int sum;
+ char buf[512], *s, *e, *p;
+
+ e = buf + sizeof buf;
+ s = p = seprint(buf, e, "$");
+ s = vseprint(s, e, fmt, args);
+
+ for(sum = 0; *p != 0; p++)
+ sum += *p;
+ seprint(s, e, "#%02x", sum & 0xff);
+ return cmdstr(estrdup9p(buf));
+}
+
+static Channel *
+cmd(char *fmt, ...)
+{+ Channel *rc;
+ va_list args;
+ va_start(args, fmt);
+ rc = vcmd(fmt, args);
+ va_end(args);
+ return rc;
+}
+
+static char *
+reply(Channel *rc)
+{+ char *rsp;
+ if(recv(rc, &rsp) < 0){+ werrstr("interrupted");+ chanclose(rc);
+ return nil;
+ }
+ chanfree(rc);
+
+ if(*rsp == 'E'){+ werrstr("%s", rsp);+ free(rsp);
+ return nil;
+ }
+ return rsp;
+}
+
+static char *
+cmdreply(char *fmt, ...)
+{+ Channel *rc;
+ va_list args;
+
+ va_start(args, fmt);
+ rc = vcmd(fmt, args);
+ va_end(args);
+
+ if(rc != nil)
+ return reply(rc);
+ return nil;
+}
+
+/* Protocol between gdbproc (G) and request handler (R):
+
+ R → G command (char*)
+ R ← G ack (nil) or err (char*)
+ if !err:
+ R ← G reply (char*) or err ("E...")+ R ← G close
+
+ R may free the channel after the final message.
+ R may close the channel if it is interrupted.
+ G may free the channel if send() or recv() fail.
+*/
+static void
+gdbproc(void *)
+{+ int c, tries;
+ Channel *rc;
+ char *req, *rsp, sum[4];
+
+ memset(sum, 0, sizeof sum);
+ while(rc = recvp(gdb.c)){+ if(recv(rc, &req) < 0){+ chanfree(rc);
+ continue;
+ }
+ tries = 5;
+ do{+ dbg("→ %s\n", req);+ if(fprint(gdb.wfd, "%s", req) < 0){+ chanprint(rc, "E.%r");
+ goto next;
+ }
+ c = Bgetc(gdb.rb);
+ dbg("← %c\n", c);+ } while(c == '-' && tries --> 0);
+ free(req);
+
+ if(c != '+'){+ chanprint(rc, "E.wanted '+', got '%c'", c);
+ goto next;
+ }
+
+ /* command ACKed. "start" needs this to end 9P req.
+ we must consume the response, so continue on
+ unconditionally, even if caller was interrupted */
+ sendp(rc, nil);
+
+ /* Skip notification packets. These are unsolicited,
+ and must not contain the start-of-packet marker '$' */
+ if(Brdline(gdb.rb, '$') == nil
+ || Blinelen(gdb.rb) > 0 && fprint(gdb.wfd, "+") < 0)
+ {+ chanprint(rc, "E.%r");
+ goto next;
+ }
+
+ rsp = Brdstr(gdb.rb, '#', 1);
+ if(rsp != nil) dbg("← %s\n", rsp);+
+ if(rsp == nil)
+ rsp = smprint("E.%r");+ else if(Bread(gdb.rb, sum, 2) < 0){+ free(rsp);
+ rsp = smprint("E.%r");+ }
+ else if(badsum(rsp, strtoul(sum, nil, 16))){+ free(rsp);
+ rsp = smprint("E.bad checksum %s", sum);+ }
+ else if(fprint(gdb.wfd, "+") < 0){+ free(rsp);
+ rsp = smprint("E.ack %r");+ }
+ if(sendp(rc, rsp) < 0){+ chanfree(rc);
+ free(rsp);
+ continue;
+ }
+next: chanclose(rc);
+ }
+ chanclose(gdb.c);
+}
+
+void
+gdbinit(int rfd, int wfd)
+{+ static char qSupported[] = "qSupported:error-message+";
+ int i, n;
+ char *rsp, *features[64], *kv[2];
+
+ gdb.wfd = wfd;
+ gdb.rb = Bfdopen(rfd, OREAD);
+ if(gdb.rb == nil)
+ sysfatal("gdbinit: %r");+
+ gdb.c = chancreate(sizeof(Channel*), 0);
+ gdb.tid = proccreate(gdbproc, nil, 8192);
+
+ if((rsp = cmdreply("%s", qSupported)) == nil)+ sysfatal("gdbinit: %r");+
+ n = getfields(rsp, features, nelem(features), 1, ";");
+ for(i = 0; i < n; i++){+ if(getfields(features[i], kv, nelem(kv), 1, "=") != 2)
+ continue;
+ if(strcmp(kv[0], "PacketSize") == 0)
+ gdb.pktlen = strtol(kv[1], nil, 16);
+ }
+ free(rsp);
+ qlock(&gdb);
+ gdb.state = Stopped;
+ qunlock(&gdb);
+}
+
+void
+gdbshutdown(void)
+{+ qlock(&gdb);
+ gdb.state = Shutdown;
+ qunlock(&gdb);
+
+ threadint(gdb.tid);
+ if(sendp(gdb.c, nil) < 0)
+ sysfatal("gdbshutdown: %r");+ recv(gdb.c, nil);
+ chanfree(gdb.c);
+
+ if(fprint(gdb.wfd, "$D#44") < 0)
+ sysfatal("gdbshutdown detach: %r");+ Bterm(gdb.rb);
+ close(gdb.wfd);
+}
+
+void
+gdbwritemem(Req *r)
+{+ Channel *rc;
+ int i, sum, lo, hi;
+ ulong count;
+ char *rsp, *req, *s, *e, *b;
+
+ count = r->ifcall.count;
+ if(gdb.pktlen > Minwrite && count > (gdb.pktlen - Minwrite)/2)
+ count = (gdb.pktlen - Minwrite)/2;
+
+ s = req = emalloc9p(count + Minwrite);
+ e = req + count + Minwrite;
+ s = seprint(s, e, "$M%llux,%lux:", off2addr(r->ifcall.offset), count);
+
+ for(b = req + 1, sum = 0; b < s; b++)
+ sum += *b;
+ for(i = 0; i < count; i++){+ hi = hex[(r->ifcall.data[i] & 0xf0) >> 4];
+ lo = hex[(r->ifcall.data[i] & 0x0f) >> 0];
+ sum += hi + lo;
+ *s++ = hi;
+ *s++ = lo;
+ }
+ seprint(s, e, "#%02x", sum & 0xff);
+
+ qlock(&gdb);
+ if(gdb.state != Stopped){+ respond(r, Ebadctl);
+ qunlock(&gdb);
+ free(req);
+ return;
+ }
+ if((rc = cmdstr(req)) == nil || (rsp = reply(rc)) == nil)
+ responderror(r);
+ else {+ r->ofcall.count = count;
+ respond(r, nil);
+ free(rsp);
+ }
+ qunlock(&gdb);
+}
+
+void
+gdbreadreg(Req *r)
+{+ char *rsp;
+ uchar *ureg;
+
+ qlock(&gdb);
+ if(gdb.state != Stopped){+ respond(r, Ebadctl);
+ qunlock(&gdb);
+ return;
+ }
+ rsp = cmdreply("g");+ qunlock(&gdb);
+
+ if(rsp == nil){+ responderror(r);
+ return;
+ }
+ if(hex2bin(rsp, rsp, strlen(rsp)) < 0)
+ responderror(r);
+ else if((ureg = bin2ureg((uchar*)rsp)) == nil)
+ responderror(r);
+ else {+ readbuf(r, ureg, mach->regsize);
+ respond(r, nil);
+ free(ureg);
+ }
+ free(rsp);
+}
+
+void
+gdbwritereg(Req *r)
+{+ respond(r, "not implemented");
+}
+
+void
+gdbreadmem(Req *r)
+{+ long n;
+ ulong len;
+ char *rsp;
+
+ len = r->ifcall.count;
+ if(gdb.pktlen > 0 && len > (gdb.pktlen - Minwrite)/2)
+ len = (gdb.pktlen - Minwrite)/2;
+
+ qlock(&gdb);
+ if(gdb.state != Stopped){+ respond(r, Ebadctl);
+ qunlock(&gdb);
+ return;
+ }
+ rsp = cmdreply("m%llux,%lux", off2addr(r->ifcall.offset), len);+ qunlock(&gdb);
+
+ if(rsp == nil){+ responderror(r);
+ return;
+ }
+ if((n = hex2bin(r->ofcall.data, rsp, len)) < 0)
+ responderror(r);
+ else {+ r->ofcall.count = n;
+ respond(r, nil);
+ }
+ free(rsp);
+}
+
+void
+gdbstart(Req *r)
+{+ static char haltcodes[] = "STXWN";
+ char *rsp;
+ Srv *srv;
+ Channel *rc;
+
+ srv = r->srv;
+
+ qlock(&gdb);
+ if(gdb.state != Stopped){+ respond(r, Ebadctl);
+ qunlock(&gdb);
+ return;
+ }
+ if((rc = cmd("c")) == nil){+ responderror(r);
+ qunlock(&gdb);
+ return;
+ }
+ gdb.state = Running;
+ respond(r, nil);
+ qunlock(&gdb);
+
+ /* we need to stick around to track the state of
+ the target, even though 9P request is done */
+ srvrelease(srv);
+ rsp = reply(rc);
+ srvacquire(srv);
+
+ if(rsp == nil)
+ sysfatal("gdbstart reply: %r");+ if(strchr(haltcodes, *rsp) == nil)
+ sysfatal("gdbstart: bad response %s", rsp);+ free(rsp);
+ qlock(&gdb);
+ gdb.state = Stopped;
+ qunlock(&gdb);
+}
+
+static int
+interrupt(void)
+{+ dbg("→ \\x03\n");+
+ /* we should probably have a lock guarding writes,
+ but this program makes small writes; it's unlikely
+ this would wind up in the middle of another one */
+ return write(gdb.wfd, "\x03", 1);
+}
+
+void
+gdbstop(Req *r)
+{+ char *rsp;
+
+ qlock(&gdb);
+ if(gdb.state == Stopped)
+ respond(r, nil);
+
+ else if(interrupt() < 0)
+ responderror(r);
+
+ else if((rsp = cmdreply("?")) == nil)+ responderror(r);
+ else {+ gdb.state = Stopped;
+ free(rsp);
+ respond(r, nil);
+ }
+ qunlock(&gdb);
+}
+
+void
+gdbstartstop(Req *r)
+{+ Channel *rc;
+ char *rsp, err[sizeof "interrupted"];
+
+ qlock(&gdb);
+ if(gdb.state != Stopped){+ respond(r, Ebadctl);
+ qunlock(&gdb);
+ return;
+ }
+ if((rc = cmd("c")) == nil){+ responderror(r);
+ qunlock(&gdb);
+ return;
+ }
+ gdb.state = Running;
+ qunlock(&gdb);
+
+ /* target could run indefinitely, or until another
+ request stops it, so we need to unblock srv */
+ srvrelease(r->srv);
+ rsp = reply(rc);
+ rerrstr(err, sizeof err);
+ srvacquire(r->srv);
+
+ if(rsp == nil && strcmp(err, "interrupted") == 0)
+ interrupt();
+
+ qlock(&gdb);
+ gdb.state = Stopped;
+ qunlock(&gdb);
+
+ if(rsp == nil){+ responderror(r);
+ } else {+ respond(r, nil);
+ free(rsp);
+ }
+}
+
+void
+gdbwaitstop(Req *r)
+{+ char *rsp;
+
+ srvrelease(r->srv);
+ rsp = cmdreply("?");+ srvacquire(r->srv);
+
+ if(rsp == nil)
+ responderror(r);
+ else {+ respond(r, nil);
+ free(rsp);
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/gdbfs/main.c
@@ -1,0 +1,330 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <mach.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include "dat.h"
+
+int debug;
+void
+dbg(char *fmt, ...)
+{+ va_list args;
+ if(debug){+ va_start(args, fmt);
+ vfprint(2, fmt, args);
+ va_end(args);
+ }
+}
+
+int textfd = -1;
+char *srvname;
+char *procname = "1";
+
+void
+usage(void)
+{+ fprint(2, "usage: gdbfs [-s srvname] [-p pid] [-t text] [addr]\n");
+ threadexitsall("usage");+}
+
+enum
+{+ Xctl = 1,
+ Xfpregs,
+ Xkregs,
+ Xmem,
+ Xproc,
+ Xregs,
+ Xtext,
+ Xstatus,
+};
+
+struct {+ char *s;
+ int id;
+ int mode;
+} tab[] = {+ "ctl", Xctl, 0666,
+ "fpregs", Xfpregs, 0666,
+ "kregs", Xkregs, 0666,
+ "mem", Xmem, 0666,
+ "regs", Xregs, 0666,
+ "text", Xtext, 0444,
+ "status", Xstatus, 0444,
+};
+
+void
+die(Srv*)
+{+ threadint(gdb.tid);
+ gdbshutdown();
+}
+
+void
+fsopen(Req *r)
+{+ switch((uintptr)r->fid->file->aux){+ case Xfpregs:
+ case Xkregs:
+ case Xregs:
+ case Xmem:
+ /* data is hex encoded, so packet size is ×2 iounit */
+ if(gdb.pktlen > Minwrite)
+ r->ofcall.iounit = (gdb.pktlen - Minwrite)/2;
+ break;
+ }
+ respond(r, nil);
+}
+
+void
+fsread(Req *r)
+{+ int n, i;
+ char buf[512], *status;
+
+ switch((uintptr)r->fid->file->aux){+ case Xctl:
+ readstr(r, procname);
+ respond(r, nil);
+ break;
+ case Xfpregs:
+ respond(r, "not implemented");
+ break;
+ case Xkregs:
+ case Xregs:
+ gdbreadreg(r);
+ break;
+ case Xmem:
+ gdbreadmem(r);
+ break;
+ case Xtext:
+ if(textfd != -1)
+ n = pread(textfd, r->ofcall.data, r->ifcall.count, r->ifcall.offset);
+ else
+ n = 0;
+ if(n < 0)
+ responderror(r);
+ else {+ r->ofcall.count = n;
+ respond(r, nil);
+ }
+ break;
+ case Xstatus:
+ qlock(&gdb);
+ switch(gdb.state){+ case Stopped:
+ status = "Stopped";
+ break;
+ case Running:
+ status = "Running";
+ break;
+ case Shutdown:
+ status = "Moribund";
+ break;
+ default:
+ status = "New";
+ break;
+ }
+ qunlock(&gdb);
+ n = sprint(buf, "%-28s%-28s%-28s", "remote", "system", status);
+ for(i = 0; i < 9; i++)
+ n += sprint(buf+n, "%-12d", 0);
+ readstr(r, buf);
+ respond(r, nil);
+ break;
+ default:
+ respond(r, "Egreg");
+ }
+}
+
+void
+doctl(Req *r)
+{+ enum {+ /* see proc(3) */
+ Stop,
+ Start,
+ Waitstop,
+ Startstop,
+ };
+ Cmdbuf *cb;
+ Cmdtab *ct;
+ static Cmdtab cmds[] = {+ { Stop, "stop", 1 },+ { Start, "start", 1 },+ { Waitstop, "waitstop", 1 },+ { Startstop, "startstop", 1 },+ };
+
+ cb = parsecmd(r->ifcall.data, r->ifcall.count);
+ ct = lookupcmd(cb, cmds, nelem(cmds));
+ free(cb);
+ if(ct == nil){+ responderror(r);
+ free(cb);
+ return;
+ }
+ r->ofcall.count = r->ifcall.count;
+
+ switch(ct->index){+ default:
+ respond(r, "not implemented");
+ break;
+ case Stop:
+ gdbstop(r);
+ break;
+ case Start:
+ gdbstart(r);
+ break;
+ /* remaining commands block, so we may interrupt them */
+ case Waitstop:
+ r->aux = (void*)threadid();
+ gdbwaitstop(r);
+ break;
+ case Startstop:
+ r->aux = (void*)threadid();
+ gdbstartstop(r);
+ break;
+ }
+}
+
+void
+fswrite(Req *r)
+{+ switch((uintptr)r->fid->file->aux){+ case Xctl:
+ doctl(r);
+ break;
+ case Xfpregs:
+ case Xkregs:
+ case Xregs:
+ gdbwritereg(r);
+ break;
+ case Xmem:
+ gdbwritemem(r);
+ break;
+ case Xtext:
+ case Xstatus:
+ default:
+ respond(r, "Egreg");
+ break;
+ }
+}
+
+void
+fsflush(Req *r)
+{+ if(r->oldreq->aux != 0)
+ threadint((uintptr)r->oldreq->aux);
+ respond(r, nil);
+}
+
+void
+fsstat(Req *r)
+{+ Dir *d;
+
+ switch((uintptr)r->fid->file->aux) {+ default:
+ respond(r, nil);
+ break;
+ case Xregs:
+ case Xkregs:
+ r->d.length = mach->regsize;
+ break;
+ case Xtext:
+ /* setting the correct size here allows libmach to seek
+ to the end of the file in thumbpctab(), and probably
+ elsewhere */
+ if((d = dirfstat(textfd)) == nil){+ responderror(r);
+ break;
+ }
+ r->d.length = d->length;
+ respond(r, nil);
+ free(d);
+ break;
+ }
+}
+
+Srv fs = {+ .open = fsopen,
+ .read = fsread,
+ .write = fswrite,
+ .flush = fsflush,
+ .stat = fsstat,
+ .end = die,
+};
+
+void
+threadmain(int argc, char *argv[])
+{+ int i, rfd, wfd;
+ File *dir;
+ Fhdr hdr;
+ char *textfile, *arch;
+
+ fmtinstall('F', fcallfmt);+ textfile = arch = nil;
+ ARGBEGIN{+ case 'D':
+ chatty9p++;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'p':
+ procname = EARGF(usage());
+ break;
+ case 'm':
+ arch = EARGF(usage());
+ break;
+ case 't':
+ textfile = EARGF(usage());
+ break;
+ case 's':
+ srvname = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if(textfile != nil){+ textfd = open(textfile, OREAD);
+ if(textfd == -1)
+ sysfatal("open %s: %r", textfile);+ if(crackhdr(textfd, &hdr) < 0)
+ sysfatal("crackhdr: %r");+ } else if(arch != nil){+ if(machbyname(arch) < 0)
+ sysfatal("machbyname: %r");+ } else
+ sysfatal("-t or -m required");+
+ rfd = 0;
+ wfd = 1;
+ switch(argc){+ case 1:
+ rfd = dial(argv[0], nil, nil, nil);
+ if(rfd == -1)
+ sysfatal("dial %s: %r", argv[1]);+ wfd = rfd;
+ break;
+ case 0:
+ break;
+ default:
+ usage();
+ }
+
+ gdbinit(rfd, wfd);
+ fs.tree = alloctree("gdbfs", "gdbfs", DMDIR|0555, nil);+ dir = createfile(fs.tree->root, procname, "gdbfs", DMDIR|0555, 0);
+ for(i = 0; i < nelem(tab); i++)
+ closefile(createfile(dir, tab[i].s, "gdbfs", tab[i].mode, (void*)tab[i].id));
+ closefile(dir);
+ threadpostmountsrv(&fs, srvname, "/proc", MBEFORE);
+ exits(0);
+}
--- /dev/null
+++ b/sys/src/cmd/gdbfs/mkfile
@@ -1,0 +1,12 @@
+</$objtype/mkfile
+
+TARG=gdbfs
+
+OFILES=\
+ main.$O\
+ gdb.$O\
+
+HFILES=dat.h
+
+BIN=/$objtype/bin
+</sys/src/cmd/mkone
--
⑨