2014-05-21 15:23:14 +00:00
|
|
|
// Package proctl provides functions for attaching to and manipulating
|
|
|
|
// a process during the debug session.
|
2014-05-20 18:23:35 +00:00
|
|
|
package proctl
|
|
|
|
|
|
|
|
import (
|
2014-05-23 14:42:06 +00:00
|
|
|
"debug/gosym"
|
2014-05-20 18:23:35 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2014-10-18 01:34:58 +00:00
|
|
|
"os/exec"
|
2014-08-06 16:14:23 +00:00
|
|
|
"sync"
|
2014-05-20 18:23:35 +00:00
|
|
|
"syscall"
|
2014-11-02 18:49:21 +00:00
|
|
|
"time"
|
2014-06-29 16:52:21 +00:00
|
|
|
|
2014-10-15 14:21:46 +00:00
|
|
|
"github.com/derekparker/delve/dwarf/frame"
|
|
|
|
"github.com/derekparker/delve/vendor/elf"
|
2014-05-20 18:23:35 +00:00
|
|
|
)
|
|
|
|
|
2014-05-21 15:23:14 +00:00
|
|
|
// Struct representing a debugged process. Holds onto pid, register values,
|
|
|
|
// process struct and process state.
|
2014-05-20 18:23:35 +00:00
|
|
|
type DebuggedProcess struct {
|
2014-10-18 01:34:58 +00:00
|
|
|
Pid int
|
|
|
|
Process *os.Process
|
|
|
|
Executable *elf.File
|
|
|
|
Symbols []elf.Symbol
|
|
|
|
GoSymTable *gosym.Table
|
|
|
|
FrameEntries *frame.FrameDescriptionEntries
|
2014-10-25 14:17:05 +00:00
|
|
|
BreakPoints map[uint64]*BreakPoint
|
2014-10-18 01:34:58 +00:00
|
|
|
Threads map[int]*ThreadContext
|
|
|
|
CurrentThread *ThreadContext
|
2014-05-24 16:22:06 +00:00
|
|
|
}
|
|
|
|
|
2014-10-25 14:17:05 +00:00
|
|
|
// Represents a single breakpoint. Stores information on the break
|
|
|
|
// point including the byte of data that originally was stored at that
|
|
|
|
// address.
|
|
|
|
type BreakPoint struct {
|
|
|
|
FunctionName string
|
|
|
|
File string
|
|
|
|
Line int
|
|
|
|
Addr uint64
|
|
|
|
OriginalData []byte
|
2014-11-08 05:44:24 +00:00
|
|
|
temp bool
|
2014-10-25 14:17:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type BreakPointExistsError struct {
|
|
|
|
file string
|
|
|
|
line int
|
|
|
|
addr uintptr
|
|
|
|
}
|
|
|
|
|
2014-11-02 18:49:21 +00:00
|
|
|
// ProcessStatus is the result of parsing the data from
|
|
|
|
// the /proc/<pid>/stats psuedo file.
|
|
|
|
type ProcessStatus struct {
|
|
|
|
pid int
|
|
|
|
comm string
|
|
|
|
state rune
|
|
|
|
ppid int
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
STATUS_SLEEPING = 'S'
|
|
|
|
STATUS_RUNNING = 'R'
|
|
|
|
STATUS_TRACE_STOP = 't'
|
|
|
|
)
|
|
|
|
|
|
|
|
func parseProcessStatus(pid int) (*ProcessStatus, error) {
|
|
|
|
var ps ProcessStatus
|
|
|
|
|
|
|
|
f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
fmt.Fscanf(f, "%d %s %c %d", &ps.pid, &ps.comm, &ps.state, &ps.ppid)
|
|
|
|
|
|
|
|
return &ps, nil
|
|
|
|
}
|
|
|
|
|
2014-10-25 14:17:05 +00:00
|
|
|
func (bpe BreakPointExistsError) Error() string {
|
|
|
|
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
func AttachBinary(name string) (*DebuggedProcess, error) {
|
|
|
|
proc := exec.Command(name)
|
|
|
|
proc.Stdout = os.Stdout
|
2014-05-20 18:23:35 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
err := proc.Start()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-07-30 00:00:24 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
dbgproc, err := NewDebugProcess(proc.Process.Pid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-15 20:43:14 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
return dbgproc, nil
|
2014-07-11 19:52:55 +00:00
|
|
|
}
|
|
|
|
|
2014-05-21 15:23:14 +00:00
|
|
|
// Returns a new DebuggedProcess struct with sensible defaults.
|
2014-05-20 18:23:35 +00:00
|
|
|
func NewDebugProcess(pid int) (*DebuggedProcess, error) {
|
2014-10-27 23:10:45 +00:00
|
|
|
dbp := DebuggedProcess{
|
2014-10-25 14:17:05 +00:00
|
|
|
Pid: pid,
|
|
|
|
Threads: make(map[int]*ThreadContext),
|
|
|
|
BreakPoints: make(map[uint64]*BreakPoint),
|
2014-05-20 18:23:35 +00:00
|
|
|
}
|
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
thread, err := dbp.AttachThread(pid)
|
2014-05-20 18:23:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-10-27 23:10:45 +00:00
|
|
|
dbp.CurrentThread = thread
|
2014-05-20 18:23:35 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
proc, err := os.FindProcess(pid)
|
2014-05-20 18:23:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
dbp.Process = proc
|
|
|
|
err = dbp.LoadInformation()
|
2014-05-23 14:42:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
// Attach to all currently active threads.
|
2014-10-18 01:34:58 +00:00
|
|
|
for _, tid := range threadIds(pid) {
|
2014-10-27 23:10:45 +00:00
|
|
|
_, err := dbp.AttachThread(tid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
2014-06-29 16:52:21 +00:00
|
|
|
}
|
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
return &dbp, nil
|
2014-05-23 14:42:06 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) {
|
|
|
|
if thread, ok := dbp.Threads[tid]; ok {
|
|
|
|
return thread, nil
|
2014-07-11 19:52:55 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
err := syscall.PtraceAttach(tid)
|
2014-10-27 22:53:11 +00:00
|
|
|
if err != nil && err != syscall.EPERM {
|
|
|
|
// Do not return err if err == EPERM,
|
|
|
|
// we may already be tracing this thread due to
|
|
|
|
// PTRACE_O_TRACECLONE. We will surely blow up later
|
|
|
|
// if we truly don't have permissions.
|
|
|
|
return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
|
|
|
|
}
|
2014-05-27 18:33:49 +00:00
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
var status syscall.WaitStatus
|
2014-10-27 22:53:11 +00:00
|
|
|
pid, e := syscall.Wait4(tid, &status, syscall.WALL, nil)
|
|
|
|
if e != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if status.Exited() {
|
|
|
|
return nil, fmt.Errorf("thread already exited %d", pid)
|
2014-05-27 18:33:49 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
return dbp.addThread(tid)
|
2014-05-27 18:33:49 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
|
|
|
err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE)
|
2014-10-27 23:10:45 +00:00
|
|
|
if err == syscall.ESRCH {
|
|
|
|
_, err = syscall.Wait4(tid, nil, syscall.WALL, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
|
2014-05-27 15:51:38 +00:00
|
|
|
}
|
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err)
|
2014-05-29 15:27:03 +00:00
|
|
|
}
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
2014-05-29 16:18:28 +00:00
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
dbp.Threads[tid] = &ThreadContext{
|
2014-10-25 14:17:05 +00:00
|
|
|
Id: tid,
|
|
|
|
Process: dbp,
|
|
|
|
Regs: new(syscall.PtraceRegs),
|
2014-05-27 15:43:47 +00:00
|
|
|
}
|
|
|
|
|
2014-10-27 23:10:45 +00:00
|
|
|
return dbp.Threads[tid], nil
|
2014-05-20 18:23:35 +00:00
|
|
|
}
|
2014-05-20 18:23:36 +00:00
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
// Sets a breakpoint in the current thread.
|
2014-10-25 14:17:05 +00:00
|
|
|
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
|
2014-11-08 05:44:24 +00:00
|
|
|
return dbp.CurrentThread.Break(addr)
|
2014-10-25 14:17:05 +00:00
|
|
|
}
|
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
// Clears a breakpoint in the current thread.
|
2014-10-25 14:17:05 +00:00
|
|
|
func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
|
2014-11-08 05:44:24 +00:00
|
|
|
return dbp.CurrentThread.Clear(pc)
|
2014-10-25 14:17:05 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
// Returns the status of the current main thread context.
|
|
|
|
func (dbp *DebuggedProcess) Status() *syscall.WaitStatus {
|
|
|
|
return dbp.CurrentThread.Status
|
|
|
|
}
|
2014-09-05 19:19:03 +00:00
|
|
|
|
2014-11-08 13:30:22 +00:00
|
|
|
// Loop through all threads, printing their information
|
|
|
|
// to the console.
|
2014-11-08 05:55:25 +00:00
|
|
|
func (dbp *DebuggedProcess) PrintThreadInfo() error {
|
|
|
|
for _, th := range dbp.Threads {
|
|
|
|
if err := th.PrintInfo(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
// Finds the executable from /proc/<pid>/exe and then
|
|
|
|
// uses that to parse the following information:
|
|
|
|
// * Dwarf .debug_frame section
|
|
|
|
// * Dwarf .debug_line section
|
|
|
|
// * Go symbol table.
|
|
|
|
func (dbp *DebuggedProcess) LoadInformation() error {
|
|
|
|
var (
|
|
|
|
wg sync.WaitGroup
|
|
|
|
err error
|
|
|
|
)
|
2014-09-17 03:37:48 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
err = dbp.findExecutable()
|
2014-09-17 03:37:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
wg.Add(2)
|
|
|
|
go dbp.parseDebugFrame(&wg)
|
|
|
|
go dbp.obtainGoSymbols(&wg)
|
2014-10-07 17:25:33 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
wg.Wait()
|
2014-07-21 23:20:16 +00:00
|
|
|
|
2014-09-17 03:37:48 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
// Steps through process.
|
|
|
|
func (dbp *DebuggedProcess) Step() (err error) {
|
|
|
|
for _, thread := range dbp.Threads {
|
|
|
|
err := thread.Step()
|
2014-10-11 22:29:10 +00:00
|
|
|
if err != nil {
|
2014-10-18 01:34:58 +00:00
|
|
|
if _, ok := err.(ProcessExitedError); !ok {
|
2014-10-13 13:56:42 +00:00
|
|
|
return err
|
2014-10-11 22:29:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
// Step over function calls.
|
|
|
|
func (dbp *DebuggedProcess) Next() error {
|
|
|
|
for _, thread := range dbp.Threads {
|
|
|
|
err := thread.Next()
|
2014-11-08 05:44:24 +00:00
|
|
|
if err != nil {
|
|
|
|
if _, ok := err.(ProcessExitedError); !ok {
|
|
|
|
return err
|
|
|
|
}
|
2014-10-07 17:25:33 +00:00
|
|
|
}
|
2014-09-25 22:20:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
// Continue process until next breakpoint.
|
|
|
|
func (dbp *DebuggedProcess) Continue() error {
|
|
|
|
for _, thread := range dbp.Threads {
|
|
|
|
err := thread.Continue()
|
2014-10-07 19:32:22 +00:00
|
|
|
if err != nil {
|
2014-10-18 01:34:58 +00:00
|
|
|
return err
|
2014-10-07 19:32:22 +00:00
|
|
|
}
|
2014-07-30 00:00:24 +00:00
|
|
|
}
|
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
_, _, err := trapWait(dbp, -1, 0)
|
2014-07-30 00:00:24 +00:00
|
|
|
if err != nil {
|
2014-10-18 01:34:58 +00:00
|
|
|
if _, ok := err.(ProcessExitedError); !ok {
|
|
|
|
return err
|
2014-08-04 20:21:35 +00:00
|
|
|
}
|
2014-08-04 18:25:00 +00:00
|
|
|
}
|
2014-07-30 00:00:24 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
return nil
|
2014-08-04 18:25:00 +00:00
|
|
|
}
|
2014-07-30 00:00:24 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
// Obtains register values from the debugged process.
|
|
|
|
func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
|
|
|
|
return dbp.CurrentThread.Registers()
|
2014-08-04 18:53:15 +00:00
|
|
|
}
|
2014-09-30 13:49:30 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
type InvalidAddressError struct {
|
|
|
|
address uintptr
|
2014-08-04 20:41:09 +00:00
|
|
|
}
|
2014-08-04 18:53:15 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
func (iae InvalidAddressError) Error() string {
|
|
|
|
return fmt.Sprintf("Invalid address %#v\n", iae.address)
|
2014-08-04 20:21:35 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
|
|
|
|
return dbp.CurrentThread.CurrentPC()
|
2014-07-30 00:00:24 +00:00
|
|
|
}
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
// Returns the value of the named symbol.
|
|
|
|
func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) {
|
|
|
|
return dbp.CurrentThread.EvalSymbol(name)
|
2014-05-20 18:23:36 +00:00
|
|
|
}
|
2014-05-23 14:42:06 +00:00
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) findExecutable() error {
|
|
|
|
procpath := fmt.Sprintf("/proc/%d/exe", dbp.Pid)
|
|
|
|
|
2014-09-13 17:28:46 +00:00
|
|
|
f, err := os.OpenFile(procpath, 0, os.ModePerm)
|
2014-05-23 14:42:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
elffile, err := elf.NewFile(f)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dbp.Executable = elffile
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-08-06 16:14:23 +00:00
|
|
|
func (dbp *DebuggedProcess) parseDebugFrame(wg *sync.WaitGroup) {
|
|
|
|
defer wg.Done()
|
|
|
|
|
2014-06-29 16:52:21 +00:00
|
|
|
debugFrame, err := dbp.Executable.Section(".debug_frame").Data()
|
|
|
|
if err != nil {
|
2014-08-06 16:14:23 +00:00
|
|
|
fmt.Println("could not get .debug_frame section", err)
|
|
|
|
os.Exit(1)
|
2014-06-29 16:52:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dbp.FrameEntries = frame.Parse(debugFrame)
|
|
|
|
}
|
|
|
|
|
2014-08-06 16:14:23 +00:00
|
|
|
func (dbp *DebuggedProcess) obtainGoSymbols(wg *sync.WaitGroup) {
|
|
|
|
defer wg.Done()
|
|
|
|
|
2014-08-07 22:57:58 +00:00
|
|
|
var (
|
|
|
|
symdat []byte
|
|
|
|
pclndat []byte
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
if sec := dbp.Executable.Section(".gosymtab"); sec != nil {
|
|
|
|
symdat, err = sec.Data()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("could not get .gosymtab section", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2014-05-23 14:42:06 +00:00
|
|
|
}
|
|
|
|
|
2014-08-07 22:57:58 +00:00
|
|
|
if sec := dbp.Executable.Section(".gopclntab"); sec != nil {
|
|
|
|
pclndat, err = sec.Data()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("could not get .gopclntab section", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2014-05-23 14:42:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pcln := gosym.NewLineTable(pclndat, dbp.Executable.Section(".text").Addr)
|
|
|
|
tab, err := gosym.NewTable(symdat, pcln)
|
|
|
|
if err != nil {
|
2014-08-06 16:14:23 +00:00
|
|
|
fmt.Println("could not get initialize line table", err)
|
|
|
|
os.Exit(1)
|
2014-05-23 14:42:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dbp.GoSymTable = tab
|
|
|
|
}
|
2014-05-27 15:43:47 +00:00
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
type ProcessExitedError struct {
|
|
|
|
pid int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pe ProcessExitedError) Error() string {
|
|
|
|
return fmt.Sprintf("process %d has exited", pe.pid)
|
|
|
|
}
|
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
func trapWait(dbp *DebuggedProcess, p int, options int) (int, *syscall.WaitStatus, error) {
|
|
|
|
var status syscall.WaitStatus
|
|
|
|
|
2014-10-18 01:34:58 +00:00
|
|
|
for {
|
2014-11-08 05:44:24 +00:00
|
|
|
pid, err := syscall.Wait4(p, &status, syscall.WALL|options, nil)
|
2014-11-02 18:49:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return -1, nil, fmt.Errorf("wait err %s %d", err, pid)
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
|
|
|
|
2014-10-27 22:47:43 +00:00
|
|
|
thread, threadtraced := dbp.Threads[pid]
|
2014-11-08 05:44:24 +00:00
|
|
|
if !threadtraced {
|
|
|
|
continue
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
2014-11-08 05:44:24 +00:00
|
|
|
thread.Status = &status
|
2014-10-18 01:34:58 +00:00
|
|
|
|
|
|
|
if status.Exited() {
|
|
|
|
if pid == dbp.Pid {
|
|
|
|
return 0, nil, ProcessExitedError{pid}
|
|
|
|
}
|
2014-10-27 22:47:43 +00:00
|
|
|
delete(dbp.Threads, pid)
|
2014-11-08 05:44:24 +00:00
|
|
|
continue
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
switch status.TrapCause() {
|
|
|
|
case syscall.PTRACE_EVENT_CLONE:
|
|
|
|
addNewThread(dbp, pid)
|
|
|
|
default:
|
|
|
|
pc, err := thread.CurrentPC()
|
|
|
|
if err != nil {
|
|
|
|
return -1, nil, fmt.Errorf("could not get current pc %s", err)
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
2014-11-10 00:20:49 +00:00
|
|
|
// Check to see if we hit a runtime.breakpoint
|
|
|
|
fn := dbp.GoSymTable.PCToFunc(pc)
|
|
|
|
if fn != nil && fn.Name == "runtime.breakpoint" {
|
|
|
|
// step twice to get back to user code
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
err = thread.Step()
|
|
|
|
if err != nil {
|
|
|
|
return -1, nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
handleBreakPoint(dbp, thread, pid)
|
|
|
|
return pid, &status, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check to see if we have hit a user set breakpoint.
|
2014-11-08 05:44:24 +00:00
|
|
|
if bp, ok := dbp.BreakPoints[pc-1]; ok {
|
|
|
|
if !bp.temp {
|
|
|
|
handleBreakPoint(dbp, thread, pid)
|
2014-11-02 18:49:21 +00:00
|
|
|
}
|
2014-11-08 05:44:24 +00:00
|
|
|
return pid, &status, nil
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if status.Stopped() {
|
2014-11-08 05:44:24 +00:00
|
|
|
// The thread has stopped, but has not hit a breakpoint.
|
|
|
|
// Continue the thread without returning control back
|
|
|
|
// to the console.
|
|
|
|
syscall.PtraceCont(pid, 0)
|
2014-10-18 01:34:58 +00:00
|
|
|
}
|
2014-08-27 22:47:04 +00:00
|
|
|
}
|
|
|
|
}
|
2014-11-02 18:49:21 +00:00
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
func addNewThread(dbp *DebuggedProcess, pid int) error {
|
|
|
|
// A traced thread has cloned a new thread, grab the pid and
|
|
|
|
// add it to our list of traced threads.
|
|
|
|
msg, err := syscall.PtraceGetEventMsg(pid)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not get event message: %s", err)
|
|
|
|
}
|
|
|
|
fmt.Println("new thread spawned", msg)
|
|
|
|
|
|
|
|
_, err = dbp.addThread(int(msg))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = syscall.PtraceCont(int(msg), 0)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not continue new thread %d %s", msg, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = syscall.PtraceCont(pid, 0)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not continue stopped thread %d %s", pid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-11-02 18:49:21 +00:00
|
|
|
type waitstats struct {
|
|
|
|
pid int
|
|
|
|
status *syscall.WaitStatus
|
|
|
|
}
|
|
|
|
|
|
|
|
type TimeoutError struct {
|
|
|
|
pid int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err TimeoutError) Error() string {
|
|
|
|
return fmt.Sprintf("timeout waiting for %d", err.pid)
|
|
|
|
}
|
|
|
|
|
|
|
|
// timeoutwait waits the specified duration before returning
|
|
|
|
// a TimeoutError.
|
|
|
|
func timeoutWait(pid int, options int) (int, *syscall.WaitStatus, error) {
|
|
|
|
var (
|
|
|
|
status syscall.WaitStatus
|
|
|
|
statchan = make(chan *waitstats)
|
|
|
|
errchan = make(chan error)
|
|
|
|
)
|
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
if pid > 0 {
|
|
|
|
ps, err := parseProcessStatus(pid)
|
|
|
|
if err != nil {
|
|
|
|
return -1, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if ps.state == STATUS_SLEEPING {
|
|
|
|
return 0, nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-02 18:49:21 +00:00
|
|
|
go func(pid int) {
|
|
|
|
pid, err := syscall.Wait4(pid, &status, syscall.WALL|options, nil)
|
|
|
|
if err != nil {
|
|
|
|
errchan <- fmt.Errorf("wait err %s %d", err, pid)
|
|
|
|
}
|
|
|
|
|
|
|
|
statchan <- &waitstats{pid: pid, status: &status}
|
|
|
|
}(pid)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case s := <-statchan:
|
|
|
|
return s.pid, s.status, nil
|
2014-11-08 05:44:24 +00:00
|
|
|
case <-time.After(2 * time.Second):
|
|
|
|
if pid > 0 {
|
|
|
|
ps, err := parseProcessStatus(pid)
|
|
|
|
if err != nil {
|
|
|
|
return -1, nil, err
|
|
|
|
}
|
|
|
|
syscall.Tgkill(ps.ppid, ps.pid, syscall.SIGSTOP)
|
2014-11-02 18:49:21 +00:00
|
|
|
}
|
|
|
|
|
2014-11-08 05:44:24 +00:00
|
|
|
return 0, nil, TimeoutError{pid}
|
2014-11-02 18:49:21 +00:00
|
|
|
case err := <-errchan:
|
|
|
|
return -1, nil, err
|
|
|
|
}
|
|
|
|
}
|
2014-11-08 05:44:24 +00:00
|
|
|
|
|
|
|
func handleBreakPoint(dbp *DebuggedProcess, thread *ThreadContext, pid int) error {
|
|
|
|
if pid != dbp.CurrentThread.Id {
|
|
|
|
fmt.Printf("thread context changed from %d to %d\n", dbp.CurrentThread.Id, pid)
|
|
|
|
dbp.CurrentThread = thread
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loop through all threads and ensure that we
|
|
|
|
// stop the rest of them, so that by the time
|
|
|
|
// we return control to the user, all threads
|
|
|
|
// are inactive. We send SIGSTOP and ensure all
|
|
|
|
// threads are in in signal-delivery-stop mode.
|
|
|
|
for _, th := range dbp.Threads {
|
|
|
|
if th.Id == pid {
|
|
|
|
// This thread is already stopped.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ps, err := parseProcessStatus(th.Id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if ps.state == STATUS_TRACE_STOP {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
err = syscall.Tgkill(dbp.Pid, th.Id, syscall.SIGSTOP)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pid, err := syscall.Wait4(th.Id, nil, syscall.WALL, nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("wait err %s %d", err, pid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|