Fix: Linux - call wait4 on thread grp leader is broken

On a thread that's leader of its group,
that is ptraced and that was survived by its children.
This commit is contained in:
aarzilli 2015-06-26 16:00:34 +02:00 committed by Derek Parker
parent 71fae8f5c6
commit d919114d32
5 changed files with 56 additions and 16 deletions

@ -15,7 +15,7 @@ func sleepytime() {
}
func main() {
for {
for i := 0; i < 500; i++ {
sleepytime()
helloworld()
}

@ -567,7 +567,7 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e
if err != nil {
return nil, err
}
_, _, err = wait(dbp.Pid, 0)
_, _, err = wait(dbp.Pid, dbp.Pid, 0)
if err != nil {
return nil, err
}

@ -226,7 +226,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
switch port {
case dbp.os.notificationPort:
_, status, err := wait(dbp.Pid, 0)
_, status, err := wait(dbp.Pid, dbp.Pid, 0)
if err != nil {
return nil, err
}
@ -267,7 +267,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
return th, nil
}
func wait(pid, options int) (int, *sys.WaitStatus, error) {
func wait(pid, tgid, options int) (int, *sys.WaitStatus, error) {
var status sys.WaitStatus
wpid, err := sys.Wait4(pid, &status, options, nil)
return wpid, &status, err

@ -10,6 +10,7 @@ import (
"strconv"
"sync"
"syscall"
"time"
sys "golang.org/x/sys/unix"
@ -21,6 +22,7 @@ const (
STATUS_SLEEPING = 'S'
STATUS_RUNNING = 'R'
STATUS_TRACE_STOP = 't'
STATUS_ZOMBIE = 'Z'
)
// Not actually needed for Linux.
@ -47,7 +49,7 @@ func Launch(cmd []string) (*Process, error) {
return nil, err
}
dbp.Pid = proc.Process.Pid
_, _, err = wait(proc.Process.Pid, 0)
_, _, err = wait(proc.Process.Pid, proc.Process.Pid, 0)
if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
}
@ -76,7 +78,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
}
pid, status, err := wait(tid, 0)
pid, status, err := wait(tid, dbp.Pid, 0)
if err != nil {
return nil, err
}
@ -88,7 +90,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
if err == syscall.ESRCH {
_, _, err = wait(tid, 0)
_, _, err = wait(tid, dbp.Pid, 0)
if err != nil {
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
}
@ -223,7 +225,7 @@ func (dbp *Process) parseDebugLineInfo(exe *elf.File, wg *sync.WaitGroup) {
func (dbp *Process) trapWait(pid int) (*Thread, error) {
for {
wpid, status, err := wait(pid, 0)
wpid, status, err := wait(pid, dbp.Pid, 0)
if err != nil {
return nil, fmt.Errorf("wait err %s %d", err, pid)
}
@ -271,6 +273,10 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
}
continue
}
if th == nil {
// Sometimes we get an unknown thread, ignore it?
continue
}
if status.StopSignal() == sys.SIGTRAP {
th.running = false
return dbp.handleBreakpointOnThread(wpid)
@ -292,10 +298,10 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
}
}
func stopped(pid int) bool {
func status(pid int) rune {
f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
if err != nil {
return false
return '\000'
}
defer f.Close()
@ -305,14 +311,48 @@ func stopped(pid int) bool {
state rune
)
fmt.Fscanf(f, "%d %s %c", &p, &comm, &state)
return state
}
func stopped(pid int) bool {
state := status(pid)
if state == STATUS_TRACE_STOP {
return true
}
return false
}
func wait(pid, options int) (int, *sys.WaitStatus, error) {
var status sys.WaitStatus
wpid, err := sys.Wait4(pid, &status, sys.WALL|options, nil)
return wpid, &status, err
func wait(pid, tgid, options int) (int, *sys.WaitStatus, error) {
var s sys.WaitStatus
if (pid != tgid) || (options != 0) {
wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
return wpid, &s, err
} else {
// If we call wait4/waitpid on a thread that is the leader of its group,
// with options == 0, while ptracing and the thread leader has exited leaving
// zombies of its own then waitpid hangs forever this is apparently intended
// behaviour in the linux kernel because it's just so convenient.
// Therefore we call wait4 in a loop with WNOHANG, sleeping a while between
// calls and exiting when either wait4 succeeds or we find out that the thread
// has become a zombie.
// References:
// https://sourceware.org/bugzilla/show_bug.cgi?id=12702
// https://sourceware.org/bugzilla/show_bug.cgi?id=10095
// https://sourceware.org/bugzilla/attachment.cgi?id=5685
for {
wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil)
if err != nil {
return 0, nil, err
}
if wpid != 0 {
return wpid, &s, err
}
if status(pid) == STATUS_ZOMBIE {
return pid, nil, nil
}
time.Sleep(200 * time.Millisecond)
}
}
}

@ -20,7 +20,7 @@ func (t *Thread) Halt() error {
if err != nil {
return fmt.Errorf("halt err %s on thread %d", err, t.Id)
}
_, _, err = wait(t.Id, 0)
_, _, err = wait(t.Id, t.dbp.Pid, 0)
if err != nil {
return fmt.Errorf("wait err %s on thread %d", err, t.Id)
}
@ -39,7 +39,7 @@ func (t *Thread) singleStep() (err error) {
if err != nil {
return err
}
_, _, err = wait(t.Id, 0)
_, _, err = wait(t.Id, t.dbp.Pid, 0)
return err
}