diff options
Diffstat (limited to 'src/libmach_amd64/linux.c')
-rw-r--r-- | src/libmach_amd64/linux.c | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/src/libmach_amd64/linux.c b/src/libmach_amd64/linux.c new file mode 100644 index 000000000..3f66d2f1b --- /dev/null +++ b/src/libmach_amd64/linux.c @@ -0,0 +1,541 @@ +// 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/ptrace.h> +#include <sys/signal.h> +#include <errno.h> +#include <libc.h> +#include <bio.h> +#include <mach_amd64.h> +#include <ureg_amd64.h> +#undef waitpid + +typedef struct Ureg Ureg; + +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; +}; + +static int +isstopped(int pid) +{ + char buf[1024]; + int fd, n; + char *p; + + snprint(buf, sizeof buf, "/proc/%d/stat", pid); + if((fd = open(buf, OREAD)) < 0) + return 0; + n = read(fd, buf, sizeof buf-1); + close(fd); + if(n <= 0) + return 0; + buf[n] = 0; + + /* command name is in parens, no parens afterward */ + p = strrchr(buf, ')'); + if(p == nil || *++p != ' ') + return 0; + ++p; + + /* next is state - T is stopped for tracing */ + return *p == 'T'; +} + +static int +waitstop(int pid) +{ + int p, status; + + if(isstopped(pid)) + return 0; + for(;;){ + p = waitpid(pid, &status, WUNTRACED|__WALL); + if(p <= 0){ + if(errno == ECHILD){ + if(isstopped(pid)) + return 0; + } + return -1; + } + if(WIFEXITED(status) || WIFSTOPPED(status)) + return 0; + } +} + +static int attachedpids[1000]; +static int nattached; + +static int +ptraceattach(int pid) +{ + int i; + + for(i=0; i<nattached; i++) + if(attachedpids[i] == pid) + return 0; + if(nattached == nelem(attachedpids)){ + werrstr("attached to too many processes"); + return -1; + } + + if(ptrace(PTRACE_ATTACH, pid, 0, 0) < 0){ + werrstr("ptrace attach %d: %r", pid); + return -1; + } + + if(waitstop(pid) < 0){ + fprint(2, "waitstop %d: %r", pid); + ptrace(PTRACE_DETACH, pid, 0, 0); + return -1; + } + attachedpids[nattached++] = pid; + return 0; +} + +Map* +attachproc(int pid, Fhdr *fp) +{ + char buf[64]; + Map *map; + vlong n; + + if(ptraceattach(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) +{ + if(m->pid > 0) + ptrace(PTRACE_DETACH, m->pid, 0, 0); + 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 +*/ + +int +procnotes(int pid, char ***pnotes) +{ + char buf[1024], *f[40]; + int fd, i, n, nf; + char *p, *s, **notes; + ulong sigs; + extern char *_p9sigstr(int, char*); + + *pnotes = nil; + snprint(buf, sizeof buf, "/proc/%d/stat", pid); + if((fd = open(buf, OREAD)) < 0){ + fprint(2, "open %s: %r\n", buf); + return -1; + } + n = read(fd, buf, sizeof buf-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, nelem(f)); + 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)); + if(nf <= 28) + return -1; + + sigs = strtoul(f[28], 0, 0) & ~(1<<SIGCONT); + if(sigs == 0){ + *pnotes = nil; + return 0; + } + + notes = mallocz(32*sizeof(char*), 0); + if(notes == nil) + return -1; + 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; +} + +int +ctlproc(int pid, char *msg) +{ + int i, p, status; + + if(strcmp(msg, "attached") == 0){ + for(i=0; i<nattached; i++) + if(attachedpids[i]==pid) + return 0; + if(nattached == nelem(attachedpids)){ + werrstr("attached to too many processes"); + return -1; + } + attachedpids[nattached++] = pid; + return 0; + } + + if(strcmp(msg, "hang") == 0){ + if(pid == getpid()) + return ptrace(PTRACE_TRACEME, 0, 0, 0); + werrstr("can only hang self"); + return -1; + } + if(strcmp(msg, "kill") == 0) + return ptrace(PTRACE_KILL, pid, 0, 0); + if(strcmp(msg, "startstop") == 0){ + if(ptrace(PTRACE_CONT, pid, 0, 0) < 0) + return -1; + goto waitstop; + } + if(strcmp(msg, "sysstop") == 0){ + if(ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) + return -1; + goto waitstop; + } + if(strcmp(msg, "stop") == 0){ + if(kill(pid, SIGSTOP) < 0) + return -1; + goto waitstop; + } + if(strcmp(msg, "step") == 0){ + if(ptrace(PTRACE_SINGLESTEP, pid, 0, 0) < 0) + return -1; + goto waitstop; + } + if(strcmp(msg, "waitstop") == 0){ + waitstop: + if(isstopped(pid)) + return 0; + for(;;){ + p = waitpid(pid, &status, WUNTRACED|__WALL); + if(p <= 0){ + if(errno == ECHILD){ + if(isstopped(pid)) + return 0; + } + return -1; + } +/*fprint(2, "got pid %d status %x\n", pid, status); */ + if(WIFEXITED(status) || WIFSTOPPED(status)) + return 0; + } + } + if(strcmp(msg, "start") == 0) + return ptrace(PTRACE_CONT, pid, 0, 0); + 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; +} + +int +procthreadpids(int pid, int **thread) +{ + int i, fd, nd, *t, nt; + char buf[100]; + Dir *d; + + snprint(buf, sizeof buf, "/proc/%d/task", pid); + if((fd = open(buf, OREAD)) < 0) + return -1; + nd = dirreadall(fd, &d); + close(fd); + if(nd < 0) + return -1; + nt = 0; + for(i=0; i<nd; i++) + if(d[i].mode&DMDIR) + nt++; + t = malloc(nt*sizeof t[0]); + nt = 0; + for(i=0; i<nd; i++) + if(d[i].mode&DMDIR) + t[nt++] = atoi(d[i].name); + *thread = t; + return nt; +} + +static int +ptracerw(int type, int xtype, int isr, int pid, uvlong addr, void *v, uint n) +{ + int i; + uintptr u; + uchar buf[sizeof(uintptr)]; + + for(i=0; i<n; i+=sizeof(uintptr)){ + if(isr){ + errno = 0; + u = ptrace(type, pid, addr+i, 0); + if(errno) + goto ptraceerr; + if(n-i >= sizeof(uintptr)) + *(uintptr*)((char*)v+i) = u; + else{ + *(uintptr*)buf = 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, addr+i, 0); + if(errno) + return -1; + *(uintptr*)buf = u; + memmove(buf, (char*)v+i, n-i); + u = *(uintptr*)buf; + } + if(ptrace(type, pid, addr+i, 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); +} + +static int +go2linux(uvlong addr) +{ + switch(addr){ + case offsetof(Ureg, ax): + return offsetof(struct user_regs_struct, rax); + case offsetof(Ureg, bx): + return offsetof(struct user_regs_struct, rbx); + case offsetof(Ureg, cx): + return offsetof(struct user_regs_struct, rcx); + case offsetof(Ureg, dx): + return offsetof(struct user_regs_struct, rdx); + case offsetof(Ureg, si): + return offsetof(struct user_regs_struct, rsi); + case offsetof(Ureg, di): + return offsetof(struct user_regs_struct, rdi); + case offsetof(Ureg, bp): + return offsetof(struct user_regs_struct, rbp); + case offsetof(Ureg, r8): + return offsetof(struct user_regs_struct, r8); + case offsetof(Ureg, r9): + return offsetof(struct user_regs_struct, r9); + case offsetof(Ureg, r10): + return offsetof(struct user_regs_struct, r10); + case offsetof(Ureg, r11): + return offsetof(struct user_regs_struct, r11); + case offsetof(Ureg, r12): + return offsetof(struct user_regs_struct, r12); + case offsetof(Ureg, r13): + return offsetof(struct user_regs_struct, r13); + case offsetof(Ureg, r14): + return offsetof(struct user_regs_struct, r14); + case offsetof(Ureg, r15): + return offsetof(struct user_regs_struct, r15); + case offsetof(Ureg, ds): + return offsetof(struct user_regs_struct, ds); + case offsetof(Ureg, es): + return offsetof(struct user_regs_struct, es); + case offsetof(Ureg, fs): + return offsetof(struct user_regs_struct, fs); + case offsetof(Ureg, gs): + return offsetof(struct user_regs_struct, gs); + case offsetof(Ureg, ip): + return offsetof(struct user_regs_struct, rip); + case offsetof(Ureg, cs): + return offsetof(struct user_regs_struct, cs); + case offsetof(Ureg, flags): + return offsetof(struct user_regs_struct, eflags); + case offsetof(Ureg, sp): + return offsetof(struct user_regs_struct, rsp); + case offsetof(Ureg, 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: %r", isr ? "read" : "write", laddr, map->pid); + return -1; +} |