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 ==
*/
// 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 (
)
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;
}
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();
}
if err != nil {
return err;
}
- t.setState(running);
+ if t.state == stoppedExiting {
+ t.setState(exiting);
+ } else {
+ t.setState(running);
+ }
}
return nil;
});
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;
}
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;