diff --git a/_fixtures/is sue239.go b/_fixtures/is sue239.go new file mode 100644 index 0000000000000000000000000000000000000000..3dbc4bb76a72c4d48ee3f70139062b29e6137505 --- /dev/null +++ b/_fixtures/is sue239.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" +) + +func fibonacci(n int, c chan int) { + x, y := 0, 1 + for i := 0; i < n; i++ { + c <- x + x, y = y, x+y + } + close(c) +} + +func main() { + a := struct { // set breakpoint here + A string + B int + }{A: "demo", B: 10} + fmt.Printf("%#v\n", a) + + c := make(chan int, 10) + go fibonacci(cap(c), c) + for i := range c { + fmt.Println(i) + } +} diff --git a/proc/proc.go b/proc/proc.go index 284f2eb493665dc65fb83c973b13c3231b442124..b32b4d3a0a17452c5347e90045b65f6a58b0f26e 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -54,6 +54,7 @@ type Process struct { exited bool ptraceChan chan func() ptraceDoneChan chan interface{} + comm string } func New(pid int) *Process { @@ -142,7 +143,8 @@ func (dbp *Process) LoadInformation(path string) error { return err } - wg.Add(3) + wg.Add(4) + go dbp.loadProcessInformation(&wg) go dbp.parseDebugFrame(exe, &wg) go dbp.obtainGoSymbols(exe, &wg) go dbp.parseDebugLineInfo(exe, &wg) @@ -570,7 +572,7 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e if err != nil { return nil, err } - _, _, err = wait(dbp.Pid, dbp.Pid, 0) + _, _, err = dbp.wait(dbp.Pid, 0) if err != nil { return nil, err } diff --git a/proc/proc_darwin.go b/proc/proc_darwin.go index 472d84aab71d51b315a9173cb8d41579bd1211a7..16073f6cba08e6c462015e5def55905a85703f8d 100644 --- a/proc/proc_darwin.go +++ b/proc/proc_darwin.go @@ -272,7 +272,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) { switch port { case dbp.os.notificationPort: - _, status, err := wait(dbp.Pid, dbp.Pid, 0) + _, status, err := dbp.wait(dbp.Pid, 0) if err != nil { return nil, err } @@ -318,7 +318,10 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) { } } -func wait(pid, tgid, options int) (int, *sys.WaitStatus, error) { +func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) { +} + +func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { var status sys.WaitStatus wpid, err := sys.Wait4(pid, &status, options, nil) return wpid, &status, err diff --git a/proc/proc_linux.go b/proc/proc_linux.go index 504ea931ce26453a06429941396b88e4e9c233d0..8a32a367104b94d689e947e1b5e2ba03f8b6a1de 100644 --- a/proc/proc_linux.go +++ b/proc/proc_linux.go @@ -5,10 +5,12 @@ import ( "debug/gosym" "errors" "fmt" + "io/ioutil" "os" "os/exec" "path/filepath" "strconv" + "strings" "sync" "syscall" "time" @@ -51,7 +53,7 @@ func Launch(cmd []string) (*Process, error) { return nil, err } dbp.Pid = proc.Process.Pid - _, _, err = wait(proc.Process.Pid, proc.Process.Pid, 0) + _, _, err = dbp.wait(proc.Process.Pid, 0) if err != nil { return nil, fmt.Errorf("waiting for target execve failed: %s", err) } @@ -73,7 +75,7 @@ func (dbp *Process) Kill() (err error) { if err = sys.Kill(-dbp.Pid, sys.SIGKILL); err != nil { return errors.New("could not deliver signal " + err.Error()) } - if _, _, err = wait(dbp.Pid, dbp.Pid, 0); err != nil { + if _, _, err = dbp.wait(dbp.Pid, 0); err != nil { return } dbp.exited = true @@ -101,7 +103,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) { // if we truly don't have permissions. return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err) } - pid, status, err := wait(tid, dbp.Pid, 0) + pid, status, err := dbp.wait(tid, 0) if err != nil { return nil, err } @@ -112,7 +114,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 { - if _, _, err = wait(tid, dbp.Pid, 0); err != nil { + if _, _, err = dbp.wait(tid, 0); err != nil { return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err) } dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) @@ -239,7 +241,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, dbp.Pid, 0) + wpid, status, err := dbp.wait(pid, 0) if err != nil { return nil, fmt.Errorf("wait err %s %d", err, pid) } @@ -309,25 +311,44 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) { } } -func status(pid int) rune { +func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) { + defer wg.Done() + + comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.Pid)) + if err != nil { + fmt.Printf("Could not read process comm name: %v\n", err) + os.Exit(1) + } + // removes newline character + comm = comm[:len(comm)-1] + dbp.comm = strings.Replace(string(comm), "%", "%%", -1) +} + +func status(pid int, comm string) rune { + // The second field of /proc/pid/stat is the name of the task in parenthesis. + // The name of the task is the base name of the executable for this process limited to 15 characters + // Since both parenthesis and spaces can appear inside the name of the task and no escaping happens we need to read the name of the executable first + // See: include/linux/sched.c:315 and include/linux/sched.c:1510 + f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { return '\000' } + defer f.Close() var ( p int - comm string state rune ) - fmt.Fscanf(f, "%d %s %c", &p, &comm, &state) + + fmt.Fscanf(f, "%d ("+comm+") %c", &p, &state) return state } -func wait(pid, tgid, options int) (int, *sys.WaitStatus, error) { +func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) { var s sys.WaitStatus - if (pid != tgid) || (options != 0) { + if (pid != dbp.Pid) || (options != 0) { wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil) return wpid, &s, err } else { @@ -350,7 +371,7 @@ func wait(pid, tgid, options int) (int, *sys.WaitStatus, error) { if wpid != 0 { return wpid, &s, err } - if status(pid) == STATUS_ZOMBIE { + if status(pid, dbp.comm) == STATUS_ZOMBIE { return pid, nil, nil } time.Sleep(200 * time.Millisecond) diff --git a/proc/proc_test.go b/proc/proc_test.go index 8256e6c07f5a9a9235a711d7ebabeea6beb9c592..b6a09590ec4dbe2872c14a0b863bf1a3e6358859 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -800,3 +800,13 @@ func TestProcessReceivesSIGCHLD(t *testing.T) { } }) } + +func TestIssue239(t *testing.T) { + withTestProcess("is sue239", t, func(p *Process, fixture protest.Fixture) { + pos, _, err := p.goSymTable.LineToPC(fixture.Source, 17) + assertNoError(err, t, "LineToPC()") + _, err = p.SetBreakpoint(pos) + assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%d)", pos)) + assertNoError(p.Continue(), t, fmt.Sprintf("Continue()")) + }) +} diff --git a/proc/threads_linux.go b/proc/threads_linux.go index 53cce505b863f82647e93b6ffa582ae2c900d1f8..d89b22d0cfd3523a333352dea11c01fa9f99e36a 100644 --- a/proc/threads_linux.go +++ b/proc/threads_linux.go @@ -18,7 +18,7 @@ func (t *Thread) halt() (err error) { err = fmt.Errorf("halt err %s on thread %d", err, t.Id) return } - _, _, err = wait(t.Id, t.dbp.Pid, 0) + _, _, err = t.dbp.wait(t.Id, 0) if err != nil { err = fmt.Errorf("wait err %s on thread %d", err, t.Id) return @@ -27,7 +27,7 @@ func (t *Thread) halt() (err error) { } func (thread *Thread) stopped() bool { - state := status(thread.Id) + state := status(thread.Id, thread.dbp.comm) return state == STATUS_TRACE_STOP } @@ -42,7 +42,7 @@ func (t *Thread) singleStep() (err error) { if err != nil { return err } - _, _, err = wait(t.Id, t.dbp.Pid, 0) + _, _, err = t.dbp.wait(t.Id, 0) return err }