diff options
Diffstat (limited to 'src/libmach/linux.c')
-rw-r--r-- | src/libmach/linux.c | 1010 |
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]; +} |