delve/proc/proc.go

690 lines
16 KiB
Go
Raw Normal View History

2015-06-12 19:49:23 +00:00
package proc
import (
2014-11-24 23:27:56 +00:00
"debug/dwarf"
"debug/gosym"
"encoding/binary"
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
"github.com/derekparker/delve/dwarf/reader"
"github.com/derekparker/delve/source"
)
func init() {
runtime.GOMAXPROCS(2)
}
2015-06-20 22:54:52 +00:00
// Process represents all of the information the debugger
// is holding onto regarding the process we are debugging.
2015-06-20 22:54:52 +00:00
type Process struct {
Pid int // Process Pid
Process *os.Process // Pointer to process struct for the actual process we are debugging
// Breakpoint table, hold information on software / hardware breakpoints.
// Maps instruction address to Breakpoint struct.
2015-06-12 19:32:32 +00:00
Breakpoints map[uint64]*Breakpoint
2015-06-12 19:51:23 +00:00
// List of threads mapped as such: pid -> *Thread
Threads map[int]*Thread
// Active thread. This is the default thread used for setting breakpoints, evaluating variables, etc..
2015-06-12 19:51:23 +00:00
CurrentThread *Thread
dwarf *dwarf.Data
goSymTable *gosym.Table
frameEntries frame.FrameDescriptionEntries
lineInfo *line.DebugLineInfo
firstStart bool
singleStepping bool
os *OSProcessDetails
arch Arch
ast *source.Searcher
breakpointIDCounter int
tempBreakpointIDCounter int
halt bool
exited bool
ptraceChan chan func()
ptraceDoneChan chan interface{}
}
2015-06-20 22:54:52 +00:00
func New(pid int) *Process {
dbp := &Process{
Pid: pid,
Threads: make(map[int]*Thread),
Breakpoints: make(map[uint64]*Breakpoint),
firstStart: true,
os: new(OSProcessDetails),
ast: source.New(),
ptraceChan: make(chan func()),
ptraceDoneChan: make(chan interface{}),
}
go dbp.handlePtraceFuncs()
return dbp
2014-12-08 23:15:52 +00:00
}
// ProcessExitedError indicates that the process has exited and contains both
// process id and exit status.
type ProcessExitedError struct {
Pid int
Status int
}
func (pe ProcessExitedError) Error() string {
2015-06-21 18:08:14 +00:00
return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status)
}
2015-02-02 21:09:56 +00:00
// Attach to an existing process with the given PID.
2015-06-20 22:54:52 +00:00
func Attach(pid int) (*Process, error) {
dbp, err := initializeDebugProcess(New(pid), "", true)
if err != nil {
return nil, err
}
return dbp, nil
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) Detach(kill bool) (err error) {
// Clean up any breakpoints we've set.
for _, bp := range dbp.Breakpoints {
if bp != nil {
2015-06-21 03:47:44 +00:00
_, err := dbp.ClearBreakpoint(bp.Addr)
if err != nil {
return err
}
}
}
dbp.execPtraceFunc(func() {
var sig int
if kill {
sig = int(sys.SIGINT)
}
err = PtraceDetach(dbp.Pid, sig)
})
return
}
// Returns whether or not Delve thinks the debugged
// process has exited.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Exited() bool {
return dbp.exited
}
2015-02-02 21:09:56 +00:00
// Returns whether or not Delve thinks the debugged
// process is currently executing.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Running() bool {
for _, th := range dbp.Threads {
if th.running {
return true
}
}
return false
}
// Finds the executable and then uses it
// to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
2015-06-20 22:54:52 +00:00
func (dbp *Process) LoadInformation(path string) error {
var wg sync.WaitGroup
exe, err := dbp.findExecutable(path)
if err != nil {
return err
}
wg.Add(3)
go dbp.parseDebugFrame(exe, &wg)
go dbp.obtainGoSymbols(exe, &wg)
go dbp.parseDebugLineInfo(exe, &wg)
wg.Wait()
return nil
}
// Find a location by string (file+line, function, breakpoint id, addr)
2015-06-20 22:54:52 +00:00
func (dbp *Process) 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
2015-03-24 13:31:56 +00:00
}
2015-03-24 13:31:56 +00:00
// Try to lookup by function name
fn := dbp.goSymTable.LookupFunc(str)
2015-03-24 13:31:56 +00:00
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)
}
if bp, ok := dbp.FindBreakpointByID(int(id)); ok {
return bp.Addr, nil
2015-03-24 13:31:56 +00:00
}
// Last resort, use as raw address
return id, nil
}
2015-02-02 21:09:56 +00:00
// Sends out a request that the debugged process halt
// execution. Sends SIGSTOP to all threads.
2015-06-20 22:54:52 +00:00
func (dbp *Process) RequestManualStop() error {
dbp.halt = true
return dbp.requestManualStop()
}
2015-01-14 02:37:10 +00:00
// Sets a breakpoint at addr, and stores it in the process wide
// break point table. Setting a break point must be thread specific due to
// ptrace actions needing the thread to be in a signal-delivery-stop.
//
// Depending on hardware support, Delve will choose to either
// set a hardware or software breakpoint. Essentially, if the
// hardware supports it, and there are free debug registers, Delve
// will set a hardware breakpoint. Otherwise we fall back to software
// breakpoints, which are a bit more work for us.
func (dbp *Process) SetBreakpoint(addr uint64) (*Breakpoint, error) {
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, false)
}
2015-04-23 01:00:42 +00:00
// Sets a temp breakpoint, for the 'next' command.
func (dbp *Process) SetTempBreakpoint(addr uint64) (*Breakpoint, error) {
return dbp.setBreakpoint(dbp.CurrentThread.Id, addr, true)
}
// Sets a breakpoint by location string (function, file+line, address)
func (dbp *Process) SetBreakpointByLocation(loc string) (*Breakpoint, error) {
addr, err := dbp.FindLocation(loc)
if err != nil {
return nil, err
}
return dbp.SetBreakpoint(addr)
}
2015-06-29 13:06:48 +00:00
// Clears a breakpoint.
//
// If it is a hardware assisted breakpoint, iterate through all threads
// and clear the debug register. Otherwise, restore original instruction.
func (dbp *Process) ClearBreakpoint(addr uint64) (*Breakpoint, error) {
bp, ok := dbp.Breakpoints[addr]
if !ok {
return nil, NoBreakpointError{addr: addr}
}
for _, thread := range dbp.Threads {
if _, err := bp.Clear(thread); err != nil {
return nil, err
}
if !bp.hardware {
break
}
}
if bp.hardware {
dbp.arch.SetHardwareBreakpointUsage(bp.reg, false)
}
delete(dbp.Breakpoints, addr)
return bp, nil
}
// Clears a breakpoint by location (function, file+line, address, breakpoint id)
func (dbp *Process) ClearBreakpointByLocation(loc string) (*Breakpoint, error) {
addr, err := dbp.FindLocation(loc)
if err != nil {
return nil, err
}
return dbp.ClearBreakpoint(addr)
}
// Returns the status of the current main thread context.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Status() *sys.WaitStatus {
return dbp.CurrentThread.Status
}
// Step over function calls.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Next() error {
return dbp.run(dbp.next)
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) next() error {
2015-04-23 14:17:14 +00:00
// Make sure we clean up the temp breakpoints created by thread.Next
defer dbp.clearTempBreakpoints()
2015-07-09 20:21:10 +00:00
// Set breakpoints for any goroutine that is currently
// blocked trying to read from a channel. This is so that
// if control flow switches to that goroutine, we end up
// somewhere useful instead of in runtime code.
2015-04-25 02:56:57 +00:00
chanRecvCount, err := dbp.setChanRecvBreakpoints()
if err != nil {
return err
}
2015-06-28 15:00:56 +00:00
g, err := dbp.CurrentThread.GetG()
if err != nil {
return err
}
if g.DeferPC != 0 {
_, err = dbp.SetTempBreakpoint(g.DeferPC)
if err != nil {
return err
}
}
var goroutineExiting bool
2015-04-25 02:56:57 +00:00
var waitCount int
for _, th := range dbp.Threads {
if th.blocked() {
// Ignore threads that aren't running go code.
continue
}
2015-04-25 02:56:57 +00:00
waitCount++
if err = th.SetNextBreakpoints(); err != nil {
if err, ok := err.(GoroutineExitingError); ok {
2015-04-25 02:56:57 +00:00
waitCount = waitCount - 1 + chanRecvCount
if err.goid == g.Id {
goroutineExiting = true
}
continue
}
return err
}
}
for _, th := range dbp.Threads {
if err = th.Continue(); err != nil {
return err
}
}
2015-04-25 02:56:57 +00:00
for waitCount > 0 {
thread, err := dbp.trapWait(-1)
if err != nil {
return err
2015-01-14 02:37:10 +00:00
}
2015-06-28 15:00:56 +00:00
tg, err := thread.GetG()
if err != nil {
return err
}
2015-04-23 15:54:26 +00:00
// Make sure we're on the same goroutine, unless it has exited.
if tg.Id == g.Id || goroutineExiting {
2015-04-03 15:32:43 +00:00
if dbp.CurrentThread != thread {
dbp.SwitchThread(thread.Id)
}
}
2015-04-25 02:56:57 +00:00
waitCount--
}
2015-04-25 02:56:57 +00:00
return dbp.Halt()
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) setChanRecvBreakpoints() (int, error) {
2015-04-25 02:56:57 +00:00
var count int
2015-04-20 21:58:49 +00:00
allg, err := dbp.GoroutinesInfo()
if err != nil {
2015-04-25 02:56:57 +00:00
return 0, err
2015-04-20 21:58:49 +00:00
}
for _, g := range allg {
if g.ChanRecvBlocked() {
ret, err := g.chanRecvReturnAddr(dbp)
if err != nil {
2015-05-07 21:55:06 +00:00
if _, ok := err.(NullAddrError); ok {
continue
}
2015-04-25 02:56:57 +00:00
return 0, err
2015-04-20 21:58:49 +00:00
}
if _, err = dbp.SetTempBreakpoint(ret); err != nil {
2015-04-25 02:56:57 +00:00
return 0, err
2015-04-20 21:58:49 +00:00
}
2015-04-25 02:56:57 +00:00
count++
2015-04-20 21:58:49 +00:00
}
}
2015-04-25 02:56:57 +00:00
return count, nil
2015-04-20 21:58:49 +00:00
}
// Resume process.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Continue() error {
for _, thread := range dbp.Threads {
err := thread.Continue()
if err != nil {
2015-06-26 12:46:46 +00:00
return fmt.Errorf("could not continue thread %d %s", thread.Id, err)
}
}
return dbp.run(dbp.resume)
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) resume() error {
thread, err := dbp.trapWait(-1)
if err != nil {
return err
}
if err := dbp.Halt(); err != nil {
return err
}
if dbp.CurrentThread != thread {
dbp.SwitchThread(thread.Id)
}
2015-04-23 15:40:33 +00:00
pc, err := thread.PC()
if err != nil {
return err
}
if dbp.CurrentBreakpoint() != nil || dbp.halt {
2015-04-13 22:17:06 +00:00
return dbp.Halt()
}
// 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++ {
if err = thread.Step(); err != nil {
return err
2015-03-01 04:03:26 +00:00
}
}
}
return nil
}
2015-04-23 01:00:42 +00:00
// Single step, will execute a single instruction.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Step() (err error) {
2015-01-14 02:37:10 +00:00
fn := func() error {
2015-04-25 19:53:55 +00:00
dbp.singleStepping = true
defer func() { dbp.singleStepping = false }()
2015-01-14 02:37:10 +00:00
for _, th := range dbp.Threads {
if th.blocked() {
continue
}
err := th.Step()
if err != nil {
return err
}
}
return nil
}
return dbp.run(fn)
}
// Change from current thread to the thread specified by `tid`.
2015-06-20 22:54:52 +00:00
func (dbp *Process) SwitchThread(tid int) error {
if th, ok := dbp.Threads[tid]; ok {
fmt.Printf("thread context changed from %d to %d\n", dbp.CurrentThread.Id, tid)
dbp.CurrentThread = th
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
2015-04-23 01:00:42 +00:00
// Returns an array of G structures representing the information
// Delve cares about from the internal runtime G structure.
2015-06-20 22:54:52 +00:00
func (dbp *Process) GoroutinesInfo() ([]*G, error) {
var (
threadg = map[int]*Thread{}
allg []*G
rdr = dbp.DwarfReader()
)
for i := range dbp.Threads {
if dbp.Threads[i].blocked() {
continue
}
2015-06-28 15:00:56 +00:00
g, _ := dbp.Threads[i].GetG()
if g != nil {
threadg[g.Id] = dbp.Threads[i]
}
}
addr, err := rdr.AddrFor("runtime.allglen")
if err != nil {
return nil, err
}
allglenBytes, err := dbp.CurrentThread.readMemory(uintptr(addr), 8)
if err != nil {
return nil, err
}
allglen := binary.LittleEndian.Uint64(allglenBytes)
rdr.Seek(0)
allgentryaddr, err := rdr.AddrFor("runtime.allg")
if err != nil {
return nil, err
}
faddr, err := dbp.CurrentThread.readMemory(uintptr(allgentryaddr), dbp.arch.PtrSize())
allgptr := binary.LittleEndian.Uint64(faddr)
for i := uint64(0); i < allglen; i++ {
g, err := parseG(dbp.CurrentThread, allgptr+(i*uint64(dbp.arch.PtrSize())), true)
if err != nil {
return nil, err
}
if thread, allocated := threadg[g.Id]; allocated {
loc, err := thread.Location()
if err != nil {
return nil, err
}
g.thread = thread
// Prefer actual thread location information.
g.File = loc.File
g.Line = loc.Line
g.Func = loc.Fn
}
allg = append(allg, g)
}
return allg, nil
}
2015-04-23 01:00:42 +00:00
// Stop all threads.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Halt() (err error) {
2015-04-13 22:17:06 +00:00
for _, th := range dbp.Threads {
if err := th.Halt(); err != nil {
return err
}
}
return nil
}
// Obtains register values from what Delve considers to be the current
// thread of the traced process.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Registers() (Registers, error) {
return dbp.CurrentThread.Registers()
}
// Returns the PC of the current thread.
2015-06-20 22:54:52 +00:00
func (dbp *Process) PC() (uint64, error) {
2015-04-23 15:40:33 +00:00
return dbp.CurrentThread.PC()
}
// Returns the PC of the current thread.
2015-06-20 22:54:52 +00:00
func (dbp *Process) CurrentBreakpoint() *Breakpoint {
return dbp.CurrentThread.CurrentBreakpoint
}
// Returns the value of the named symbol.
2015-06-20 22:54:52 +00:00
func (dbp *Process) EvalVariable(name string) (*Variable, error) {
2015-06-12 19:04:14 +00:00
return dbp.CurrentThread.EvalVariable(name)
}
// Returns a reader for the dwarf data
2015-06-20 22:54:52 +00:00
func (dbp *Process) DwarfReader() *reader.Reader {
return reader.New(dbp.dwarf)
}
2015-04-23 01:00:42 +00:00
// Returns list of source files that comprise the debugged binary.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Sources() map[string]*gosym.Obj {
return dbp.goSymTable.Files
}
2015-04-23 01:00:42 +00:00
// Returns list of functions present in the debugged program.
2015-06-20 22:54:52 +00:00
func (dbp *Process) Funcs() []gosym.Func {
return dbp.goSymTable.Funcs
}
2015-04-23 01:00:42 +00:00
// Converts an instruction address to a file/line/function.
2015-06-20 22:54:52 +00:00
func (dbp *Process) PCToLine(pc uint64) (string, int, *gosym.Func) {
return dbp.goSymTable.PCToLine(pc)
}
// Finds the breakpoint for the given ID.
2015-06-20 22:54:52 +00:00
func (dbp *Process) FindBreakpointByID(id int) (*Breakpoint, bool) {
for _, bp := range dbp.Breakpoints {
if bp.ID == id {
return bp, true
}
}
return nil, false
}
// Finds the breakpoint for the given pc.
2015-06-20 22:54:52 +00:00
func (dbp *Process) FindBreakpoint(pc uint64) (*Breakpoint, bool) {
// Check for software breakpoint. PC will be at
// breakpoint instruction + size of breakpoint.
if bp, ok := dbp.Breakpoints[pc-uint64(dbp.arch.BreakpointSize())]; ok {
return bp, true
}
// Check for hardware breakpoint. PC will equal
// the breakpoint address since the CPU will stop
// the process without executing the instruction at
// this address.
2015-06-12 19:32:32 +00:00
if bp, ok := dbp.Breakpoints[pc]; ok {
return bp, true
}
return nil, false
}
2015-06-20 22:54:52 +00:00
// Returns a new Process struct.
func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, error) {
2015-02-27 23:11:13 +00:00
if attach {
var err error
dbp.execPtraceFunc(func() { err = sys.PtraceAttach(dbp.Pid) })
2015-02-27 23:11:13 +00:00
if err != nil {
return nil, err
}
_, _, err = wait(dbp.Pid, dbp.Pid, 0)
2015-02-27 23:11:13 +00:00
if err != nil {
return nil, err
}
}
proc, err := os.FindProcess(dbp.Pid)
2015-02-27 23:11:13 +00:00
if err != nil {
return nil, err
}
dbp.Process = proc
err = dbp.LoadInformation(path)
2015-02-27 23:11:13 +00:00
if err != nil {
return nil, err
}
if err := dbp.updateThreadList(); err != nil {
return nil, err
}
switch runtime.GOARCH {
case "amd64":
dbp.arch = AMD64Arch()
}
return dbp, nil
2015-02-27 23:11:13 +00:00
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) clearTempBreakpoints() error {
2015-06-12 19:32:32 +00:00
for _, bp := range dbp.Breakpoints {
if !bp.Temp {
continue
}
if _, err := dbp.ClearBreakpoint(bp.Addr); err != nil {
return err
}
}
return nil
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) handleBreakpointOnThread(id int) (*Thread, error) {
2015-04-03 15:52:31 +00:00
thread, ok := dbp.Threads[id]
if !ok {
return nil, fmt.Errorf("could not find thread for %d", id)
2015-04-03 15:52:31 +00:00
}
2015-04-23 15:40:33 +00:00
pc, err := thread.PC()
2015-04-03 15:52:31 +00:00
if err != nil {
return nil, err
2015-04-03 15:52:31 +00:00
}
// Check to see if we have hit a breakpoint.
if bp, ok := dbp.FindBreakpoint(pc); ok {
thread.CurrentBreakpoint = bp
2015-04-23 15:54:26 +00:00
if err = thread.SetPC(bp.Addr); err != nil {
return nil, err
}
return thread, nil
}
if dbp.halt {
return thread, nil
2015-04-03 15:52:31 +00:00
}
2015-04-25 19:13:35 +00:00
fn := dbp.goSymTable.PCToFunc(pc)
if fn != nil && fn.Name == "runtime.breakpoint" {
2015-04-25 19:53:55 +00:00
thread.singleStepping = true
defer func() { thread.singleStepping = false }()
2015-04-25 19:13:35 +00:00
for i := 0; i < 2; i++ {
if err := thread.Step(); err != nil {
return nil, err
}
}
return thread, nil
}
2015-06-12 19:32:32 +00:00
return nil, NoBreakpointError{addr: pc}
2015-04-03 15:52:31 +00:00
}
2015-02-27 23:11:13 +00:00
2015-06-20 22:54:52 +00:00
func (dbp *Process) run(fn func() error) error {
if dbp.exited {
2015-05-04 22:31:13 +00:00
return fmt.Errorf("process has already exited")
}
for _, th := range dbp.Threads {
th.CurrentBreakpoint = nil
}
if err := fn(); err != nil {
return err
}
return nil
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) handlePtraceFuncs() {
// We must ensure here that we are running on the same thread during
// the execution of dbg. This is due to the fact that ptrace(2) expects
// all commands after PTRACE_ATTACH to come from the same thread.
runtime.LockOSThread()
for fn := range dbp.ptraceChan {
fn()
dbp.ptraceDoneChan <- nil
}
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) execPtraceFunc(fn func()) {
dbp.ptraceChan <- fn
<-dbp.ptraceDoneChan
}