summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr/austin/ptrace/ptrace-nptl.txt3
-rw-r--r--usr/austin/ptrace/ptrace_linux.go39
2 files changed, 33 insertions, 9 deletions
diff --git a/usr/austin/ptrace/ptrace-nptl.txt b/usr/austin/ptrace/ptrace-nptl.txt
index c52640473..62cbf7700 100644
--- a/usr/austin/ptrace/ptrace-nptl.txt
+++ b/usr/austin/ptrace/ptrace-nptl.txt
@@ -115,7 +115,8 @@ don't think the process is done until all of the threads have exited.
Unfortunately, signals cannot be delivered to non-waitable zombies.
Most notably, SIGSTOP cannot be delivered; as a result, when you
broadcast SIGSTOP to all of the threads, you must not wait for
-non-waitable zombies to stop.
+non-waitable zombies to stop. Furthermore, any ptrace command on a
+non-waitable zombie, including PTRACE_DETACH, will return ESRCH.
== Multi-threaded debuggers ==
diff --git a/usr/austin/ptrace/ptrace_linux.go b/usr/austin/ptrace/ptrace_linux.go
index 43a509401..b1e1b3da9 100644
--- a/usr/austin/ptrace/ptrace_linux.go
+++ b/usr/austin/ptrace/ptrace_linux.go
@@ -43,7 +43,21 @@ const (
*/
// Each thread can be in one of the following set of states.
-// Each state satisfies (isRunning() || isStopped() || isTerminal()).
+// Each state satisfies
+// isRunning() || isStopped() || isZombie() || isTerminal().
+//
+// Running threads can be sent signals and must be waited on, but they
+// cannot be inspected using ptrace.
+//
+// Stopped threads can be inspected and continued, but cannot be
+// meaningfully waited on. They can be sent signals, but the signals
+// will be queued until they are running again.
+//
+// Zombie threads cannot be inspected, continued, or sent signals (and
+// therefore they cannot be stopped), but they must be waited on.
+//
+// Terminal threads no longer exist in the OS and thus you can't do
+// anything with them.
type threadState string;
const (
@@ -61,13 +75,17 @@ const (
)
func (ts threadState) isRunning() bool {
- return ts == running || ts == singleStepping || ts == stopping || ts == exiting;
+ return ts == running || ts == singleStepping || ts == stopping;
}
func (ts threadState) isStopped() bool {
return ts == stopped || ts == stoppedBreakpoint || ts == stoppedSignal || ts == stoppedThreadCreate || ts == stoppedExiting;
}
+func (ts threadState) isZombie() bool {
+ return ts == exiting;
+}
+
func (ts threadState) isTerminal() bool {
return ts == exited || ts == detached;
}
@@ -429,7 +447,7 @@ func (t *thread) setState(new threadState) {
t.state = new;
t.logTrace("state %v -> %v", old, new);
- if !old.isRunning() && new.isRunning() {
+ if !old.isRunning() && (new.isRunning() || new.isZombie()) {
// Start waiting on this thread
go t.wait();
}
@@ -1020,7 +1038,11 @@ func (p *process) Continue() os.Error {
if err != nil {
return err;
}
- t.setState(running);
+ if t.state == stoppedExiting {
+ t.setState(exiting);
+ } else {
+ t.setState(running);
+ }
}
return nil;
});
@@ -1050,8 +1072,6 @@ func (p *process) WaitStop() os.Error {
h := &transitionHandler{};
h.handle = func (st *thread, old, new threadState) {
if !new.isRunning() {
- // TODO(austin) This gets stuck on
- // zombie threads.
if p.someRunningThread() == nil {
ready <- nil;
return;
@@ -1094,8 +1114,11 @@ func (p *process) Detach() os.Error {
}
for pid, t := range p.threads {
- if err := t.ptraceDetach(); err != nil {
- return err;
+ if t.state.isStopped() {
+ // We can't detach from zombies.
+ if err := t.ptraceDetach(); err != nil {
+ return err;
+ }
}
t.setState(detached);
p.threads[pid] = nil, false;