mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-01 03:42:59 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			256 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package proctl provides functions for attaching to and manipulating
 | |
| // a process during the debug session.
 | |
| package proctl
 | |
| 
 | |
| import (
 | |
| 	"debug/elf"
 | |
| 	"debug/gosym"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"syscall"
 | |
| )
 | |
| 
 | |
| // Struct representing a debugged process. Holds onto pid, register values,
 | |
| // process struct and process state.
 | |
| type DebuggedProcess struct {
 | |
| 	Pid          int
 | |
| 	Regs         *syscall.PtraceRegs
 | |
| 	Process      *os.Process
 | |
| 	ProcessState *os.ProcessState
 | |
| 	Executable   *elf.File
 | |
| 	Symbols      []elf.Symbol
 | |
| 	GoSymTable   *gosym.Table
 | |
| 	BreakPoints  map[string]*BreakPoint
 | |
| }
 | |
| 
 | |
| // Represents a single breakpoint. Stores information on the break
 | |
| // point include the byte of data that originally was stored at that
 | |
| // address.
 | |
| type BreakPoint struct {
 | |
| 	FunctionName string
 | |
| 	File         string
 | |
| 	Line         int
 | |
| 	Addr         uint64
 | |
| 	OriginalData []byte
 | |
| }
 | |
| 
 | |
| // Returns a new DebuggedProcess struct with sensible defaults.
 | |
| func NewDebugProcess(pid int) (*DebuggedProcess, error) {
 | |
| 	proc, err := os.FindProcess(pid)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	err = syscall.PtraceAttach(pid)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ps, err := proc.Wait()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	debuggedProc := DebuggedProcess{
 | |
| 		Pid:          pid,
 | |
| 		Regs:         &syscall.PtraceRegs{},
 | |
| 		Process:      proc,
 | |
| 		ProcessState: ps,
 | |
| 		BreakPoints:  make(map[string]*BreakPoint),
 | |
| 	}
 | |
| 
 | |
| 	err = debuggedProc.LoadInformation()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &debuggedProc, nil
 | |
| }
 | |
| 
 | |
| // Finds the executable from /proc/<pid>/exe and then
 | |
| // uses that to parse the Go symbol table.
 | |
| func (dbp *DebuggedProcess) LoadInformation() error {
 | |
| 	err := dbp.findExecutable()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	err = dbp.obtainGoSymbols()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Obtains register values from the debugged process.
 | |
| func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
 | |
| 	err := syscall.PtraceGetRegs(dbp.Pid, dbp.Regs)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("Registers():", err)
 | |
| 	}
 | |
| 
 | |
| 	return dbp.Regs, nil
 | |
| }
 | |
| 
 | |
| // Sets a breakpoint in the running process.
 | |
| func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
 | |
| 	var (
 | |
| 		int3        = []byte{0xCC}
 | |
| 		f, l, fn    = dbp.GoSymTable.PCToLine(uint64(addr))
 | |
| 		orginalData = make([]byte, 1)
 | |
| 	)
 | |
| 
 | |
| 	_, err := syscall.PtracePeekData(dbp.Pid, addr, orginalData)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	_, err = syscall.PtracePokeData(dbp.Pid, addr, int3)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	breakpoint := &BreakPoint{
 | |
| 		FunctionName: fn.Name,
 | |
| 		File:         f,
 | |
| 		Line:         l,
 | |
| 		Addr:         uint64(addr),
 | |
| 		OriginalData: orginalData,
 | |
| 	}
 | |
| 
 | |
| 	fname := fmt.Sprintf("%s:%d", f, l)
 | |
| 	dbp.BreakPoints[fname] = breakpoint
 | |
| 
 | |
| 	return breakpoint, nil
 | |
| }
 | |
| 
 | |
| // Clears a breakpoint.
 | |
| func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
 | |
| 	bp, ok := dbp.PCtoBP(pc)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("No breakpoint currently set for %s", bp.FunctionName)
 | |
| 	}
 | |
| 
 | |
| 	err := dbp.restoreInstruction(bp.Addr, bp.OriginalData)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	delete(dbp.BreakPoints, fmt.Sprintf("%s:%d", bp.File, bp.Line))
 | |
| 
 | |
| 	return bp, nil
 | |
| }
 | |
| 
 | |
| // Steps through process.
 | |
| func (dbp *DebuggedProcess) Step() error {
 | |
| 	regs, err := dbp.Registers()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	bp, ok := dbp.PCtoBP(regs.PC())
 | |
| 	if ok {
 | |
| 		err = dbp.restoreInstruction(bp.Addr, bp.OriginalData)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	err = dbp.handleResult(syscall.PtraceSingleStep(dbp.Pid))
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("step failed: ", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	// Restore breakpoint
 | |
| 	if ok {
 | |
| 		_, err := dbp.Break(uintptr(bp.Addr))
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Continue process until next breakpoint.
 | |
| func (dbp *DebuggedProcess) Continue() error {
 | |
| 	// Stepping first will ensure we are able to continue
 | |
| 	// past a breakpoint if that's currently where we are stopped.
 | |
| 	err := dbp.Step()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return dbp.handleResult(syscall.PtraceCont(dbp.Pid, 0))
 | |
| }
 | |
| 
 | |
| func (dbp *DebuggedProcess) handleResult(err error) error {
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ps, err := dbp.Process.Wait()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	dbp.ProcessState = ps
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (dbp *DebuggedProcess) findExecutable() error {
 | |
| 	procpath := fmt.Sprintf("/proc/%d/exe", dbp.Pid)
 | |
| 
 | |
| 	f, err := os.Open(procpath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	elffile, err := elf.NewFile(f)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	dbp.Executable = elffile
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (dbp *DebuggedProcess) obtainGoSymbols() error {
 | |
| 	symdat, err := dbp.Executable.Section(".gosymtab").Data()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	pclndat, err := dbp.Executable.Section(".gopclntab").Data()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	pcln := gosym.NewLineTable(pclndat, dbp.Executable.Section(".text").Addr)
 | |
| 	tab, err := gosym.NewTable(symdat, pcln)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	dbp.GoSymTable = tab
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Converts a program counter value into a breakpoint, if one was set
 | |
| // for the function containing pc.
 | |
| func (dbp *DebuggedProcess) PCtoBP(pc uint64) (*BreakPoint, bool) {
 | |
| 	f, l, _ := dbp.GoSymTable.PCToLine(pc)
 | |
| 	bp, ok := dbp.BreakPoints[fmt.Sprintf("%s:%d", f, l)]
 | |
| 	return bp, ok
 | |
| }
 | |
| 
 | |
| func (dbp *DebuggedProcess) restoreInstruction(pc uint64, data []byte) error {
 | |
| 	_, err := syscall.PtracePokeData(dbp.Pid, uintptr(pc), data)
 | |
| 	return err
 | |
| }
 | 
