| // 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) | |
| { | |
| USED(seg); | |
| 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; | |
| USED(seg); | |
| 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]; | |
| } |