1
0
mirror of https://github.com/beego/bee.git synced 2025-06-26 19:10:19 +00:00

Update vendors

This commit is contained in:
MZI
2018-10-13 21:45:53 +08:00
parent bf5480b2df
commit db6c162b03
451 changed files with 139580 additions and 42578 deletions

View File

@ -2,15 +2,14 @@ package api
import (
"bytes"
"debug/gosym"
"go/constant"
"go/printer"
"go/token"
"reflect"
"strconv"
"github.com/derekparker/delve/proc"
"golang.org/x/debug/dwarf"
"github.com/derekparker/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/proc"
)
// ConvertBreakpoint converts from a proc.Breakpoint to
@ -46,7 +45,7 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
// ConvertThread converts a proc.Thread into an
// api thread.
func ConvertThread(th *proc.Thread) *Thread {
func ConvertThread(th proc.Thread) *Thread {
var (
function *Function
file string
@ -65,16 +64,16 @@ func ConvertThread(th *proc.Thread) *Thread {
var bp *Breakpoint
if th.CurrentBreakpoint != nil && th.BreakpointConditionMet {
bp = ConvertBreakpoint(th.CurrentBreakpoint)
if b := th.Breakpoint(); b.Active {
bp = ConvertBreakpoint(b.Breakpoint)
}
if g, _ := th.GetG(); g != nil {
if g, _ := proc.GetG(th); g != nil {
gid = g.ID
}
return &Thread{
ID: th.ID,
ID: th.ThreadID(),
PC: pc,
File: file,
Line: line,
@ -84,7 +83,7 @@ func ConvertThread(th *proc.Thread) *Thread {
}
}
func prettyTypeName(typ dwarf.Type) string {
func prettyTypeName(typ godwarf.Type) string {
if typ == nil {
return ""
}
@ -98,6 +97,19 @@ func prettyTypeName(typ dwarf.Type) string {
return r
}
func convertFloatValue(v *proc.Variable, sz int) string {
switch v.FloatSpecial {
case proc.FloatIsPosInf:
return "+Inf"
case proc.FloatIsNegInf:
return "-Inf"
case proc.FloatIsNaN:
return "NaN"
}
f, _ := constant.Float64Val(v.Value)
return strconv.FormatFloat(f, 'f', -1, sz)
}
// ConvertVar converts from proc.Variable to api.Variable.
func ConvertVar(v *proc.Variable) *Variable {
r := Variable{
@ -107,6 +119,11 @@ func ConvertVar(v *proc.Variable) *Variable {
Kind: v.Kind,
Len: v.Len,
Cap: v.Cap,
Flags: VariableFlags(v.Flags),
Base: v.Base,
LocationExpr: v.LocationExpr,
DeclLine: v.DeclLine,
}
r.Type = prettyTypeName(v.DwarfType)
@ -119,15 +136,16 @@ func ConvertVar(v *proc.Variable) *Variable {
if v.Value != nil {
switch v.Kind {
case reflect.Float32:
f, _ := constant.Float64Val(v.Value)
r.Value = strconv.FormatFloat(f, 'f', -1, 32)
r.Value = convertFloatValue(v, 32)
case reflect.Float64:
f, _ := constant.Float64Val(v.Value)
r.Value = strconv.FormatFloat(f, 'f', -1, 64)
r.Value = convertFloatValue(v, 64)
case reflect.String, reflect.Func:
r.Value = constant.StringVal(v.Value)
default:
r.Value = v.Value.String()
r.Value = v.ConstDescr()
if r.Value == "" {
r.Value = v.Value.String()
}
}
}
@ -136,30 +154,43 @@ func ConvertVar(v *proc.Variable) *Variable {
r.Children = make([]Variable, 2)
r.Len = 2
real, _ := constant.Float64Val(constant.Real(v.Value))
imag, _ := constant.Float64Val(constant.Imag(v.Value))
r.Children[0].Name = "real"
r.Children[0].Kind = reflect.Float32
r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 32)
r.Children[1].Name = "imaginary"
r.Children[1].Kind = reflect.Float32
r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 32)
if v.Value != nil {
real, _ := constant.Float64Val(constant.Real(v.Value))
r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 32)
imag, _ := constant.Float64Val(constant.Imag(v.Value))
r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 32)
} else {
r.Children[0].Value = "nil"
r.Children[1].Value = "nil"
}
case reflect.Complex128:
r.Children = make([]Variable, 2)
r.Len = 2
real, _ := constant.Float64Val(constant.Real(v.Value))
imag, _ := constant.Float64Val(constant.Imag(v.Value))
r.Children[0].Name = "real"
r.Children[0].Kind = reflect.Float64
r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 64)
r.Children[1].Name = "imaginary"
r.Children[1].Kind = reflect.Float64
r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 64)
if v.Value != nil {
real, _ := constant.Float64Val(constant.Real(v.Value))
r.Children[0].Value = strconv.FormatFloat(real, 'f', -1, 64)
imag, _ := constant.Float64Val(constant.Imag(v.Value))
r.Children[1].Value = strconv.FormatFloat(imag, 'f', -1, 64)
} else {
r.Children[0].Value = "nil"
r.Children[1].Value = "nil"
}
default:
r.Children = make([]Variable, len(v.Children))
@ -174,33 +205,43 @@ func ConvertVar(v *proc.Variable) *Variable {
// ConvertFunction converts from gosym.Func to
// api.Function.
func ConvertFunction(fn *gosym.Func) *Function {
func ConvertFunction(fn *proc.Function) *Function {
if fn == nil {
return nil
}
// fn here used to be a *gosym.Func, the fields Type and GoType below
// corresponded to the homonymous field of gosym.Func. Since the contents of
// those fields is not documented their value was replaced with 0 when
// gosym.Func was replaced by debug_info entries.
return &Function{
Name: fn.Name,
Type: fn.Type,
Value: fn.Value,
GoType: fn.GoType,
Name_: fn.Name,
Type: 0,
Value: fn.Entry,
GoType: 0,
Optimized: fn.Optimized(),
}
}
// ConvertGoroutine converts from proc.G to api.Goroutine.
func ConvertGoroutine(g *proc.G) *Goroutine {
th := g.Thread()
th := g.Thread
tid := 0
if th != nil {
tid = th.ID
tid = th.ThreadID()
}
return &Goroutine{
r := &Goroutine{
ID: g.ID,
CurrentLoc: ConvertLocation(g.CurrentLoc),
UserCurrentLoc: ConvertLocation(g.UserCurrent()),
GoStatementLoc: ConvertLocation(g.Go()),
StartLoc: ConvertLocation(g.StartLoc()),
ThreadID: tid,
}
if g.Unreadable != nil {
r.Unreadable = g.Unreadable.Error()
}
return r
}
// ConvertLocation converts from proc.Location to api.Location.
@ -213,6 +254,7 @@ func ConvertLocation(loc proc.Location) Location {
}
}
// ConvertAsmInstruction converts from proc.AsmInstruction to api.AsmInstruction.
func ConvertAsmInstruction(inst proc.AsmInstruction, text string) AsmInstruction {
var destloc *Location
if inst.DestLoc != nil {
@ -229,6 +271,7 @@ func ConvertAsmInstruction(inst proc.AsmInstruction, text string) AsmInstruction
}
}
// LoadConfigToProc converts an api.LoadConfig to proc.LoadConfig.
func LoadConfigToProc(cfg *LoadConfig) *proc.LoadConfig {
if cfg == nil {
return nil
@ -242,6 +285,7 @@ func LoadConfigToProc(cfg *LoadConfig) *proc.LoadConfig {
}
}
// LoadConfigFromProc converts a proc.LoadConfig to api.LoadConfig.
func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig {
if cfg == nil {
return nil
@ -255,6 +299,7 @@ func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig {
}
}
// ConvertRegisters converts proc.Register to api.Register for a slice.
func ConvertRegisters(in []proc.Register) (out []Register) {
out = make([]Register, len(in))
for i := range in {
@ -262,3 +307,8 @@ func ConvertRegisters(in []proc.Register) (out []Register) {
}
return
}
// ConvertCheckpoint converts proc.Chekcpoint to api.Checkpoint.
func ConvertCheckpoint(in proc.Checkpoint) (out Checkpoint) {
return Checkpoint(in)
}

View File

@ -34,11 +34,11 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
return
}
if !top && v.Addr == 0 {
if !top && v.Addr == 0 && v.Value == "" {
if includeType && v.Type != "void" {
fmt.Fprintf(buf, "%s nil", v.Type)
} else {
fmt.Fprintf(buf, "nil")
fmt.Fprint(buf, "nil")
}
return
}
@ -49,16 +49,20 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
case reflect.Array:
v.writeArrayTo(buf, newlines, includeType, indent)
case reflect.Ptr:
if v.Type == "" {
fmt.Fprintf(buf, "nil")
if v.Type == "" || len(v.Children) == 0 {
fmt.Fprint(buf, "nil")
} else if v.Children[0].OnlyAddr && v.Children[0].Addr != 0 {
fmt.Fprintf(buf, "(%s)(0x%x)", v.Type, v.Children[0].Addr)
} else {
fmt.Fprintf(buf, "*")
fmt.Fprint(buf, "*")
v.Children[0].writeTo(buf, false, newlines, includeType, indent)
}
case reflect.UnsafePointer:
fmt.Fprintf(buf, "unsafe.Pointer(0x%x)", v.Children[0].Addr)
if len(v.Children) == 0 {
fmt.Fprintf(buf, "unsafe.Pointer(nil)")
} else {
fmt.Fprintf(buf, "unsafe.Pointer(0x%x)", v.Children[0].Addr)
}
case reflect.String:
v.writeStringTo(buf)
case reflect.Chan:
@ -74,11 +78,17 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
case reflect.Struct:
v.writeStructTo(buf, newlines, includeType, indent)
case reflect.Interface:
if v.Addr == 0 {
// an escaped interface variable that points to nil, this shouldn't
// happen in normal code but can happen if the variable is out of scope.
fmt.Fprintf(buf, "nil")
return
}
if includeType {
if v.Children[0].Kind == reflect.Invalid {
fmt.Fprintf(buf, "%s ", v.Type)
if v.Children[0].Addr == 0 {
fmt.Fprintf(buf, "nil")
fmt.Fprint(buf, "nil")
return
}
} else {
@ -87,13 +97,17 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
}
data := v.Children[0]
if data.Kind == reflect.Ptr {
if data.Children[0].Addr == 0 {
fmt.Fprintf(buf, "nil")
if len(data.Children) == 0 {
fmt.Fprint(buf, "...")
} else if data.Children[0].Addr == 0 {
fmt.Fprint(buf, "nil")
} else if data.Children[0].OnlyAddr {
fmt.Fprintf(buf, "0x%x", v.Children[0].Addr)
} else {
v.Children[0].writeTo(buf, false, newlines, !includeType, indent)
}
} else if data.OnlyAddr {
fmt.Fprintf(buf, "*(*%q)(0x%x)", v.Type, v.Addr)
} else {
v.Children[0].writeTo(buf, false, newlines, !includeType, indent)
}
@ -101,7 +115,7 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden
v.writeMapTo(buf, newlines, includeType, indent)
case reflect.Func:
if v.Value == "" {
fmt.Fprintf(buf, "nil")
fmt.Fprint(buf, "nil")
} else {
fmt.Fprintf(buf, "%s", v.Value)
}
@ -128,6 +142,10 @@ func (v *Variable) writeSliceTo(buf io.Writer, newlines, includeType bool, inden
if includeType {
fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.Type, v.Len, v.Cap)
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
v.writeSliceOrArrayTo(buf, newlines, indent)
}
@ -150,7 +168,7 @@ func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, inde
nl := v.shouldNewlineStruct(newlines)
fmt.Fprintf(buf, "{")
fmt.Fprint(buf, "{")
for i := range v.Children {
if nl {
@ -159,9 +177,9 @@ func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, inde
fmt.Fprintf(buf, "%s: ", v.Children[i].Name)
v.Children[i].writeTo(buf, false, nl, true, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprintf(buf, ",")
fmt.Fprint(buf, ",")
if !nl {
fmt.Fprintf(buf, " ")
fmt.Fprint(buf, " ")
}
}
}
@ -170,22 +188,26 @@ func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, inde
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprintf(buf, ",")
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
}
fmt.Fprintf(buf, "}")
fmt.Fprint(buf, "}")
}
func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent string) {
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
nl := newlines && (len(v.Children) > 0)
fmt.Fprintf(buf, "[")
fmt.Fprint(buf, "[")
for i := 0; i < len(v.Children); i += 2 {
key := &v.Children[i]
@ -196,10 +218,10 @@ func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent
}
key.writeTo(buf, false, false, false, indent+indentString)
fmt.Fprintf(buf, ": ")
fmt.Fprint(buf, ": ")
value.writeTo(buf, false, nl, false, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprintf(buf, ", ")
fmt.Fprint(buf, ", ")
}
}
@ -208,18 +230,18 @@ func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprintf(buf, ",")
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2))
} else {
fmt.Fprintf(buf, "...")
fmt.Fprint(buf, "...")
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprintf(buf, "]")
fmt.Fprint(buf, "]")
}
func (v *Variable) shouldNewlineArray(newlines bool) bool {
@ -288,7 +310,7 @@ func (v *Variable) shouldNewlineStruct(newlines bool) bool {
func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent string) {
nl := v.shouldNewlineArray(newlines)
fmt.Fprintf(buf, "[")
fmt.Fprint(buf, "[")
for i := range v.Children {
if nl {
@ -296,7 +318,7 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri
}
v.Children[i].writeTo(buf, false, nl, false, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprintf(buf, ",")
fmt.Fprint(buf, ",")
}
}
@ -305,11 +327,11 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprintf(buf, ",")
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
} else {
fmt.Fprintf(buf, "...")
fmt.Fprint(buf, "...")
}
}
@ -317,5 +339,5 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprintf(buf, "]")
fmt.Fprint(buf, "]")
}

View File

@ -8,13 +8,17 @@ import (
"strconv"
"unicode"
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/pkg/proc"
)
var NotExecutableErr = proc.NotExecutableErr
// ErrNotExecutable is an error returned when trying
// to debug a non-executable file.
var ErrNotExecutable = proc.ErrNotExecutable
// DebuggerState represents the current context of the debugger.
type DebuggerState struct {
// Running is true if the process is running and no other information can be collected.
Running bool
// CurrentThread is the currently selected debugger thread.
CurrentThread *Thread `json:"currentThread,omitempty"`
// SelectedGoroutine is the currently selected goroutine
@ -29,6 +33,8 @@ type DebuggerState struct {
// Exited indicates whether the debugged process has exited.
Exited bool `json:"exited"`
ExitStatus int `json:"exitStatus"`
// When contains a description of the current position in a recording
When string
// Filled by RPCClient.Continue, indicates an error
Err error `json:"-"`
}
@ -71,6 +77,10 @@ type Breakpoint struct {
TotalHitCount uint64 `json:"totalHitCount"`
}
// ValidBreakpointName returns an error if
// the name to be chosen for a breakpoint is invalid.
// The name can not be just a number, and must contain a series
// of letters or numbers.
func ValidBreakpointName(name string) error {
if _, err := strconv.Atoi(name); err == nil {
return errors.New("breakpoint name can not be a number")
@ -104,9 +114,13 @@ type Thread struct {
// Breakpoint this thread is stopped at
Breakpoint *Breakpoint `json:"breakPoint,omitempty"`
// Informations requested by the current breakpoint
BreakpointInfo *BreakpointInfo `json:"breakPointInfo,omitrempty"`
BreakpointInfo *BreakpointInfo `json:"breakPointInfo,omitempty"`
// ReturnValues contains the return values of the function we just stepped out of
ReturnValues []Variable
}
// Location holds program location information.
type Location struct {
PC uint64 `json:"pc"`
File string `json:"file"`
@ -114,12 +128,32 @@ type Location struct {
Function *Function `json:"function,omitempty"`
}
// Stackframe describes one frame in a stack trace.
type Stackframe struct {
Location
Locals []Variable
Arguments []Variable
FrameOffset int64
FramePointerOffset int64
Defers []Defer
Bottom bool `json:"Bottom,omitempty"` // Bottom is true if this is the bottom frame of the stack
Err string
}
// Defer describes a deferred function.
type Defer struct {
DeferredLoc Location // deferred function
DeferLoc Location // location of the defer statement
SP uint64 // value of SP when the function was deferred
Unreadable string
}
// Var will return the variable described by 'name' within
// this stack frame.
func (frame *Stackframe) Var(name string) *Variable {
for i := range frame.Locals {
if frame.Locals[i].Name == name {
@ -137,12 +171,48 @@ func (frame *Stackframe) Var(name string) *Variable {
// Function represents thread-scoped function information.
type Function struct {
// Name is the function name.
Name string `json:"name"`
Name_ string `json:"name"`
Value uint64 `json:"value"`
Type byte `json:"type"`
GoType uint64 `json:"goType"`
// Optimized is true if the function was optimized
Optimized bool `json:"optimized"`
}
// Name will return the function name.
func (fn *Function) Name() string {
if fn == nil {
return "???"
}
return fn.Name_
}
// VariableFlags is the type of the Flags field of Variable.
type VariableFlags uint16
const (
// VariableEscaped is set for local variables that escaped to the heap
//
// The compiler performs escape analysis on local variables, the variables
// that may outlive the stack frame are allocated on the heap instead and
// only the address is recorded on the stack. These variables will be
// marked with this flag.
VariableEscaped = VariableFlags(proc.VariableEscaped)
// VariableShadowed is set for local variables that are shadowed by a
// variable with the same name in another scope
VariableShadowed = VariableFlags(proc.VariableShadowed)
// VariableConstant means this variable is a constant value
VariableConstant
// VariableArgument means this variable is a function argument
VariableArgument
// VariableReturnArgument means this variable is a function return value
VariableReturnArgument
)
// Variable describes a variable.
type Variable struct {
// Name of the variable or struct member
@ -156,6 +226,8 @@ type Variable struct {
// Type of the variable after resolving any typedefs
RealType string `json:"realType"`
Flags VariableFlags `json:"flags"`
Kind reflect.Kind `json:"kind"`
//Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string
@ -170,12 +242,23 @@ type Variable struct {
// Array and slice elements, member fields of structs, key/value pairs of maps, value of complex numbers
// The Name field in this slice will always be the empty string except for structs (when it will be the field name) and for complex numbers (when it will be "real" and "imaginary")
// For maps each map entry will have to items in this slice, even numbered items will represent map keys and odd numbered items will represent their values
// This field's length is capped at proc.maxArrayValues for slices and arrays and 2*proc.maxArrayValues for maps, in the circumnstances where the cap takes effect len(Children) != Len
// This field's length is capped at proc.maxArrayValues for slices and arrays and 2*proc.maxArrayValues for maps, in the circumstances where the cap takes effect len(Children) != Len
// The other length cap applied to this field is related to maximum recursion depth, when the maximum recursion depth is reached this field is left empty, contrary to the previous one this cap also applies to structs (otherwise structs will always have all their member fields returned)
Children []Variable `json:"children"`
// Base address of arrays, Base address of the backing array for slices (0 for nil slices)
// Base address of the backing byte array for strings
// address of the struct backing chan and map variables
// address of the function entry point for function variables (0 for nil function pointers)
Base uintptr `json:"base"`
// Unreadable addresses will have this field set
Unreadable string `json:"unreadable"`
// LocationExpr describes the location expression of this variable's address
LocationExpr string
// DeclLine is the line number of this variable's declaration
DeclLine int64
}
// LoadConfig describes how to load values from target's memory
@ -203,8 +286,11 @@ type Goroutine struct {
UserCurrentLoc Location `json:"userCurrentLoc"`
// Location of the go instruction that started this goroutine
GoStatementLoc Location `json:"goStatementLoc"`
// Location of the starting function
StartLoc Location `json:"startLoc"`
// ID of the associated thread for running goroutines
ThreadID int `json:"threadID"`
ThreadID int `json:"threadID"`
Unreadable string `json:"unreadable"`
}
// DebuggerCommand is a command which changes the debugger's execution state.
@ -217,9 +303,14 @@ type DebuggerCommand struct {
// GoroutineID is used to specify which thread to use with the SwitchGoroutine
// command.
GoroutineID int `json:"goroutineID,omitempty"`
// When ReturnInfoLoadConfig is not nil it will be used to load the value
// of any return variables.
ReturnInfoLoadConfig *LoadConfig
// Expr is the expression argument for a Call command
Expr string `json:"expr,omitempty"`
}
// Informations about the current breakpoint
// BreakpointInfo contains informations about the current breakpoint
type BreakpointInfo struct {
Stacktrace []Stackframe `json:"stacktrace,omitempty"`
Goroutine *Goroutine `json:"goroutine,omitempty"`
@ -228,6 +319,8 @@ type BreakpointInfo struct {
Locals []Variable `json:"locals,omitempty"`
}
// EvalScope is the scope a command should
// be evaluated in. Describes the goroutine and frame number.
type EvalScope struct {
GoroutineID int
Frame int
@ -236,11 +329,13 @@ type EvalScope struct {
const (
// Continue resumes process execution.
Continue = "continue"
// Rewind resumes process execution backwards (target must be a recording).
Rewind = "rewind"
// Step continues to next source line, entering function calls.
Step = "step"
// StepOut continues to the return address of the current function
StepOut = "stepOut"
// SingleStep continues for exactly 1 cpu instruction.
// StepInstruction continues for exactly 1 cpu instruction.
StepInstruction = "stepInstruction"
// Next continues to the next source line, not entering function calls.
Next = "next"
@ -250,12 +345,18 @@ const (
SwitchGoroutine = "switchGoroutine"
// Halt suspends the process.
Halt = "halt"
// Call resumes process execution injecting a function call.
Call = "call"
)
// AssemblyFlavour describes the output
// of disassembled code.
type AssemblyFlavour int
const (
GNUFlavour = AssemblyFlavour(proc.GNUFlavour)
// GNUFlavour will disassemble using GNU assembly syntax.
GNUFlavour = AssemblyFlavour(proc.GNUFlavour)
// IntelFlavour will disassemble using Intel assembly syntax.
IntelFlavour = AssemblyFlavour(proc.IntelFlavour)
)
@ -275,28 +376,35 @@ type AsmInstruction struct {
AtPC bool
}
// AsmInstructions is a slice of single instructions.
type AsmInstructions []AsmInstruction
// GetVersionIn is the argument for GetVersion.
type GetVersionIn struct {
}
// GetVersionOut is the result of GetVersion.
type GetVersionOut struct {
DelveVersion string
APIVersion int
}
// SetAPIVersionIn is the input for SetAPIVersion.
type SetAPIVersionIn struct {
APIVersion int
}
// SetAPIVersionOut is the output for SetAPIVersion.
type SetAPIVersionOut struct {
}
// Register holds information on a CPU register.
type Register struct {
Name string
Value string
}
// Registers is a list of CPU registers.
type Registers []Register
func (regs Registers) String() string {
@ -314,7 +422,17 @@ func (regs Registers) String() string {
return buf.String()
}
// DiscardedBreakpoint is a breakpoint that is not
// reinstated during a restart.
type DiscardedBreakpoint struct {
Breakpoint *Breakpoint
Reason string
}
// Checkpoint is a point in the program that
// can be returned to in certain execution modes.
type Checkpoint struct {
ID int
When string
Where string
}

View File

@ -20,18 +20,26 @@ type Client interface {
// Restarts program.
Restart() ([]api.DiscardedBreakpoint, error)
// Restarts program from the specified position.
RestartFrom(pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error)
// GetState returns the current debugger state.
GetState() (*api.DebuggerState, error)
// GetStateNonBlocking returns the current debugger state, returning immediately if the target is already running.
GetStateNonBlocking() (*api.DebuggerState, error)
// Continue resumes process execution.
Continue() <-chan *api.DebuggerState
// Rewind resumes process execution backwards.
Rewind() <-chan *api.DebuggerState
// Next continues to the next source line, not entering function calls.
Next() (*api.DebuggerState, error)
// Step continues to the next source line, entering function calls.
Step() (*api.DebuggerState, error)
// StepOut continues to the return address of the current function
StepOut() (*api.DebuggerState, error)
// Call resumes process execution while making a function call.
Call(expr string) (*api.DebuggerState, error)
// SingleStep will step a single cpu instruction.
StepInstruction() (*api.DebuggerState, error)
@ -90,7 +98,7 @@ type Client interface {
ListGoroutines() ([]*api.Goroutine, error)
// Returns stacktrace
Stacktrace(int, int, *api.LoadConfig) ([]api.Stackframe, error)
Stacktrace(goroutineID int, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error)
// Returns whether we attached to a running process or not
AttachedToExistingProcess() bool
@ -112,4 +120,25 @@ type Client interface {
DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error)
// Disassemble code of the function containing PC
DisassemblePC(scope api.EvalScope, pc uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error)
// Recorded returns true if the target is a recording.
Recorded() bool
// TraceDirectory returns the path to the trace directory for a recording.
TraceDirectory() (string, error)
// Checkpoint sets a checkpoint at the current position.
Checkpoint(where string) (checkpointID int, err error)
// ListCheckpoints gets all checkpoints.
ListCheckpoints() ([]api.Checkpoint, error)
// ClearCheckpoint removes a checkpoint
ClearCheckpoint(id int) error
// SetReturnValuesLoadConfig sets the load configuration for return values.
SetReturnValuesLoadConfig(*api.LoadConfig)
// IsMulticlien returns true if the headless instance is multiclient.
IsMulticlient() bool
// Disconnect closes the connection to the server without sending a Detach request first.
// If cont is true a continue command will be sent instead.
Disconnect(cont bool) error
}

View File

@ -25,4 +25,16 @@ type Config struct {
AcceptMulti bool
// APIVersion selects which version of the API to serve (default: 1).
APIVersion int
// CoreFile specifies the path to the core dump to open.
CoreFile string
// Selects server backend.
Backend string
// Foreground lets target process access stdin.
Foreground bool
// DisconnectChan will be closed by the server when the client disconnects
DisconnectChan chan<- struct{}
}

View File

@ -1,11 +1,11 @@
package debugger
import (
"debug/gosym"
"debug/dwarf"
"errors"
"fmt"
"go/parser"
"log"
"io/ioutil"
"path/filepath"
"regexp"
"runtime"
@ -13,8 +13,14 @@ import (
"sync"
"time"
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/pkg/goversion"
"github.com/derekparker/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/pkg/proc/core"
"github.com/derekparker/delve/pkg/proc/gdbserial"
"github.com/derekparker/delve/pkg/proc/native"
"github.com/derekparker/delve/service/api"
"github.com/sirupsen/logrus"
)
// Debugger service.
@ -26,9 +32,16 @@ import (
// functionality needed by clients, but not needed in
// lower lever packages such as proc.
type Debugger struct {
config *Config
config *Config
// arguments to launch a new process.
processArgs []string
// TODO(DO NOT MERGE WITHOUT) rename to targetMutex
processMutex sync.Mutex
process *proc.Process
target proc.Process
log *logrus.Entry
running bool
runningMutex sync.Mutex
}
// Config provides the configuration to start a Debugger.
@ -37,8 +50,6 @@ type Debugger struct {
// 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
// WorkingDir is working directory of the new process. This field is used
// only when launching a new process.
WorkingDir string
@ -46,46 +57,142 @@ type Config struct {
// AttachPid is the PID of an existing process to which the debugger should
// attach.
AttachPid int
// CoreFile specifies the path to the core dump to open.
CoreFile string
// Backend specifies the debugger backend.
Backend string
// Foreground lets target process access stdin.
Foreground bool
}
// New creates a new Debugger.
func New(config *Config) (*Debugger, error) {
// New creates a new Debugger. ProcessArgs specify the commandline arguments for the
// new process.
func New(config *Config, processArgs []string) (*Debugger, error) {
logger := logrus.New().WithFields(logrus.Fields{"layer": "debugger"})
logger.Logger.Level = logrus.DebugLevel
if !logflags.Debugger() {
logger.Logger.Out = ioutil.Discard
}
d := &Debugger{
config: config,
config: config,
processArgs: processArgs,
log: logger,
}
// Create the process by either attaching or launching.
if d.config.AttachPid > 0 {
log.Printf("attaching to pid %d", d.config.AttachPid)
p, err := proc.Attach(d.config.AttachPid)
switch {
case d.config.AttachPid > 0:
d.log.Infof("attaching to pid %d", d.config.AttachPid)
path := ""
if len(d.processArgs) > 0 {
path = d.processArgs[0]
}
p, err := d.Attach(d.config.AttachPid, path)
if err != nil {
err = go11DecodeErrorCheck(err)
return nil, attachErrorMessage(d.config.AttachPid, err)
}
d.process = p
} else {
log.Printf("launching process with args: %v", d.config.ProcessArgs)
p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir)
d.target = p
case d.config.CoreFile != "":
var p proc.Process
var err error
switch d.config.Backend {
case "rr":
d.log.Infof("opening trace %s", d.config.CoreFile)
p, err = gdbserial.Replay(d.config.CoreFile, false)
default:
d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0])
p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0])
}
if err != nil {
if err != proc.NotExecutableErr && err != proc.UnsupportedArchErr {
err = go11DecodeErrorCheck(err)
return nil, err
}
d.target = p
default:
d.log.Infof("launching process with args: %v", d.processArgs)
p, err := d.Launch(d.processArgs, d.config.WorkingDir)
if err != nil {
if err != proc.ErrNotExecutable && err != proc.ErrUnsupportedLinuxArch && err != proc.ErrUnsupportedWindowsArch && err != proc.ErrUnsupportedDarwinArch {
err = go11DecodeErrorCheck(err)
err = fmt.Errorf("could not launch process: %s", err)
}
return nil, err
}
d.process = p
d.target = p
}
return d, nil
}
// Launch will start a process with the given args and working directory.
func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) {
switch d.config.Backend {
case "native":
return native.Launch(processArgs, wd, d.config.Foreground)
case "lldb":
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground))
case "rr":
p, _, err := gdbserial.RecordAndReplay(processArgs, wd, false)
return p, err
case "default":
if runtime.GOOS == "darwin" {
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground))
}
return native.Launch(processArgs, wd, d.config.Foreground)
default:
return nil, fmt.Errorf("unknown backend %q", d.config.Backend)
}
}
// ErrNoAttachPath is the error returned when the client tries to attach to
// a process on macOS using the lldb backend without specifying the path to
// the target's executable.
var ErrNoAttachPath = errors.New("must specify executable path on macOS")
// Attach will attach to the process specified by 'pid'.
func (d *Debugger) Attach(pid int, path string) (proc.Process, error) {
switch d.config.Backend {
case "native":
return native.Attach(pid)
case "lldb":
return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path))
case "default":
if runtime.GOOS == "darwin" {
return betterGdbserialLaunchError(gdbserial.LLDBAttach(pid, path))
}
return native.Attach(pid)
default:
return nil, fmt.Errorf("unknown backend %q", d.config.Backend)
}
}
var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install XCode's command line tools or lldb-server")
func betterGdbserialLaunchError(p proc.Process, err error) (proc.Process, error) {
if runtime.GOOS != "darwin" {
return p, err
}
if _, isUnavailable := err.(*gdbserial.ErrBackendUnavailable); !isUnavailable {
return p, err
}
return p, errMacOSBackendUnavailable
}
// ProcessPid returns the PID of the process
// the debugger is debugging.
func (d *Debugger) ProcessPid() int {
return d.process.Pid
return d.target.Pid()
}
// LastModified returns the time that the process' executable was last
// modified.
func (d *Debugger) LastModified() time.Time {
return d.process.LastModified
return d.target.BinInfo().LastModified()
}
// Detach detaches from the target process.
@ -99,31 +206,42 @@ func (d *Debugger) Detach(kill bool) error {
}
func (d *Debugger) detach(kill bool) error {
if d.config.AttachPid != 0 {
return d.process.Detach(kill)
if d.config.AttachPid == 0 {
kill = true
}
return d.process.Kill()
return d.target.Detach(kill)
}
// Restart will restart the target process, first killing
// and then exec'ing it again.
func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) {
// If the target process is a recording it will restart it from the given
// position. If pos starts with 'c' it's a checkpoint ID, otherwise it's an
// event number. If resetArgs is true, newArgs will replace the process args.
func (d *Debugger) Restart(pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
if !d.process.Exited() {
if d.process.Running() {
d.process.Halt()
}
if recorded, _ := d.target.Recorded(); recorded {
return nil, d.target.Restart(pos)
}
if pos != "" {
return nil, proc.ErrNotRecorded
}
if valid, _ := d.target.Valid(); valid {
// Ensure the process is in a PTRACE_STOP.
if err := stopProcess(d.ProcessPid()); err != nil {
return nil, err
}
if err := d.detach(true); err != nil {
return nil, err
}
}
p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir)
if err := d.detach(true); err != nil {
return nil, err
}
if resetArgs {
d.processArgs = append([]string{d.processArgs[0]}, newArgs...)
}
p, err := d.Launch(d.processArgs, d.config.WorkingDir)
if err != nil {
return nil, fmt.Errorf("could not launch process: %s", err)
}
@ -133,9 +251,10 @@ func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) {
continue
}
if len(oldBp.File) > 0 {
oldBp.Addr, err = p.FindFileLocation(oldBp.File, oldBp.Line)
var err error
oldBp.Addr, err = proc.FindFileLocation(p, oldBp.File, oldBp.Line)
if err != nil {
discarded = append(discarded, api.DiscardedBreakpoint{oldBp, err.Error()})
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
continue
}
}
@ -147,20 +266,24 @@ func (d *Debugger) Restart() ([]api.DiscardedBreakpoint, error) {
return nil, err
}
}
d.process = p
d.target = p
return discarded, nil
}
// State returns the current state of the debugger.
func (d *Debugger) State() (*api.DebuggerState, error) {
func (d *Debugger) State(nowait bool) (*api.DebuggerState, error) {
if d.isRunning() && nowait {
return &api.DebuggerState{Running: true}, nil
}
d.processMutex.Lock()
defer d.processMutex.Unlock()
return d.state()
return d.state(nil)
}
func (d *Debugger) state() (*api.DebuggerState, error) {
if d.process.Exited() {
return nil, proc.ProcessExitedError{Pid: d.ProcessPid()}
func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error) {
if _, err := d.target.Valid(); err != nil {
return nil, err
}
var (
@ -168,28 +291,37 @@ func (d *Debugger) state() (*api.DebuggerState, error) {
goroutine *api.Goroutine
)
if d.process.SelectedGoroutine != nil {
goroutine = api.ConvertGoroutine(d.process.SelectedGoroutine)
if d.target.SelectedGoroutine() != nil {
goroutine = api.ConvertGoroutine(d.target.SelectedGoroutine())
}
exited := false
if _, err := d.target.Valid(); err != nil {
_, exited = err.(*proc.ErrProcessExited)
}
state = &api.DebuggerState{
SelectedGoroutine: goroutine,
Exited: d.process.Exited(),
Exited: exited,
}
for i := range d.process.Threads {
th := api.ConvertThread(d.process.Threads[i])
for _, thread := range d.target.ThreadList() {
th := api.ConvertThread(thread)
if retLoadCfg != nil {
th.ReturnValues = convertVars(thread.Common().ReturnValues(*retLoadCfg))
}
state.Threads = append(state.Threads, th)
if i == d.process.CurrentThread.ID {
if thread.ThreadID() == d.target.CurrentThread().ThreadID() {
state.CurrentThread = th
}
}
for _, bp := range d.process.Breakpoints {
if bp.Internal() {
state.NextInProgress = true
break
}
state.NextInProgress = d.target.Breakpoints().HasInternalBreakpoints()
if recorded, _ := d.target.Recorded(); recorded {
state.When, _ = d.target.When()
}
return state, nil
@ -221,19 +353,19 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
if runtime.GOOS == "windows" {
// Accept fileName which is case-insensitive and slash-insensitive match
fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
for symFile := range d.process.Sources() {
for _, symFile := range d.target.BinInfo().Sources {
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
fileName = symFile
break
}
}
}
addr, err = d.process.FindFileLocation(fileName, requestedBp.Line)
addr, err = proc.FindFileLocation(d.target, fileName, requestedBp.Line)
case len(requestedBp.FunctionName) > 0:
if requestedBp.Line >= 0 {
addr, err = d.process.FindFunctionLocation(requestedBp.FunctionName, false, requestedBp.Line)
addr, err = proc.FindFunctionLocation(d.target, requestedBp.FunctionName, false, requestedBp.Line)
} else {
addr, err = d.process.FindFunctionLocation(requestedBp.FunctionName, true, 0)
addr, err = proc.FindFunctionLocation(d.target, requestedBp.FunctionName, true, 0)
}
default:
addr = requestedBp.Addr
@ -243,21 +375,22 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
return nil, err
}
bp, err := d.process.SetBreakpoint(addr, proc.UserBreakpoint, nil)
bp, err := d.target.SetBreakpoint(addr, proc.UserBreakpoint, nil)
if err != nil {
return nil, err
}
if err := copyBreakpointInfo(bp, requestedBp); err != nil {
if _, err1 := d.process.ClearBreakpoint(bp.Addr); err1 != nil {
if _, err1 := d.target.ClearBreakpoint(bp.Addr); err1 != nil {
err = fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
}
return nil, err
}
createdBp = api.ConvertBreakpoint(bp)
log.Printf("created breakpoint: %#v", createdBp)
d.log.Infof("created breakpoint: %#v", createdBp)
return createdBp, nil
}
// AmendBreakpoint will update the breakpoint with the matching ID.
func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -272,8 +405,10 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
return copyBreakpointInfo(original, amend)
}
// CancelNext will clear internal breakpoints, thus cancelling the 'next',
// 'step' or 'stepout' operation.
func (d *Debugger) CancelNext() error {
return d.process.ClearInternalBreakpoints()
return d.target.ClearInternalBreakpoints()
}
func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err error) {
@ -297,12 +432,12 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
defer d.processMutex.Unlock()
var clearedBp *api.Breakpoint
bp, err := d.process.ClearBreakpoint(requestedBp.Addr)
bp, err := d.target.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)
d.log.Infof("cleared breakpoint: %#v", clearedBp)
return clearedBp, err
}
@ -315,11 +450,10 @@ func (d *Debugger) Breakpoints() []*api.Breakpoint {
func (d *Debugger) breakpoints() []*api.Breakpoint {
bps := []*api.Breakpoint{}
for _, bp := range d.process.Breakpoints {
if bp.Internal() {
continue
for _, bp := range d.target.Breakpoints().M {
if bp.IsUser() {
bps = append(bps, api.ConvertBreakpoint(bp))
}
bps = append(bps, api.ConvertBreakpoint(bp))
}
return bps
}
@ -337,7 +471,7 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
}
func (d *Debugger) findBreakpoint(id int) *proc.Breakpoint {
for _, bp := range d.process.Breakpoints {
for _, bp := range d.target.Breakpoints().M {
if bp.ID == id {
return bp
}
@ -366,11 +500,12 @@ func (d *Debugger) Threads() ([]*api.Thread, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
if d.process.Exited() {
return nil, &proc.ProcessExitedError{}
if _, err := d.target.Valid(); err != nil {
return nil, err
}
threads := []*api.Thread{}
for _, th := range d.process.Threads {
for _, th := range d.target.ThreadList() {
threads = append(threads, api.ConvertThread(th))
}
return threads, nil
@ -381,18 +516,30 @@ func (d *Debugger) FindThread(id int) (*api.Thread, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
if d.process.Exited() {
return nil, &proc.ProcessExitedError{}
if _, err := d.target.Valid(); err != nil {
return nil, err
}
for _, th := range d.process.Threads {
if th.ID == id {
for _, th := range d.target.ThreadList() {
if th.ThreadID() == id {
return api.ConvertThread(th), nil
}
}
return nil, nil
}
func (d *Debugger) setRunning(running bool) {
d.runningMutex.Lock()
d.running = running
d.runningMutex.Unlock()
}
func (d *Debugger) isRunning() bool {
d.runningMutex.Lock()
defer d.runningMutex.Unlock()
return d.running
}
// Command handles commands which control the debugger lifecycle
func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, error) {
var err error
@ -400,59 +547,77 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
if command.Name == 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()
d.log.Debug("halting")
err = d.target.RequestManualStop()
}
withBreakpointInfo := true
d.processMutex.Lock()
defer d.processMutex.Unlock()
d.setRunning(true)
defer d.setRunning(false)
switch command.Name {
case api.Continue:
log.Print("continuing")
err = d.process.Continue()
if err != nil {
if exitedErr, exited := err.(proc.ProcessExitedError); exited {
state := &api.DebuggerState{}
state.Exited = true
state.ExitStatus = exitedErr.Status
state.Err = errors.New(exitedErr.Error())
return state, nil
}
d.log.Debug("continuing")
err = proc.Continue(d.target)
case api.Call:
d.log.Debugf("function call %s", command.Expr)
err = proc.CallFunction(d.target, command.Expr, api.LoadConfigToProc(command.ReturnInfoLoadConfig))
case api.Rewind:
d.log.Debug("rewinding")
if err := d.target.Direction(proc.Backward); err != nil {
return nil, err
}
state, stateErr := d.state()
if stateErr != nil {
return state, stateErr
}
err = d.collectBreakpointInformation(state)
return state, err
defer func() {
d.target.Direction(proc.Forward)
}()
err = proc.Continue(d.target)
case api.Next:
log.Print("nexting")
err = d.process.Next()
d.log.Debug("nexting")
err = proc.Next(d.target)
case api.Step:
log.Print("stepping")
err = d.process.Step()
d.log.Debug("stepping")
err = proc.Step(d.target)
case api.StepInstruction:
log.Print("single stepping")
err = d.process.StepInstruction()
d.log.Debug("single stepping")
err = d.target.StepInstruction()
case api.StepOut:
log.Print("step out")
err = d.process.StepOut()
d.log.Debug("step out")
err = proc.StepOut(d.target)
case api.SwitchThread:
log.Printf("switching to thread %d", command.ThreadID)
err = d.process.SwitchThread(command.ThreadID)
d.log.Debugf("switching to thread %d", command.ThreadID)
err = d.target.SwitchThread(command.ThreadID)
withBreakpointInfo = false
case api.SwitchGoroutine:
log.Printf("switching to goroutine %d", command.GoroutineID)
err = d.process.SwitchGoroutine(command.GoroutineID)
d.log.Debugf("switching to goroutine %d", command.GoroutineID)
err = d.target.SwitchGoroutine(command.GoroutineID)
withBreakpointInfo = false
case api.Halt:
// RequestManualStop already called
withBreakpointInfo = false
}
if err != nil {
if exitedErr, exited := err.(proc.ErrProcessExited); command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread && exited {
state := &api.DebuggerState{}
state.Exited = true
state.ExitStatus = exitedErr.Status
state.Err = errors.New(exitedErr.Error())
return state, nil
}
return nil, err
}
return d.state()
state, stateErr := d.state(api.LoadConfigToProc(command.ReturnInfoLoadConfig))
if stateErr != nil {
return state, stateErr
}
if withBreakpointInfo {
err = d.collectBreakpointInformation(state)
}
return state, err
}
func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error {
@ -461,7 +626,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
}
for i := range state.Threads {
if state.Threads[i].Breakpoint == nil {
if state.Threads[i].Breakpoint == nil || state.Threads[i].BreakpointInfo != nil {
continue
}
@ -470,7 +635,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
state.Threads[i].BreakpointInfo = bpi
if bp.Goroutine {
g, err := d.process.CurrentThread.GetG()
g, err := proc.GetG(d.target.CurrentThread())
if err != nil {
return err
}
@ -478,7 +643,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
}
if bp.Stacktrace > 0 {
rawlocs, err := d.process.CurrentThread.Stacktrace(bp.Stacktrace)
rawlocs, err := proc.ThreadStacktrace(d.target.CurrentThread(), bp.Stacktrace)
if err != nil {
return err
}
@ -488,7 +653,17 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
}
}
s, err := d.process.Threads[state.Threads[i].ID].Scope()
thread, found := d.target.FindThread(state.Threads[i].ID)
if !found {
return fmt.Errorf("could not find thread %d", state.Threads[i].ID)
}
if len(bp.Variables) == 0 && bp.LoadArgs == nil && bp.LoadLocals == nil {
// don't try to create goroutine scope if there is nothing to load
continue
}
s, err := proc.GoroutineScope(thread)
if err != nil {
return err
}
@ -497,11 +672,12 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
bpi.Variables = make([]api.Variable, len(bp.Variables))
}
for i := range bp.Variables {
v, err := s.EvalVariable(bp.Variables[i], proc.LoadConfig{true, 1, 64, 64, -1})
v, err := s.EvalVariable(bp.Variables[i], proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1})
if err != nil {
return err
bpi.Variables[i] = api.Variable{Name: bp.Variables[i], Unreadable: fmt.Sprintf("eval error: %v", err)}
} else {
bpi.Variables[i] = *api.ConvertVar(v)
}
bpi.Variables[i] = *api.ConvertVar(v)
}
if bp.LoadArgs != nil {
if vars, err := s.FunctionArguments(*api.LoadConfigToProc(bp.LoadArgs)); err == nil {
@ -529,7 +705,7 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
}
files := []string{}
for f := range d.process.Sources() {
for _, f := range d.target.BinInfo().Sources {
if regex.Match([]byte(f)) {
files = append(files, f)
}
@ -542,9 +718,10 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return regexFilterFuncs(filter, d.process.Funcs())
return regexFilterFuncs(filter, d.target.BinInfo().Functions)
}
// Types returns all type information in the binary.
func (d *Debugger) Types(filter string) ([]string, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -554,7 +731,7 @@ func (d *Debugger) Types(filter string) ([]string, error) {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
types, err := d.process.Types()
types, err := d.target.BinInfo().Types()
if err != nil {
return nil, err
}
@ -569,7 +746,7 @@ func (d *Debugger) Types(filter string) ([]string, error) {
return r, nil
}
func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
@ -577,7 +754,7 @@ func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
funcs := []string{}
for _, f := range allFuncs {
if f.Sym != nil && regex.Match([]byte(f.Name)) {
if regex.Match([]byte(f.Name)) {
funcs = append(funcs, f.Name)
}
}
@ -596,11 +773,11 @@ func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadCo
}
vars := []api.Variable{}
thread, found := d.process.Threads[threadID]
thread, found := d.target.FindThread(threadID)
if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID)
}
scope, err := thread.Scope()
scope, err := proc.ThreadScope(thread)
if err != nil {
return nil, err
}
@ -621,7 +798,7 @@ func (d *Debugger) Registers(threadID int, floatingPoint bool) (api.Registers, e
d.processMutex.Lock()
defer d.processMutex.Unlock()
thread, found := d.process.Threads[threadID]
thread, found := d.target.FindThread(threadID)
if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID)
}
@ -633,6 +810,9 @@ func (d *Debugger) Registers(threadID int, floatingPoint bool) (api.Registers, e
}
func convertVars(pv []*proc.Variable) []api.Variable {
if pv == nil {
return nil
}
vars := make([]api.Variable, 0, len(pv))
for _, v := range pv {
vars = append(vars, *api.ConvertVar(v))
@ -645,7 +825,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope, cfg proc.LoadConfig) ([]a
d.processMutex.Lock()
defer d.processMutex.Unlock()
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame)
if err != nil {
return nil, err
}
@ -661,7 +841,7 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope, cfg proc.LoadConfig) (
d.processMutex.Lock()
defer d.processMutex.Unlock()
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame)
if err != nil {
return nil, err
}
@ -678,7 +858,7 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string, cfg p
d.processMutex.Lock()
defer d.processMutex.Unlock()
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame)
if err != nil {
return nil, err
}
@ -695,7 +875,7 @@ func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string)
d.processMutex.Lock()
defer d.processMutex.Unlock()
s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
s, err := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame)
if err != nil {
return err
}
@ -708,7 +888,7 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
defer d.processMutex.Unlock()
goroutines := []*api.Goroutine{}
gs, err := d.process.GoroutinesInfo()
gs, err := proc.GoroutinesInfo(d.target)
if err != nil {
return nil, err
}
@ -721,21 +901,25 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
// 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, cfg *proc.LoadConfig) ([]api.Stackframe, error) {
func (d *Debugger) Stacktrace(goroutineID, depth int, readDefers bool, cfg *proc.LoadConfig) ([]api.Stackframe, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
if _, err := d.target.Valid(); err != nil {
return nil, err
}
var rawlocs []proc.Stackframe
g, err := d.process.FindGoroutine(goroutineID)
g, err := proc.FindGoroutine(d.target, goroutineID)
if err != nil {
return nil, err
}
if g == nil {
rawlocs, err = d.process.CurrentThread.Stacktrace(depth)
rawlocs, err = proc.ThreadStacktrace(d.target.CurrentThread(), depth)
} else {
rawlocs, err = g.Stacktrace(depth)
rawlocs, err = g.Stacktrace(depth, readDefers)
}
if err != nil {
return nil, err
@ -747,10 +931,22 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, cfg *proc.LoadConfig) ([]a
func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadConfig) ([]api.Stackframe, error) {
locations := make([]api.Stackframe, 0, len(rawlocs))
for i := range rawlocs {
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
if cfg != nil {
frame := api.Stackframe{
Location: api.ConvertLocation(rawlocs[i].Call),
FrameOffset: rawlocs[i].FrameOffset(),
FramePointerOffset: rawlocs[i].FramePointerOffset(),
Defers: d.convertDefers(rawlocs[i].Defers),
Bottom: rawlocs[i].Bottom,
}
if rawlocs[i].Err != nil {
frame.Err = rawlocs[i].Err.Error()
}
if cfg != nil && rawlocs[i].Current.Fn != nil {
var err error
scope := rawlocs[i].Scope(d.process.CurrentThread)
scope := proc.FrameToScope(d.target.BinInfo(), d.target.CurrentThread(), nil, rawlocs[i:]...)
locals, err := scope.LocalVariables(*cfg)
if err != nil {
return nil, err
@ -769,21 +965,55 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
return locations, nil
}
func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
r := make([]api.Defer, len(defers))
for i := range defers {
ddf, ddl, ddfn := d.target.BinInfo().PCToLine(defers[i].DeferredPC)
drf, drl, drfn := d.target.BinInfo().PCToLine(defers[i].DeferPC)
r[i] = api.Defer{
DeferredLoc: api.ConvertLocation(proc.Location{
PC: defers[i].DeferredPC,
File: ddf,
Line: ddl,
Fn: ddfn,
}),
DeferLoc: api.ConvertLocation(proc.Location{
PC: defers[i].DeferPC,
File: drf,
Line: drl,
Fn: drfn,
}),
SP: defers[i].SP,
}
if defers[i].Unreadable != nil {
r[i].Unreadable = defers[i].Unreadable.Error()
}
}
return r
}
// FindLocation will find the location specified by 'locStr'.
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
if _, err := d.target.Valid(); err != nil {
return nil, err
}
loc, err := parseLocationSpec(locStr)
if err != nil {
return nil, err
}
s, _ := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
s, _ := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame)
locs, err := loc.Find(d, s, locStr)
for i := range locs {
file, line, fn := d.process.PCToLine(locs[i].PC)
file, line, fn := d.target.BinInfo().PCToLine(locs[i].PC)
locs[i].File = file
locs[i].Line = line
locs[i].Function = api.ConvertFunction(fn)
@ -791,14 +1021,18 @@ func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Locat
return locs, err
}
// Disassembles code between startPC and endPC
// Disassemble code between startPC and endPC
// if endPC == 0 it will find the function containing startPC and disassemble the whole function
func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
if _, err := d.target.Valid(); err != nil {
return nil, err
}
if endPC == 0 {
_, _, fn := d.process.PCToLine(startPC)
_, _, fn := d.target.BinInfo().PCToLine(startPC)
if fn == nil {
return nil, fmt.Errorf("Address 0x%x does not belong to any function", startPC)
}
@ -806,28 +1040,69 @@ func (d *Debugger) Disassemble(scope api.EvalScope, startPC, endPC uint64, flavo
endPC = fn.End
}
currentGoroutine := true
thread := d.process.CurrentThread
if s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame); err == nil {
thread = s.Thread
if scope.GoroutineID != -1 {
g, _ := s.Thread.GetG()
if g == nil || g.ID != scope.GoroutineID {
currentGoroutine = false
}
}
g, err := proc.FindGoroutine(d.target, scope.GoroutineID)
if err != nil {
return nil, err
}
insts, err := thread.Disassemble(startPC, endPC, currentGoroutine)
insts, err := proc.Disassemble(d.target, g, startPC, endPC)
if err != nil {
return nil, err
}
disass := make(api.AsmInstructions, len(insts))
for i := range insts {
disass[i] = api.ConvertAsmInstruction(insts[i], insts[i].Text(proc.AssemblyFlavour(flavour)))
disass[i] = api.ConvertAsmInstruction(insts[i], insts[i].Text(proc.AssemblyFlavour(flavour), d.target.BinInfo()))
}
return disass, nil
}
// Recorded returns true if the target is a recording.
func (d *Debugger) Recorded() (recorded bool, tracedir string) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return d.target.Recorded()
}
// Checkpoint will set a checkpoint specified by the locspec.
func (d *Debugger) Checkpoint(where string) (int, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return d.target.Checkpoint(where)
}
// Checkpoints will return a list of checkpoints.
func (d *Debugger) Checkpoints() ([]api.Checkpoint, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
cps, err := d.target.Checkpoints()
if err != nil {
return nil, err
}
r := make([]api.Checkpoint, len(cps))
for i := range cps {
r[i] = api.ConvertCheckpoint(cps[i])
}
return r, nil
}
// ClearCheckpoint will clear the checkpoint of the given ID.
func (d *Debugger) ClearCheckpoint(id int) error {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return d.target.ClearCheckpoint(id)
}
func go11DecodeErrorCheck(err error) error {
if _, isdecodeerr := err.(dwarf.DecodeError); !isdecodeerr {
return err
}
gover, ok := goversion.Installed()
if !ok || !gover.AfterOrEqual(goversion.GoVersion{1, 11, -1, 0, 0, ""}) || goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
return err
}
return fmt.Errorf("executables built by Go 1.11 or later need Delve built by Go 1.11 or later")
}

View File

@ -2,10 +2,11 @@ package debugger
import (
"fmt"
sys "golang.org/x/sys/unix"
"io/ioutil"
"os"
"syscall"
sys "golang.org/x/sys/unix"
)
func attachErrorMessage(pid int, err error) error {
@ -16,7 +17,7 @@ func attachErrorMessage(pid int, err error) error {
bs, err := ioutil.ReadFile("/proc/sys/kernel/yama/ptrace_scope")
if err == nil && len(bs) >= 1 && bs[0] != '0' {
// Yama documentation: https://www.kernel.org/doc/Documentation/security/Yama.txt
return fmt.Errorf("Could not attach to pid %d: set /proc/sys/kernel/yama/ptrace_scope to 0", pid)
return fmt.Errorf("Could not attach to pid %d: this could be caused by a kernel security setting, try writing \"0\" to /proc/sys/kernel/yama/ptrace_scope", pid)
}
fi, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
if err != nil {

View File

@ -1,7 +1,6 @@
package debugger
import (
"debug/gosym"
"fmt"
"go/constant"
"path/filepath"
@ -10,7 +9,7 @@ import (
"strconv"
"strings"
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/service/api"
)
@ -178,7 +177,7 @@ func parseFuncLocationSpec(in string) *FuncLocationSpec {
r := stripReceiverDecoration(v[0])
if r != v[0] {
spec.ReceiverName = r
} else if strings.Index(r, "/") >= 0 {
} else if strings.Contains(r, "/") {
spec.PackageName = r
} else {
spec.PackageOrReceiverName = r
@ -198,7 +197,7 @@ func parseFuncLocationSpec(in string) *FuncLocationSpec {
spec.AbsolutePackage = true
}
if strings.Index(spec.BaseName, "/") >= 0 || strings.Index(spec.ReceiverName, "/") >= 0 {
if strings.Contains(spec.BaseName, "/") || strings.Contains(spec.ReceiverName, "/") {
return nil
}
@ -216,7 +215,7 @@ func stripReceiverDecoration(in string) string {
return in[2 : len(in)-1]
}
func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
func (spec *FuncLocationSpec) Match(sym proc.Function) bool {
if spec.BaseName != sym.BaseName() {
return false
}
@ -243,14 +242,14 @@ func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
}
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
funcs := d.process.Funcs()
funcs := d.target.BinInfo().Functions
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
if err != nil {
return nil, err
}
r := make([]api.Location, 0, len(matches))
for i := range matches {
addr, err := d.process.FindFunctionLocation(matches[i], true, 0)
addr, err := proc.FindFunctionLocation(d.target, matches[i], true, 0)
if err == nil {
r = append(r, api.Location{PC: addr})
}
@ -278,8 +277,8 @@ func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
addr, _ := constant.Uint64Val(v.Value)
return []api.Location{{PC: addr}}, nil
case reflect.Func:
_, _, fn := d.process.PCToLine(uint64(v.Base))
pc, err := d.process.FirstPCAfterPrologue(fn, false)
_, _, fn := d.target.BinInfo().PCToLine(uint64(v.Base))
pc, err := proc.FirstPCAfterPrologue(d.target, fn, false)
if err != nil {
return nil, err
}
@ -317,7 +316,7 @@ func (ale AmbiguousLocationError) Error() string {
var candidates []string
if ale.CandidatesLocation != nil {
for i := range ale.CandidatesLocation {
candidates = append(candidates, ale.CandidatesLocation[i].Function.Name)
candidates = append(candidates, ale.CandidatesLocation[i].Function.Name())
}
} else {
@ -327,74 +326,85 @@ func (ale AmbiguousLocationError) Error() string {
}
func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
funcs := d.process.Funcs()
files := d.process.Sources()
candidates := []string{}
for file := range files {
limit := maxFindLocationCandidates
var candidateFiles []string
for _, file := range d.target.BinInfo().Sources {
if loc.FileMatch(file) {
candidates = append(candidates, file)
if len(candidates) >= maxFindLocationCandidates {
candidateFiles = append(candidateFiles, file)
if len(candidateFiles) >= limit {
break
}
}
}
limit -= len(candidateFiles)
var candidateFuncs []string
if loc.FuncBase != nil {
for _, f := range funcs {
if f.Sym == nil {
for _, f := range d.target.BinInfo().Functions {
if !loc.FuncBase.Match(f) {
continue
}
if loc.FuncBase.Match(f.Sym) {
if loc.Base == f.Name {
// if an exact match for the function name is found use it
candidates = []string{f.Name}
break
}
if len(candidates) < maxFindLocationCandidates {
candidates = append(candidates, f.Name)
}
if loc.Base == f.Name {
// if an exact match for the function name is found use it
candidateFuncs = []string{f.Name}
break
}
candidateFuncs = append(candidateFuncs, f.Name)
if len(candidateFuncs) >= limit {
break
}
}
}
switch len(candidates) {
case 1:
var addr uint64
var err error
if filepath.IsAbs(candidates[0]) {
if loc.LineOffset < 0 {
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
}
addr, err = d.process.FindFileLocation(candidates[0], loc.LineOffset)
} else {
if loc.LineOffset < 0 {
addr, err = d.process.FindFunctionLocation(candidates[0], true, 0)
} else {
addr, err = d.process.FindFunctionLocation(candidates[0], false, loc.LineOffset)
}
}
if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
// if no result was found treat this locations string could be an
// expression that the user forgot to prefix with '*', try treating it as
// such.
addrSpec := &AddrLocationSpec{locStr}
locs, err := addrSpec.Find(d, scope, locStr)
if err != nil {
return nil, err
return nil, fmt.Errorf("Location \"%s\" not found", locStr)
}
return []api.Location{{PC: addr}}, nil
case 0:
return nil, fmt.Errorf("Location \"%s\" not found", locStr)
default:
return nil, AmbiguousLocationError{Location: locStr, CandidatesString: candidates}
return locs, nil
} else if matching > 1 {
return nil, AmbiguousLocationError{Location: locStr, CandidatesString: append(candidateFiles, candidateFuncs...)}
}
// len(candidateFiles) + len(candidateFuncs) == 1
var addr uint64
var err error
if len(candidateFiles) == 1 {
if loc.LineOffset < 0 {
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
}
addr, err = proc.FindFileLocation(d.target, candidateFiles[0], loc.LineOffset)
} else { // len(candidateFUncs) == 1
if loc.LineOffset < 0 {
addr, err = proc.FindFunctionLocation(d.target, candidateFuncs[0], true, 0)
} else {
addr, err = proc.FindFunctionLocation(d.target, candidateFuncs[0], false, loc.LineOffset)
}
}
if err != nil {
return nil, err
}
return []api.Location{{PC: addr}}, nil
}
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
if scope == nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)")
}
file, line, fn := d.process.PCToLine(scope.PC)
if loc.Offset == 0 {
return []api.Location{{PC: scope.PC}}, nil
}
file, line, fn := d.target.BinInfo().PCToLine(scope.PC)
if fn == nil {
return nil, fmt.Errorf("could not determine current location")
}
addr, err := d.process.FindFileLocation(file, line+loc.Offset)
addr, err := proc.FindFileLocation(d.target, file, line+loc.Offset)
return []api.Location{{PC: addr}}, err
}
@ -402,10 +412,10 @@ func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
if scope == nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)")
}
file, _, fn := d.process.PCToLine(scope.PC)
file, _, fn := d.target.BinInfo().PCToLine(scope.PC)
if fn == nil {
return nil, fmt.Errorf("could not determine current location")
}
addr, err := d.process.FindFileLocation(file, loc.Line)
addr, err := proc.FindFileLocation(d.target, file, loc.Line)
return []api.Location{{PC: addr}}, err
}

View File

@ -0,0 +1,60 @@
package service
import (
"errors"
"net"
"sync"
)
// ListenerPipe returns a full-duplex in-memory connection, like net.Pipe.
// Unlike net.Pipe one end of the connection is returned as an object
// satisfying the net.Listener interface.
// The first call to the Accept method of this object will return a net.Conn
// connected to the other net.Conn returned by ListenerPipe.
// Any subsequent calls to Accept will block until the listener is closed.
func ListenerPipe() (net.Listener, net.Conn) {
conn0, conn1 := net.Pipe()
return &preconnectedListener{conn: conn0, closech: make(chan struct{})}, conn1
}
// preconnectedListener satisfies the net.Listener interface by accepting a
// single pre-established connection.
// The first call to Accept will return the conn field, any subsequent call
// will block until the listener is closed.
type preconnectedListener struct {
accepted bool
conn net.Conn
closech chan struct{}
closeMu sync.Mutex
acceptMu sync.Mutex
}
// Accept returns the pre-established connection the first time it's called,
// it blocks until the listener is closed on every subsequent call.
func (l *preconnectedListener) Accept() (net.Conn, error) {
l.acceptMu.Lock()
defer l.acceptMu.Unlock()
if !l.accepted {
l.accepted = true
return l.conn, nil
}
<-l.closech
return nil, errors.New("accept failed: listener closed")
}
// Close closes the listener.
func (l *preconnectedListener) Close() error {
l.closeMu.Lock()
defer l.closeMu.Unlock()
if l.closech == nil {
return nil
}
close(l.closech)
l.closech = nil
return nil
}
// Addr returns the listener's network address.
func (l *preconnectedListener) Addr() net.Addr {
return l.conn.LocalAddr()
}

View File

@ -14,11 +14,10 @@ import (
// Client is a RPC service.Client.
type RPCClient struct {
addr string
processPid int
client *rpc.Client
haltMu sync.Mutex
haltReq bool
addr string
client *rpc.Client
haltMu sync.Mutex
haltReq bool
}
var unsupportedApiError = errors.New("unsupported")
@ -114,6 +113,12 @@ func (c *RPCClient) Step() (*api.DebuggerState, error) {
return state, err
}
func (c *RPCClient) Call(expr string) (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("Command", &api.DebuggerCommand{Name: api.Call, Expr: expr}, state)
return state, err
}
func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("Command", &api.DebuggerCommand{Name: api.StepInstruction}, state)
@ -303,10 +308,6 @@ func (c *RPCClient) DisassemblePC(scope api.EvalScope, pc uint64, flavour api.As
return r, err
}
func (c *RPCClient) url(path string) string {
return fmt.Sprintf("http://%s%s", c.addr, path)
}
func (c *RPCClient) call(method string, args, reply interface{}) error {
return c.client.Call("RPCServer."+method, args, reply)
}

View File

@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/pkg/proc"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/debugger"
@ -29,19 +29,23 @@ func (s *RPCServer) ProcessPid(arg1 interface{}, pid *int) error {
}
func (s *RPCServer) Detach(kill bool, ret *int) error {
return s.debugger.Detach(kill)
err := s.debugger.Detach(kill)
if s.config.DisconnectChan != nil {
close(s.config.DisconnectChan)
}
return err
}
func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
if s.config.AttachPid != 0 {
return errors.New("cannot restart process Delve did not create")
}
_, err := s.debugger.Restart()
_, err := s.debugger.Restart("", false, nil)
return err
}
func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
st, err := s.debugger.State()
st, err := s.debugger.State(false)
if err != nil {
return err
}
@ -83,7 +87,7 @@ func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations
if args.Full {
loadcfg = &defaultLoadConfig
}
locs, err := s.debugger.Stacktrace(args.Id, args.Depth, loadcfg)
locs, err := s.debugger.Stacktrace(args.Id, args.Depth, false, loadcfg)
if err != nil {
return err
}
@ -154,7 +158,7 @@ func (s *RPCServer) GetThread(id int, thread *api.Thread) error {
}
func (s *RPCServer) ListPackageVars(filter string, variables *[]api.Variable) error {
state, err := s.debugger.State()
state, err := s.debugger.State(false)
if err != nil {
return err
}
@ -195,7 +199,7 @@ func (s *RPCServer) ListThreadPackageVars(args *ThreadListArgs, variables *[]api
}
func (s *RPCServer) ListRegisters(arg interface{}, registers *string) error {
state, err := s.debugger.State()
state, err := s.debugger.State(false)
if err != nil {
return err
}

View File

@ -3,6 +3,7 @@ package rpc2
import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"time"
@ -13,9 +14,9 @@ import (
// Client is a RPC service.Client.
type RPCClient struct {
addr string
processPid int
client *rpc.Client
client *rpc.Client
retValLoadCfg *api.LoadConfig
}
// Ensure the implementation satisfies the interface.
@ -27,11 +28,20 @@ func NewClient(addr string) *RPCClient {
if err != nil {
log.Fatal("dialing:", err)
}
c := &RPCClient{addr: addr, client: client}
return newFromRPCClient(client)
}
func newFromRPCClient(client *rpc.Client) *RPCClient {
c := &RPCClient{client: client}
c.call("SetApiVersion", api.SetAPIVersionIn{2}, &api.SetAPIVersionOut{})
return c
}
// NewClientFromConn creates a new RPCClient from the given connection.
func NewClientFromConn(conn net.Conn) *RPCClient {
return newFromRPCClient(jsonrpc.NewClient(conn))
}
func (c *RPCClient) ProcessPid() int {
out := new(ProcessPidOut)
c.call("ProcessPid", ProcessPidIn{}, out)
@ -45,13 +55,20 @@ func (c *RPCClient) LastModified() time.Time {
}
func (c *RPCClient) Detach(kill bool) error {
defer c.client.Close()
out := new(DetachOut)
return c.call("Detach", DetachIn{kill}, out)
}
func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut)
err := c.call("Restart", RestartIn{}, out)
err := c.call("Restart", RestartIn{"", false, nil}, out)
return out.DiscardedBreakpoints, err
}
func (c *RPCClient) RestartFrom(pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut)
err := c.call("Restart", RestartIn{pos, resetArgs, newArgs}, out)
return out.DiscardedBreakpoints, err
}
@ -61,18 +78,32 @@ func (c *RPCClient) GetState() (*api.DebuggerState, error) {
return out.State, err
}
func (c *RPCClient) GetStateNonBlocking() (*api.DebuggerState, error) {
var out StateOut
err := c.call("State", StateIn{NonBlocking: true}, &out)
return out.State, err
}
func (c *RPCClient) Continue() <-chan *api.DebuggerState {
return c.continueDir(api.Continue)
}
func (c *RPCClient) Rewind() <-chan *api.DebuggerState {
return c.continueDir(api.Rewind)
}
func (c *RPCClient) continueDir(cmd string) <-chan *api.DebuggerState {
ch := make(chan *api.DebuggerState)
go func() {
for {
out := new(CommandOut)
err := c.call("Command", &api.DebuggerCommand{Name: api.Continue}, &out)
err := c.call("Command", &api.DebuggerCommand{Name: cmd, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
state := out.State
if err != nil {
state.Err = err
}
if state.Exited {
// Error types apparantly cannot be marshalled by Go correctly. Must reset error here.
// Error types apparently cannot be marshalled by Go correctly. Must reset error here.
state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus)
}
ch <- &state
@ -101,19 +132,25 @@ func (c *RPCClient) Continue() <-chan *api.DebuggerState {
func (c *RPCClient) Next() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.Next}, &out)
err := c.call("Command", api.DebuggerCommand{Name: api.Next, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
return &out.State, err
}
func (c *RPCClient) Step() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.Step}, &out)
err := c.call("Command", api.DebuggerCommand{Name: api.Step, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
return &out.State, err
}
func (c *RPCClient) StepOut() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", &api.DebuggerCommand{Name: api.StepOut}, &out)
err := c.call("Command", &api.DebuggerCommand{Name: api.StepOut, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
return &out.State, err
}
func (c *RPCClient) Call(expr string) (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", &api.DebuggerCommand{Name: api.Call, ReturnInfoLoadConfig: c.retValLoadCfg, Expr: expr}, &out)
return &out.State, err
}
@ -267,9 +304,9 @@ func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
return out.Goroutines, err
}
func (c *RPCClient) Stacktrace(goroutineId, depth int, cfg *api.LoadConfig) ([]api.Stackframe, error) {
func (c *RPCClient) Stacktrace(goroutineId, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) {
var out StacktraceOut
err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, cfg}, &out)
err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, readDefers, cfg}, &out)
return out.Locations, err
}
@ -299,8 +336,57 @@ func (c *RPCClient) DisassemblePC(scope api.EvalScope, pc uint64, flavour api.As
return out.Disassemble, err
}
func (c *RPCClient) url(path string) string {
return fmt.Sprintf("http://%s%s", c.addr, path)
// Recorded returns true if the debugger target is a recording.
func (c *RPCClient) Recorded() bool {
out := new(RecordedOut)
c.call("Recorded", RecordedIn{}, out)
return out.Recorded
}
// TraceDirectory returns the path to the trace directory for a recording.
func (c *RPCClient) TraceDirectory() (string, error) {
var out RecordedOut
err := c.call("Recorded", RecordedIn{}, &out)
return out.TraceDirectory, err
}
// Checkpoint sets a checkpoint at the current position.
func (c *RPCClient) Checkpoint(where string) (checkpointID int, err error) {
var out CheckpointOut
err = c.call("Checkpoint", CheckpointIn{where}, &out)
return out.ID, err
}
// ListCheckpoints gets all checkpoints.
func (c *RPCClient) ListCheckpoints() ([]api.Checkpoint, error) {
var out ListCheckpointsOut
err := c.call("ListCheckpoints", ListCheckpointsIn{}, &out)
return out.Checkpoints, err
}
// ClearCheckpoint removes a checkpoint
func (c *RPCClient) ClearCheckpoint(id int) error {
var out ClearCheckpointOut
err := c.call("ClearCheckpoint", ClearCheckpointIn{id}, &out)
return err
}
func (c *RPCClient) SetReturnValuesLoadConfig(cfg *api.LoadConfig) {
c.retValLoadCfg = cfg
}
func (c *RPCClient) IsMulticlient() bool {
var out IsMulticlientOut
c.call("IsMulticlient", IsMulticlientIn{}, &out)
return out.IsMulticlient
}
func (c *RPCClient) Disconnect(cont bool) error {
if cont {
out := new(CommandOut)
c.client.Go("RPCServer.Command", &api.DebuggerCommand{Name: api.Continue, ReturnInfoLoadConfig: c.retValLoadCfg}, &out, nil)
}
return c.client.Close()
}
func (c *RPCClient) call(method string, args, reply interface{}) error {

View File

@ -55,10 +55,24 @@ type DetachOut struct {
// Detach detaches the debugger, optionally killing the process.
func (s *RPCServer) Detach(arg DetachIn, out *DetachOut) error {
return s.debugger.Detach(arg.Kill)
err := s.debugger.Detach(arg.Kill)
if s.config.DisconnectChan != nil {
close(s.config.DisconnectChan)
s.config.DisconnectChan = nil
}
return err
}
type RestartIn struct {
// Position to restart from, if it starts with 'c' it's a checkpoint ID,
// otherwise it's an event number. Only valid for recorded targets.
Position string
// ResetArgs tell whether NewArgs should take effect.
ResetArgs bool
// NewArgs are arguments to launch a new process. They replace only the
// argv[1] and later. Argv[0] cannot be changed.
NewArgs []string
}
type RestartOut struct {
@ -71,11 +85,13 @@ func (s *RPCServer) Restart(arg RestartIn, out *RestartOut) error {
return errors.New("cannot restart process Delve did not create")
}
var err error
out.DiscardedBreakpoints, err = s.debugger.Restart()
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Position, arg.ResetArgs, arg.NewArgs)
return err
}
type StateIn struct {
// If NonBlocking is true State will return immediately even if the target process is running.
NonBlocking bool
}
type StateOut struct {
@ -84,7 +100,7 @@ type StateOut struct {
// State returns the current debugger state.
func (s *RPCServer) State(arg StateIn, out *StateOut) error {
st, err := s.debugger.State()
st, err := s.debugger.State(arg.NonBlocking)
if err != nil {
return err
}
@ -106,7 +122,6 @@ func (s *RPCServer) Command(command api.DebuggerCommand, cb service.RPCCallback)
var out CommandOut
out.State = *st
cb.Return(out, nil)
return
}
type GetBreakpointIn struct {
@ -137,10 +152,11 @@ func (s *RPCServer) GetBreakpoint(arg GetBreakpointIn, out *GetBreakpointOut) er
}
type StacktraceIn struct {
Id int
Depth int
Full bool
Cfg *api.LoadConfig
Id int
Depth int
Full bool
Defers bool // read deferred functions
Cfg *api.LoadConfig
}
type StacktraceOut struct {
@ -156,11 +172,11 @@ func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error {
if cfg == nil && arg.Full {
cfg = &api.LoadConfig{true, 1, 64, 64, -1}
}
locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, api.LoadConfigToProc(cfg))
var err error
out.Locations, err = s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Defers, api.LoadConfigToProc(cfg))
if err != nil {
return err
}
out.Locations = locs
return nil
}
@ -309,7 +325,7 @@ type ListPackageVarsOut struct {
// ListPackageVars lists all package variables in the context of the current thread.
func (s *RPCServer) ListPackageVars(arg ListPackageVarsIn, out *ListPackageVarsOut) error {
state, err := s.debugger.State()
state, err := s.debugger.State(false)
if err != nil {
return err
}
@ -340,7 +356,7 @@ type ListRegistersOut struct {
// ListRegisters lists registers and their values.
func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) error {
if arg.ThreadID == 0 {
state, err := s.debugger.State()
state, err := s.debugger.State(false)
if err != nil {
return err
}
@ -565,7 +581,7 @@ type DisassembleOut struct {
//
// If both StartPC and EndPC are non-zero the specified range will be disassembled, otherwise the function containing StartPC will be disassembled.
//
// Scope is used to mark the instruction the specified gorutine is stopped at.
// Scope is used to mark the instruction the specified goroutine is stopped at.
//
// Disassemble will also try to calculate the destination address of an absolute indirect CALL if it happens to be the instruction the selected goroutine is stopped at.
func (c *RPCServer) Disassemble(arg DisassembleIn, out *DisassembleOut) error {
@ -573,3 +589,69 @@ func (c *RPCServer) Disassemble(arg DisassembleIn, out *DisassembleOut) error {
out.Disassemble, err = c.debugger.Disassemble(arg.Scope, arg.StartPC, arg.EndPC, arg.Flavour)
return err
}
type RecordedIn struct {
}
type RecordedOut struct {
Recorded bool
TraceDirectory string
}
func (s *RPCServer) Recorded(arg RecordedIn, out *RecordedOut) error {
out.Recorded, out.TraceDirectory = s.debugger.Recorded()
return nil
}
type CheckpointIn struct {
Where string
}
type CheckpointOut struct {
ID int
}
func (s *RPCServer) Checkpoint(arg CheckpointIn, out *CheckpointOut) error {
var err error
out.ID, err = s.debugger.Checkpoint(arg.Where)
return err
}
type ListCheckpointsIn struct {
}
type ListCheckpointsOut struct {
Checkpoints []api.Checkpoint
}
func (s *RPCServer) ListCheckpoints(arg ListCheckpointsIn, out *ListCheckpointsOut) error {
var err error
out.Checkpoints, err = s.debugger.Checkpoints()
return err
}
type ClearCheckpointIn struct {
ID int
}
type ClearCheckpointOut struct {
}
func (s *RPCServer) ClearCheckpoint(arg ClearCheckpointIn, out *ClearCheckpointOut) error {
return s.debugger.ClearCheckpoint(arg.ID)
}
type IsMulticlientIn struct {
}
type IsMulticlientOut struct {
// IsMulticlient returns true if the headless instance was started with --accept-multiclient
IsMulticlient bool
}
func (s *RPCServer) IsMulticlient(arg IsMulticlientIn, out *IsMulticlientOut) error {
*out = IsMulticlientOut{
IsMulticlient: s.config.AcceptMulti,
}
return nil
}

View File

@ -2,11 +2,11 @@ package rpccommon
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
@ -16,12 +16,14 @@ import (
"unicode"
"unicode/utf8"
"github.com/derekparker/delve/pkg/logflags"
"github.com/derekparker/delve/pkg/version"
"github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/debugger"
"github.com/derekparker/delve/service/rpc1"
"github.com/derekparker/delve/service/rpc2"
"github.com/derekparker/delve/version"
"github.com/sirupsen/logrus"
)
// ServerImpl implements a JSON-RPC server that can switch between two
@ -41,6 +43,7 @@ type ServerImpl struct {
s2 *rpc2.RPCServer
// maps of served methods, one for each supported API.
methodMaps []map[string]*methodType
log *logrus.Entry
}
type RPCCallback struct {
@ -64,27 +67,34 @@ type methodType struct {
}
// NewServer creates a new RPCServer.
func NewServer(config *service.Config, logEnabled bool) *ServerImpl {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
if !logEnabled {
log.SetOutput(ioutil.Discard)
func NewServer(config *service.Config) *ServerImpl {
logger := logrus.New().WithFields(logrus.Fields{"layer": "rpc"})
logger.Logger.Level = logrus.DebugLevel
if !logflags.RPC() {
logger.Logger.Out = ioutil.Discard
}
if config.APIVersion < 2 {
log.Printf("Using API v1")
logger.Info("Using API v1")
}
if config.Foreground {
// Print listener address
fmt.Printf("API server listening at: %s\n", config.Listener.Addr())
}
return &ServerImpl{
config: config,
listener: config.Listener,
stopChan: make(chan struct{}),
log: logger,
}
}
// Stop stops the JSON-RPC server.
func (s *ServerImpl) Stop(kill bool) error {
func (s *ServerImpl) Stop() error {
if s.config.AcceptMulti {
close(s.stopChan)
s.listener.Close()
}
kill := s.config.AttachPid == 0
return s.debugger.Detach(kill)
}
@ -111,10 +121,13 @@ func (s *ServerImpl) Run() error {
// Create and start the debugger
if s.debugger, err = debugger.New(&debugger.Config{
ProcessArgs: s.config.ProcessArgs,
AttachPid: s.config.AttachPid,
WorkingDir: s.config.WorkingDir,
}); err != nil {
AttachPid: s.config.AttachPid,
WorkingDir: s.config.WorkingDir,
CoreFile: s.config.CoreFile,
Backend: s.config.Backend,
Foreground: s.config.Foreground,
},
s.config.ProcessArgs); err != nil {
return err
}
@ -127,10 +140,10 @@ func (s *ServerImpl) Run() error {
s.methodMaps[0] = map[string]*methodType{}
s.methodMaps[1] = map[string]*methodType{}
suitableMethods(s.s1, s.methodMaps[0])
suitableMethods(rpcServer, s.methodMaps[0])
suitableMethods(s.s2, s.methodMaps[1])
suitableMethods(rpcServer, s.methodMaps[1])
suitableMethods(s.s1, s.methodMaps[0], s.log)
suitableMethods(rpcServer, s.methodMaps[0], s.log)
suitableMethods(s.s2, s.methodMaps[1], s.log)
suitableMethods(rpcServer, s.methodMaps[1], s.log)
go func() {
defer s.listener.Close()
@ -180,12 +193,12 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
// two signatures:
// func (rcvr ReceiverType) Method(in InputType, out *ReplyType) error
// func (rcvr ReceiverType) Method(in InputType, cb service.RPCCallback)
func suitableMethods(rcvr interface{}, methods map[string]*methodType) {
func suitableMethods(rcvr interface{}, methods map[string]*methodType, log *logrus.Entry) {
typ := reflect.TypeOf(rcvr)
rcvrv := reflect.ValueOf(rcvr)
sname := reflect.Indirect(rcvrv).Type().Name()
if sname == "" {
log.Printf("rpc.Register: no service name for type %s", typ)
log.Debugf("rpc.Register: no service name for type %s", typ)
return
}
for m := 0; m < typ.NumMethod(); m++ {
@ -198,13 +211,13 @@ func suitableMethods(rcvr interface{}, methods map[string]*methodType) {
}
// Method needs three ins: (receive, *args, *reply) or (receiver, *args, *RPCCallback)
if mtype.NumIn() != 3 {
log.Println("method", mname, "has wrong number of ins:", mtype.NumIn())
log.Warn("method", mname, "has wrong number of ins:", mtype.NumIn())
continue
}
// First arg need not be a pointer.
argType := mtype.In(1)
if !isExportedOrBuiltinType(argType) {
log.Println(mname, "argument type not exported:", argType)
log.Warn(mname, "argument type not exported:", argType)
continue
}
@ -214,38 +227,43 @@ func suitableMethods(rcvr interface{}, methods map[string]*methodType) {
if synchronous {
// Second arg must be a pointer.
if replyType.Kind() != reflect.Ptr {
log.Println("method", mname, "reply type not a pointer:", replyType)
log.Warn("method", mname, "reply type not a pointer:", replyType)
continue
}
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) {
log.Println("method", mname, "reply type not exported:", replyType)
log.Warn("method", mname, "reply type not exported:", replyType)
continue
}
// Method needs one out.
if mtype.NumOut() != 1 {
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut())
continue
}
// The return type of the method must be error.
if returnType := mtype.Out(0); returnType != typeOfError {
log.Println("method", mname, "returns", returnType.String(), "not error")
log.Warn("method", mname, "returns", returnType.String(), "not error")
continue
}
} else {
// Method needs zero outs.
if mtype.NumOut() != 0 {
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut())
continue
}
}
methods[sname+"."+mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType, Synchronous: synchronous, Rcvr: rcvrv}
}
return
}
func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
defer func() {
if !s.config.AcceptMulti && s.config.DisconnectChan != nil {
close(s.config.DisconnectChan)
}
}()
sending := new(sync.Mutex)
codec := jsonrpc.NewServerCodec(conn)
var req rpc.Request
@ -255,14 +273,14 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
err := codec.ReadRequestHeader(&req)
if err != nil {
if err != io.EOF {
log.Println("rpc:", err)
s.log.Error("rpc:", err)
}
break
}
mtype, ok := s.methodMaps[s.config.APIVersion-1][req.ServiceMethod]
if !ok {
log.Printf("rpc: can't find method %s", req.ServiceMethod)
s.log.Errorf("rpc: can't find method %s", req.ServiceMethod)
continue
}
@ -285,6 +303,10 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
}
if mtype.Synchronous {
if logflags.RPC() {
argvbytes, _ := json.Marshal(argv.Interface())
s.log.Debugf("<- %s(%T%s)", req.ServiceMethod, argv.Interface(), argvbytes)
}
replyv = reflect.New(mtype.ReplyType.Elem())
function := mtype.method.Func
var returnValues []reflect.Value
@ -304,8 +326,16 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
errmsg = errInter.(error).Error()
}
resp = rpc.Response{}
if logflags.RPC() {
replyvbytes, _ := json.Marshal(replyv.Interface())
s.log.Debugf("-> %T%s error: %q", replyv.Interface(), replyvbytes, errmsg)
}
s.sendResponse(sending, &req, &resp, replyv.Interface(), codec, errmsg)
} else {
if logflags.RPC() {
argvbytes, _ := json.Marshal(argv.Interface())
s.log.Debugf("(async %d) <- %s(%T%s)", req.Seq, req.ServiceMethod, argv.Interface(), argvbytes)
}
function := mtype.method.Func
ctl := &RPCCallback{s, sending, codec, req}
go func() {
@ -337,7 +367,7 @@ func (s *ServerImpl) sendResponse(sending *sync.Mutex, req *rpc.Request, resp *r
defer sending.Unlock()
err := codec.WriteResponse(resp, reply)
if err != nil {
log.Println("rpc: writing response:", err)
s.log.Error("writing response:", err)
}
}
@ -347,6 +377,10 @@ func (cb *RPCCallback) Return(out interface{}, err error) {
errmsg = err.Error()
}
var resp rpc.Response
if logflags.RPC() {
outbytes, _ := json.Marshal(out)
cb.s.log.Debugf("(async %d) -> %T%s error: %q", cb.req.Seq, out, outbytes, errmsg)
}
cb.s.sendResponse(cb.sending, &cb.req, &resp, out, cb.codec, errmsg)
}
@ -403,7 +437,7 @@ func (err *internalError) Error() string {
var out bytes.Buffer
fmt.Fprintf(&out, "Internal debugger error: %v\n", err.Err)
for _, frame := range err.Stack {
fmt.Fprintf(&out, "%s (%#x)\n\t%s%d\n", frame.Func, frame.Pc, frame.File, frame.Line)
fmt.Fprintf(&out, "%s (%#x)\n\t%s:%d\n", frame.Func, frame.Pc, frame.File, frame.Line)
}
return out.String()
}

View File

@ -4,5 +4,5 @@ package service
// to connect to.
type Server interface {
Run() error
Stop(bool) error
Stop() error
}