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:
parent
71fae8f5c6
commit
d919114d32
@ -15,7 +15,7 @@ func sleepytime() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
for {
|
for i := 0; i < 500; i++ {
|
||||||
sleepytime()
|
sleepytime()
|
||||||
helloworld()
|
helloworld()
|
||||||
}
|
}
|
||||||
|
@ -567,7 +567,7 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, _, err = wait(dbp.Pid, 0)
|
_, _, err = wait(dbp.Pid, dbp.Pid, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -226,7 +226,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
|||||||
|
|
||||||
switch port {
|
switch port {
|
||||||
case dbp.os.notificationPort:
|
case dbp.os.notificationPort:
|
||||||
_, status, err := wait(dbp.Pid, 0)
|
_, status, err := wait(dbp.Pid, dbp.Pid, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
|||||||
return th, nil
|
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
|
var status sys.WaitStatus
|
||||||
wpid, err := sys.Wait4(pid, &status, options, nil)
|
wpid, err := sys.Wait4(pid, &status, options, nil)
|
||||||
return wpid, &status, err
|
return wpid, &status, err
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ const (
|
|||||||
STATUS_SLEEPING = 'S'
|
STATUS_SLEEPING = 'S'
|
||||||
STATUS_RUNNING = 'R'
|
STATUS_RUNNING = 'R'
|
||||||
STATUS_TRACE_STOP = 't'
|
STATUS_TRACE_STOP = 't'
|
||||||
|
STATUS_ZOMBIE = 'Z'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Not actually needed for Linux.
|
// Not actually needed for Linux.
|
||||||
@ -47,7 +49,7 @@ func Launch(cmd []string) (*Process, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dbp.Pid = proc.Process.Pid
|
dbp.Pid = proc.Process.Pid
|
||||||
_, _, err = wait(proc.Process.Pid, 0)
|
_, _, err = wait(proc.Process.Pid, proc.Process.Pid, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) })
|
dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
|
||||||
if err == syscall.ESRCH {
|
if err == syscall.ESRCH {
|
||||||
_, _, err = wait(tid, 0)
|
_, _, err = wait(tid, dbp.Pid, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
|
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) {
|
func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
||||||
for {
|
for {
|
||||||
wpid, status, err := wait(pid, 0)
|
wpid, status, err := wait(pid, dbp.Pid, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("wait err %s %d", err, pid)
|
return nil, fmt.Errorf("wait err %s %d", err, pid)
|
||||||
}
|
}
|
||||||
@ -271,6 +273,10 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if th == nil {
|
||||||
|
// Sometimes we get an unknown thread, ignore it?
|
||||||
|
continue
|
||||||
|
}
|
||||||
if status.StopSignal() == sys.SIGTRAP {
|
if status.StopSignal() == sys.SIGTRAP {
|
||||||
th.running = false
|
th.running = false
|
||||||
return dbp.handleBreakpointOnThread(wpid)
|
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))
|
f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return '\000'
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@ -305,14 +311,48 @@ func stopped(pid int) bool {
|
|||||||
state rune
|
state rune
|
||||||
)
|
)
|
||||||
fmt.Fscanf(f, "%d %s %c", &p, &comm, &state)
|
fmt.Fscanf(f, "%d %s %c", &p, &comm, &state)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopped(pid int) bool {
|
||||||
|
state := status(pid)
|
||||||
if state == STATUS_TRACE_STOP {
|
if state == STATUS_TRACE_STOP {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func wait(pid, options int) (int, *sys.WaitStatus, error) {
|
func wait(pid, tgid, options int) (int, *sys.WaitStatus, error) {
|
||||||
var status sys.WaitStatus
|
var s sys.WaitStatus
|
||||||
wpid, err := sys.Wait4(pid, &status, sys.WALL|options, nil)
|
if (pid != tgid) || (options != 0) {
|
||||||
return wpid, &status, err
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("halt err %s on thread %d", err, t.Id)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("wait err %s on thread %d", err, t.Id)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _, err = wait(t.Id, 0)
|
_, _, err = wait(t.Id, t.dbp.Pid, 0)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user