2015-03-20 21:11:11 +00:00
|
|
|
package debugger
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"regexp"
|
|
|
|
|
2015-06-12 19:49:23 +00:00
|
|
|
"github.com/derekparker/delve/proc"
|
2015-03-20 21:11:11 +00:00
|
|
|
"github.com/derekparker/delve/service/api"
|
|
|
|
)
|
|
|
|
|
2015-06-13 04:47:30 +00:00
|
|
|
// Debugger provides a conveniant way to convert from internal
|
|
|
|
// structure representations to API representations.
|
2015-03-20 21:11:11 +00:00
|
|
|
type Debugger struct {
|
2015-06-13 04:47:30 +00:00
|
|
|
config *Config
|
|
|
|
process *proc.DebuggedProcess
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2015-06-13 04:47:30 +00:00
|
|
|
func New(config *Config) (*Debugger, error) {
|
|
|
|
d := &Debugger{
|
|
|
|
config: config,
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
2015-03-20 21:11:11 +00:00
|
|
|
if err != nil {
|
2015-06-13 04:47:30 +00:00
|
|
|
return nil, fmt.Errorf("couldn't attach to pid %d: %s", d.config.AttachPid, err)
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
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)
|
2015-03-20 21:11:11 +00:00
|
|
|
if err != nil {
|
2015-06-13 04:47:30 +00:00
|
|
|
return nil, fmt.Errorf("couldn't launch process: %s", err)
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
d.process = p
|
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
return d, nil
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Debugger) Detach(kill bool) error {
|
2015-06-13 04:47:30 +00:00
|
|
|
return d.process.Detach(kill)
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Debugger) State() (*api.DebuggerState, error) {
|
2015-06-13 04:47:30 +00:00
|
|
|
var (
|
|
|
|
state *api.DebuggerState
|
|
|
|
thread *api.Thread
|
|
|
|
)
|
|
|
|
th := d.process.CurrentThread
|
|
|
|
if th != nil {
|
|
|
|
thread = api.ConvertThread(th)
|
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
|
2015-06-13 04:47:30 +00:00
|
|
|
var breakpoint *api.Breakpoint
|
|
|
|
bp := d.process.CurrentBreakpoint()
|
|
|
|
if bp != nil {
|
|
|
|
breakpoint = api.ConvertBreakpoint(bp)
|
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
|
2015-06-13 04:47:30 +00:00
|
|
|
state = &api.DebuggerState{
|
|
|
|
Breakpoint: breakpoint,
|
|
|
|
CurrentThread: thread,
|
|
|
|
Exited: d.process.Exited(),
|
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
|
2015-06-13 04:47:30 +00:00
|
|
|
return state, nil
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
2015-06-12 19:32:32 +00:00
|
|
|
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
|
|
|
var createdBp *api.Breakpoint
|
2015-06-13 04:47:30 +00:00
|
|
|
var loc string
|
|
|
|
switch {
|
|
|
|
case len(requestedBp.File) > 0:
|
|
|
|
loc = fmt.Sprintf("%s:%d", requestedBp.File, requestedBp.Line)
|
|
|
|
case len(requestedBp.FunctionName) > 0:
|
|
|
|
loc = requestedBp.FunctionName
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("no file or function name specified")
|
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
|
2015-06-13 04:47:30 +00:00
|
|
|
bp, err := d.process.BreakByLocation(loc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
createdBp = api.ConvertBreakpoint(bp)
|
|
|
|
log.Printf("created breakpoint: %#v", createdBp)
|
|
|
|
return createdBp, nil
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
2015-06-12 19:32:32 +00:00
|
|
|
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
|
|
|
var clearedBp *api.Breakpoint
|
2015-06-13 04:47:30 +00:00
|
|
|
bp, err := d.process.Clear(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)
|
2015-03-20 21:11:11 +00:00
|
|
|
return clearedBp, err
|
|
|
|
}
|
|
|
|
|
2015-06-12 19:32:32 +00:00
|
|
|
func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
|
|
|
bps := []*api.Breakpoint{}
|
2015-06-13 04:47:30 +00:00
|
|
|
for _, bp := range d.process.Breakpoints {
|
|
|
|
if bp.Temp {
|
|
|
|
continue
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
bps = append(bps, api.ConvertBreakpoint(bp))
|
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
return bps
|
|
|
|
}
|
|
|
|
|
2015-06-12 19:32:32 +00:00
|
|
|
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
|
|
|
for _, bp := range d.Breakpoints() {
|
2015-03-20 21:11:11 +00:00
|
|
|
if bp.ID == id {
|
|
|
|
return bp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Debugger) Threads() []*api.Thread {
|
|
|
|
threads := []*api.Thread{}
|
2015-06-13 04:47:30 +00:00
|
|
|
for _, th := range d.process.Threads {
|
|
|
|
threads = append(threads, api.ConvertThread(th))
|
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
return threads
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Debugger) FindThread(id int) *api.Thread {
|
|
|
|
for _, thread := range d.Threads() {
|
|
|
|
if thread.ID == id {
|
|
|
|
return thread
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Command handles commands which control the debugger lifecycle. Like other
|
|
|
|
// debugger operations, these are executed one at a time as part of the
|
|
|
|
// process operation pipeline.
|
|
|
|
//
|
|
|
|
// The one exception is the Halt command, which can be executed concurrently
|
|
|
|
// with any operation.
|
|
|
|
func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, error) {
|
|
|
|
var err error
|
|
|
|
switch command.Name {
|
|
|
|
case api.Continue:
|
2015-06-13 04:47:30 +00:00
|
|
|
log.Print("continuing")
|
|
|
|
err = d.process.Continue()
|
2015-03-20 21:11:11 +00:00
|
|
|
case api.Next:
|
2015-06-13 04:47:30 +00:00
|
|
|
log.Print("nexting")
|
|
|
|
err = d.process.Next()
|
2015-03-20 21:11:11 +00:00
|
|
|
case api.Step:
|
2015-06-13 04:47:30 +00:00
|
|
|
log.Print("stepping")
|
|
|
|
err = d.process.Step()
|
2015-03-20 21:11:11 +00:00
|
|
|
case api.SwitchThread:
|
2015-06-13 04:47:30 +00:00
|
|
|
log.Printf("switching to thread %d", command.ThreadID)
|
|
|
|
err = d.process.SwitchThread(command.ThreadID)
|
2015-03-20 21:11:11 +00:00
|
|
|
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 {
|
|
|
|
// Only report the error if it's not a process exit.
|
2015-06-12 19:49:23 +00:00
|
|
|
if _, exited := err.(proc.ProcessExitedError); !exited {
|
2015-03-20 21:11:11 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return d.State()
|
|
|
|
}
|
|
|
|
|
|
|
|
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{}
|
2015-06-13 04:47:30 +00:00
|
|
|
for f := range d.process.Sources() {
|
|
|
|
if regex.Match([]byte(f)) {
|
|
|
|
files = append(files, f)
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
return files, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Debugger) Functions(filter string) ([]string, error) {
|
|
|
|
regex, err := regexp.Compile(filter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
funcs := []string{}
|
2015-06-13 04:47:30 +00:00
|
|
|
for _, f := range d.process.Funcs() {
|
|
|
|
if f.Sym != nil && regex.Match([]byte(f.Name)) {
|
|
|
|
funcs = append(funcs, f.Name)
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
return funcs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
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{}
|
2015-06-13 04:47:30 +00:00
|
|
|
thread, found := d.process.Threads[threadID]
|
|
|
|
if !found {
|
|
|
|
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
|
|
|
}
|
|
|
|
pv, err := thread.PackageVariables()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, v := range pv {
|
|
|
|
if regex.Match([]byte(v.Name)) {
|
|
|
|
vars = append(vars, api.ConvertVar(v))
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
}
|
2015-03-20 21:11:11 +00:00
|
|
|
return vars, err
|
|
|
|
}
|
|
|
|
|
2015-06-19 07:20:10 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-05-08 20:16:22 +00:00
|
|
|
func (d *Debugger) LocalVariables(threadID int) ([]api.Variable, error) {
|
|
|
|
vars := []api.Variable{}
|
2015-06-13 04:47:30 +00:00
|
|
|
thread, found := d.process.Threads[threadID]
|
|
|
|
if !found {
|
|
|
|
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
|
|
|
}
|
|
|
|
pv, err := thread.LocalVariables()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, v := range pv {
|
|
|
|
vars = append(vars, api.ConvertVar(v))
|
|
|
|
}
|
2015-05-08 20:16:22 +00:00
|
|
|
return vars, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Debugger) FunctionArguments(threadID int) ([]api.Variable, error) {
|
|
|
|
vars := []api.Variable{}
|
2015-06-13 04:47:30 +00:00
|
|
|
thread, found := d.process.Threads[threadID]
|
|
|
|
if !found {
|
|
|
|
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
pv, err := thread.FunctionArguments()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
for _, v := range pv {
|
|
|
|
vars = append(vars, api.ConvertVar(v))
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
return vars, err
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
2015-06-13 04:47:30 +00:00
|
|
|
func (d *Debugger) EvalVariableInThread(threadID int, symbol string) (*api.Variable, error) {
|
|
|
|
thread, found := d.process.Threads[threadID]
|
|
|
|
if !found {
|
|
|
|
return nil, fmt.Errorf("couldn't find thread %d", threadID)
|
|
|
|
}
|
|
|
|
v, err := thread.EvalVariable(symbol)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
converted := api.ConvertVar(v)
|
|
|
|
return &converted, err
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
2015-06-13 04:47:30 +00:00
|
|
|
func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
|
|
|
|
goroutines := []*api.Goroutine{}
|
|
|
|
gs, err := d.process.GoroutinesInfo()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
for _, g := range gs {
|
|
|
|
goroutines = append(goroutines, api.ConvertGoroutine(g))
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-13 04:47:30 +00:00
|
|
|
return goroutines, err
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
2015-06-17 17:11:57 +00:00
|
|
|
|
|
|
|
func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
|
|
|
var rawlocs []proc.Location
|
|
|
|
var rawloc *proc.Location
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if goroutineId < 0 {
|
|
|
|
rawlocs, err = d.process.CurrentThread.Stacktrace(depth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rawloc, err = d.process.CurrentThread.Location()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
gs, err := d.process.GoroutinesInfo()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, g := range gs {
|
|
|
|
if g.Id == goroutineId {
|
|
|
|
rawlocs, err = d.process.GoroutineStacktrace(g, depth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rawloc = d.process.GoroutineLocation(g)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if rawlocs == nil {
|
|
|
|
return nil, fmt.Errorf("Unknown goroutine id %d\n", goroutineId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
locations := make([]api.Location, 0, len(rawlocs)+1)
|
|
|
|
|
|
|
|
locations = append(locations, api.ConvertLocation(*rawloc))
|
|
|
|
for i := range rawlocs {
|
|
|
|
rawlocs[i].Line--
|
|
|
|
locations = append(locations, api.ConvertLocation(rawlocs[i]))
|
|
|
|
}
|
|
|
|
|
|
|
|
return locations, nil
|
|
|
|
}
|