blob: 005d6b38824e83a8523b9efc51b968994e58e56b [file] [log] [blame]
/*
* 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;
}