|  | /* | 
|  | * 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; | 
|  | } | 
|  |  |