Commit 736903c1 authored by Russ Cox's avatar Russ Cox

libmach:

	* heuristic to go farther during stack traces.
	* significantly improved Linux thread handing.

acid:
	* update to new libmach interface.

prof:
	* use new libmach interface.
	* multiple thread support (derived from Rob's copy).
	* first steps toward pprof-like graphs:
	  keep counters indexed by pc,callerpc pairs.

R=r
DELTA=909  (576 added, 123 deleted, 210 changed)
OCL=24240
CL=24259
parent 9aa28f92
...@@ -415,7 +415,7 @@ int ctlproc(int pid, char *msg); ...@@ -415,7 +415,7 @@ int ctlproc(int pid, char *msg);
void detachproc(Map *m); void detachproc(Map *m);
int procnotes(int pid, char ***pnotes); int procnotes(int pid, char ***pnotes);
char* proctextfile(int pid); char* proctextfile(int pid);
int procthreadpids(int pid, int **thread); int procthreadpids(int pid, int *tid, int ntid);
char* procstatus(int); char* procstatus(int);
Maprw fdrw; Maprw fdrw;
...@@ -11,17 +11,16 @@ ...@@ -11,17 +11,16 @@
#include <ureg_amd64.h> #include <ureg_amd64.h>
#include <mach_amd64.h> #include <mach_amd64.h>
int pid;
char* file = "6.out"; char* file = "6.out";
static Fhdr fhdr; static Fhdr fhdr;
int have_syms; int have_syms;
int fd; int fd;
Map *map;
Map *symmap; Map *symmap;
struct Ureg ureg; struct Ureg ureg;
int total_sec = 0; int total_sec = 0;
int delta_msec = 100; int delta_msec = 100;
int collapse = 1; // collapse histogram trace points in same function int nsample;
int nsamplethread;
// output formats // output formats
int functions; // print functions int functions; // print functions
...@@ -30,6 +29,12 @@ int linenums; // print file and line numbers rather than function names ...@@ -30,6 +29,12 @@ int linenums; // print file and line numbers rather than function names
int registers; // print registers int registers; // print registers
int stacks; // print stack traces int stacks; // print stack traces
int pid; // main process pid
int nthread; // number of threads
int thread[32]; // thread pids
Map *map[32]; // thread maps
void void
Usage(void) Usage(void)
{ {
...@@ -40,12 +45,14 @@ Usage(void) ...@@ -40,12 +45,14 @@ Usage(void)
fprint(2, "\t\t-l: dynamic file and line numbers\n"); fprint(2, "\t\t-l: dynamic file and line numbers\n");
fprint(2, "\t\t-r: dynamic registers\n"); fprint(2, "\t\t-r: dynamic registers\n");
fprint(2, "\t\t-s: dynamic function stack traces\n"); fprint(2, "\t\t-s: dynamic function stack traces\n");
fprint(2, "\t\t-hs: include stack info in histograms\n");
exit(2); exit(2);
} }
typedef struct PC PC; typedef struct PC PC;
struct PC { struct PC {
uvlong pc; uvlong pc;
uvlong callerpc;
unsigned int count; unsigned int count;
PC* next; PC* next;
}; };
...@@ -88,20 +95,105 @@ regprint(void) ...@@ -88,20 +95,105 @@ regprint(void)
} }
int int
sample(void) getthreads(void)
{
int i, j, curn, found;
Map *curmap[nelem(map)];
int curthread[nelem(map)];
static int complained = 0;
curn = procthreadpids(pid, curthread, nelem(curthread));
if(curn <= 0)
return curn;
if(curn > nelem(map)) {
if(complained == 0) {
fprint(2, "prof: too many threads; limiting to %d\n", nthread, nelem(map));
complained = 1;
}
curn = nelem(map);
}
if(curn == nthread && memcmp(thread, curthread, curn*sizeof(*thread)) == 0)
return curn; // no changes
// Number of threads has changed (might be the init case).
// A bit expensive but rare enough not to bother being clever.
for(i = 0; i < curn; i++) {
found = 0;
for(j = 0; j < nthread; j++) {
if(curthread[i] == thread[j]) {
found = 1;
curmap[i] = map[j];
map[j] = nil;
break;
}
}
if(found)
continue;
// map new thread
curmap[i] = attachproc(curthread[i], &fhdr);
if(curmap[i] == nil) {
fprint(2, "prof: can't attach to %d: %r\n", curthread[i]);
return -1;
}
}
for(j = 0; j < nthread; j++)
if(map[j] != nil)
detachproc(map[j]);
nthread = curn;
memmove(thread, curthread, nthread*sizeof thread[0]);
memmove(map, curmap, sizeof map);
return nthread;
}
int
sample(Map *map)
{ {
int i; int i;
static int n; static int n;
n++; n++;
for(i = 0; i < sizeof ureg; i+=8) { if(registers) {
if(get8(map, (uvlong)i, &((uvlong*)&ureg)[i/8]) < 0) { for(i = 0; i < sizeof ureg; i+=8) {
if(n == 1) if(get8(map, (uvlong)i, &((uvlong*)&ureg)[i/8]) < 0)
fprint(2, "prof: can't read registers at %d: %r\n", i); goto bad;
return 0;
} }
} else {
// we need only two registers
if(get8(map, offsetof(struct Ureg, ip), (uvlong*)&ureg.ip) < 0)
goto bad;
if(get8(map, offsetof(struct Ureg, sp), (uvlong*)&ureg.sp) < 0)
goto bad;
} }
return 1; return 1;
bad:
if(n == 1)
fprint(2, "prof: can't read registers: %r\n");
return 0;
}
void
addtohistogram(uvlong pc, uvlong callerpc, uvlong sp)
{
int h;
PC *x;
h = (pc + callerpc*101) % Ncounters;
for(x = counters[h]; x != NULL; x = x->next) {
if(x->pc == pc && x->callerpc == callerpc) {
x->count++;
return;
}
}
x = malloc(sizeof(PC));
x->pc = pc;
x->callerpc = callerpc;
x->count = 1;
x->next = counters[h];
counters[h] = x;
} }
uvlong nextpc; uvlong nextpc;
...@@ -114,53 +206,40 @@ xptrace(Map *map, uvlong pc, uvlong sp, Symbol *sym) ...@@ -114,53 +206,40 @@ xptrace(Map *map, uvlong pc, uvlong sp, Symbol *sym)
print("syms\n"); print("syms\n");
return; return;
} }
if(nextpc == 0) if(histograms)
nextpc = sym->value; addtohistogram(nextpc, pc, sp);
print("%s(", sym->name); if(!histograms || stacks > 1) {
print(")"); if(nextpc == 0)
if(nextpc != sym->value) nextpc = sym->value;
print("+%#llux ", nextpc - sym->value); print("%s(", sym->name);
if(have_syms && linenums && fileline(buf, sizeof buf, pc)) { print(")");
print(" %s", buf); if(nextpc != sym->value)
print("+%#llux ", nextpc - sym->value);
if(have_syms && linenums && fileline(buf, sizeof buf, pc)) {
print(" %s", buf);
}
print("\n");
} }
print("\n");
nextpc = pc; nextpc = pc;
} }
void void
stacktracepcsp(uvlong pc, uvlong sp) stacktracepcsp(Map *map, uvlong pc, uvlong sp)
{ {
nextpc = 0; nextpc = pc;
if(machdata->ctrace==nil) if(machdata->ctrace==nil)
fprint(2, "no machdata->ctrace\n"); fprint(2, "no machdata->ctrace\n");
else if(machdata->ctrace(map, pc, sp, 0, xptrace) <= 0) else if(machdata->ctrace(map, pc, sp, 0, xptrace) <= 0)
fprint(2, "no stack frame: pc=%#p sp=%#p\n", pc, sp); fprint(2, "no stack frame: pc=%#p sp=%#p\n", pc, sp);
else else {
print("\n"); addtohistogram(nextpc, 0, sp);
} if(!histograms || stacks > 1)
print("\n");
void
addtohistogram(uvlong pc, uvlong sp)
{
int h;
PC *x;
h = pc % Ncounters;
for(x = counters[h]; x != NULL; x = x->next) {
if(x->pc == pc) {
x->count++;
return;
}
} }
x = malloc(sizeof(PC));
x->pc = pc;
x->count = 1;
x->next = counters[h];
counters[h] = x;
} }
void void
printpc(uvlong pc, uvlong sp) printpc(Map *map, uvlong pc, uvlong sp)
{ {
char buf[1024]; char buf[1024];
if(registers) if(registers)
...@@ -172,117 +251,136 @@ printpc(uvlong pc, uvlong sp) ...@@ -172,117 +251,136 @@ printpc(uvlong pc, uvlong sp)
print("%s\n", buf); print("%s\n", buf);
} }
if(stacks){ if(stacks){
stacktracepcsp(pc, sp); stacktracepcsp(map, pc, sp);
} }
if(histograms){ else if(histograms){
addtohistogram(pc, sp); addtohistogram(pc, 0, sp);
} }
} }
void void
samples(void) samples(void)
{ {
int msec; int i, pid, msec;
struct timespec req; struct timespec req;
req.tv_sec = delta_msec/1000; req.tv_sec = delta_msec/1000;
req.tv_nsec = 1000000*(delta_msec % 1000); req.tv_nsec = 1000000*(delta_msec % 1000);
for(msec = 0; total_sec <= 0 || msec < 1000*total_sec; msec += delta_msec) { for(msec = 0; total_sec <= 0 || msec < 1000*total_sec; msec += delta_msec) {
ctlproc(pid, "stop"); nsample++;
if(!sample()) { nsamplethread += nthread;
for(i = 0; i < nthread; i++) {
pid = thread[i];
if(ctlproc(pid, "stop") < 0)
return;
if(!sample(map[i])) {
ctlproc(pid, "start");
return;
}
printpc(map[i], ureg.ip, ureg.sp);
ctlproc(pid, "start"); ctlproc(pid, "start");
break;
} }
printpc(ureg.ip, ureg.sp);
ctlproc(pid, "start");
nanosleep(&req, NULL); nanosleep(&req, NULL);
getthreads();
if(nthread == 0)
break;
} }
} }
int typedef struct Func Func;
comparepc(const void *va, const void *vb) struct Func
{ {
const PC *const*a = va; Func *next;
const PC *const*b = vb; Symbol s;
return (*a)->pc - (*b)->pc; uint onstack;
} uint leaf;
};
int Func *func[257];
comparecount(const void *va, const void *vb) int nfunc;
Func*
findfunc(uvlong pc)
{ {
const PC *const*a = va; Func *f;
const PC *const*b = vb; uint h;
return (*b)->count - (*a)->count; // sort downwards Symbol s;
if(pc == 0)
return nil;
if(!findsym(pc, CTEXT, &s))
return nil;
h = s.value % nelem(func);
for(f = func[h]; f != NULL; f = f->next)
if(f->s.value == s.value)
return f;
f = mallocz(sizeof *f, 1);
f->s = s;
f->next = func[h];
func[h] = f;
nfunc++;
return f;
} }
void int
func(char *s, int n, uvlong pc) compareleaf(const void *va, const void *vb)
{ {
char *p; Func *a, *b;
symoff(s, n, pc, CANY); a = *(Func**)va;
p = strchr(s, '+'); b = *(Func**)vb;
if(p != NULL) if(a->leaf != b->leaf)
*p = 0; return b->leaf - a->leaf;
if(a->onstack != b->onstack)
return b->onstack - a->onstack;
return strcmp(a->s.name, b->s.name);
} }
void void
dumphistogram() dumphistogram()
{ {
int h; int i, h, n;
PC *x; PC *x;
PC **pcs; Func *f, **ff;
uint i;
uint j;
uint npc;
uint ncount;
char b1[100];
char b2[100];
if(!histograms) if(!histograms)
return; return;
// count samples // assign counts to functions.
ncount = 0; for(h = 0; h < Ncounters; h++) {
npc = 0;
for(h = 0; h < Ncounters; h++)
for(x = counters[h]; x != NULL; x = x->next) { for(x = counters[h]; x != NULL; x = x->next) {
ncount += x->count; f = findfunc(x->pc);
npc++; if(f) {
} f->onstack += x->count;
// build array f->leaf += x->count;
pcs = malloc(npc*sizeof(PC*));
i = 0;
for(h = 0; h < Ncounters; h++)
for(x = counters[h]; x != NULL; x = x->next)
pcs[i++] = x;
if(collapse) {
// combine counts in same function
// sort by address
qsort(pcs, npc, sizeof(PC*), comparepc);
for(i = j = 0; i < npc; i++){
x = pcs[i];
func(b2, sizeof(b2), x->pc);
if(j > 0 && strcmp(b1, b2) == 0) {
pcs[j-1]->count += x->count;
} else {
strcpy(b1, b2);
pcs[j++] = x;
} }
f = findfunc(x->callerpc);
if(f)
f->leaf -= x->count;
} }
npc = j;
} }
// sort by count
qsort(pcs, npc, sizeof(PC*), comparecount); // build array
// print array ff = malloc(nfunc*sizeof ff[0]);
for(i = 0; i < npc; i++){ n = 0;
x = pcs[i]; for(h = 0; h < nelem(func); h++)
print("%5.2f%%\t", 100.0*(double)x->count/(double)ncount); for(f = func[h]; f != NULL; f = f->next)
if(collapse) ff[n++] = f;
func(b2, sizeof b2, x->pc);
else // sort by leaf counts
symoff(b2, sizeof(b2), x->pc, CANY); qsort(ff, nfunc, sizeof ff[0], compareleaf);
print("%s\n", b2);
// print.
print("%d samples (avg %.1g threads)\n", nsample, (double)nsamplethread/nsample);
for(i = 0; i < nfunc; i++) {
f = ff[i];
print("%6.2f%%\t", 100.0*(double)f->leaf/nsample);
if(stacks)
print("%6.2f%%\t", 100.0*(double)f->onstack/nsample);
print("%s\n", f->s.name);
} }
} }
...@@ -313,13 +411,21 @@ startprocess(char **argv) ...@@ -313,13 +411,21 @@ startprocess(char **argv)
return pid; return pid;
} }
void
detach(void)
{
int i;
for(i = 0; i < nthread; i++)
detachproc(map[i]);
}
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
int i;
ARGBEGIN{ ARGBEGIN{
case 'c':
collapse = 0;
break;
case 'd': case 'd':
delta_msec = atoi(EARGF(Usage())); delta_msec = atoi(EARGF(Usage()));
break; break;
...@@ -342,7 +448,7 @@ main(int argc, char *argv[]) ...@@ -342,7 +448,7 @@ main(int argc, char *argv[])
registers = 1; registers = 1;
break; break;
case 's': case 's':
stacks = 1; stacks++;
break; break;
}ARGEND }ARGEND
if(pid <= 0 && argc == 0) if(pid <= 0 && argc == 0)
...@@ -355,18 +461,13 @@ main(int argc, char *argv[]) ...@@ -355,18 +461,13 @@ main(int argc, char *argv[])
} }
if(argc > 0) if(argc > 0)
file = argv[0]; file = argv[0];
else if(pid)
file = proctextfile(pid);
fd = open(file, 0); fd = open(file, 0);
if(fd < 0) { if(fd < 0) {
fprint(2, "prof: can't open %s: %r\n", file); fprint(2, "prof: can't open %s: %r\n", file);
exit(1); exit(1);
} }
if(pid <= 0)
pid = startprocess(argv);
map = attachproc(pid, &fhdr);
if(map == nil) {
fprint(2, "prof: can't attach to %d: %r\n", pid);
exit(1);
}
if(crackhdr(fd, &fhdr)) { if(crackhdr(fd, &fhdr)) {
have_syms = syminit(fd, &fhdr); have_syms = syminit(fd, &fhdr);
if(!have_syms) { if(!have_syms) {
...@@ -376,9 +477,18 @@ main(int argc, char *argv[]) ...@@ -376,9 +477,18 @@ main(int argc, char *argv[])
fprint(2, "prof: crack header for %s: %r\n", file); fprint(2, "prof: crack header for %s: %r\n", file);
exit(1); exit(1);
} }
ctlproc(pid, "start"); if(pid <= 0)
pid = startprocess(argv);
attachproc(pid, &fhdr); // initializes thread list
if(getthreads() <= 0) {
detach();
fprint(2, "prof: can't find threads for pid %d\n", pid);
exit(1);
}
for(i = 0; i < nthread; i++)
ctlproc(thread[i], "start");
samples(); samples();
detachproc(map); detach();
dumphistogram(); dumphistogram();
exit(0); exit(0);
} }
...@@ -145,7 +145,7 @@ static int ...@@ -145,7 +145,7 @@ static int
i386trace(Map *map, uvlong pc, uvlong sp, uvlong link, Tracer trace) i386trace(Map *map, uvlong pc, uvlong sp, uvlong link, Tracer trace)
{ {
int i; int i;
uvlong osp; uvlong osp, pc1;
Symbol s, f, s1; Symbol s, f, s1;
extern Mach mamd64; extern Mach mamd64;
int isamd64; int isamd64;
...@@ -189,19 +189,30 @@ i386trace(Map *map, uvlong pc, uvlong sp, uvlong link, Tracer trace) ...@@ -189,19 +189,30 @@ i386trace(Map *map, uvlong pc, uvlong sp, uvlong link, Tracer trace)
break; break;
} }
s1 = s; s1 = s;
pc1 = 0;
if(pc != s.value) { /* not at first instruction */ if(pc != s.value) { /* not at first instruction */
if(findlocal(&s, FRAMENAME, &f) == 0) if(findlocal(&s, FRAMENAME, &f) == 0)
break; break;
geta(map, sp, &pc1);
sp += f.value-mach->szaddr; sp += f.value-mach->szaddr;
} }
if (geta(map, sp, &pc) < 0) if(geta(map, sp, &pc) < 0)
break; break;
// If PC is not valid, assume we caught the function
// before it moved the stack pointer down or perhaps
// after it moved the stack pointer back up.
// Try the PC we'd have gotten without the stack
// pointer adjustment above (pc != s.value).
// This only matters for the first frame, and it is only
// a heuristic, but it does help.
if(!findsym(pc, CTEXT, &s) || strcmp(s.name, "etext") == 0)
pc = pc1;
if(pc == 0) if(pc == 0)
break; break;
if (pc != retfromnewstack) if(pc != retfromnewstack)
(*trace)(map, pc, sp, &s1); (*trace)(map, pc, sp, &s1);
sp += mach->szaddr; sp += mach->szaddr;
......
...@@ -341,11 +341,10 @@ attachproc(int id, Fhdr *fp) ...@@ -341,11 +341,10 @@ attachproc(int id, Fhdr *fp)
// Return list of ids for threads in id. // Return list of ids for threads in id.
int int
procthreadpids(int id, int **thread) procthreadpids(int id, int *out, int nout)
{ {
Thread *t; Thread *t;
int i, n, pid; int i, n, pid;
int *out;
t = idtotable(id); t = idtotable(id);
if(t == nil) if(t == nil)
...@@ -353,17 +352,13 @@ procthreadpids(int id, int **thread) ...@@ -353,17 +352,13 @@ procthreadpids(int id, int **thread)
pid = t->pid; pid = t->pid;
addpid(pid, 1); // force refresh of thread list addpid(pid, 1); // force refresh of thread list
n = 0; n = 0;
for(i=0; i<nthr; i++) for(i=0; i<nthr; i++) {
if(thr[i].pid == pid) if(thr[i].pid == pid) {
if(n < nout)
out[n] = -(i+1);
n++; n++;
out = malloc(n*sizeof out[0]); }
if(out == nil) }
return -1;
n = 0;
for(i=0; i<nthr; i++)
if(thr[i].pid == pid)
out[n++] = -(i+1);
*thread = out;
return n; return n;
} }
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <u.h> #include <u.h>
#include <sys/syscall.h> /* for tkill */ #include <sys/syscall.h> /* for tkill */
#include <unistd.h> #include <unistd.h>
#include <dirent.h>
#include <sys/ptrace.h> #include <sys/ptrace.h>
#include <sys/signal.h> #include <sys/signal.h>
#include <sys/wait.h> #include <sys/wait.h>
...@@ -55,96 +56,364 @@ struct user_regs_struct { ...@@ -55,96 +56,364 @@ struct user_regs_struct {
unsigned long ds,es,fs,gs; unsigned long ds,es,fs,gs;
}; };
// return pid's state letter or -1 on error. // Linux gets very upset if a debugger forgets the reported state
// set *tpid to tracer pid // of a debugged process, so we keep everything we know about
static int // a debugged process in the LinuxThread structure.
procstate(int pid, int *tpid) //
// 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
{ {
char buf[1024]; int pid;
int fd, n; int tid;
char *p; int state;
int signal;
int child;
int exitcode;
};
snprint(buf, sizeof buf, "/proc/%d/stat", pid); static int trace = 0;
if((fd = open(buf, OREAD)) < 0)
return -1;
n = read(fd, buf, sizeof buf-1);
close(fd);
if(n <= 0)
return -1;
buf[n] = 0;
/* command name is in parens, no parens afterward */ static LinuxThread **thr;
p = strrchr(buf, ')'); static int nthr;
if(p == nil || *++p != ' ') static int mthr;
return -1;
++p; 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;
thr[nthr++] = t;
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 |
PTRACE_O_TRACEEXIT;
if(ptrace(PTRACE_SETOPTIONS, tid, 0, (void*)flags) < 0) {
fprint(2, "ptrace PTRACE_SETOPTIONS %d: %r\n", tid);
return nil;
}
t->state = Stopped;
}
/* p is now state letter. p+1 is tracer pid */ return t;
if(tpid)
*tpid = atoi(p+1);
return *p;
} }
static int static LinuxThread*
attached(int pid) findthread(int tid)
{
return attachthread(0, tid, nil, 0);
}
int
procthreadpids(int pid, int *p, int np)
{ {
int tpid; int i, n;
LinuxThread *t;
return procstate(pid, &tpid) == 'T' && tpid == pid; 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 static int
waitstop(int pid) wait1(int nohang)
{ {
int p, status; int tid, new, status, event;
ulong data;
p = procstate(pid, nil); LinuxThread *t;
if(p < 0) enum
{
NormalStop = 0x137f
};
if(nohang != 0)
nohang = WNOHANG;
tid = waitpid(-1, &status, __WALL|WUNTRACED|WSTOPPED|WCONTINUED|nohang);
if(tid < 0)
return -1; return -1;
if(p == 'T') if(tid == 0)
return 0; return 0;
for(;;){ if(trace > 0 && status != NormalStop)
p = waitpid(pid, &status, WUNTRACED|__WALL); fprint(2, "TID %d: %#x\n", tid, status);
if(p <= 0){
if(errno == ECHILD){ // If we've not heard of this tid, something is wrong.
if(procstate(pid, nil) == 'T') t = findthread(tid);
return 0; if(t == nil) {
fprint(2, "ptrace waitpid: unexpected new tid %d status %#x\n", tid, status);
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);
if(!new)
fprint(2, "ptrace child: not new\n");
break;
case PTRACE_EVENT_EXEC:
t->state = Execing;
break;
case PTRACE_EVENT_VFORK_DONE:
t->state = VforkDone;
break;
case PTRACE_EVENT_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;
} }
return -1;
} }
if(WIFEXITED(status) || WIFSTOPPED(status))
return 0;
} }
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 attachedpids[1000];
static int nattached;
static int static int
ptraceattach(int pid) waitstop(LinuxThread *t)
{ {
int i; while(t->state == Running)
if(wait1(0) < 0)
return -1;
return 0;
}
for(i=0; i<nattached; i++) // Attach to and stop all threads in process pid.
if(attachedpids[i] == pid) // Must stop everyone in order to make sure we set
return 0; // the "tell me about new threads" option in every
if(nattached == nelem(attachedpids)){ // task.
werrstr("attached to too many processes"); int
return -1; attachallthreads(int pid)
} {
int tid, foundnew, new;
char buf[100];
DIR *d;
struct dirent *de;
LinuxThread *t;
if(!attached(pid) && ptrace(PTRACE_ATTACH, pid, 0, 0) < 0){ if(pid == 0) {
werrstr("ptrace attach %d: %r", pid); fprint(2, "attachallthreads(0)\n");
return -1; return -1;
} }
if(waitstop(pid) < 0){ pid = realpid(pid);
fprint(2, "waitstop %d: %r", pid);
ptrace(PTRACE_DETACH, pid, 0, 0); snprint(buf, sizeof buf, "/proc/%d/task", pid);
if((d = opendir(buf)) == nil) {
fprint(2, "opendir %s: %r\n", buf);
return -1; return -1;
} }
attachedpids[nattached++] = pid;
// 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; return 0;
} }
...@@ -153,7 +422,12 @@ attachproc(int pid, Fhdr *fp) ...@@ -153,7 +422,12 @@ attachproc(int pid, Fhdr *fp)
{ {
Map *map; Map *map;
if(ptraceattach(pid) < 0) if(pid == 0) {
fprint(2, "attachproc(0)\n");
return nil;
}
if(findthread(pid) == nil && attachallthreads(pid) < 0)
return nil; return nil;
map = newmap(0, 4); map = newmap(0, 4);
...@@ -172,8 +446,16 @@ attachproc(int pid, Fhdr *fp) ...@@ -172,8 +446,16 @@ attachproc(int pid, Fhdr *fp)
void void
detachproc(Map *m) detachproc(Map *m)
{ {
if(m->pid > 0) LinuxThread *t;
ptrace(PTRACE_DETACH, m->pid, 0, 0);
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); free(m);
} }
...@@ -219,22 +501,18 @@ detachproc(Map *m) ...@@ -219,22 +501,18 @@ detachproc(Map *m)
36. processor 36. processor
*/ */
int static int
procnotes(int pid, char ***pnotes) readstat(int pid, char *buf, int nbuf, char **f, int nf)
{ {
char buf[1024], *f[40]; int fd, n;
int fd, i, n, nf; char *p;
char *p, *s, **notes;
ulong sigs;
extern char *_p9sigstr(int, char*);
*pnotes = nil; snprint(buf, nbuf, "/proc/%d/stat", pid);
snprint(buf, sizeof buf, "/proc/%d/stat", pid);
if((fd = open(buf, OREAD)) < 0){ if((fd = open(buf, OREAD)) < 0){
fprint(2, "open %s: %r\n", buf); fprint(2, "open %s: %r\n", buf);
return -1; return -1;
} }
n = read(fd, buf, sizeof buf-1); n = read(fd, buf, nbuf-1);
close(fd); close(fd);
if(n <= 0){ if(n <= 0){
fprint(2, "read %s: %r\n", buf); fprint(2, "read %s: %r\n", buf);
...@@ -250,10 +528,49 @@ procnotes(int pid, char ***pnotes) ...@@ -250,10 +528,49 @@ procnotes(int pid, char ***pnotes)
} }
++p; ++p;
nf = tokenize(p, f, nelem(f)); 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", 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[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)); 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) if(nf <= 28)
return -1; return -1;
...@@ -278,20 +595,31 @@ procnotes(int pid, char ***pnotes) ...@@ -278,20 +595,31 @@ procnotes(int pid, char ***pnotes)
return n; 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 int
ctlproc(int pid, char *msg) ctlproc(int pid, char *msg)
{ {
int i; int new;
LinuxThread *t;
uintptr data;
while(wait1(1) > 0)
;
if(strcmp(msg, "attached") == 0){ if(strcmp(msg, "attached") == 0){
for(i=0; i<nattached; i++) t = attachthread(pid, pid, &new, Attached);
if(attachedpids[i]==pid) if(t == nil)
return 0;
if(nattached == nelem(attachedpids)){
werrstr("attached to too many processes");
return -1; return -1;
}
attachedpids[nattached++] = pid;
return 0; return 0;
} }
...@@ -301,32 +629,72 @@ ctlproc(int pid, char *msg) ...@@ -301,32 +629,72 @@ ctlproc(int pid, char *msg)
werrstr("can only hang self"); werrstr("can only hang self");
return -1; return -1;
} }
if(strcmp(msg, "kill") == 0)
return ptrace(PTRACE_KILL, pid, 0, 0); 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(strcmp(msg, "startstop") == 0){
if(ptrace(PTRACE_CONT, pid, 0, 0) < 0) if(ctlproc(pid, "start") < 0)
return -1; return -1;
return waitstop(pid); return waitstop(t);
} }
if(strcmp(msg, "sysstop") == 0){ if(strcmp(msg, "sysstop") == 0){
if(ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) if(ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
return -1; return -1;
return waitstop(pid); t->state = Running;
return waitstop(t);
} }
if(strcmp(msg, "stop") == 0){ 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) if(syscall(__NR_tkill, pid, SIGSTOP) < 0)
return -1; return -1;
return waitstop(pid); return waitstop(t);
} }
if(strcmp(msg, "step") == 0){ 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) if(ptrace(PTRACE_SINGLESTEP, pid, 0, 0) < 0)
return -1; return -1;
return waitstop(pid); return waitstop(t);
}
if(strcmp(msg, "start") == 0) {
if(t->state == Running)
return 0;
data = 0;
if(t->state == Stopped && t->signal != SIGSTOP)
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);
} }
if(strcmp(msg, "waitstop") == 0)
return waitstop(pid);
if(strcmp(msg, "start") == 0)
return ptrace(PTRACE_CONT, pid, 0, 0);
werrstr("unknown control message '%s'", msg); werrstr("unknown control message '%s'", msg);
return -1; return -1;
} }
...@@ -344,32 +712,6 @@ proctextfile(int pid) ...@@ -344,32 +712,6 @@ proctextfile(int pid)
return nil; 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 static int
ptracerw(int type, int xtype, int isr, int pid, uvlong addr, void *v, uint n) ptracerw(int type, int xtype, int isr, int pid, uvlong addr, void *v, uint n)
...@@ -544,19 +886,11 @@ ptraceerr: ...@@ -544,19 +886,11 @@ ptraceerr:
char* char*
procstatus(int pid) procstatus(int pid)
{ {
int c; LinuxThread *t;
c = procstate(pid, nil); t = findthread(pid);
if(c < 0) if(t == nil)
return "Dead"; return "???";
switch(c) {
case 'T': return statestr[t->state];
return "Stopped";
case 'Z':
return "Zombie";
case 'R':
return "Running";
// TODO: translate more characters here
}
return "Running";
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment