delve/service/debugger/debugger.go

557 lines
14 KiB
Go
Raw Normal View History

package debugger
import (
"debug/gosym"
2015-07-03 19:18:03 +00:00
"errors"
"fmt"
"log"
"regexp"
2015-06-12 19:49:23 +00:00
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/service/api"
)
2015-06-20 22:50:24 +00:00
// Debugger service.
//
// Debugger provides a higher level of
2015-06-20 22:54:52 +00:00
// abstraction over proc.Process.
2015-06-20 22:50:24 +00:00
// It handles converting from internal types to
// the types expected by clients. It also handles
// functionality needed by clients, but not needed in
// lower lever packages such as proc.
type Debugger struct {
config *Config
2015-06-20 22:54:52 +00:00
process *proc.Process
}
// Config provides the configuration to start a Debugger.
//
// Only one of ProcessArgs or AttachPid should be specified. If ProcessArgs is
// provided, a new process will be launched. Otherwise, the debugger will try
// to attach to an existing process with AttachPid.
type Config struct {
// ProcessArgs are the arguments to launch a new process.
ProcessArgs []string
// AttachPid is the PID of an existing process to which the debugger should
// attach.
AttachPid int
}
// New creates a new Debugger.
func New(config *Config) (*Debugger, error) {
d := &Debugger{
config: config,
}
// Create the process by either attaching or launching.
if d.config.AttachPid > 0 {
log.Printf("attaching to pid %d", d.config.AttachPid)
2015-06-12 19:49:23 +00:00
p, err := proc.Attach(d.config.AttachPid)
if err != nil {
return nil, attachErrorMessage(d.config.AttachPid, err)
}
d.process = p
} else {
log.Printf("launching process with args: %v", d.config.ProcessArgs)
2015-06-12 19:49:23 +00:00
p, err := proc.Launch(d.config.ProcessArgs)
if err != nil {
return nil, fmt.Errorf("could not launch process: %s", err)
}
d.process = p
}
return d, nil
}
2016-01-10 08:57:52 +00:00
// ProcessPid returns the PID of the process
// the debugger is debugging.
func (d *Debugger) ProcessPid() int {
return d.process.Pid
}
2016-01-10 08:57:52 +00:00
// Detach detaches from the target process.
// If `kill` is true we will kill the process after
// detaching.
func (d *Debugger) Detach(kill bool) error {
if d.config.AttachPid != 0 {
return d.process.Detach(kill)
}
2016-01-10 08:57:52 +00:00
return d.process.Kill()
}
2016-01-10 08:57:52 +00:00
// Restart will restart the target process, first killing
// and then exec'ing it again.
func (d *Debugger) Restart() error {
if !d.process.Exited() {
if d.process.Running() {
d.process.Halt()
}
// Ensure the process is in a PTRACE_STOP.
2016-01-15 05:26:54 +00:00
if err := stopProcess(d.ProcessPid()); err != nil {
return err
}
if err := d.Detach(true); err != nil {
return err
}
}
p, err := proc.Launch(d.config.ProcessArgs)
if err != nil {
return fmt.Errorf("could not launch process: %s", err)
}
for addr, bp := range d.process.Breakpoints {
if bp.Temp {
continue
}
if _, err := p.SetBreakpoint(addr); err != nil {
return err
}
}
d.process = p
return nil
}
2016-01-10 08:57:52 +00:00
// State returns the current state of the debugger.
func (d *Debugger) State() (*api.DebuggerState, error) {
if d.process.Exited() {
return nil, proc.ProcessExitedError{Pid: d.ProcessPid()}
}
var (
state *api.DebuggerState
goroutine *api.Goroutine
)
if d.process.SelectedGoroutine != nil {
goroutine = api.ConvertGoroutine(d.process.SelectedGoroutine)
}
state = &api.DebuggerState{
SelectedGoroutine: goroutine,
Exited: d.process.Exited(),
}
for i := range d.process.Threads {
th := api.ConvertThread(d.process.Threads[i])
state.Threads = append(state.Threads, th)
2016-01-10 08:57:52 +00:00
if i == d.process.CurrentThread.ID {
state.CurrentThread = th
}
}
return state, nil
}
2016-01-10 08:57:52 +00:00
// CreateBreakpoint creates a breakpoint.
2015-06-12 19:32:32 +00:00
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
var (
createdBp *api.Breakpoint
addr uint64
err error
)
switch {
case len(requestedBp.File) > 0:
addr, err = d.process.FindFileLocation(requestedBp.File, requestedBp.Line)
case len(requestedBp.FunctionName) > 0:
if requestedBp.Line >= 0 {
addr, err = d.process.FindFunctionLocation(requestedBp.FunctionName, false, requestedBp.Line)
} else {
addr, err = d.process.FindFunctionLocation(requestedBp.FunctionName, true, 0)
}
default:
addr = requestedBp.Addr
}
if err != nil {
return nil, err
}
bp, err := d.process.SetBreakpoint(addr)
if err != nil {
return nil, err
}
2015-06-28 15:00:56 +00:00
bp.Tracepoint = requestedBp.Tracepoint
bp.Goroutine = requestedBp.Goroutine
bp.Stacktrace = requestedBp.Stacktrace
bp.Variables = requestedBp.Variables
bp.Cond = nil
createdBp = api.ConvertBreakpoint(bp)
log.Printf("created breakpoint: %#v", createdBp)
return createdBp, nil
}
2016-01-10 08:57:52 +00:00
// ClearBreakpoint clears a breakpoint.
2015-06-12 19:32:32 +00:00
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
var clearedBp *api.Breakpoint
bp, err := d.process.ClearBreakpoint(requestedBp.Addr)
if err != nil {
return nil, fmt.Errorf("Can't clear breakpoint @%x: %s", requestedBp.Addr, err)
}
clearedBp = api.ConvertBreakpoint(bp)
log.Printf("cleared breakpoint: %#v", clearedBp)
return clearedBp, err
}
2016-01-10 08:57:52 +00:00
// Breakpoints returns the list of current breakpoints.
2015-06-12 19:32:32 +00:00
func (d *Debugger) Breakpoints() []*api.Breakpoint {
bps := []*api.Breakpoint{}
for _, bp := range d.process.Breakpoints {
if bp.Temp {
continue
}
bps = append(bps, api.ConvertBreakpoint(bp))
}
return bps
}
2016-01-10 08:57:52 +00:00
// FindBreakpoint returns the breakpoint specified by 'id'.
2015-06-12 19:32:32 +00:00
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
for _, bp := range d.Breakpoints() {
if bp.ID == id {
return bp
}
}
return nil
}
2016-01-10 08:57:52 +00:00
// Threads returns the threads of the target process.
func (d *Debugger) Threads() []*api.Thread {
threads := []*api.Thread{}
for _, th := range d.process.Threads {
threads = append(threads, api.ConvertThread(th))
}
return threads
}
2016-01-10 08:57:52 +00:00
// FindThread returns the thread for the given 'id'.
func (d *Debugger) FindThread(id int) *api.Thread {
for _, thread := range d.Threads() {
if thread.ID == id {
return thread
}
}
return nil
}
2015-06-20 22:50:24 +00:00
// Command handles commands which control the debugger lifecycle
func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, error) {
var err error
switch command.Name {
case api.Continue:
log.Print("continuing")
err = d.process.Continue()
2015-06-28 15:00:56 +00:00
if err != nil {
if exitedErr, exited := err.(proc.ProcessExitedError); exited {
state := &api.DebuggerState{}
2015-07-03 19:18:03 +00:00
state.Exited = true
state.ExitStatus = exitedErr.Status
state.Err = errors.New(exitedErr.Error())
return state, nil
2015-06-28 15:00:56 +00:00
}
return nil, err
}
state, stateErr := d.State()
if stateErr != nil {
return state, stateErr
}
2015-06-28 15:00:56 +00:00
err = d.collectBreakpointInformation(state)
return state, err
case api.Next:
log.Print("nexting")
err = d.process.Next()
case api.Step:
log.Print("stepping")
err = d.process.Step()
case api.StepInstruction:
log.Print("single stepping")
err = d.process.StepInstruction()
case api.SwitchThread:
log.Printf("switching to thread %d", command.ThreadID)
err = d.process.SwitchThread(command.ThreadID)
case api.SwitchGoroutine:
log.Printf("switching to goroutine %d", command.GoroutineID)
err = d.process.SwitchGoroutine(command.GoroutineID)
case api.Halt:
// RequestManualStop does not invoke any ptrace syscalls, so it's safe to
// access the process directly.
log.Print("halting")
err = d.process.RequestManualStop()
}
if err != nil {
2015-06-21 18:08:14 +00:00
return nil, err
}
return d.State()
}
2015-06-28 15:00:56 +00:00
func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error {
if state == nil {
2015-06-28 15:00:56 +00:00
return nil
}
for i := range state.Threads {
if state.Threads[i].Breakpoint == nil {
continue
}
bp := state.Threads[i].Breakpoint
bpi := &api.BreakpointInfo{}
state.Threads[i].BreakpointInfo = bpi
2015-06-28 15:00:56 +00:00
if bp.Goroutine {
g, err := d.process.CurrentThread.GetG()
if err != nil {
return err
}
bpi.Goroutine = api.ConvertGoroutine(g)
2015-06-28 15:00:56 +00:00
}
if bp.Stacktrace > 0 {
rawlocs, err := d.process.CurrentThread.Stacktrace(bp.Stacktrace)
if err != nil {
return err
}
bpi.Stacktrace, err = d.convertStacktrace(rawlocs, false)
if err != nil {
return err
}
2015-06-28 15:00:56 +00:00
}
s, err := d.process.CurrentThread.Scope()
if err != nil {
return err
}
2015-06-28 15:00:56 +00:00
if len(bp.Variables) > 0 {
bpi.Variables = make([]api.Variable, len(bp.Variables))
}
for i := range bp.Variables {
v, err := s.EvalVariable(bp.Variables[i])
if err != nil {
return err
}
bpi.Variables[i] = *api.ConvertVar(v)
}
vars, err := s.FunctionArguments()
if err == nil {
bpi.Arguments = convertVars(vars)
2015-06-28 15:00:56 +00:00
}
}
2015-06-28 15:00:56 +00:00
return nil
}
2016-01-10 08:57:52 +00:00
// Sources returns a list of the source files for target binary.
func (d *Debugger) Sources(filter string) ([]string, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
files := []string{}
for f := range d.process.Sources() {
if regex.Match([]byte(f)) {
files = append(files, f)
}
}
return files, nil
}
2016-01-10 08:57:52 +00:00
// Functions returns a list of functions in the target process.
func (d *Debugger) Functions(filter string) ([]string, error) {
return regexFilterFuncs(filter, d.process.Funcs())
}
func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
funcs := []string{}
for _, f := range allFuncs {
if f.Sym != nil && regex.Match([]byte(f.Name)) {
funcs = append(funcs, f.Name)
}
}
return funcs, nil
}
2016-01-10 08:57:52 +00:00
// PackageVariables returns a list of package variables for the thread,
// optionally regexp filtered using regexp described in 'filter'.
func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
vars := []api.Variable{}
thread, found := d.process.Threads[threadID]
if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID)
}
scope, err := thread.Scope()
if err != nil {
return nil, err
}
pv, err := scope.PackageVariables()
if err != nil {
return nil, err
}
for _, v := range pv {
if regex.Match([]byte(v.Name)) {
vars = append(vars, *api.ConvertVar(v))
}
}
return vars, err
}
2016-01-10 08:57:52 +00:00
// Registers returns string representation of the CPU registers.
func (d *Debugger) Registers(threadID int) (string, error) {
thread, found := d.process.Threads[threadID]
if !found {
return "", fmt.Errorf("couldn't find thread %d", threadID)
}
regs, err := thread.Registers()
if err != nil {
return "", err
}
return regs.String(), err
}
func convertVars(pv []*proc.Variable) []api.Variable {
vars := make([]api.Variable, 0, len(pv))
for _, v := range pv {
vars = append(vars, *api.ConvertVar(v))
}
return vars
}
2016-01-10 08:57:52 +00:00
// LocalVariables returns a list of the local variables.
func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) {
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
if err != nil {
return nil, err
}
pv, err := s.LocalVariables()
if err != nil {
return nil, err
}
return convertVars(pv), err
}
2016-01-10 08:57:52 +00:00
// FunctionArguments returns the arguments to the current function.
func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error) {
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
if err != nil {
return nil, err
}
pv, err := s.FunctionArguments()
if err != nil {
return nil, err
}
return convertVars(pv), nil
}
2016-01-10 08:57:52 +00:00
// EvalVariableInScope will attempt to evaluate the variable represented by 'symbol'
// in the scope provided.
func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) {
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
if err != nil {
return nil, err
}
v, err := s.EvalVariable(symbol)
if err != nil {
return nil, err
}
return api.ConvertVar(v), err
}
2016-01-10 08:57:52 +00:00
// SetVariableInScope will set the value of the variable represented by
// 'symbol' to the value given, in the given scope.
func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) error {
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
if err != nil {
return err
}
return s.SetVariable(symbol, value)
}
2016-01-10 08:57:52 +00:00
// Goroutines will return a list of goroutines in the target process.
func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
goroutines := []*api.Goroutine{}
gs, err := d.process.GoroutinesInfo()
if err != nil {
return nil, err
}
for _, g := range gs {
goroutines = append(goroutines, api.ConvertGoroutine(g))
}
return goroutines, err
}
2016-01-10 08:57:52 +00:00
// Stacktrace returns a list of Stackframes for the given goroutine. The
// length of the returned list will be min(stack_len, depth).
// If 'full' is true, then local vars, function args, etc will be returned as well.
func (d *Debugger) Stacktrace(goroutineID, depth int, full bool) ([]api.Stackframe, error) {
var rawlocs []proc.Stackframe
2016-01-10 08:57:52 +00:00
g, err := d.process.FindGoroutine(goroutineID)
if err != nil {
return nil, err
}
if g == nil {
rawlocs, err = d.process.CurrentThread.Stacktrace(depth)
} else {
rawlocs, err = d.process.GoroutineStacktrace(g, depth)
}
if err != nil {
return nil, err
}
return d.convertStacktrace(rawlocs, full)
2015-06-28 15:00:56 +00:00
}
func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, full bool) ([]api.Stackframe, error) {
locations := make([]api.Stackframe, 0, len(rawlocs))
for i := range rawlocs {
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
if full {
var err error
scope := rawlocs[i].Scope(d.process.CurrentThread)
locals, err := scope.LocalVariables()
if err != nil {
return nil, err
}
arguments, err := scope.FunctionArguments()
if err != nil {
return nil, err
}
frame.Locals = convertVars(locals)
frame.Arguments = convertVars(arguments)
}
locations = append(locations, frame)
}
return locations, nil
}
2016-01-10 08:57:52 +00:00
// FindLocation will find the location specified by 'locStr'.
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) {
loc, err := parseLocationSpec(locStr)
if err != nil {
return nil, err
}
s, _ := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
locs, err := loc.Find(d, s, locStr)
for i := range locs {
file, line, fn := d.process.PCToLine(locs[i].PC)
locs[i].File = file
locs[i].Line = line
locs[i].Function = api.ConvertFunction(fn)
}
return locs, err
}