mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 06:32:16 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			250 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package proctl
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"strconv"
 | 
						|
	"syscall"
 | 
						|
 | 
						|
	"github.com/derekparker/delve/dwarf/frame"
 | 
						|
)
 | 
						|
 | 
						|
// ThreadContext represents a single thread of execution in the
 | 
						|
// traced program.
 | 
						|
type ThreadContext struct {
 | 
						|
	Id      int
 | 
						|
	Process *DebuggedProcess
 | 
						|
	Status  *syscall.WaitStatus
 | 
						|
	Regs    *syscall.PtraceRegs
 | 
						|
}
 | 
						|
 | 
						|
// Obtains register values from the debugged process.
 | 
						|
func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) {
 | 
						|
	err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
 | 
						|
	if err != nil {
 | 
						|
		syscall.Tgkill(thread.Process.Pid, thread.Id, syscall.SIGSTOP)
 | 
						|
		err = thread.wait()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("Registers(): %s", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return thread.Regs, nil
 | 
						|
}
 | 
						|
 | 
						|
func (thread *ThreadContext) CurrentPC() (uint64, error) {
 | 
						|
	regs, err := thread.Registers()
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	return regs.PC(), nil
 | 
						|
}
 | 
						|
 | 
						|
func (thread *ThreadContext) Continue() error {
 | 
						|
	// Stepping first will ensure we are able to continue
 | 
						|
	// past a breakpoint if that's currently where we are stopped.
 | 
						|
	err := thread.Step()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return syscall.PtraceCont(thread.Id, 0)
 | 
						|
}
 | 
						|
 | 
						|
// Steps through thread of execution.
 | 
						|
func (thread *ThreadContext) Step() (err error) {
 | 
						|
	regs, err := thread.Registers()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	bp, ok := thread.Process.BreakPoints[regs.PC()-1]
 | 
						|
	if ok {
 | 
						|
		// Clear the breakpoint so that we can continue execution.
 | 
						|
		_, err = thread.Process.Clear(bp.Addr)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// Reset program counter to our restored instruction.
 | 
						|
		regs.SetPC(bp.Addr)
 | 
						|
		err = syscall.PtraceSetRegs(thread.Id, regs)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// Restore breakpoint now that we have passed it.
 | 
						|
		defer func() {
 | 
						|
			_, err = thread.Process.Break(uintptr(bp.Addr))
 | 
						|
		}()
 | 
						|
	}
 | 
						|
 | 
						|
	err = syscall.PtraceSingleStep(thread.Id)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("step failed: %s", err.Error())
 | 
						|
	}
 | 
						|
 | 
						|
	_, _, err = wait(thread.Process, thread.Id, 0)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("step failed: %s", err.Error())
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Steps through thread of execution.
 | 
						|
func (thread *ThreadContext) Next() (err error) {
 | 
						|
	pc, err := thread.CurrentPC()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if _, ok := thread.Process.BreakPoints[pc-1]; ok {
 | 
						|
		// Decrement the PC to be before
 | 
						|
		// the breakpoint instruction.
 | 
						|
		pc--
 | 
						|
	}
 | 
						|
 | 
						|
	_, l, _ := thread.Process.GoSymTable.PCToLine(pc)
 | 
						|
	fde, err := thread.Process.FrameEntries.FDEForPC(pc)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	step := func() (uint64, error) {
 | 
						|
		err = thread.Step()
 | 
						|
		if err != nil {
 | 
						|
			return 0, fmt.Errorf("next stepping failed: %s", err.Error())
 | 
						|
		}
 | 
						|
 | 
						|
		return thread.CurrentPC()
 | 
						|
	}
 | 
						|
 | 
						|
	ret := thread.Process.ReturnAddressFromOffset(fde.ReturnAddressOffset(pc))
 | 
						|
	for {
 | 
						|
		pc, err = step()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		if !fde.Cover(pc) && pc != ret {
 | 
						|
			thread.continueToReturnAddress(pc, fde)
 | 
						|
			if err != nil {
 | 
						|
				if ierr, ok := err.(InvalidAddressError); ok {
 | 
						|
					return ierr
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			pc, _ = thread.CurrentPC()
 | 
						|
		}
 | 
						|
 | 
						|
		_, nl, _ := thread.Process.GoSymTable.PCToLine(pc)
 | 
						|
		if nl != l {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.FrameDescriptionEntry) error {
 | 
						|
	for !fde.Cover(pc) {
 | 
						|
		// Our offset here is be 0 because we
 | 
						|
		// have stepped into the first instruction
 | 
						|
		// of this function. Therefore the function
 | 
						|
		// has not had a chance to modify its' stack
 | 
						|
		// and change our offset.
 | 
						|
		addr := thread.Process.ReturnAddressFromOffset(0)
 | 
						|
		bp, err := thread.Process.Break(uintptr(addr))
 | 
						|
		if err != nil {
 | 
						|
			if _, ok := err.(BreakPointExistsError); !ok {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		err = thread.Continue()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		err = thread.wait()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		err = thread.clearTempBreakpoint(bp.Addr)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		pc, _ = thread.CurrentPC()
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error {
 | 
						|
	if bp, ok := thread.Process.BreakPoints[pc]; ok {
 | 
						|
		regs, err := thread.Registers()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// Reset program counter to our restored instruction.
 | 
						|
		bp, err = thread.Process.Clear(bp.Addr)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		regs.SetPC(bp.Addr)
 | 
						|
		return syscall.PtraceSetRegs(thread.Id, regs)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (thread *ThreadContext) wait() error {
 | 
						|
	var status syscall.WaitStatus
 | 
						|
	_, err := syscall.Wait4(thread.Id, &status, 0, nil)
 | 
						|
	if err != nil {
 | 
						|
		if status.Exited() {
 | 
						|
			delete(thread.Process.Threads, thread.Id)
 | 
						|
			return ProcessExitedError{thread.Id}
 | 
						|
		}
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func threadIds(pid int) []int {
 | 
						|
	var threads []int
 | 
						|
	dir, err := os.Open(fmt.Sprintf("/proc/%d/task", pid))
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	defer dir.Close()
 | 
						|
 | 
						|
	names, err := dir.Readdirnames(0)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	for _, strid := range names {
 | 
						|
		tid, err := strconv.Atoi(strid)
 | 
						|
		if err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
 | 
						|
		threads = append(threads, tid)
 | 
						|
	}
 | 
						|
 | 
						|
	return threads
 | 
						|
}
 |