mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 14:36:47 +08:00 
			
		
		
		
	* Ignore signals we do not care about * Implement custom fork/exec for Darwin to convert signals to mach exceptions
		
			
				
	
	
		
			308 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			308 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package proctl
 | 
						|
 | 
						|
import (
 | 
						|
	"debug/elf"
 | 
						|
	"debug/gosym"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"path/filepath"
 | 
						|
	"strconv"
 | 
						|
	"sync"
 | 
						|
	"syscall"
 | 
						|
 | 
						|
	sys "golang.org/x/sys/unix"
 | 
						|
 | 
						|
	"github.com/derekparker/delve/dwarf/frame"
 | 
						|
	"github.com/derekparker/delve/dwarf/line"
 | 
						|
	"github.com/derekparker/delve/source"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	STATUS_SLEEPING   = 'S'
 | 
						|
	STATUS_RUNNING    = 'R'
 | 
						|
	STATUS_TRACE_STOP = 't'
 | 
						|
)
 | 
						|
 | 
						|
// Not actually needed for Linux.
 | 
						|
type OSProcessDetails interface{}
 | 
						|
 | 
						|
// 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)
 | 
						|
	}
 | 
						|
	dbp := &DebuggedProcess{
 | 
						|
		Pid:         proc.Process.Pid,
 | 
						|
		Threads:     make(map[int]*ThreadContext),
 | 
						|
		BreakPoints: make(map[uint64]*BreakPoint),
 | 
						|
		os:          new(OSProcessDetails),
 | 
						|
		ast:         source.New(),
 | 
						|
	}
 | 
						|
 | 
						|
	return initializeDebugProcess(dbp, proc.Path, false)
 | 
						|
}
 | 
						|
 | 
						|
func (dbp *DebuggedProcess) requestManualStop() (err error) {
 | 
						|
	return sys.Kill(dbp.Pid, sys.SIGSTOP)
 | 
						|
}
 | 
						|
 | 
						|
// Attach to a newly created thread, and store that thread in our list of
 | 
						|
// known threads.
 | 
						|
func (dbp *DebuggedProcess) addThread(tid int, attach bool) (*ThreadContext, error) {
 | 
						|
	if thread, ok := dbp.Threads[tid]; ok {
 | 
						|
		return thread, nil
 | 
						|
	}
 | 
						|
	fmt.Println("new thread spawned", tid)
 | 
						|
 | 
						|
	if attach {
 | 
						|
		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)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE)
 | 
						|
	if err == syscall.ESRCH {
 | 
						|
		_, _, err = wait(tid, 0)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
 | 
						|
		}
 | 
						|
 | 
						|
		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)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	dbp.Threads[tid] = &ThreadContext{
 | 
						|
		Id:      tid,
 | 
						|
		Process: dbp,
 | 
						|
		os:      new(OSSpecificDetails),
 | 
						|
	}
 | 
						|
 | 
						|
	if dbp.CurrentThread == nil {
 | 
						|
		dbp.CurrentThread = dbp.Threads[tid]
 | 
						|
	}
 | 
						|
 | 
						|
	return dbp.Threads[tid], nil
 | 
						|
}
 | 
						|
 | 
						|
func (dbp *DebuggedProcess) updateThreadList() error {
 | 
						|
	var attach bool
 | 
						|
	tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.Pid))
 | 
						|
	for _, tidpath := range tids {
 | 
						|
		tidstr := filepath.Base(tidpath)
 | 
						|
		tid, err := strconv.Atoi(tidstr)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if tid != dbp.Pid {
 | 
						|
			attach = true
 | 
						|
		}
 | 
						|
		if _, err := dbp.addThread(tid, attach); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (dbp *DebuggedProcess) findExecutable(path string) (*elf.File, error) {
 | 
						|
	if path == "" {
 | 
						|
		path = fmt.Sprintf("/proc/%d/exe", dbp.Pid)
 | 
						|
	}
 | 
						|
	f, err := os.OpenFile(path, 0, os.ModePerm)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	elffile, err := elf.NewFile(f)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	data, err := elffile.DWARF()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	dbp.dwarf = data
 | 
						|
 | 
						|
	return elffile, nil
 | 
						|
}
 | 
						|
 | 
						|
func (dbp *DebuggedProcess) parseDebugFrame(exe *elf.File, wg *sync.WaitGroup) {
 | 
						|
	defer wg.Done()
 | 
						|
 | 
						|
	if sec := exe.Section(".debug_frame"); sec != nil {
 | 
						|
		debugFrame, err := exe.Section(".debug_frame").Data()
 | 
						|
		if err != nil {
 | 
						|
			fmt.Println("could not get .debug_frame section", err)
 | 
						|
			os.Exit(1)
 | 
						|
		}
 | 
						|
		dbp.frameEntries = frame.Parse(debugFrame)
 | 
						|
	} else {
 | 
						|
		fmt.Println("could not find .debug_frame section in binary")
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (dbp *DebuggedProcess) obtainGoSymbols(exe *elf.File, wg *sync.WaitGroup) {
 | 
						|
	defer wg.Done()
 | 
						|
 | 
						|
	var (
 | 
						|
		symdat  []byte
 | 
						|
		pclndat []byte
 | 
						|
		err     error
 | 
						|
	)
 | 
						|
 | 
						|
	if sec := exe.Section(".gosymtab"); sec != nil {
 | 
						|
		symdat, err = sec.Data()
 | 
						|
		if err != nil {
 | 
						|
			fmt.Println("could not get .gosymtab section", err)
 | 
						|
			os.Exit(1)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if sec := exe.Section(".gopclntab"); sec != nil {
 | 
						|
		pclndat, err = sec.Data()
 | 
						|
		if err != nil {
 | 
						|
			fmt.Println("could not get .gopclntab section", err)
 | 
						|
			os.Exit(1)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pcln := gosym.NewLineTable(pclndat, exe.Section(".text").Addr)
 | 
						|
	tab, err := gosym.NewTable(symdat, pcln)
 | 
						|
	if err != nil {
 | 
						|
		fmt.Println("could not get initialize line table", err)
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
 | 
						|
	dbp.goSymTable = tab
 | 
						|
}
 | 
						|
 | 
						|
func (dbp *DebuggedProcess) parseDebugLineInfo(exe *elf.File, wg *sync.WaitGroup) {
 | 
						|
	defer wg.Done()
 | 
						|
 | 
						|
	if sec := exe.Section(".debug_line"); sec != nil {
 | 
						|
		debugLine, err := exe.Section(".debug_line").Data()
 | 
						|
		if err != nil {
 | 
						|
			fmt.Println("could not get .debug_line section", err)
 | 
						|
			os.Exit(1)
 | 
						|
		}
 | 
						|
		dbp.lineInfo = line.Parse(debugLine)
 | 
						|
	} else {
 | 
						|
		fmt.Println("could not find .debug_line section in binary")
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) {
 | 
						|
	for {
 | 
						|
		wpid, status, err := wait(pid, 0)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("wait err %s %d", err, pid)
 | 
						|
		}
 | 
						|
		if wpid == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		th, ok := dbp.Threads[wpid]
 | 
						|
		if ok {
 | 
						|
			th.Status = status
 | 
						|
		}
 | 
						|
 | 
						|
		if status.Exited() {
 | 
						|
			if wpid == dbp.Pid {
 | 
						|
				dbp.exited = true
 | 
						|
				return nil, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()}
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
 | 
						|
			// A traced thread has cloned a new thread, grab the pid and
 | 
						|
			// add it to our list of traced threads.
 | 
						|
			cloned, err := sys.PtraceGetEventMsg(wpid)
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("could not get event message: %s", err)
 | 
						|
			}
 | 
						|
 | 
						|
			th, err = dbp.addThread(int(cloned), false)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
 | 
						|
			err = th.Continue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
 | 
						|
			}
 | 
						|
 | 
						|
			err = dbp.Threads[int(wpid)].Continue()
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if status.StopSignal() == sys.SIGTRAP {
 | 
						|
			return dbp.handleBreakpointOnThread(wpid)
 | 
						|
		}
 | 
						|
		if status.StopSignal() == sys.SIGSTOP && dbp.halt {
 | 
						|
			return nil, ManualStopError{}
 | 
						|
		}
 | 
						|
		if th != nil {
 | 
						|
			if err := th.Continue(); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func stopped(pid int) bool {
 | 
						|
	f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
 | 
						|
	if err != nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	var (
 | 
						|
		p     int
 | 
						|
		comm  string
 | 
						|
		state rune
 | 
						|
	)
 | 
						|
	fmt.Fscanf(f, "%d %s %c", &p, &comm, &state)
 | 
						|
	if state == STATUS_TRACE_STOP {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |