summaryrefslogtreecommitdiff
path: root/src/libmach/linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libmach/linux.c')
-rw-r--r--src/libmach/linux.c1010
1 files changed, 1010 insertions, 0 deletions
diff --git a/src/libmach/linux.c b/src/libmach/linux.c
new file mode 100644
index 000000000..6ce18957f
--- /dev/null
+++ b/src/libmach/linux.c
@@ -0,0 +1,1010 @@
+// Derived from Plan 9 from User Space src/libmach/Linux.c
+// http://code.swtch.com/plan9port/src/tip/src/libmach/Linux.c
+//
+// Copyright © 1994-1999 Lucent Technologies Inc.
+// Power PC support Copyright © 1995-2004 C H Forsyth (forsyth@terzarima.net).
+// Portions Copyright © 1997-1999 Vita Nuova Limited.
+// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com).
+// Revisions Copyright © 2000-2004 Lucent Technologies Inc. and others.
+// Portions Copyright © 2001-2007 Russ Cox.
+// Portions Copyright © 2009 The Go Authors. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#include <u.h>
+#include <sys/syscall.h> /* for tkill */
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/ptrace.h>
+#include <sys/signal.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <libc.h>
+#include <bio.h>
+#include <mach.h>
+#define Ureg Ureg32
+#include <ureg_x86.h>
+#undef Ureg
+#define Ureg Ureg64
+#include <ureg_amd64.h>
+#undef Ureg
+#undef waitpid
+
+// The old glibc used with crosstool compilers on thresher
+// doesn't know these numbers, but the Linux kernel
+// had them as far back as 2.6.0.
+#ifndef WSTOPPED
+#define WSTOPPED 2
+#define WCONTINUED 8
+#define WIFCONTINUED(x) ((x) == 0xffff)
+#endif
+#ifndef PTRACE_SETOPTIONS
+#define PTRACE_SETOPTIONS 0x4200
+#define PTRACE_GETEVENTMSG 0x4201
+#define PTRACE_O_TRACEFORK 0x2
+#define PTRACE_O_TRACEVFORK 0x4
+#define PTRACE_O_TRACECLONE 0x8
+#define PTRACE_O_TRACEEXEC 0x10
+#define PTRACE_O_TRACEVFORKDONE 0x20
+#define PTRACE_O_TRACEEXIT 0x40
+#define PTRACE_EVENT_FORK 0x1
+#define PTRACE_EVENT_VFORK 0x2
+#define PTRACE_EVENT_CLONE 0x3
+#define PTRACE_EVENT_EXEC 0x4
+#define PTRACE_EVENT_VFORK_DONE 0x5
+#define PTRACE_EVENT_EXIT 0x6
+#endif
+
+typedef struct Ureg64 Ureg64;
+
+static Maprw ptracesegrw;
+static Maprw ptraceregrw;
+
+// /usr/include/asm-x86_64/user.h
+struct user_regs_struct {
+ unsigned long r15,r14,r13,r12,rbp,rbx,r11,r10;
+ unsigned long r9,r8,rax,rcx,rdx,rsi,rdi,orig_rax;
+ unsigned long rip,cs,eflags;
+ unsigned long rsp,ss;
+ unsigned long fs_base, gs_base;
+ unsigned long ds,es,fs,gs;
+};
+
+// Linux gets very upset if a debugger forgets the reported state
+// of a debugged process, so we keep everything we know about
+// a debugged process in the LinuxThread structure.
+//
+// We can poll for state changes by calling waitpid and interpreting
+// the integer status code that comes back. Wait1 does this.
+//
+// If the process is already running, it is an error to PTRACE_CONT it.
+//
+// If the process is already stopped, it is an error to stop it again.
+//
+// If the process is stopped because of a signal, the debugger must
+// relay the signal to the PTRACE_CONT call, or else the signal is
+// dropped.
+//
+// If the process exits, the debugger should detach so that the real
+// parent can reap the zombie.
+//
+// On first attach, the debugger should set a handful of flags in order
+// to catch future events like fork, clone, exec, etc.
+
+// One for every attached thread.
+typedef struct LinuxThread LinuxThread;
+struct LinuxThread
+{
+ int pid;
+ int tid;
+ int state;
+ int signal;
+ int child;
+ int exitcode;
+};
+
+static int trace = 0;
+
+static LinuxThread **thr;
+static int nthr;
+static int mthr;
+
+static int realpid(int pid);
+
+enum
+{
+ Unknown,
+ Detached,
+ Attached,
+ AttachStop,
+ Stopped,
+ Running,
+ Forking,
+ Vforking,
+ VforkDone,
+ Cloning,
+ Execing,
+ Exiting,
+ Exited,
+ Killed,
+
+ NSTATE,
+};
+
+static char* statestr[NSTATE] = {
+ "Unknown",
+ "Detached",
+ "Attached",
+ "AttachStop",
+ "Stopped",
+ "Running",
+ "Forking",
+ "Vforking",
+ "VforkDone",
+ "Cloning",
+ "Execing",
+ "Exiting",
+ "Exited",
+ "Killed"
+};
+
+static LinuxThread*
+attachthread(int pid, int tid, int *new, int newstate)
+{
+ int i, n, status;
+ LinuxThread **p, *t;
+ uintptr flags;
+
+ if(new)
+ *new = 0;
+
+ for(i=0; i<nthr; i++)
+ if((pid == 0 || thr[i]->pid == pid) && thr[i]->tid == tid) {
+ t = thr[i];
+ goto fixup;
+ }
+
+ if(!new)
+ return nil;
+
+ if(nthr >= mthr) {
+ n = mthr;
+ if(n == 0)
+ n = 64;
+ else
+ n *= 2;
+ p = realloc(thr, n*sizeof thr[0]);
+ if(p == nil)
+ return nil;
+ thr = p;
+ mthr = n;
+ }
+
+ t = malloc(sizeof *t);
+ if(t == nil)
+ return nil;
+ memset(t, 0, sizeof *t);
+
+ thr[nthr++] = t;
+ if(pid == 0 && nthr > 0)
+ pid = thr[0]->pid;
+ t->pid = pid;
+ t->tid = tid;
+ t->state = newstate;
+ if(trace)
+ fprint(2, "new thread %d %d\n", t->pid, t->tid);
+ if(new)
+ *new = 1;
+
+fixup:
+ if(t->state == Detached) {
+ if(ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) {
+ fprint(2, "ptrace ATTACH %d: %r\n", tid);
+ return nil;
+ }
+ t->state = Attached;
+ }
+
+ if(t->state == Attached) {
+ // wait for stop, so we can set options
+ if(waitpid(tid, &status, __WALL|WUNTRACED|WSTOPPED) < 0)
+ return nil;
+ if(!WIFSTOPPED(status)) {
+ fprint(2, "waitpid %d: status=%#x not stopped\n", tid);
+ return nil;
+ }
+ t->state = AttachStop;
+ }
+
+ if(t->state == AttachStop) {
+ // set options so we'll find out about new threads
+ flags = PTRACE_O_TRACEFORK |
+ PTRACE_O_TRACEVFORK |
+ PTRACE_O_TRACECLONE |
+ PTRACE_O_TRACEEXEC |
+ PTRACE_O_TRACEVFORKDONE;
+ if(ptrace(PTRACE_SETOPTIONS, tid, 0, (void*)flags) < 0) {
+ fprint(2, "ptrace PTRACE_SETOPTIONS %d: %r\n", tid);
+ return nil;
+ }
+ t->state = Stopped;
+ }
+
+ return t;
+}
+
+static LinuxThread*
+findthread(int tid)
+{
+ return attachthread(0, tid, nil, 0);
+}
+
+int
+procthreadpids(int pid, int *p, int np)
+{
+ int i, n;
+ LinuxThread *t;
+
+ n = 0;
+ for(i=0; i<nthr; i++) {
+ t = thr[i];
+ if(t->pid == pid) {
+ switch(t->state) {
+ case Exited:
+ case Detached:
+ case Killed:
+ break;
+
+ default:
+ if(n < np)
+ p[n] = t->tid;
+ n++;
+ break;
+ }
+ }
+ }
+ return n;
+}
+
+// Execute a single wait and update the corresponding thread.
+static int
+wait1(int nohang)
+{
+ int tid, new, status, event;
+ ulong data;
+ LinuxThread *t;
+ enum
+ {
+ NormalStop = 0x137f
+ };
+
+ if(nohang != 0)
+ nohang = WNOHANG;
+
+ status = 0;
+ tid = waitpid(-1, &status, __WALL|WUNTRACED|WSTOPPED|WCONTINUED|nohang);
+
+ if(tid < 0)
+ return -1;
+ if(tid == 0)
+ return 0;
+
+ if(trace > 0 && status != NormalStop)
+ fprint(2, "TID %d: %#x\n", tid, status);
+
+ t = findthread(tid);
+ if(t == nil) {
+ // Sometimes the kernel tells us about new threads
+ // before we see the parent clone.
+ t = attachthread(0, tid, &new, Stopped);
+ if(t == nil) {
+ fprint(2, "failed to attach to new thread %d\n", tid);
+ return -1;
+ }
+ }
+
+ if(WIFSTOPPED(status)) {
+ t->state = Stopped;
+ t->signal = WSTOPSIG(status);
+ if(trace)
+ fprint(2, "tid %d: stopped %#x%s\n", tid, status,
+ status != NormalStop ? " ***" : "");
+ if(t->signal == SIGTRAP && (event = status>>16) != 0) { // ptrace event
+ switch(event) {
+ case PTRACE_EVENT_FORK:
+ t->state = Forking;
+ goto child;
+
+ case PTRACE_EVENT_VFORK:
+ t->state = Vforking;
+ goto child;
+
+ case PTRACE_EVENT_CLONE:
+ t->state = Cloning;
+ goto child;
+
+ child:
+ if(ptrace(PTRACE_GETEVENTMSG, t->tid, 0, &data) < 0) {
+ fprint(2, "ptrace GETEVENTMSG tid %d: %r\n", tid);
+ break;
+ }
+ t->child = data;
+ attachthread(t->pid, t->child, &new, Running);
+ break;
+
+ case PTRACE_EVENT_EXEC:
+ t->state = Execing;
+ break;
+
+ case PTRACE_EVENT_VFORK_DONE:
+ t->state = VforkDone;
+ break;
+
+ case PTRACE_EVENT_EXIT:
+ // We won't see this unless we set PTRACE_O_TRACEEXIT.
+ // The debuggers assume that a read or write on a Map
+ // will fail for a thread that has exited. This event
+ // breaks that assumption. It's not a big deal: we
+ // only lose the ability to see the register state at
+ // the time of exit.
+ if(trace)
+ fprint(2, "tid %d: exiting %#x\n", tid, status);
+ t->state = Exiting;
+ if(ptrace(PTRACE_GETEVENTMSG, t->tid, 0, &data) < 0) {
+ fprint(2, "ptrace GETEVENTMSG tid %d: %r\n", tid);
+ break;
+ }
+ t->exitcode = data;
+ break;
+ }
+ }
+ }
+ if(WIFCONTINUED(status)) {
+ if(trace)
+ fprint(2, "tid %d: continued %#x\n", tid, status);
+ t->state = Running;
+ }
+ if(WIFEXITED(status)) {
+ if(trace)
+ fprint(2, "tid %d: exited %#x\n", tid, status);
+ t->state = Exited;
+ t->exitcode = WEXITSTATUS(status);
+ t->signal = -1;
+ ptrace(PTRACE_DETACH, t->tid, 0, 0);
+ if(trace)
+ fprint(2, "tid %d: detach exited\n", tid);
+ }
+ if(WIFSIGNALED(status)) {
+ if(trace)
+ fprint(2, "tid %d: signaled %#x\n", tid, status);
+ t->state = Exited;
+ t->signal = WTERMSIG(status);
+ t->exitcode = -1;
+ ptrace(PTRACE_DETACH, t->tid, 0, 0);
+ if(trace)
+ fprint(2, "tid %d: detach signaled\n", tid);
+ }
+ return 1;
+}
+
+static int
+waitstop(LinuxThread *t)
+{
+ while(t->state == Running)
+ if(wait1(0) < 0)
+ return -1;
+ return 0;
+}
+
+// Attach to and stop all threads in process pid.
+// Must stop everyone in order to make sure we set
+// the "tell me about new threads" option in every
+// task.
+int
+attachallthreads(int pid)
+{
+ int tid, foundnew, new;
+ char buf[100];
+ DIR *d;
+ struct dirent *de;
+ LinuxThread *t;
+
+ if(pid == 0) {
+ fprint(2, "attachallthreads(0)\n");
+ return -1;
+ }
+
+ pid = realpid(pid);
+
+ snprint(buf, sizeof buf, "/proc/%d/task", pid);
+ if((d = opendir(buf)) == nil) {
+ fprint(2, "opendir %s: %r\n", buf);
+ return -1;
+ }
+
+ // Loop in case new threads are being created right now.
+ // We stop every thread as we find it, so eventually
+ // this has to stop (or the system runs out of procs).
+ do {
+ foundnew = 0;
+ while((de = readdir(d)) != nil) {
+ tid = atoi(de->d_name);
+ if(tid == 0)
+ continue;
+ t = attachthread(pid, tid, &new, Detached);
+ foundnew |= new;
+ if(t)
+ waitstop(t);
+ }
+ rewinddir(d);
+ } while(foundnew);
+ closedir(d);
+
+ return 0;
+}
+
+Map*
+attachproc(int pid, Fhdr *fp)
+{
+ Map *map;
+
+ if(pid == 0) {
+ fprint(2, "attachproc(0)\n");
+ return nil;
+ }
+
+ if(findthread(pid) == nil && attachallthreads(pid) < 0)
+ return nil;
+
+ map = newmap(0, 4);
+ if (!map)
+ return 0;
+ map->pid = pid;
+ if(mach->regsize)
+ setmap(map, -1, 0, mach->regsize, 0, "regs", ptraceregrw);
+// if(mach->fpregsize)
+// setmap(map, -1, mach->regsize, mach->regsize+mach->fpregsize, 0, "fpregs", ptraceregrw);
+ setmap(map, -1, fp->txtaddr, fp->txtaddr+fp->txtsz, fp->txtaddr, "*text", ptracesegrw);
+ setmap(map, -1, fp->dataddr, mach->utop, fp->dataddr, "*data", ptracesegrw);
+ return map;
+}
+
+void
+detachproc(Map *m)
+{
+ LinuxThread *t;
+
+ t = findthread(m->pid);
+ if(t != nil) {
+ ptrace(PTRACE_DETACH, t->tid, 0, 0);
+ t->state = Detached;
+ if(trace)
+ fprint(2, "tid %d: detachproc\n", t->tid);
+ // TODO(rsc): Reclaim thread structs somehow?
+ }
+ free(m);
+}
+
+/* /proc/pid/stat contains
+ pid
+ command in parens
+ 0. state
+ 1. ppid
+ 2. pgrp
+ 3. session
+ 4. tty_nr
+ 5. tpgid
+ 6. flags (math=4, traced=10)
+ 7. minflt
+ 8. cminflt
+ 9. majflt
+ 10. cmajflt
+ 11. utime
+ 12. stime
+ 13. cutime
+ 14. cstime
+ 15. priority
+ 16. nice
+ 17. 0
+ 18. itrealvalue
+ 19. starttime
+ 20. vsize
+ 21. rss
+ 22. rlim
+ 23. startcode
+ 24. endcode
+ 25. startstack
+ 26. kstkesp
+ 27. kstkeip
+ 28. pending signal bitmap
+ 29. blocked signal bitmap
+ 30. ignored signal bitmap
+ 31. caught signal bitmap
+ 32. wchan
+ 33. nswap
+ 34. cnswap
+ 35. exit_signal
+ 36. processor
+*/
+
+static int
+readstat(int pid, char *buf, int nbuf, char **f, int nf)
+{
+ int fd, n;
+ char *p;
+
+ snprint(buf, nbuf, "/proc/%d/stat", pid);
+ if((fd = open(buf, OREAD)) < 0){
+ fprint(2, "open %s: %r\n", buf);
+ return -1;
+ }
+ n = read(fd, buf, nbuf-1);
+ close(fd);
+ if(n <= 0){
+ fprint(2, "read %s: %r\n", buf);
+ return -1;
+ }
+ buf[n] = 0;
+
+ /* command name is in parens, no parens afterward */
+ p = strrchr(buf, ')');
+ if(p == nil || *++p != ' '){
+ fprint(2, "bad format in /proc/%d/stat\n", pid);
+ return -1;
+ }
+ ++p;
+
+ nf = tokenize(p, f, nf);
+ if(0) print("code 0x%lux-0x%lux stack 0x%lux kstk 0x%lux keip 0x%lux pending 0x%lux\n",
+ strtoul(f[23], 0, 0), strtoul(f[24], 0, 0), strtoul(f[25], 0, 0),
+ strtoul(f[26], 0, 0), strtoul(f[27], 0, 0), strtoul(f[28], 0, 0));
+
+ return nf;
+}
+
+static char*
+readstatus(int pid, char *buf, int nbuf, char *key)
+{
+ int fd, n;
+ char *p;
+
+ snprint(buf, nbuf, "/proc/%d/status", pid);
+ if((fd = open(buf, OREAD)) < 0){
+ fprint(2, "open %s: %r\n", buf);
+ return nil;
+ }
+ n = read(fd, buf, nbuf-1);
+ close(fd);
+ if(n <= 0){
+ fprint(2, "read %s: %r\n", buf);
+ return nil;
+ }
+ buf[n] = 0;
+ p = strstr(buf, key);
+ if(p)
+ return p+strlen(key);
+ return nil;
+}
+
+int
+procnotes(int pid, char ***pnotes)
+{
+ char buf[1024], *f[40];
+ int i, n, nf;
+ char *s, **notes;
+ ulong sigs;
+ extern char *_p9sigstr(int, char*);
+
+ *pnotes = nil;
+ nf = readstat(pid, buf, sizeof buf, f, nelem(f));
+ if(nf <= 28)
+ return -1;
+
+ sigs = strtoul(f[28], 0, 0) & ~(1<<SIGCONT);
+ if(sigs == 0){
+ *pnotes = nil;
+ return 0;
+ }
+
+ notes = malloc(32*sizeof(char*));
+ if(notes == nil)
+ return -1;
+ memset(notes, 0, 32*sizeof(char*));
+ n = 0;
+ for(i=0; i<32; i++){
+ if((sigs&(1<<i)) == 0)
+ continue;
+ if((s = _p9sigstr(i, nil)) == nil)
+ continue;
+ notes[n++] = s;
+ }
+ *pnotes = notes;
+ return n;
+}
+
+static int
+realpid(int pid)
+{
+ char buf[1024], *p;
+
+ p = readstatus(pid, buf, sizeof buf, "\nTgid:");
+ if(p == nil)
+ return pid;
+ return atoi(p);
+}
+
+int
+ctlproc(int pid, char *msg)
+{
+ int new;
+ LinuxThread *t;
+ uintptr data;
+
+ while(wait1(1) > 0)
+ ;
+
+ if(strcmp(msg, "attached") == 0){
+ t = attachthread(pid, pid, &new, Attached);
+ if(t == nil)
+ return -1;
+ return 0;
+ }
+
+ if(strcmp(msg, "hang") == 0){
+ if(pid == getpid())
+ return ptrace(PTRACE_TRACEME, 0, 0, 0);
+ werrstr("can only hang self");
+ return -1;
+ }
+
+ t = findthread(pid);
+ if(t == nil) {
+ werrstr("not attached to pid %d", pid);
+ return -1;
+ }
+ if(t->state == Exited) {
+ werrstr("pid %d has exited", pid);
+ return -1;
+ }
+ if(t->state == Killed) {
+ werrstr("pid %d has been killed", pid);
+ return -1;
+ }
+
+ if(strcmp(msg, "kill") == 0) {
+ if(ptrace(PTRACE_KILL, pid, 0, 0) < 0)
+ return -1;
+ t->state = Killed;
+ return 0;
+ }
+ if(strcmp(msg, "startstop") == 0){
+ if(ctlproc(pid, "start") < 0)
+ return -1;
+ return waitstop(t);
+ }
+ if(strcmp(msg, "sysstop") == 0){
+ if(ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
+ return -1;
+ t->state = Running;
+ return waitstop(t);
+ }
+ if(strcmp(msg, "stop") == 0){
+ if(trace > 1)
+ fprint(2, "tid %d: tkill stop\n", pid);
+ if(t->state == Stopped)
+ return 0;
+ if(syscall(__NR_tkill, pid, SIGSTOP) < 0)
+ return -1;
+ return waitstop(t);
+ }
+ if(strcmp(msg, "step") == 0){
+ if(t->state == Running) {
+ werrstr("cannot single-step unstopped %d", pid);
+ return -1;
+ }
+ if(ptrace(PTRACE_SINGLESTEP, pid, 0, 0) < 0)
+ return -1;
+ return waitstop(t);
+ }
+ if(strcmp(msg, "start") == 0) {
+ if(t->state == Running)
+ return 0;
+ data = 0;
+ if(t->state == Stopped && t->signal != SIGSTOP && t->signal != SIGTRAP)
+ data = t->signal;
+ if(trace && data)
+ fprint(2, "tid %d: continue %lud\n", pid, (ulong)data);
+ if(ptrace(PTRACE_CONT, pid, 0, (void*)data) < 0)
+ return -1;
+ t->state = Running;
+ return 0;
+ }
+ if(strcmp(msg, "waitstop") == 0) {
+ return waitstop(t);
+ }
+ werrstr("unknown control message '%s'", msg);
+ return -1;
+}
+
+char*
+proctextfile(int pid)
+{
+ static char buf[1024], pbuf[128];
+
+ snprint(pbuf, sizeof pbuf, "/proc/%d/exe", pid);
+ if(readlink(pbuf, buf, sizeof buf) >= 0)
+ return strdup(buf);
+ if(access(pbuf, AEXIST) >= 0)
+ return strdup(pbuf);
+ return nil;
+}
+
+
+static int
+ptracerw(int type, int xtype, int isr, int pid, uvlong addr, void *v, uint n)
+{
+ int i;
+ uintptr u, a;
+ uchar buf[sizeof(uintptr)];
+
+ for(i=0; i<n; i+=sizeof(uintptr)){
+ // Tread carefully here. On recent versions of glibc,
+ // ptrace is a variadic function which means the third
+ // argument will be pushed onto the stack as a uvlong.
+ // This is fine on amd64 but will not work for 386.
+ // We must convert addr to a uintptr.
+ a = addr+i;
+ if(isr){
+ errno = 0;
+ u = ptrace(type, pid, a, 0);
+ if(errno)
+ goto ptraceerr;
+ if(n-i >= sizeof(uintptr))
+ memmove((char*)v+i, &u, sizeof(uintptr));
+ else{
+ memmove(buf, &u, sizeof u);
+ memmove((char*)v+i, buf, n-i);
+ }
+ }else{
+ if(n-i >= sizeof(uintptr))
+ u = *(uintptr*)((char*)v+i);
+ else{
+ errno = 0;
+ u = ptrace(xtype, pid, a, 0);
+ if(errno)
+ return -1;
+ memmove(buf, &u, sizeof u);
+ memmove(buf, (char*)v+i, n-i);
+ memmove(&u, buf, sizeof u);
+ }
+ if(ptrace(type, pid, a, u) < 0)
+ goto ptraceerr;
+ }
+ }
+ return 0;
+
+ptraceerr:
+ werrstr("ptrace %s addr=%#llux pid=%d: %r", isr ? "read" : "write", addr, pid);
+ return -1;
+}
+
+static int
+ptracesegrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr)
+{
+ return ptracerw(isr ? PTRACE_PEEKDATA : PTRACE_POKEDATA, PTRACE_PEEKDATA,
+ isr, map->pid, addr, v, n);
+}
+
+// If the debugger is compiled as an x86-64 program,
+// then all the ptrace register read/writes are done on
+// a 64-bit register set. If the target program
+// is a 32-bit program, the debugger is expected to
+// read the bottom half of the relevant registers
+// out of the 64-bit set.
+
+// Linux 32-bit is
+// BX CX DX SI DI BP AX DS ES FS GS OrigAX IP CS EFLAGS SP SS
+
+// Linux 64-bit is
+// R15 R14 R13 R12 BP BX R11 R10 R9 R8 AX CX DX SI DI OrigAX IP CS EFLAGS SP SS FSBase GSBase DS ES FS GS
+
+// Go 32-bit is
+// DI SI BP NSP BX DX CX AX GS FS ES DS TRAP ECODE PC CS EFLAGS SP SS
+
+uint go32tolinux32tab[] = {
+ 4, 3, 5, 15, 0, 2, 1, 6, 10, 9, 8, 7, -1, -1, 12, 13, 14, 15, 16
+};
+static int
+go32tolinux32(uvlong addr)
+{
+ int r;
+
+ if(addr%4 || addr/4 >= nelem(go32tolinux32tab))
+ return -1;
+ r = go32tolinux32tab[addr/4];
+ if(r < 0)
+ return -1;
+ return r*4;
+}
+
+uint go32tolinux64tab[] = {
+ 14, 13, 4, 19, 5, 12, 11, 10, 26, 25, 24, 23, -1, -1, 16, 17, 18, 19, 20
+};
+static int
+go32tolinux64(uvlong addr)
+{
+ int r;
+
+ if(addr%4 || addr/4 >= nelem(go32tolinux64tab))
+ return -1;
+ r = go32tolinux64tab[addr/4];
+ if(r < 0)
+ return -1;
+ return r*8;
+}
+
+extern Mach mi386;
+extern Mach mamd64;
+
+static int
+go2linux(uvlong addr)
+{
+ if(sizeof(void*) == 4) {
+ if(mach == &mi386)
+ return go32tolinux32(addr);
+ werrstr("unsupported architecture");
+ return -1;
+ }
+
+ if(mach == &mi386)
+ return go32tolinux64(addr);
+ if(mach != &mamd64) {
+ werrstr("unsupported architecture");
+ return -1;
+ }
+
+ switch(addr){
+ case offsetof(Ureg64, ax):
+ return offsetof(struct user_regs_struct, rax);
+ case offsetof(Ureg64, bx):
+ return offsetof(struct user_regs_struct, rbx);
+ case offsetof(Ureg64, cx):
+ return offsetof(struct user_regs_struct, rcx);
+ case offsetof(Ureg64, dx):
+ return offsetof(struct user_regs_struct, rdx);
+ case offsetof(Ureg64, si):
+ return offsetof(struct user_regs_struct, rsi);
+ case offsetof(Ureg64, di):
+ return offsetof(struct user_regs_struct, rdi);
+ case offsetof(Ureg64, bp):
+ return offsetof(struct user_regs_struct, rbp);
+ case offsetof(Ureg64, r8):
+ return offsetof(struct user_regs_struct, r8);
+ case offsetof(Ureg64, r9):
+ return offsetof(struct user_regs_struct, r9);
+ case offsetof(Ureg64, r10):
+ return offsetof(struct user_regs_struct, r10);
+ case offsetof(Ureg64, r11):
+ return offsetof(struct user_regs_struct, r11);
+ case offsetof(Ureg64, r12):
+ return offsetof(struct user_regs_struct, r12);
+ case offsetof(Ureg64, r13):
+ return offsetof(struct user_regs_struct, r13);
+ case offsetof(Ureg64, r14):
+ return offsetof(struct user_regs_struct, r14);
+ case offsetof(Ureg64, r15):
+ return offsetof(struct user_regs_struct, r15);
+ case offsetof(Ureg64, ds):
+ return offsetof(struct user_regs_struct, ds);
+ case offsetof(Ureg64, es):
+ return offsetof(struct user_regs_struct, es);
+ case offsetof(Ureg64, fs):
+ return offsetof(struct user_regs_struct, fs);
+ case offsetof(Ureg64, gs):
+ return offsetof(struct user_regs_struct, gs);
+ case offsetof(Ureg64, ip):
+ return offsetof(struct user_regs_struct, rip);
+ case offsetof(Ureg64, cs):
+ return offsetof(struct user_regs_struct, cs);
+ case offsetof(Ureg64, flags):
+ return offsetof(struct user_regs_struct, eflags);
+ case offsetof(Ureg64, sp):
+ return offsetof(struct user_regs_struct, rsp);
+ case offsetof(Ureg64, ss):
+ return offsetof(struct user_regs_struct, ss);
+ }
+ return -1;
+}
+
+static int
+ptraceregrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr)
+{
+ int laddr;
+ uvlong u;
+
+ if((laddr = go2linux(addr)) < 0){
+ if(isr){
+ memset(v, 0, n);
+ return 0;
+ }
+ werrstr("register %llud not available", addr);
+ return -1;
+ }
+
+ if(isr){
+ errno = 0;
+ u = ptrace(PTRACE_PEEKUSER, map->pid, laddr, 0);
+ if(errno)
+ goto ptraceerr;
+ switch(n){
+ case 1:
+ *(uint8*)v = u;
+ break;
+ case 2:
+ *(uint16*)v = u;
+ break;
+ case 4:
+ *(uint32*)v = u;
+ break;
+ case 8:
+ *(uint64*)v = u;
+ break;
+ default:
+ werrstr("bad register size");
+ return -1;
+ }
+ }else{
+ switch(n){
+ case 1:
+ u = *(uint8*)v;
+ break;
+ case 2:
+ u = *(uint16*)v;
+ break;
+ case 4:
+ u = *(uint32*)v;
+ break;
+ case 8:
+ u = *(uint64*)v;
+ break;
+ default:
+ werrstr("bad register size");
+ return -1;
+ }
+ if(ptrace(PTRACE_POKEUSER, map->pid, laddr, (void*)(uintptr)u) < 0)
+ goto ptraceerr;
+ }
+ return 0;
+
+ptraceerr:
+ werrstr("ptrace %s register laddr=%d pid=%d n=%d: %r", isr ? "read" : "write", laddr, map->pid, n);
+ return -1;
+}
+
+char*
+procstatus(int pid)
+{
+ LinuxThread *t;
+
+ t = findthread(pid);
+ if(t == nil)
+ return "???";
+
+ return statestr[t->state];
+}