| /* |
| * process interface for Linux. |
| * |
| * Uses ptrace for registers and data, |
| * /proc for some process status. |
| * There's not much point to worrying about |
| * byte order here -- using ptrace means |
| * we're running on the architecture we're debugging, |
| * unless truly weird stuff is going on. |
| * |
| * It is tempting to use /proc/%d/mem along with |
| * the sp and pc in the stat file to get a stack trace |
| * without attaching to the program, but unfortunately |
| * you can't read the mem file unless you've attached. |
| */ |
| |
| #include <u.h> |
| #include <sys/ptrace.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/procfs.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <libc.h> |
| #include <mach.h> |
| #include <elf.h> |
| #include "ureg386.h" |
| |
| Mach *machcpu = &mach386; |
| |
| typedef struct PtraceRegs PtraceRegs; |
| |
| struct PtraceRegs |
| { |
| Regs r; |
| int pid; |
| }; |
| |
| static int ptracesegrw(Map*, Seg*, u64int, void*, uint, int); |
| static int ptraceregrw(Regs*, char*, u64int*, int); |
| |
| 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(ctlproc(pid, "waitstop") < 0){ |
| fprint(2, "waitstop: %r"); |
| ptrace(PTRACE_DETACH, pid, 0, 0); |
| return -1; |
| } |
| attachedpids[nattached++] = pid; |
| return 0; |
| } |
| |
| void |
| unmapproc(Map *map) |
| { |
| int i; |
| |
| if(map == nil) |
| return; |
| for(i=0; i<map->nseg; i++) |
| while(i<map->nseg && map->seg[i].pid){ |
| map->nseg--; |
| memmove(&map->seg[i], &map->seg[i+1], |
| (map->nseg-i)*sizeof(map->seg[0])); |
| } |
| } |
| |
| int |
| mapproc(int pid, Map *map, Regs **rp) |
| { |
| Seg s; |
| PtraceRegs *r; |
| |
| if(ptraceattach(pid) < 0) |
| return -1; |
| |
| memset(&s, 0, sizeof s); |
| s.base = 0; |
| s.size = 0xFFFFFFFF; |
| s.offset = 0; |
| s.name = "data"; |
| s.file = nil; |
| s.rw = ptracesegrw; |
| s.pid = pid; |
| if(addseg(map, s) < 0){ |
| fprint(2, "addseg: %r\n"); |
| return -1; |
| } |
| |
| if((r = mallocz(sizeof(PtraceRegs), 1)) == nil){ |
| fprint(2, "mallocz: %r\n"); |
| return -1; |
| } |
| r->r.rw = ptraceregrw; |
| r->pid = pid; |
| *rp = (Regs*)r; |
| return 0; |
| } |
| |
| int |
| detachproc(int pid) |
| { |
| int i; |
| |
| for(i=0; i<nattached; i++){ |
| if(attachedpids[i] == pid){ |
| attachedpids[i] = attachedpids[--nattached]; |
| break; |
| } |
| } |
| return ptrace(PTRACE_DETACH, pid, 0, 0); |
| } |
| |
| static int |
| ptracerw(int type, int xtype, int isr, int pid, ulong addr, void *v, uint n) |
| { |
| int i; |
| u32int u; |
| uchar buf[4]; |
| |
| for(i=0; i<n; i+=4){ |
| if(isr){ |
| errno = 0; |
| u = ptrace(type, pid, addr+i, 0); |
| if(errno) |
| goto ptraceerr; |
| if(n-i >= 4) |
| *(u32int*)((char*)v+i) = u; |
| else{ |
| memmove(buf, &u, 4); |
| memmove((char*)v+i, buf, n-i); |
| } |
| }else{ |
| if(n-i >= 4) |
| u = *(u32int*)((char*)v+i); |
| else{ |
| errno = 0; |
| u = ptrace(xtype, pid, addr+i, 0); |
| if(errno) |
| return -1; |
| memmove(buf, &u, 4); |
| memmove(buf, (char*)v+i, n-i); |
| memmove(&u, buf, 4); |
| } |
| if(ptrace(type, pid, addr+i, u) < 0) |
| goto ptraceerr; |
| } |
| } |
| return 0; |
| |
| ptraceerr: |
| werrstr("ptrace: %r"); |
| return -1; |
| } |
| |
| static int |
| ptracesegrw(Map *map, Seg *seg, u64int addr, void *v, uint n, int isr) |
| { |
| addr += seg->base; |
| return ptracerw(isr ? PTRACE_PEEKDATA : PTRACE_POKEDATA, PTRACE_PEEKDATA, |
| isr, seg->pid, addr, v, n); |
| } |
| |
| static char* linuxregs[] = { |
| "BX", |
| "CX", |
| "DX", |
| "SI", |
| "DI", |
| "BP", |
| "AX", |
| "DS", |
| "ES", |
| "FS", |
| "GS", |
| "OAX", |
| "PC", |
| "CS", |
| "EFLAGS", |
| "SP", |
| "SS", |
| }; |
| |
| static ulong |
| reg2linux(char *reg) |
| { |
| int i; |
| |
| for(i=0; i<nelem(linuxregs); i++) |
| if(strcmp(linuxregs[i], reg) == 0) |
| return 4*i; |
| return ~(ulong)0; |
| } |
| |
| static int |
| ptraceregrw(Regs *regs, char *name, u64int *val, int isr) |
| { |
| int pid; |
| ulong addr; |
| u32int u; |
| |
| pid = ((PtraceRegs*)regs)->pid; |
| addr = reg2linux(name); |
| if(~addr == 0){ |
| if(isr){ |
| *val = ~(ulong)0; |
| return 0; |
| } |
| werrstr("register not available"); |
| return -1; |
| } |
| if(isr){ |
| errno = 0; |
| u = ptrace(PTRACE_PEEKUSER, pid, addr, 0); |
| if(errno) |
| goto ptraceerr; |
| *val = u; |
| }else{ |
| u = *val; |
| if(ptrace(PTRACE_POKEUSER, pid, addr, (void*)(uintptr)u) < 0) |
| goto ptraceerr; |
| } |
| return 0; |
| |
| ptraceerr: |
| werrstr("ptrace: %r"); |
| return -1; |
| } |
| |
| 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'; |
| } |
| |
| /* /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; |
| } |
| |
| #undef waitpid |
| |
| 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 buf; |
| if(access(pbuf, AEXIST) >= 0) |
| return pbuf; |
| return nil; |
| } |
| |