diff --git a/usr/austin/ptrace/ptrace_linux.go b/usr/austin/ptrace/ptrace_linux.go index e241c5bc81..43a509401a 100644 --- a/usr/austin/ptrace/ptrace_linux.go +++ b/usr/austin/ptrace/ptrace_linux.go @@ -158,7 +158,7 @@ type thread struct { exitStatus int; } -func (p *process) newThread(tid int) (*thread, os.Error) +func (p *process) newThread(tid int, signal int, cloned bool) (*thread, os.Error) /* * Errors @@ -186,6 +186,16 @@ func (e noBreakpointError) String() string { return fmt.Sprintf("no breakpoint at PC %#x", e); } +type newThreadError struct { + *os.Waitmsg; + wantPid int; + wantSig int; +} + +func (e *newThreadError) String() string { + return fmt.Sprintf("newThread wait wanted pid %v and signal %v, got %v and %v", e.Pid, e.StopSignal(), e.wantPid, e.wantSig); +} + /* * Ptrace wrappers */ @@ -218,7 +228,6 @@ func (t *thread) ptraceSetOptions(options int) os.Error { func (t *thread) ptraceGetEventMsg() (uint, os.Error) { msg, err := syscall.PtraceGetEventMsg(t.tid); return msg, os.NewSyscallError("ptrace(GETEVENTMSG)", err); - } func (t *thread) ptraceCont() os.Error { @@ -464,6 +473,8 @@ func (p *process) stopAsync() os.Error { // doTrap handles SIGTRAP debug events with a cause of 0. These can // be caused either by an installed breakpoint, a breakpoint in the // program text, or by single stepping. +// +// TODO(austin) I think we also get this on an execve syscall. func (ev *debugEvent) doTrap() (threadState, os.Error) { t := ev.t; @@ -512,7 +523,7 @@ func (ev *debugEvent) doPtraceClone() (threadState, os.Error) { return stopped, err; } - nt, err := t.proc.newThread(int(tid)); + nt, err := t.proc.newThread(int(tid), syscall.SIGSTOP, true); if err != nil { return stopped, err; } @@ -620,6 +631,9 @@ func (ev *debugEvent) process() os.Error { t.ignoreNextSigstop = true; } + // TODO(austin) If we're in state stopping and get a SIGSTOP, + // set state stopped instead of stoppedSignal. + t.setState(state); if t.proc.someRunningThread() == nil { @@ -1036,6 +1050,8 @@ 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; @@ -1091,20 +1107,28 @@ func (p *process) Detach() os.Error { } // newThread creates a new thread object and waits for its initial -// SIGSTOP. +// signal. If cloned is true, this thread was cloned from a thread we +// are already attached to. // // Must be run from the monitor thread. -func (p *process) newThread(tid int) (*thread, os.Error) { +func (p *process) newThread(tid int, signal int, cloned bool) (*thread, os.Error) { t := &thread{tid: tid, proc: p, state: stopped}; - // Get the SIGSTOP from the thread - // TODO(austin) Thread might already be stopped + // Get the signal from the thread + // TODO(austin) Thread might already be stopped if we're attaching. w, err := os.Wait(tid, syscall.WALL); if err != nil { return nil, err; } - if w.Pid != tid || w.StopSignal() != syscall.SIGSTOP { - return nil, os.EINVAL; + if w.Pid != tid || w.StopSignal() != signal { + return nil, &newThreadError{w, tid, signal}; + } + + if !cloned { + err = t.ptraceSetOptions(syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEEXIT); + if err != nil { + return nil, err; + } } p.threads[tid] = t; @@ -1125,7 +1149,7 @@ func (p *process) attachThread(tid int) (*thread, os.Error) { } var err os.Error; - thr, err = p.newThread(tid); + thr, err = p.newThread(tid, syscall.SIGSTOP, false); return err; }); return thr, err; @@ -1196,8 +1220,8 @@ func (p *process) attachAllThreads() os.Error { return nil; } -// Attach attaches to process pid and stops all of its threads. -func Attach(pid int) (Process, os.Error) { +// newProcess creates a new process object and starts its monitor thread. +func newProcess(pid int) *process { p := &process{ pid: pid, threads: make(map[int] *thread), @@ -1208,10 +1232,15 @@ func Attach(pid int) (Process, os.Error) { transitionHandlers: vector.New(0) }; - // All ptrace calls must be done from the same thread. Start - // the monitor thread now so we can attach from within it. go p.monitor(); + return p; +} + +// Attach attaches to process pid and stops all of its threads. +func Attach(pid int) (Process, os.Error) { + p := newProcess(pid); + // Attach to all threads err := p.attachAllThreads(); if err != nil { @@ -1221,20 +1250,41 @@ func Attach(pid int) (Process, os.Error) { return nil, err; } - // Set ptrace options for all threads - err = p.do(func () os.Error { - for _, t := range p.threads { - err := t.ptraceSetOptions(syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEEXIT); - if err != nil { - return err; - } + return p, nil; +} + +// ForkExec forks the current process and execs argv0, stopping the +// new process after the exec syscall. See os.ForkExec for additional +// details. +func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) + (Process, os.Error) +{ + p := newProcess(-1); + + // Create array of integer (system) fds. + intfd := make([]int, len(fd)); + for i, f := range fd { + if f == nil { + intfd[i] = -1; + } else { + intfd[i] = f.Fd(); } - return nil; + } + + // Fork from the monitor thread so we get the right tracer pid. + err := p.do(func () os.Error { + pid, errno := syscall.PtraceForkExec(argv0, argv, envv, dir, intfd); + if errno != 0 { + return &os.PathError{"fork/exec", argv0, os.Errno(errno)}; + } + p.pid = pid; + + // The process will raise SIGTRAP when it reaches execve. + t, err := p.newThread(pid, syscall.SIGTRAP, false); + return err; }); if err != nil { - p.Detach(); - // TODO(austin) - //p.stopMonitor(err); + p.stopMonitor(err); return nil, err; }