mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-01 03:42:59 +08:00 
			
		
		
		
	 4d88d9ed8d
			
		
	
	4d88d9ed8d
	
	
	
		
			
			FDEs previously were loaded into a red/black tree and searched. This is significantly more expensive than a binary search over a slice. Not sure what I was thinking using a red/black tree - this binary search implementation is significantly more efficient.
		
			
				
	
	
		
			554 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			554 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package proctl provides functions for attaching to and manipulating
 | |
| // a process during the debug session.
 | |
| package proctl
 | |
| 
 | |
| import (
 | |
| 	"debug/dwarf"
 | |
| 	"debug/gosym"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| 
 | |
| 	sys "golang.org/x/sys/unix"
 | |
| 
 | |
| 	"github.com/derekparker/delve/dwarf/frame"
 | |
| 	"github.com/derekparker/delve/dwarf/reader"
 | |
| )
 | |
| 
 | |
| // Struct representing a debugged process. Holds onto pid, register values,
 | |
| // process struct and process state.
 | |
| type DebuggedProcess struct {
 | |
| 	Pid                 int
 | |
| 	Process             *os.Process
 | |
| 	Dwarf               *dwarf.Data
 | |
| 	GoSymTable          *gosym.Table
 | |
| 	FrameEntries        frame.FrameDescriptionEntries
 | |
| 	HWBreakPoints       [4]*BreakPoint
 | |
| 	BreakPoints         map[uint64]*BreakPoint
 | |
| 	Threads             map[int]*ThreadContext
 | |
| 	CurrentThread       *ThreadContext
 | |
| 	breakpointIDCounter int
 | |
| 	running             bool
 | |
| 	halt                bool
 | |
| }
 | |
| 
 | |
| // A ManualStopError happens when the user triggers a
 | |
| // manual stop via SIGERM.
 | |
| type ManualStopError struct{}
 | |
| 
 | |
| func (mse ManualStopError) Error() string {
 | |
| 	return "Manual stop requested"
 | |
| }
 | |
| 
 | |
| // Attach to an existing process with the given PID.
 | |
| func Attach(pid int) (*DebuggedProcess, error) {
 | |
| 	dbp, err := newDebugProcess(pid, true)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	// Attach to all currently active threads.
 | |
| 	allm, err := dbp.CurrentThread.AllM()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	for _, m := range allm {
 | |
| 		if m.procid == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		_, err := dbp.AttachThread(m.procid)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	return dbp, nil
 | |
| }
 | |
| 
 | |
| // Create and begin debugging a new process. First entry in
 | |
| // `cmd` is the program to run, and then rest are the arguments
 | |
| // to be supplied to that process.
 | |
| func Launch(cmd []string) (*DebuggedProcess, error) {
 | |
| 	proc := exec.Command(cmd[0])
 | |
| 	proc.Args = cmd
 | |
| 	proc.Stdout = os.Stdout
 | |
| 	proc.Stderr = os.Stderr
 | |
| 	proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
 | |
| 
 | |
| 	if err := proc.Start(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	_, _, err := wait(proc.Process.Pid, 0)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("waiting for target execve failed: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	return newDebugProcess(proc.Process.Pid, false)
 | |
| }
 | |
| 
 | |
| // Returns a new DebuggedProcess struct with sensible defaults.
 | |
| func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) {
 | |
| 	dbp := DebuggedProcess{
 | |
| 		Pid:         pid,
 | |
| 		Threads:     make(map[int]*ThreadContext),
 | |
| 		BreakPoints: make(map[uint64]*BreakPoint),
 | |
| 	}
 | |
| 
 | |
| 	if attach {
 | |
| 		thread, err := dbp.AttachThread(pid)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		dbp.CurrentThread = thread
 | |
| 	} else {
 | |
| 		thread, err := dbp.addThread(pid)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		dbp.CurrentThread = thread
 | |
| 	}
 | |
| 
 | |
| 	proc, err := os.FindProcess(pid)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	dbp.Process = proc
 | |
| 	err = dbp.LoadInformation()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &dbp, nil
 | |
| }
 | |
| 
 | |
| // Attach to a newly created thread, and store that thread in our list of
 | |
| // known threads.
 | |
| func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) {
 | |
| 	if thread, ok := dbp.Threads[tid]; ok {
 | |
| 		return thread, nil
 | |
| 	}
 | |
| 
 | |
| 	err := sys.PtraceAttach(tid)
 | |
| 	if err != nil && err != sys.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)
 | |
| 	}
 | |
| 
 | |
| 	pid, status, err := wait(tid, 0)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if status.Exited() {
 | |
| 		return nil, fmt.Errorf("thread already exited %d", pid)
 | |
| 	}
 | |
| 
 | |
| 	return dbp.addThread(tid)
 | |
| }
 | |
| 
 | |
| // Returns whether or not Delve thinks the debugged
 | |
| // process is currently executing.
 | |
| func (dbp *DebuggedProcess) Running() bool {
 | |
| 	return dbp.running
 | |
| }
 | |
| 
 | |
| // Find a location by string (file+line, function, breakpoint id, addr)
 | |
| func (dbp *DebuggedProcess) FindLocation(str string) (uint64, error) {
 | |
| 	// File + Line
 | |
| 	if strings.ContainsRune(str, ':') {
 | |
| 		fl := strings.Split(str, ":")
 | |
| 
 | |
| 		fileName, err := filepath.Abs(fl[0])
 | |
| 		if err != nil {
 | |
| 			return 0, err
 | |
| 		}
 | |
| 
 | |
| 		line, err := strconv.Atoi(fl[1])
 | |
| 		if err != nil {
 | |
| 			return 0, err
 | |
| 		}
 | |
| 
 | |
| 		pc, _, err := dbp.GoSymTable.LineToPC(fileName, line)
 | |
| 		if err != nil {
 | |
| 			return 0, err
 | |
| 		}
 | |
| 		return pc, nil
 | |
| 	} else {
 | |
| 		// Try to lookup by function name
 | |
| 		fn := dbp.GoSymTable.LookupFunc(str)
 | |
| 		if fn != nil {
 | |
| 			return fn.Entry, nil
 | |
| 		}
 | |
| 
 | |
| 		// Attempt to parse as number for breakpoint id or raw address
 | |
| 		id, err := strconv.ParseUint(str, 0, 64)
 | |
| 		if err != nil {
 | |
| 			return 0, fmt.Errorf("unable to find location for %s", str)
 | |
| 		}
 | |
| 
 | |
| 		// Use as breakpoint id
 | |
| 		for _, bp := range dbp.HWBreakPoints {
 | |
| 			if bp == nil {
 | |
| 				continue
 | |
| 			}
 | |
| 			if uint64(bp.ID) == id {
 | |
| 				return bp.Addr, nil
 | |
| 			}
 | |
| 		}
 | |
| 		for _, bp := range dbp.BreakPoints {
 | |
| 			if uint64(bp.ID) == id {
 | |
| 				return bp.Addr, nil
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Last resort, use as raw address
 | |
| 		return id, nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Sends out a request that the debugged process halt
 | |
| // execution. Sends SIGSTOP to all threads.
 | |
| func (dbp *DebuggedProcess) RequestManualStop() {
 | |
| 	dbp.halt = true
 | |
| 	for _, th := range dbp.Threads {
 | |
| 		if stopped(th.Id) {
 | |
| 			continue
 | |
| 		}
 | |
| 		sys.Tgkill(dbp.Pid, th.Id, sys.SIGSTOP)
 | |
| 	}
 | |
| 	dbp.running = false
 | |
| }
 | |
| 
 | |
| // Sets a breakpoint, adding it to our list of known breakpoints. Uses
 | |
| // the "current thread" when setting the breakpoint.
 | |
| func (dbp *DebuggedProcess) Break(addr uint64) (*BreakPoint, error) {
 | |
| 	return dbp.CurrentThread.Break(addr)
 | |
| }
 | |
| 
 | |
| // Sets a breakpoint by location string (function, file+line, address)
 | |
| func (dbp *DebuggedProcess) BreakByLocation(loc string) (*BreakPoint, error) {
 | |
| 	addr, err := dbp.FindLocation(loc)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return dbp.CurrentThread.Break(addr)
 | |
| }
 | |
| 
 | |
| // Clears a breakpoint in the current thread.
 | |
| func (dbp *DebuggedProcess) Clear(addr uint64) (*BreakPoint, error) {
 | |
| 	return dbp.CurrentThread.Clear(addr)
 | |
| }
 | |
| 
 | |
| // Clears a breakpoint by location (function, file+line, address, breakpoint id)
 | |
| func (dbp *DebuggedProcess) ClearByLocation(loc string) (*BreakPoint, error) {
 | |
| 	addr, err := dbp.FindLocation(loc)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return dbp.CurrentThread.Clear(addr)
 | |
| }
 | |
| 
 | |
| // Returns the status of the current main thread context.
 | |
| func (dbp *DebuggedProcess) Status() *sys.WaitStatus {
 | |
| 	return dbp.CurrentThread.Status
 | |
| }
 | |
| 
 | |
| // Loop through all threads, printing their information
 | |
| // to the console.
 | |
| func (dbp *DebuggedProcess) PrintThreadInfo() error {
 | |
| 	for _, th := range dbp.Threads {
 | |
| 		if err := th.PrintInfo(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Steps through process.
 | |
| func (dbp *DebuggedProcess) Step() (err error) {
 | |
| 	var (
 | |
| 		th *ThreadContext
 | |
| 		ok bool
 | |
| 	)
 | |
| 
 | |
| 	allm, err := dbp.CurrentThread.AllM()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	fn := func() error {
 | |
| 		for _, m := range allm {
 | |
| 			th, ok = dbp.Threads[m.procid]
 | |
| 			if !ok {
 | |
| 				th = dbp.Threads[dbp.Pid]
 | |
| 			}
 | |
| 
 | |
| 			if m.blocked == 0 {
 | |
| 				err := th.Step()
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return dbp.run(fn)
 | |
| }
 | |
| 
 | |
| // Step over function calls.
 | |
| func (dbp *DebuggedProcess) Next() error {
 | |
| 	var (
 | |
| 		th *ThreadContext
 | |
| 		ok bool
 | |
| 	)
 | |
| 
 | |
| 	fn := func() error {
 | |
| 		allm, err := dbp.CurrentThread.AllM()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		for _, m := range allm {
 | |
| 			th, ok = dbp.Threads[m.procid]
 | |
| 			if !ok {
 | |
| 				th = dbp.Threads[dbp.Pid]
 | |
| 			}
 | |
| 
 | |
| 			if m.blocked == 1 {
 | |
| 				// Continue any blocked M so that the
 | |
| 				// scheduler can continue to do its'
 | |
| 				// job correctly.
 | |
| 				err := th.Continue()
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			err := th.Next()
 | |
| 			if err != nil && err != sys.ESRCH {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 		return stopTheWorld(dbp)
 | |
| 	}
 | |
| 	return dbp.run(fn)
 | |
| }
 | |
| 
 | |
| // Resume process.
 | |
| func (dbp *DebuggedProcess) Continue() error {
 | |
| 	for _, thread := range dbp.Threads {
 | |
| 		err := thread.Continue()
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fn := func() error {
 | |
| 		wpid, _, err := trapWait(dbp, -1)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		return handleBreakPoint(dbp, wpid)
 | |
| 	}
 | |
| 	return dbp.run(fn)
 | |
| }
 | |
| 
 | |
| // Obtains register values from what Delve considers to be the current
 | |
| // thread of the traced process.
 | |
| func (dbp *DebuggedProcess) Registers() (Registers, error) {
 | |
| 	return dbp.CurrentThread.Registers()
 | |
| }
 | |
| 
 | |
| func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
 | |
| 	return dbp.CurrentThread.CurrentPC()
 | |
| }
 | |
| 
 | |
| // Returns the value of the named symbol.
 | |
| func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) {
 | |
| 	return dbp.CurrentThread.EvalSymbol(name)
 | |
| }
 | |
| 
 | |
| // Returns a reader for the dwarf data
 | |
| func (dbp *DebuggedProcess) DwarfReader() *reader.Reader {
 | |
| 	return reader.New(dbp.Dwarf)
 | |
| }
 | |
| 
 | |
| func (dbp *DebuggedProcess) run(fn func() error) error {
 | |
| 	dbp.running = true
 | |
| 	dbp.halt = false
 | |
| 	defer func() { dbp.running = false }()
 | |
| 	if err := fn(); err != nil {
 | |
| 		if _, ok := err.(ManualStopError); !ok {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type ProcessExitedError struct {
 | |
| 	pid int
 | |
| }
 | |
| 
 | |
| func (pe ProcessExitedError) Error() string {
 | |
| 	return fmt.Sprintf("process %d has exited", pe.pid)
 | |
| }
 | |
| 
 | |
| func trapWait(dbp *DebuggedProcess, pid int) (int, *sys.WaitStatus, error) {
 | |
| 	for {
 | |
| 		wpid, status, err := wait(pid, 0)
 | |
| 		if err != nil {
 | |
| 			return -1, nil, fmt.Errorf("wait err %s %d", err, pid)
 | |
| 		}
 | |
| 		if wpid == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if th, ok := dbp.Threads[wpid]; ok {
 | |
| 			th.Status = status
 | |
| 		}
 | |
| 		if status.Exited() && wpid == dbp.Pid {
 | |
| 			return -1, status, ProcessExitedError{wpid}
 | |
| 		}
 | |
| 		if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
 | |
| 			err = addNewThread(dbp, wpid)
 | |
| 			if err != nil {
 | |
| 				return -1, nil, err
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		if status.StopSignal() == sys.SIGTRAP {
 | |
| 			return wpid, status, nil
 | |
| 		}
 | |
| 		if status.StopSignal() == sys.SIGSTOP && dbp.halt {
 | |
| 			return -1, nil, ManualStopError{}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func handleBreakPoint(dbp *DebuggedProcess, pid int) error {
 | |
| 	thread := dbp.Threads[pid]
 | |
| 	if pid != dbp.CurrentThread.Id {
 | |
| 		fmt.Printf("thread context changed from %d to %d\n", dbp.CurrentThread.Id, pid)
 | |
| 		dbp.CurrentThread = thread
 | |
| 	}
 | |
| 
 | |
| 	pc, err := thread.CurrentPC()
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not get current pc %s", err)
 | |
| 	}
 | |
| 
 | |
| 	// 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 err
 | |
| 			}
 | |
| 		}
 | |
| 		stopTheWorld(dbp)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Check for hardware breakpoint
 | |
| 	for _, bp := range dbp.HWBreakPoints {
 | |
| 		if bp.Addr == pc {
 | |
| 			if !bp.temp {
 | |
| 				stopTheWorld(dbp)
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 	// Check to see if we have hit a software breakpoint.
 | |
| 	if bp, ok := dbp.BreakPoints[pc-1]; ok {
 | |
| 		if !bp.temp {
 | |
| 			stopTheWorld(dbp)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Errorf("did not hit recognized breakpoint")
 | |
| }
 | |
| 
 | |
| // Ensure execution of every traced thread is halted.
 | |
| func stopTheWorld(dbp *DebuggedProcess) error {
 | |
| 	// 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 stopped(th.Id) {
 | |
| 			continue
 | |
| 		}
 | |
| 		err := sys.Tgkill(dbp.Pid, th.Id, sys.SIGSTOP)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		pid, _, err := wait(th.Id, sys.WNOHANG)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("wait err %s %d", err, pid)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| 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 := sys.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 = sys.PtraceCont(int(msg), 0)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("could not continue new thread %d %s", msg, err)
 | |
| 	}
 | |
| 
 | |
| 	// Here we loop for a while to ensure that the once we continue
 | |
| 	// the newly created thread, we allow enough time for the runtime
 | |
| 	// to assign m->procid. This is important because we rely on
 | |
| 	// looping through runtime.allm in other parts of the code, so
 | |
| 	// we require that this is set before we do anything else.
 | |
| 	// TODO(dp): we might be able to eliminate this loop by telling
 | |
| 	// the CPU to emit a breakpoint exception on write to this location
 | |
| 	// in memory. That way we prevent having to loop, and can be
 | |
| 	// notified as soon as m->procid is set.
 | |
| 	th := dbp.Threads[pid]
 | |
| 	for {
 | |
| 		allm, _ := th.AllM()
 | |
| 		for _, m := range allm {
 | |
| 			if m.procid == int(msg) {
 | |
| 				// Continue the thread that cloned
 | |
| 				return sys.PtraceCont(pid, 0)
 | |
| 			}
 | |
| 		}
 | |
| 		time.Sleep(time.Millisecond)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 |