whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,168 @@
package api
import (
"errors"
"fmt"
"strconv"
"strings"
)
type PrintGoroutinesFlags uint8
const (
PrintGoroutinesStack PrintGoroutinesFlags = 1 << iota
PrintGoroutinesLabels
PrintGoroutinesExec
)
type FormatGoroutineLoc int
const (
FglRuntimeCurrent = FormatGoroutineLoc(iota)
FglUserCurrent
FglGo
FglStart
)
const (
maxGroupMembers = 5
maxGoroutineGroups = 50
)
// The number of goroutines we're going to request on each RPC call
const goroutineBatchSize = 10000
func ParseGoroutineArgs(argstr string) ([]ListGoroutinesFilter, GoroutineGroupingOptions, FormatGoroutineLoc, PrintGoroutinesFlags, int, int, string, error) {
args := strings.Split(argstr, " ")
var filters []ListGoroutinesFilter
var group GoroutineGroupingOptions
var fgl = FglUserCurrent
var flags PrintGoroutinesFlags
var depth = 10
var batchSize = goroutineBatchSize
var cmd string
group.MaxGroupMembers = maxGroupMembers
group.MaxGroups = maxGoroutineGroups
for i := 0; i < len(args); i++ {
arg := args[i]
switch arg {
case "-u":
fgl = FglUserCurrent
case "-r":
fgl = FglRuntimeCurrent
case "-g":
fgl = FglGo
case "-s":
fgl = FglStart
case "-l":
flags |= PrintGoroutinesLabels
case "-t":
flags |= PrintGoroutinesStack
// optional depth argument
if i+1 < len(args) && len(args[i+1]) > 0 {
n, err := strconv.Atoi(args[i+1])
if err == nil {
depth = n
i++
}
}
case "-w", "-with":
filter, err := readGoroutinesFilter(args, &i)
if err != nil {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
filters = append(filters, *filter)
case "-wo", "-without":
filter, err := readGoroutinesFilter(args, &i)
if err != nil {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
filter.Negated = true
filters = append(filters, *filter)
case "-group":
var err error
group.GroupBy, err = readGoroutinesFilterKind(args, i+1)
if err != nil {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
i++
if group.GroupBy == GoroutineLabel {
if i+1 >= len(args) {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
group.GroupByKey = args[i+1]
i++
}
batchSize = 0 // grouping only works well if run on all goroutines
case "-chan":
i++
if i >= len(args) {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", errors.New("not enough arguments after -chan")
}
filters = append(filters, ListGoroutinesFilter{Kind: GoroutineWaitingOnChannel, Arg: args[i]})
case "-exec":
flags |= PrintGoroutinesExec
cmd = strings.Join(args[i+1:], " ")
i = len(args)
case "":
// nothing to do
default:
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
}
return filters, group, fgl, flags, depth, batchSize, cmd, nil
}
func readGoroutinesFilterKind(args []string, i int) (GoroutineField, error) {
if i >= len(args) {
return GoroutineFieldNone, fmt.Errorf("%s must be followed by an argument", args[i-1])
}
switch args[i] {
case "curloc":
return GoroutineCurrentLoc, nil
case "userloc":
return GoroutineUserLoc, nil
case "goloc":
return GoroutineGoLoc, nil
case "startloc":
return GoroutineStartLoc, nil
case "label":
return GoroutineLabel, nil
case "running":
return GoroutineRunning, nil
case "user":
return GoroutineUser, nil
default:
return GoroutineFieldNone, fmt.Errorf("unrecognized argument to %s %s", args[i-1], args[i])
}
}
func readGoroutinesFilter(args []string, pi *int) (*ListGoroutinesFilter, error) {
r := new(ListGoroutinesFilter)
var err error
r.Kind, err = readGoroutinesFilterKind(args, *pi+1)
if err != nil {
return nil, err
}
*pi++
switch r.Kind {
case GoroutineRunning, GoroutineUser:
return r, nil
}
if *pi+1 >= len(args) {
return nil, fmt.Errorf("%s %s needs to be followed by an expression", args[*pi-1], args[*pi])
}
r.Arg = args[*pi+1]
*pi++
return r, nil
}
@@ -0,0 +1,471 @@
package api
import (
"bytes"
"fmt"
"go/constant"
"go/printer"
"go/token"
"reflect"
"sort"
"strconv"
"strings"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/proc"
)
// ConvertLogicalBreakpoint converts a proc.LogicalBreakpoint into an API breakpoint.
func ConvertLogicalBreakpoint(lbp *proc.LogicalBreakpoint) *Breakpoint {
b := &Breakpoint{
ID: lbp.LogicalID,
FunctionName: lbp.FunctionName,
File: lbp.File,
Line: lbp.Line,
Name: lbp.Name,
Tracepoint: lbp.Tracepoint,
TraceReturn: lbp.TraceReturn,
Stacktrace: lbp.Stacktrace,
Goroutine: lbp.Goroutine,
Variables: lbp.Variables,
LoadArgs: LoadConfigFromProc(lbp.LoadArgs),
LoadLocals: LoadConfigFromProc(lbp.LoadLocals),
TotalHitCount: lbp.TotalHitCount,
Disabled: !lbp.Enabled,
UserData: lbp.UserData,
RootFuncName: lbp.RootFuncName,
TraceFollowCalls: lbp.TraceFollowCalls,
}
b.HitCount = map[string]uint64{}
for idx := range lbp.HitCount {
b.HitCount[strconv.FormatInt(idx, 10)] = lbp.HitCount[idx]
}
if lbp.HitCond != nil {
b.HitCond = fmt.Sprintf("%s %d", lbp.HitCond.Op.String(), lbp.HitCond.Val)
b.HitCondPerG = lbp.HitCondPerG
}
var buf bytes.Buffer
printer.Fprint(&buf, token.NewFileSet(), lbp.Cond)
b.Cond = buf.String()
return b
}
// ConvertPhysicalBreakpoints adds information from physical breakpoints to an API breakpoint.
func ConvertPhysicalBreakpoints(b *Breakpoint, lbp *proc.LogicalBreakpoint, pids []int, bps []*proc.Breakpoint) {
if len(bps) == 0 {
if lbp != nil {
b.ExprString = lbp.Set.ExprString
}
return
}
b.WatchExpr = bps[0].WatchExpr
b.WatchType = WatchType(bps[0].WatchType)
lg := false
for i, bp := range bps {
b.Addrs = append(b.Addrs, bp.Addr)
b.AddrPid = append(b.AddrPid, pids[i])
if b.FunctionName != bp.FunctionName && b.FunctionName != "" {
if !lg {
b.FunctionName = removeTypeParams(b.FunctionName)
lg = true
}
fn := removeTypeParams(bp.FunctionName)
if b.FunctionName != fn {
b.FunctionName = "(multiple functions)"
}
}
}
if len(b.Addrs) > 0 {
b.Addr = b.Addrs[0]
}
}
func removeTypeParams(name string) string {
fn := proc.Function{Name: name}
return fn.NameWithoutTypeParams()
}
// ConvertThread converts a proc.Thread into an
// api thread.
func ConvertThread(th proc.Thread, bp *Breakpoint) *Thread {
var (
function *Function
file string
line int
pc uint64
gid int64
)
loc, err := th.Location()
if err == nil {
pc = loc.PC
file = loc.File
line = loc.Line
function = ConvertFunction(loc.Fn)
}
if g, _ := proc.GetG(th); g != nil {
gid = g.ID
}
return &Thread{
ID: th.ThreadID(),
PC: pc,
File: file,
Line: line,
Function: function,
GoroutineID: gid,
Breakpoint: bp,
}
}
// ConvertThreads converts a slice of proc.Thread into a slice of api.Thread.
func ConvertThreads(threads []proc.Thread, convertBreakpoint func(proc.Thread) *Breakpoint) []*Thread {
r := make([]*Thread, len(threads))
for i := range threads {
r[i] = ConvertThread(threads[i], convertBreakpoint(threads[i]))
}
return r
}
func PrettyTypeName(typ godwarf.Type) string {
if typ == nil {
return ""
}
if typ.Common().Name != "" {
return typ.Common().Name
}
r := typ.String()
if r == "*void" {
return "unsafe.Pointer"
}
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{
Addr: v.Addr,
OnlyAddr: v.OnlyAddr,
Name: v.Name,
Kind: v.Kind,
Len: v.Len,
Cap: v.Cap,
Flags: VariableFlags(v.Flags),
Base: v.Base,
LocationExpr: v.LocationExpr.String(),
DeclLine: v.DeclLine,
}
r.Type = PrettyTypeName(v.DwarfType)
r.RealType = PrettyTypeName(v.RealType)
if v.Unreadable != nil {
r.Unreadable = v.Unreadable.Error()
}
r.Value = VariableValueAsString(v)
switch v.Kind {
case reflect.Complex64:
r.Children = make([]Variable, 2)
r.Len = 2
r.Children[0].Name = "real"
r.Children[0].Kind = reflect.Float32
r.Children[1].Name = "imaginary"
r.Children[1].Kind = reflect.Float32
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
r.Children[0].Name = "real"
r.Children[0].Kind = reflect.Float64
r.Children[1].Name = "imaginary"
r.Children[1].Kind = reflect.Float64
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))
for i := range v.Children {
r.Children[i] = *ConvertVar(&v.Children[i])
}
}
return &r
}
func VariableValueAsString(v *proc.Variable) string {
if v.Value == nil {
return ""
}
switch v.Kind {
case reflect.Float32:
return convertFloatValue(v, 32)
case reflect.Float64:
return convertFloatValue(v, 64)
case reflect.String, reflect.Func, reflect.Struct:
return constant.StringVal(v.Value)
default:
if cd := v.ConstDescr(); cd != "" {
return fmt.Sprintf("%s (%s)", cd, v.Value.String())
} else {
return v.Value.String()
}
}
}
// ConvertVars converts from []*proc.Variable to []api.Variable.
func ConvertVars(pv []*proc.Variable) []Variable {
if pv == nil {
return nil
}
vars := make([]Variable, 0, len(pv))
for _, v := range pv {
vars = append(vars, *ConvertVar(v))
}
return vars
}
// ConvertFunction converts from gosym.Func to
// api.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: 0,
Value: fn.Entry,
GoType: 0,
Optimized: fn.Optimized(),
}
}
// ConvertGoroutine converts from proc.G to api.Goroutine.
func ConvertGoroutine(tgt *proc.Target, g *proc.G) *Goroutine {
th := g.Thread
tid := 0
if th != nil {
tid = th.ThreadID()
}
if g.Unreadable != nil {
return &Goroutine{Unreadable: g.Unreadable.Error()}
}
return &Goroutine{
ID: g.ID,
CurrentLoc: ConvertLocation(g.CurrentLoc),
UserCurrentLoc: ConvertLocation(g.UserCurrent()),
GoStatementLoc: ConvertLocation(g.Go()),
StartLoc: ConvertLocation(g.StartLoc(tgt)),
ThreadID: tid,
WaitSince: g.WaitSince,
WaitReason: g.WaitReason,
Labels: g.Labels(),
Status: g.Status,
}
}
// ConvertGoroutines converts from []*proc.G to []*api.Goroutine.
func ConvertGoroutines(tgt *proc.Target, gs []*proc.G) []*Goroutine {
goroutines := make([]*Goroutine, len(gs))
for i := range gs {
goroutines[i] = ConvertGoroutine(tgt, gs[i])
}
return goroutines
}
// ConvertLocation converts from proc.Location to api.Location.
func ConvertLocation(loc proc.Location) Location {
return Location{
PC: loc.PC,
File: loc.File,
Line: loc.Line,
Function: ConvertFunction(loc.Fn),
}
}
// ConvertAsmInstruction converts from proc.AsmInstruction to api.AsmInstruction.
func ConvertAsmInstruction(inst proc.AsmInstruction, text string) AsmInstruction {
var destloc *Location
if inst.DestLoc != nil {
r := ConvertLocation(*inst.DestLoc)
destloc = &r
}
return AsmInstruction{
Loc: ConvertLocation(inst.Loc),
DestLoc: destloc,
Text: text,
Bytes: inst.Bytes,
Breakpoint: inst.Breakpoint,
AtPC: inst.AtPC,
}
}
// LoadConfigToProc converts an api.LoadConfig to proc.LoadConfig.
func LoadConfigToProc(cfg *LoadConfig) *proc.LoadConfig {
if cfg == nil {
return nil
}
return &proc.LoadConfig{
FollowPointers: cfg.FollowPointers,
MaxVariableRecurse: cfg.MaxVariableRecurse,
MaxStringLen: cfg.MaxStringLen,
MaxArrayValues: cfg.MaxArrayValues,
MaxStructFields: cfg.MaxStructFields,
MaxMapBuckets: 0, // MaxMapBuckets is set internally by pkg/proc, read its documentation for an explanation.
}
}
// LoadConfigFromProc converts a proc.LoadConfig to api.LoadConfig.
func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig {
if cfg == nil {
return nil
}
return &LoadConfig{
FollowPointers: cfg.FollowPointers,
MaxVariableRecurse: cfg.MaxVariableRecurse,
MaxStringLen: cfg.MaxStringLen,
MaxArrayValues: cfg.MaxArrayValues,
MaxStructFields: cfg.MaxStructFields,
}
}
var canonicalRegisterOrder = map[string]int{
// amd64
"rip": 0,
"rsp": 1,
"rax": 2,
"rbx": 3,
"rcx": 4,
"rdx": 5,
// arm64
"pc": 0,
"sp": 1,
}
// ConvertRegisters converts proc.Register to api.Register for a slice.
func ConvertRegisters(in *op.DwarfRegisters, dwarfRegisterToString func(int, *op.DwarfRegister) (string, bool, string), floatingPoint bool) (out []Register) {
out = make([]Register, 0, in.CurrentSize())
for i := 0; i < in.CurrentSize(); i++ {
reg := in.Reg(uint64(i))
if reg == nil {
continue
}
name, fp, repr := dwarfRegisterToString(i, reg)
if !floatingPoint && fp {
continue
}
out = append(out, Register{name, repr, i})
}
// Sort the registers in a canonical order we prefer, this is mostly
// because the DWARF register numbering for AMD64 is weird.
sort.Slice(out, func(i, j int) bool {
a, b := out[i], out[j]
an, aok := canonicalRegisterOrder[strings.ToLower(a.Name)]
bn, bok := canonicalRegisterOrder[strings.ToLower(b.Name)]
// Registers that don't appear in canonicalRegisterOrder sort after registers that do.
if !aok {
an = 1000
}
if !bok {
bn = 1000
}
if an == bn {
// keep registers that don't appear in canonicalRegisterOrder in DWARF order
return a.DwarfNumber < b.DwarfNumber
}
return an < bn
})
return
}
// ConvertImage converts proc.Image to api.Image.
func ConvertImage(image *proc.Image) Image {
err := image.LoadError()
lerr := ""
if err != nil {
lerr = err.Error()
}
return Image{Path: image.Path, Address: image.StaticBase, LoadError: lerr}
}
// ConvertDumpState converts proc.DumpState to api.DumpState.
func ConvertDumpState(dumpState *proc.DumpState) *DumpState {
dumpState.Mutex.Lock()
defer dumpState.Mutex.Unlock()
r := &DumpState{
Dumping: dumpState.Dumping,
AllDone: dumpState.AllDone,
ThreadsDone: dumpState.ThreadsDone,
ThreadsTotal: dumpState.ThreadsTotal,
MemDone: dumpState.MemDone,
MemTotal: dumpState.MemTotal,
}
if dumpState.Err != nil {
r.Err = dumpState.Err.Error()
}
return r
}
// ConvertTarget converts a proc.Target into a api.Target.
func ConvertTarget(tgt *proc.Target, convertThreadBreakpoint func(proc.Thread) *Breakpoint) *Target {
return &Target{
Pid: tgt.Pid(),
CmdLine: tgt.CmdLine,
CurrentThread: ConvertThread(tgt.CurrentThread(), convertThreadBreakpoint(tgt.CurrentThread())),
}
}
@@ -0,0 +1,654 @@
package api
import (
"bytes"
"fmt"
"io"
"math"
"reflect"
"strconv"
"strings"
"text/tabwriter"
)
const (
// strings longer than this will cause slices, arrays and structs to be printed on multiple lines when newlines is enabled
maxShortStringLen = 7
// string used for one indentation level (when printing on multiple lines)
indentString = "\t"
)
type prettyFlags uint8
const (
prettyTop prettyFlags = 1 << iota
prettyNewlines
prettyIncludeType
prettyShortenType
)
func (flags prettyFlags) top() bool { return flags&prettyTop != 0 }
func (flags prettyFlags) includeType() bool { return flags&prettyIncludeType != 0 }
func (flags prettyFlags) newlines() bool { return flags&prettyNewlines != 0 }
func (flags prettyFlags) shortenType() bool { return flags&prettyShortenType != 0 }
func (flags prettyFlags) set(flag prettyFlags, v bool) prettyFlags {
if v {
return flags | flag
} else {
return flags &^ flag
}
}
// SinglelineString returns a representation of v on a single line.
func (v *Variable) SinglelineString() string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyIncludeType, "", "")
return buf.String()
}
// SinglelineStringWithShortTypes returns a representation of v on a single line, with types shortened.
func (v *Variable) SinglelineStringWithShortTypes() string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyIncludeType|prettyShortenType, "", "")
return buf.String()
}
// SinglelineStringFormatted returns a representation of v on a single line, using the format specified by fmtstr.
func (v *Variable) SinglelineStringFormatted(fmtstr string) string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyIncludeType, "", fmtstr)
return buf.String()
}
// MultilineString returns a representation of v on multiple lines.
func (v *Variable) MultilineString(indent, fmtstr string) string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyNewlines|prettyIncludeType, indent, fmtstr)
return buf.String()
}
func (v *Variable) typeStr(flags prettyFlags) string {
if flags.shortenType() {
return ShortenType(v.Type)
}
return v.Type
}
func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if v.Unreadable != "" {
fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable)
return
}
if !flags.top() && v.Addr == 0 && v.Value == "" {
if flags.includeType() && v.Type != "void" {
fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
} else {
fmt.Fprint(buf, "nil")
}
return
}
switch v.Kind {
case reflect.Slice:
v.writeSliceTo(buf, flags, indent, fmtstr)
case reflect.Array:
v.writeArrayTo(buf, flags, indent, fmtstr)
case reflect.Ptr:
if v.Type == "" || len(v.Children) == 0 {
fmt.Fprint(buf, "nil")
} else if v.Children[0].OnlyAddr && v.Children[0].Addr != 0 {
v.writePointerTo(buf, flags)
} else {
if flags.top() && flags.newlines() && v.Children[0].Addr != 0 {
v.writePointerTo(buf, flags)
fmt.Fprint(buf, "\n")
}
fmt.Fprint(buf, "*")
v.Children[0].writeTo(buf, flags.set(prettyTop, false), indent, fmtstr)
}
case reflect.UnsafePointer:
if len(v.Children) == 0 {
fmt.Fprintf(buf, "unsafe.Pointer(nil)")
} else {
fmt.Fprintf(buf, "unsafe.Pointer(%#x)", v.Children[0].Addr)
}
case reflect.Chan:
if flags.newlines() {
v.writeStructTo(buf, flags, indent, fmtstr)
} else {
if len(v.Children) == 0 {
fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
} else {
fmt.Fprintf(buf, "%s %s/%s", v.typeStr(flags), v.Children[0].Value, v.Children[1].Value)
}
}
case reflect.Struct:
if v.Value != "" {
fmt.Fprintf(buf, "%s(%s)", v.typeStr(flags), v.Value)
flags = flags.set(prettyIncludeType, false)
}
v.writeStructTo(buf, flags, indent, fmtstr)
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 flags.includeType() {
if v.Children[0].Kind == reflect.Invalid {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
if v.Children[0].Addr == 0 {
fmt.Fprint(buf, "nil")
return
}
} else {
fmt.Fprintf(buf, "%s(%s) ", v.typeStr(flags), v.Children[0].Type)
}
}
data := v.Children[0]
if data.Kind == reflect.Ptr {
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, flags.set(prettyTop, false).set(prettyIncludeType, !flags.includeType()), indent, fmtstr)
}
} else if data.OnlyAddr {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "*(*%q)(%#x)", v.typeStr(flags), v.Addr)
} else {
fmt.Fprintf(buf, "*(*%s)(%#x)", v.typeStr(flags), v.Addr)
}
} else {
v.Children[0].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, !flags.includeType()), indent, fmtstr)
}
case reflect.Map:
v.writeMapTo(buf, flags, indent, fmtstr)
case reflect.Func:
if v.Value == "" {
fmt.Fprint(buf, "nil")
} else {
fmt.Fprintf(buf, "%s", v.Value)
if flags.newlines() && len(v.Children) > 0 {
fmt.Fprintf(buf, " {\n")
for i := range v.Children {
fmt.Fprintf(buf, "%s%s%s %s = ", indent, indentString, v.Children[i].Name, v.Children[i].typeStr(flags))
v.Children[i].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, false), indent+indentString, fmtstr)
fmt.Fprintf(buf, "\n")
}
fmt.Fprintf(buf, "%s}", indent)
}
}
default:
v.writeBasicType(buf, fmtstr)
}
}
func (v *Variable) writePointerTo(buf io.Writer, flags prettyFlags) {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "(%q)(%#x)", v.typeStr(flags), v.Children[0].Addr)
} else {
fmt.Fprintf(buf, "(%s)(%#x)", v.typeStr(flags), v.Children[0].Addr)
}
}
func (v *Variable) writeBasicType(buf io.Writer, fmtstr string) {
if v.Value == "" && v.Kind != reflect.String {
fmt.Fprintf(buf, "(unknown %s)", v.Kind)
return
}
switch v.Kind {
case reflect.Bool:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
var b bool = v.Value == "true"
fmt.Fprintf(buf, fmtstr, b)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
n, _ := strconv.ParseInt(ExtractIntValue(v.Value), 10, 64)
fmt.Fprintf(buf, fmtstr, n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
n, _ := strconv.ParseUint(ExtractIntValue(v.Value), 10, 64)
fmt.Fprintf(buf, fmtstr, n)
case reflect.Float32, reflect.Float64:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
x, _ := strconv.ParseFloat(v.Value, 64)
fmt.Fprintf(buf, fmtstr, x)
case reflect.Complex64, reflect.Complex128:
if fmtstr == "" {
fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
return
}
real, _ := strconv.ParseFloat(v.Children[0].Value, 64)
imag, _ := strconv.ParseFloat(v.Children[1].Value, 64)
var x complex128 = complex(real, imag)
fmt.Fprintf(buf, fmtstr, x)
case reflect.String:
if fmtstr == "" {
s := v.Value
if len(s) != int(v.Len) {
s = fmt.Sprintf("%s...+%d more", s, int(v.Len)-len(s))
}
fmt.Fprintf(buf, "%q", s)
return
}
fmt.Fprintf(buf, fmtstr, v.Value)
}
}
func ExtractIntValue(s string) string {
if s == "" || s[len(s)-1] != ')' {
return s
}
open := strings.LastIndex(s, "(")
if open < 0 {
return s
}
return s[open+1 : len(s)-1]
}
func (v *Variable) writeSliceTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if flags.includeType() {
fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.typeStr(flags), v.Len, v.Cap)
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
v.writeSliceOrArrayTo(buf, flags, indent, fmtstr)
}
func (v *Variable) writeArrayTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if flags.includeType() {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
}
v.writeSliceOrArrayTo(buf, flags, indent, fmtstr)
}
func (v *Variable) writeStructTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if int(v.Len) != len(v.Children) && len(v.Children) == 0 {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "(*%q)(%#x)", v.typeStr(flags), v.Addr)
} else {
fmt.Fprintf(buf, "(*%s)(%#x)", v.typeStr(flags), v.Addr)
}
return
}
if flags.includeType() {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
}
nl := v.shouldNewlineStruct(flags.newlines())
fmt.Fprint(buf, "{")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
fmt.Fprintf(buf, "%s: ", v.Children[i].Name)
v.Children[i].writeTo(buf, prettyIncludeType.set(prettyNewlines, nl), indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
if !nl {
fmt.Fprint(buf, " ")
}
}
}
if len(v.Children) != int(v.Len) {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
}
fmt.Fprint(buf, "}")
}
func (v *Variable) writeMapTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if flags.includeType() {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
nl := flags.newlines() && (len(v.Children) > 0)
fmt.Fprint(buf, "[")
for i := 0; i < len(v.Children); i += 2 {
key := &v.Children[i]
value := &v.Children[i+1]
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
key.writeTo(buf, 0, indent+indentString, fmtstr)
fmt.Fprint(buf, ": ")
value.writeTo(buf, prettyFlags(0).set(prettyNewlines, nl), indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ", ")
}
}
if len(v.Children)/2 != int(v.Len) {
if len(v.Children) != 0 {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2))
} else {
fmt.Fprint(buf, "...")
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprint(buf, "]")
}
func (v *Variable) shouldNewlineArray(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
kind, hasptr := (&v.Children[0]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface:
return true
case reflect.String:
if hasptr {
return true
}
for i := range v.Children {
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
return false
default:
return false
}
}
func (v *Variable) recursiveKind() (reflect.Kind, bool) {
hasptr := false
var kind reflect.Kind
for {
kind = v.Kind
if kind == reflect.Ptr {
hasptr = true
if len(v.Children) == 0 {
return kind, hasptr
}
v = &(v.Children[0])
} else {
break
}
}
return kind, hasptr
}
func (v *Variable) shouldNewlineStruct(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
for i := range v.Children {
kind, hasptr := (&v.Children[i]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface:
return true
case reflect.String:
if hasptr {
return true
}
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
}
return false
}
func (v *Variable) writeSliceOrArrayTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
nl := v.shouldNewlineArray(flags.newlines())
fmt.Fprint(buf, "[")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
v.Children[i].writeTo(buf, prettyFlags(0).set(prettyNewlines, nl), indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
}
}
if len(v.Children) != int(v.Len) {
if len(v.Children) != 0 {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
} else {
fmt.Fprint(buf, "...")
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprint(buf, "]")
}
// PrettyExamineMemory examine the memory and format data
//
// `format` specifies the data format (or data type), `size` specifies size of each data,
// like 4byte integer, 1byte character, etc. `count` specifies the number of values.
func PrettyExamineMemory(address uintptr, memArea []byte, isLittleEndian bool, format byte, size int) string {
var (
cols int
colFormat string
colBytes = size
addrLen int
addrFmt string
)
// Different versions of golang output differently about '#'.
// See https://ci.appveyor.com/project/derekparker/delve-facy3/builds/30179356.
switch format {
case 'b':
cols = 4 // Avoid emitting rows that are too long when using binary format
colFormat = fmt.Sprintf("%%0%db", colBytes*8)
case 'o':
cols = 8
colFormat = fmt.Sprintf("0%%0%do", colBytes*3) // Always keep one leading zero for octal.
case 'd':
cols = 8
colFormat = fmt.Sprintf("%%0%dd", colBytes*3)
case 'x':
cols = 8
colFormat = fmt.Sprintf("0x%%0%dx", colBytes*2) // Always keep one leading '0x' for hex.
default:
return fmt.Sprintf("not supported format %q\n", string(format))
}
colFormat += "\t"
l := len(memArea)
rows := l / (cols * colBytes)
if l%(cols*colBytes) != 0 {
rows++
}
// Avoid the lens of two adjacent address are different, so always use the last addr's len to format.
if l != 0 {
addrLen = len(fmt.Sprintf("%x", uint64(address)+uint64(l)))
}
addrFmt = "0x%0" + strconv.Itoa(addrLen) + "x:\t"
var b strings.Builder
w := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
for i := 0; i < rows; i++ {
fmt.Fprintf(w, addrFmt, address)
for j := 0; j < cols; j++ {
offset := i*(cols*colBytes) + j*colBytes
if offset+colBytes <= len(memArea) {
n := byteArrayToUInt64(memArea[offset:offset+colBytes], isLittleEndian)
fmt.Fprintf(w, colFormat, n)
}
}
fmt.Fprintln(w, "")
address += uintptr(cols * colBytes)
}
w.Flush()
return b.String()
}
func byteArrayToUInt64(buf []byte, isLittleEndian bool) uint64 {
var n uint64
if isLittleEndian {
for i := len(buf) - 1; i >= 0; i-- {
n = n<<8 + uint64(buf[i])
}
} else {
for i := 0; i < len(buf); i++ {
n = n<<8 + uint64(buf[i])
}
}
return n
}
const stacktraceTruncatedMessage = "(truncated)"
func digits(n int) int {
if n <= 0 {
return 1
}
return int(math.Floor(math.Log10(float64(n)))) + 1
}
type StackTraceColors struct {
FunctionColor string
BasenameColor string
NormalColor string
}
func PrintStack(formatPath func(string) string, out io.Writer, stack []Stackframe, ind string, offsets bool, stc StackTraceColors, include func(Stackframe) bool) {
if len(stack) == 0 {
return
}
extranl := offsets
for i := range stack {
if extranl {
break
}
extranl = extranl || (len(stack[i].Defers) > 0) || (len(stack[i].Arguments) > 0) || (len(stack[i].Locals) > 0)
}
fileLine := func(file string, line int) string {
file = formatPath(file)
if stc.BasenameColor == "" {
return fmt.Sprintf("%s:%d", file, line)
}
slash := strings.LastIndex(file, "/")
if slash < 0 {
slash = 0
} else {
slash++
}
return fmt.Sprintf("%s%s%s:%d%s", file[:slash], stc.BasenameColor, file[slash:], line, stc.NormalColor)
}
d := digits(len(stack) - 1)
fmtstr := "%s%" + strconv.Itoa(d) + "d 0x%016x in " + stc.FunctionColor + "%s" + stc.NormalColor + "\n"
s := ind + strings.Repeat(" ", d+2+len(ind))
for i := range stack {
if !include(stack[i]) {
continue
}
if stack[i].Err != "" {
fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
continue
}
fmt.Fprintf(out, fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
fmt.Fprintf(out, "%sat %s\n", s, fileLine(stack[i].File, stack[i].Line))
if offsets {
fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
}
for j, d := range stack[i].Defers {
deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
s2 := strings.Repeat(" ", len(deferHeader))
if d.Unreadable != "" {
fmt.Fprintf(out, "%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
continue
}
fmt.Fprintf(out, "%s%#016x in %s%s%s\n", deferHeader, d.DeferredLoc.PC, stc.FunctionColor, d.DeferredLoc.Function.Name(), stc.NormalColor)
fmt.Fprintf(out, "%sat %s:%d\n", s2, formatPath(d.DeferredLoc.File), d.DeferredLoc.Line)
fmt.Fprintf(out, "%sdeferred by %s at %s\n", s2, d.DeferLoc.Function.Name(), fileLine(d.DeferLoc.File, d.DeferLoc.Line))
}
for j := range stack[i].Arguments {
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
}
for j := range stack[i].Locals {
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
}
if extranl {
fmt.Fprintln(out)
}
}
if len(stack) > 0 && !stack[len(stack)-1].Bottom {
fmt.Fprintf(out, "%s"+stacktraceTruncatedMessage+"\n", ind)
}
}
@@ -0,0 +1,59 @@
package api
import (
"fmt"
"math"
"strings"
"testing"
)
func TestPrettyExamineMemory(t *testing.T) {
// Test whether always use the last addr's len to format when the lens of two adjacent address are different
addr := uintptr(0xffff)
memArea := []byte("abcdefghijklmnopqrstuvwxyz")
format := byte('o')
display := []string{
"0x0ffff: 0141 0142 0143 0144 0145 0146 0147 0150 ",
"0x10007: 0151 0152 0153 0154 0155 0156 0157 0160 ",
"0x1000f: 0161 0162 0163 0164 0165 0166 0167 0170 ",
"0x10017: 0171 0172"}
res := strings.Split(strings.TrimSpace(PrettyExamineMemory(addr, memArea, true, format, 1)), "\n")
if len(display) != len(res) {
t.Fatalf("wrong lines return, expected %d but got %d", len(display), len(res))
}
for i := 0; i < len(display); i++ {
if display[i] != res[i] {
errInfo := fmt.Sprintf("wrong display return at line %d\n", i+1)
errInfo += fmt.Sprintf("expected:\n %q\n", display[i])
errInfo += fmt.Sprintf("but got:\n %q\n", res[i])
t.Fatal(errInfo)
}
}
}
func Test_byteArrayToUInt64(t *testing.T) {
tests := []struct {
name string
args []byte
want uint64
}{
{"case-nil", nil, 0},
{"case-empty", []byte{}, 0},
{"case-1", []byte{0x1}, 1},
{"case-2", []byte{0x12}, 18},
{"case-3", []byte{0x1, 0x2}, 513},
{"case-4", []byte{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2}, 144397766876004609},
{"case-5", []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, math.MaxUint64},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := byteArrayToUInt64(tt.args, true)
if got != tt.want {
t.Errorf("byteArrayToUInt64() got = %v, want %v", got, tt.want)
}
})
}
}
@@ -0,0 +1,102 @@
package api
import (
"strings"
"unicode"
)
func ShortenType(typ string) string {
out, ok := shortenTypeEx(typ)
if !ok {
return typ
}
return out
}
func shortenTypeEx(typ string) (string, bool) {
switch {
case strings.HasPrefix(typ, "["):
for i := range typ {
if typ[i] == ']' {
sub, ok := shortenTypeEx(typ[i+1:])
return typ[:i+1] + sub, ok
}
}
return "", false
case strings.HasPrefix(typ, "*"):
sub, ok := shortenTypeEx(typ[1:])
return "*" + sub, ok
case strings.HasPrefix(typ, "map["):
depth := 1
for i := 4; i < len(typ); i++ {
switch typ[i] {
case '[':
depth++
case ']':
depth--
if depth == 0 {
key, keyok := shortenTypeEx(typ[4:i])
val, valok := shortenTypeEx(typ[i+1:])
return "map[" + key + "]" + val, keyok && valok
}
}
}
return "", false
case typ == "interface {}" || typ == "interface{}":
return typ, true
case typ == "struct {}" || typ == "struct{}":
return typ, true
default:
if containsAnonymousType(typ) {
return "", false
}
if lbrk := strings.Index(typ, "["); lbrk >= 0 {
if typ[len(typ)-1] != ']' {
return "", false
}
typ0, ok := shortenTypeEx(typ[:lbrk])
if !ok {
return "", false
}
args := strings.Split(typ[lbrk+1:len(typ)-1], ",")
for i := range args {
var ok bool
args[i], ok = shortenTypeEx(strings.TrimSpace(args[i]))
if !ok {
return "", false
}
}
return typ0 + "[" + strings.Join(args, ", ") + "]", true
}
slashnum := 0
slash := -1
for i, ch := range typ {
if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' && ch != '.' && ch != '/' && ch != '@' && ch != '%' && ch != '-' {
return "", false
}
if ch == '/' {
slash = i
slashnum++
}
}
if slashnum <= 1 || slash < 0 {
return typ, true
}
return typ[slash+1:], true
}
}
func containsAnonymousType(typ string) bool {
for _, thing := range []string{"interface {", "interface{", "struct {", "struct{", "func (", "func("} {
idx := strings.Index(typ, thing)
if idx >= 0 && idx+len(thing) < len(typ) {
ch := typ[idx+len(thing)]
if ch != '}' && ch != ')' {
return true
}
}
}
return false
}
@@ -0,0 +1,36 @@
package api
import "testing"
func TestShortenType(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"long/package/path/pkg.A", "pkg.A"},
{"[]long/package/path/pkg.A", "[]pkg.A"},
{"map[long/package/path/pkg.A]long/package/path/pkg.B", "map[pkg.A]pkg.B"},
{"map[long/package/path/pkg.A]interface {}", "map[pkg.A]interface {}"},
{"map[long/package/path/pkg.A]interface{}", "map[pkg.A]interface{}"},
{"map[long/package/path/pkg.A]struct {}", "map[pkg.A]struct {}"},
{"map[long/package/path/pkg.A]struct{}", "map[pkg.A]struct{}"},
{"map[long/package/path/pkg.A]map[long/package/path/pkg.B]long/package/path/pkg.C", "map[pkg.A]map[pkg.B]pkg.C"},
{"map[long/package/path/pkg.A][]long/package/path/pkg.B", "map[pkg.A][]pkg.B"},
{"map[uint64]*github.com/aarzilli/dwarf5/dwarf.typeUnit", "map[uint64]*dwarf.typeUnit"},
{"uint8", "uint8"},
{"encoding/binary", "encoding/binary"},
{"*github.com/go-delve/delve/pkg/proc.Target", "*proc.Target"},
{"long/package/path/pkg.Parametric[long/package/path/pkg.A, map[long/package/path/pkg.B]long/package/path/pkg.A]", "pkg.Parametric[pkg.A, map[pkg.B]pkg.A]"},
{"[]long/package/path/pkg.Parametric[long/package/path/pkg.A]", "[]pkg.Parametric[pkg.A]"},
{"[24]long/package/path/pkg.A", "[24]pkg.A"},
{"chan func()", "chan func()"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := ShortenType(tt.input)
if result != tt.expected {
t.Errorf("shortenTypeEx() got = %v, want %v", result, tt.expected)
}
})
}
}
@@ -0,0 +1,679 @@
package api
import (
"bytes"
"errors"
"fmt"
"reflect"
"strconv"
"unicode"
"github.com/go-delve/delve/pkg/proc"
)
// ErrNotExecutable is an error returned when trying
// to debug a non-executable file.
var ErrNotExecutable = errors.New("not an executable file")
// DebuggerState represents the current context of the debugger.
type DebuggerState struct {
// PID of the process we are debugging.
Pid int
// Command line of the process we are debugging.
TargetCommandLine string
// Running is true if the process is running and no other information can be collected.
Running bool
// Recording is true if the process is currently being recorded and no other
// information can be collected. While the debugger is in this state
// sending a StopRecording request will halt the recording, every other
// request will block until the process has been recorded.
Recording bool
// Core dumping currently in progress.
CoreDumping bool
// CurrentThread is the currently selected debugger thread.
CurrentThread *Thread `json:"currentThread,omitempty"`
// SelectedGoroutine is the currently selected goroutine
SelectedGoroutine *Goroutine `json:"currentGoroutine,omitempty"`
// List of all the process threads
Threads []*Thread
// NextInProgress indicates that a next or step operation was interrupted by another breakpoint
// or a manual stop and is waiting to complete.
// While NextInProgress is set further requests for next or step may be rejected.
// Either execute continue until NextInProgress is false or call CancelNext
NextInProgress bool
// WatchOutOfScope contains the list of watchpoints that went out of scope
// during the last continue.
WatchOutOfScope []*Breakpoint
// 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:"-"`
}
type TracepointResult struct {
// Addr is the address of this tracepoint.
Addr uint64 `json:"addr"`
// File is the source file for the breakpoint.
File string `json:"file"`
// Line is a line in File for the breakpoint.
Line int `json:"line"`
IsRet bool `json:"is_ret"`
// FunctionName is the name of the function at the current breakpoint, and
// may not always be available.
FunctionName string `json:"functionName,omitempty"`
GoroutineID int `json:"goroutineID"`
InputParams []Variable `json:"inputParams,omitempty"`
ReturnParams []Variable `json:"returnParams,omitempty"`
}
// Breakpoint addresses a set of locations at which process execution may be
// suspended.
type Breakpoint struct {
// ID is a unique identifier for the breakpoint.
ID int `json:"id"`
// User defined name of the breakpoint.
Name string `json:"name"`
// Addr is deprecated, use Addrs.
Addr uint64 `json:"addr"`
// Addrs is the list of addresses for this breakpoint.
Addrs []uint64 `json:"addrs"`
// AddrPid[i] is the PID associated with by Addrs[i], when debugging a
// single target process this is optional, otherwise it is mandatory.
AddrPid []int `json:"addrpid"`
// File is the source file for the breakpoint.
File string `json:"file"`
// Line is a line in File for the breakpoint.
Line int `json:"line"`
// FunctionName is the name of the function at the current breakpoint, and
// may not always be available.
FunctionName string `json:"functionName,omitempty"`
// ExprString is the string that will be used to set a suspended breakpoint.
ExprString string
// Breakpoint condition
Cond string
// Breakpoint hit count condition.
// Supported hit count conditions are "NUMBER" and "OP NUMBER".
HitCond string
// HitCondPerG use per goroutine hitcount as HitCond operand, instead of total hitcount
HitCondPerG bool
// Tracepoint flag, signifying this is a tracepoint.
Tracepoint bool `json:"continue"`
// TraceReturn flag signifying this is a breakpoint set at a return
// statement in a traced function.
TraceReturn bool `json:"traceReturn"`
// retrieve goroutine information
Goroutine bool `json:"goroutine"`
// number of stack frames to retrieve
Stacktrace int `json:"stacktrace"`
// expressions to evaluate
Variables []string `json:"variables,omitempty"`
// LoadArgs requests loading function arguments when the breakpoint is hit
LoadArgs *LoadConfig
// LoadLocals requests loading function locals when the breakpoint is hit
LoadLocals *LoadConfig
// WatchExpr is the expression used to create this watchpoint
WatchExpr string
WatchType WatchType
VerboseDescr []string `json:"VerboseDescr,omitempty"`
// number of times a breakpoint has been reached in a certain goroutine
HitCount map[string]uint64 `json:"hitCount"`
// number of times a breakpoint has been reached
TotalHitCount uint64 `json:"totalHitCount"`
// Disabled flag, signifying the state of the breakpoint
Disabled bool `json:"disabled"`
UserData interface{} `json:"-"`
// RootFuncName is the Root function from where tracing needs to be done
RootFuncName string
// TraceFollowCalls indicates the Depth of tracing
TraceFollowCalls int
}
// 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")
}
for _, ch := range name {
if !(unicode.IsLetter(ch) || unicode.IsDigit(ch)) {
return fmt.Errorf("invalid character in breakpoint name '%c'", ch)
}
}
return nil
}
// WatchType is the watchpoint type
type WatchType uint8
const (
WatchRead WatchType = 1 << iota
WatchWrite
)
// Thread is a thread within the debugged process.
type Thread struct {
// ID is a unique identifier for the thread.
ID int `json:"id"`
// PC is the current program counter for the thread.
PC uint64 `json:"pc"`
// File is the file for the program counter.
File string `json:"file"`
// Line is the line number for the program counter.
Line int `json:"line"`
// Function is function information at the program counter. May be nil.
Function *Function `json:"function,omitempty"`
// ID of the goroutine running on this thread
GoroutineID int64 `json:"goroutineID"`
// Breakpoint this thread is stopped at
Breakpoint *Breakpoint `json:"breakPoint,omitempty"`
// Information requested by the current breakpoint
BreakpointInfo *BreakpointInfo `json:"breakPointInfo,omitempty"`
// ReturnValues contains the return values of the function we just stepped out of
ReturnValues []Variable
// CallReturn is true if ReturnValues are the return values of an injected call.
CallReturn bool
}
// Location holds program location information.
// In most cases a Location object will represent a physical location, with
// a single PC address held in the PC field.
// FindLocations however returns logical locations that can either have
// multiple PC addresses each (due to inlining) or no PC address at all.
type Location struct {
PC uint64 `json:"pc"`
File string `json:"file"`
Line int `json:"line"`
Function *Function `json:"function,omitempty"`
PCs []uint64 `json:"pcs,omitempty"`
PCPids []int `json:"pcpids,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 {
return &frame.Locals[i]
}
}
for i := range frame.Arguments {
if frame.Arguments[i].Name == name {
return &frame.Arguments[i]
}
}
return nil
}
// Function represents thread-scoped function information.
type Function struct {
// Name is the function 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 = 1 << iota
// VariableShadowed is set for local variables that are shadowed by a
// variable with the same name in another scope
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
// VariableFakeAddress means the address of this variable is either fake
// (i.e. the variable is partially or completely stored in a CPU register
// and doesn't have a real address) or possibly no longer available (because
// the variable is the return value of a function call and allocated on a
// frame that no longer exists)
VariableFakeAddress
// VariableCPtr means the variable is a C pointer
VariableCPtr
// VariableCPURegister means this variable is a CPU register.
VariableCPURegister
)
// Variable describes a variable.
type Variable struct {
// Name of the variable or struct member
Name string `json:"name"`
// Address of the variable or struct member
Addr uint64 `json:"addr"`
// Only the address field is filled (result of evaluating expressions like &<expr>)
OnlyAddr bool `json:"onlyAddr"`
// Go type of the variable
Type string `json:"type"`
// 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
// Function variables will store the name of the function in this field
Value string `json:"value"`
// Number of elements in an array or a slice, number of keys for a map, number of struct members for a struct, length of strings, number of captured variables for functions
Len int64 `json:"len"`
// Cap value for slices
Cap int64 `json:"cap"`
// Array and slice elements, member fields of structs, key/value pairs of maps, value of complex numbers, captured variables of functions.
// 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 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 uint64 `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
type LoadConfig struct {
// FollowPointers requests pointers to be automatically dereferenced.
FollowPointers bool
// MaxVariableRecurse is how far to recurse when evaluating nested types.
MaxVariableRecurse int
// MaxStringLen is the maximum number of bytes read from a string
MaxStringLen int
// MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
MaxArrayValues int
// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
MaxStructFields int
}
// Goroutine represents the information relevant to Delve from the runtime's
// internal G structure.
type Goroutine struct {
// ID is a unique identifier for the goroutine.
ID int64 `json:"id"`
// Current location of the goroutine
CurrentLoc Location `json:"currentLoc"`
// Current location of the goroutine, excluding calls inside runtime
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"`
Status uint64 `json:"status"`
WaitSince int64 `json:"waitSince"`
WaitReason int64 `json:"waitReason"`
Unreadable string `json:"unreadable"`
// Goroutine's pprof labels
Labels map[string]string `json:"labels,omitempty"`
}
const (
GoroutineWaiting = proc.Gwaiting
GoroutineSyscall = proc.Gsyscall
)
// DebuggerCommand is a command which changes the debugger's execution state.
type DebuggerCommand struct {
// Name is the command to run.
Name string `json:"name"`
// ThreadID is used to specify which thread to use with the SwitchThread
// command.
ThreadID int `json:"threadID,omitempty"`
// GoroutineID is used to specify which thread to use with the SwitchGoroutine
// and Call commands.
GoroutineID int64 `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"`
// UnsafeCall disables parameter escape checking for function calls.
// Go objects can be allocated on the stack or on the heap. Heap objects
// can be used by any goroutine; stack objects can only be used by the
// goroutine that owns the stack they are allocated on and can not survive
// the stack frame of allocation.
// The Go compiler will use escape analysis to determine whether to
// allocate an object on the stack or the heap.
// When injecting a function call Delve will check that no address of a
// stack allocated object is passed to the called function: this ensures
// the rules for stack objects will not be violated.
// If you are absolutely sure that the function you are calling will not
// violate the rules about stack objects you can disable this safety check
// by setting UnsafeCall to true.
UnsafeCall bool `json:"unsafeCall,omitempty"`
}
// BreakpointInfo contains information about the current breakpoint
type BreakpointInfo struct {
Stacktrace []Stackframe `json:"stacktrace,omitempty"`
Goroutine *Goroutine `json:"goroutine,omitempty"`
Variables []Variable `json:"variables,omitempty"`
Arguments []Variable `json:"arguments,omitempty"`
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 int64
Frame int
DeferredCall int // when DeferredCall is n > 0 this eval scope is relative to the n-th deferred call in the current frame
}
const (
// Continue resumes process execution.
Continue = "continue"
// Rewind resumes process execution backwards (target must be a recording).
Rewind = "rewind"
// DirectionCongruentContinue resumes process execution, if a reverse next, step or stepout operation is in progress it will resume execution backward.
DirectionCongruentContinue = "directionCongruentContinue"
// Step continues to next source line, entering function calls.
Step = "step"
// ReverseStep continues backward to the previous line of source code, entering function calls.
ReverseStep = "reverseStep"
// StepOut continues to the return address of the current function
StepOut = "stepOut"
// ReverseStepOut continues backward to the caller of the current function.
ReverseStepOut = "reverseStepOut"
// StepInstruction continues for exactly 1 cpu instruction.
StepInstruction = "stepInstruction"
// NextInstruction continues for 1 cpu instruction, skipping over CALL instructions.
NextInstruction = "nextInstruction"
// ReverseStepInstruction reverses execution for exactly 1 cpu instruction.
ReverseStepInstruction = "reverseStepInstruction"
// ReverseNextInstruction reverses execution for 1 cpu instruction, skipping over CALL instructions.
ReverseNextInstruction = "reverseNextInstruction"
// Next continues to the next source line, not entering function calls.
Next = "next"
// ReverseNext continues backward to the previous line of source code, not entering function calls.
ReverseNext = "reverseNext"
// SwitchThread switches the debugger's current thread context.
SwitchThread = "switchThread"
// SwitchGoroutine switches the debugger's current thread context to the thread running the specified goroutine
SwitchGoroutine = "switchGoroutine"
// Halt suspends the process.
// The effect of Halt while the target process is stopped, or in the
// process of stopping, is operating system and timing dependent. It will
// either have no effect or cause the following resume to stop immediately.
Halt = "halt"
// Call resumes process execution injecting a function call.
Call = "call"
)
// AssemblyFlavour describes the output
// of disassembled code.
type AssemblyFlavour int
const (
// GNUFlavour will disassemble using GNU assembly syntax.
GNUFlavour = AssemblyFlavour(proc.GNUFlavour)
// IntelFlavour will disassemble using Intel assembly syntax.
IntelFlavour = AssemblyFlavour(proc.IntelFlavour)
// GoFlavour will disassemble using Go assembly syntax.
GoFlavour = AssemblyFlavour(proc.GoFlavour)
)
// AsmInstruction represents one assembly instruction at some address
type AsmInstruction struct {
// Loc is the location of this instruction
Loc Location
// Destination of CALL instructions
DestLoc *Location
// Text is the formatted representation of the instruction
Text string
// Bytes is the instruction as read from memory
Bytes []byte
// If Breakpoint is true a breakpoint is set at this instruction
Breakpoint bool
// In AtPC is true this is the instruction the current thread is stopped at
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
Backend string // backend currently in use
TargetGoVersion string
MinSupportedVersionOfGo string
MaxSupportedVersionOfGo string
}
// 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
DwarfNumber int
}
// Registers is a list of CPU registers.
type Registers []Register
func (regs Registers) String() string {
maxlen := 0
for _, reg := range regs {
if n := len(reg.Name); n > maxlen {
maxlen = n
}
}
var buf bytes.Buffer
for _, reg := range regs {
fmt.Fprintf(&buf, "%*s = %s\n", maxlen, reg.Name, reg.Value)
}
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
}
// Image represents a loaded shared object (go plugin or shared library)
type Image struct {
Path string
Address uint64
LoadError string
}
// Ancestor represents a goroutine ancestor
type Ancestor struct {
ID int64
Stack []Stackframe
Unreadable string
}
// StacktraceOptions is the type of the Opts field of StacktraceIn that
// configures the stacktrace.
// Tracks proc.StacktraceOptions
type StacktraceOptions uint16
const (
// StacktraceReadDefers requests a stacktrace decorated with deferred calls
// for each frame.
StacktraceReadDefers StacktraceOptions = 1 << iota
// StacktraceSimple requests a stacktrace where no stack switches will be
// attempted.
StacktraceSimple
// StacktraceG requests a stacktrace starting with the register
// values saved in the runtime.g structure.
StacktraceG
)
// PackageBuildInfo maps an import path to a directory path.
type PackageBuildInfo struct {
ImportPath string
DirectoryPath string
Files []string
}
// DumpState describes the state of a core dump in progress
type DumpState struct {
Dumping bool
AllDone bool
ThreadsDone, ThreadsTotal int
MemDone, MemTotal uint64
Err string
}
// ListGoroutinesFilter describes a filtering condition for the
// ListGoroutines API call.
type ListGoroutinesFilter struct {
Kind GoroutineField
Negated bool
Arg string
}
// GoroutineField allows referring to a field of a goroutine object.
type GoroutineField uint8
const (
GoroutineFieldNone GoroutineField = iota
GoroutineCurrentLoc // the goroutine's CurrentLoc
GoroutineUserLoc // the goroutine's UserLoc
GoroutineGoLoc // the goroutine's GoStatementLoc
GoroutineStartLoc // the goroutine's StartLoc
GoroutineLabel // the goroutine's label
GoroutineRunning // the goroutine is running
GoroutineUser // the goroutine is a user goroutine
GoroutineWaitingOnChannel // the goroutine is waiting on the channel specified by the argument
)
// GoroutineGroup represents a group of goroutines in the return value of
// the ListGoroutines API call.
type GoroutineGroup struct {
Name string // name of this group
Offset int // start offset in the list of goroutines of this group
Count int // number of goroutines that belong to this group in the list of goroutines
Total int // total number of goroutines that belong to this group
}
type GoroutineGroupingOptions struct {
GroupBy GoroutineField
GroupByKey string
MaxGroupMembers int
MaxGroups int
}
// Target represents a debugging target.
type Target struct {
Pid int
CmdLine string
CurrentThread *Thread
}
@@ -0,0 +1,210 @@
package service
import (
"time"
"github.com/go-delve/delve/service/api"
)
// Client represents a debugger service client. All client methods are
// synchronous.
type Client interface {
// ProcessPid returns the pid of the process we are debugging.
ProcessPid() int
// BuildID returns the BuildID of the process' executable we are debugging.
BuildID() string
// LastModified returns the time that the process' executable was modified.
LastModified() time.Time
// Detach detaches the debugger, optionally killing the process.
Detach(killProcess bool) error
// Restart restarts program. Set true if you want to rebuild the process we are debugging.
Restart(rebuild bool) ([]api.DiscardedBreakpoint, error)
// RestartFrom restarts program from the specified position.
RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]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
// DirectionCongruentContinue resumes process execution, if a reverse next, step or stepout operation is in progress it will resume execution backward.
DirectionCongruentContinue() <-chan *api.DebuggerState
// Next continues to the next source line, not entering function calls.
Next() (*api.DebuggerState, error)
// ReverseNext continues backward to the previous line of source code, not entering function calls.
ReverseNext() (*api.DebuggerState, error)
// Step continues to the next source line, entering function calls.
Step() (*api.DebuggerState, error)
// ReverseStep continues backward to the previous line of source code, entering function calls.
ReverseStep() (*api.DebuggerState, error)
// StepOut continues to the return address of the current function.
StepOut() (*api.DebuggerState, error)
// ReverseStepOut continues backward to the caller of the current function.
ReverseStepOut() (*api.DebuggerState, error)
// Call resumes process execution while making a function call.
Call(goroutineID int64, expr string, unsafe bool) (*api.DebuggerState, error)
// StepInstruction will step a single cpu instruction.
StepInstruction(skipCalls bool) (*api.DebuggerState, error)
// ReverseStepInstruction will reverse step a single cpu instruction.
ReverseStepInstruction(skipCalls bool) (*api.DebuggerState, error)
// SwitchThread switches the current thread context.
SwitchThread(threadID int) (*api.DebuggerState, error)
// SwitchGoroutine switches the current goroutine (and the current thread as well)
SwitchGoroutine(goroutineID int64) (*api.DebuggerState, error)
// Halt suspends the process.
Halt() (*api.DebuggerState, error)
// GetBreakpoint gets a breakpoint by ID.
GetBreakpoint(id int) (*api.Breakpoint, error)
// GetBreakpointByName gets a breakpoint by name.
GetBreakpointByName(name string) (*api.Breakpoint, error)
// CreateBreakpoint creates a new breakpoint.
CreateBreakpoint(*api.Breakpoint) (*api.Breakpoint, error)
// CreateBreakpointWithExpr creates a new breakpoint and sets an expression to restore it after it is disabled.
CreateBreakpointWithExpr(*api.Breakpoint, string, [][2]string, bool) (*api.Breakpoint, error)
// CreateWatchpoint creates a new watchpoint.
CreateWatchpoint(api.EvalScope, string, api.WatchType) (*api.Breakpoint, error)
// ListBreakpoints gets all breakpoints.
ListBreakpoints(bool) ([]*api.Breakpoint, error)
// ClearBreakpoint deletes a breakpoint by ID.
ClearBreakpoint(id int) (*api.Breakpoint, error)
// ClearBreakpointByName deletes a breakpoint by name
ClearBreakpointByName(name string) (*api.Breakpoint, error)
// ToggleBreakpoint toggles on or off a breakpoint by ID.
ToggleBreakpoint(id int) (*api.Breakpoint, error)
// ToggleBreakpointByName toggles on or off a breakpoint by name.
ToggleBreakpointByName(name string) (*api.Breakpoint, error)
// AmendBreakpoint allows user to update an existing breakpoint for example to change the information
// retrieved when the breakpoint is hit or to change, add or remove the break condition
AmendBreakpoint(*api.Breakpoint) error
// CancelNext cancels a Next or Step call that was interrupted by a manual stop or by another breakpoint
CancelNext() error
// ListThreads lists all threads.
ListThreads() ([]*api.Thread, error)
// GetThread gets a thread by its ID.
GetThread(id int) (*api.Thread, error)
// ListPackageVariables lists all package variables in the context of the current thread.
ListPackageVariables(filter string, cfg api.LoadConfig) ([]api.Variable, error)
// EvalVariable returns a variable in the context of the current thread.
EvalVariable(scope api.EvalScope, symbol string, cfg api.LoadConfig) (*api.Variable, error)
// SetVariable sets the value of a variable
SetVariable(scope api.EvalScope, symbol, value string) error
// ListSources lists all source files in the process matching filter.
ListSources(filter string) ([]string, error)
// ListFunctions lists all functions in the process matching filter.
ListFunctions(filter string, tracefollow int) ([]string, error)
// ListTypes lists all types in the process matching filter.
ListTypes(filter string) ([]string, error)
// ListPackagesBuildInfo lists all packages in the process matching filter.
ListPackagesBuildInfo(filter string, includeFiles bool) ([]api.PackageBuildInfo, error)
// ListLocalVariables lists all local variables in scope.
ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error)
// ListFunctionArgs lists all arguments to the current function.
ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error)
// ListThreadRegisters lists registers and their values, for the given thread.
ListThreadRegisters(threadID int, includeFp bool) (api.Registers, error)
// ListScopeRegisters lists registers and their values, for the given scope.
ListScopeRegisters(scope api.EvalScope, includeFp bool) (api.Registers, error)
// ListGoroutines lists all goroutines.
ListGoroutines(start, count int) ([]*api.Goroutine, int, error)
// ListGoroutinesWithFilter lists goroutines matching the filters
ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions, scope *api.EvalScope) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error)
// Stacktrace returns stacktrace
Stacktrace(goroutineID int64, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error)
// Ancestors returns ancestor stacktraces
Ancestors(goroutineID int64, numAncestors int, depth int) ([]api.Ancestor, error)
// AttachedToExistingProcess returns whether we attached to a running process or not
AttachedToExistingProcess() bool
// FindLocation returns concrete location information described by a location expression
// loc ::= <filename>:<line> | <function>[:<line>] | /<regex>/ | (+|-)<offset> | <line> | *<address>
// * <filename> can be the full path of a file or just a suffix
// * <function> ::= <package>.<receiver type>.<name> | <package>.(*<receiver type>).<name> | <receiver type>.<name> | <package>.<name> | (*<receiver type>).<name> | <name>
// * <function> must be unambiguous
// * /<regex>/ will return a location for each function matched by regex
// * +<offset> returns a location for the line that is <offset> lines after the current line
// * -<offset> returns a location for the line that is <offset> lines before the current line
// * <line> returns a location for a line in the current file
// * *<address> returns the location corresponding to the specified address
// NOTE: this function does not actually set breakpoints.
// If findInstruction is true FindLocation will only return locations that correspond to instructions.
FindLocation(scope api.EvalScope, loc string, findInstruction bool, substitutePathRules [][2]string) ([]api.Location, string, error)
// DisassembleRange disassemble code between startPC and endPC
DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error)
// DisassemblePC 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)
// IsMulticlient returns true if the headless instance is multiclient.
IsMulticlient() bool
// ListDynamicLibraries returns a list of loaded dynamic libraries.
ListDynamicLibraries() ([]api.Image, error)
// ExamineMemory returns the raw memory stored at the given address.
// The amount of data to be read is specified by length which must be less than or equal to 1000.
// This function will return an error if it reads less than `length` bytes.
ExamineMemory(address uint64, length int) ([]byte, bool, error)
// StopRecording stops a recording if one is in progress.
StopRecording() error
// CoreDumpStart starts creating a core dump to the specified file
CoreDumpStart(dest string) (api.DumpState, error)
// CoreDumpWait waits for the core dump to finish, or for the specified amount of milliseconds
CoreDumpWait(msec int) api.DumpState
// CoreDumpCancel cancels a core dump in progress
CoreDumpCancel() error
// ListTargets returns the list of connected targets
ListTargets() ([]api.Target, error)
// FollowExec enables or disables the follow exec mode. In follow exec mode
// Delve will automatically debug child processes launched by the target
// process
FollowExec(bool, string) error
FollowExecEnabled() 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
// SetDebugInfoDirectories sets directories used to search for debug symbols
SetDebugInfoDirectories([]string) error
// GetDebugInfoDirectories returns the list of directories used to search for debug symbols
GetDebugInfoDirectories() ([]string, error)
// CallAPI allows calling an arbitrary rpc method (used by starlark bindings)
CallAPI(method string, args, reply interface{}) error
}
@@ -0,0 +1,39 @@
package service
import (
"net"
"github.com/go-delve/delve/service/debugger"
)
// Config provides the configuration to start a Debugger and expose it with a
// service.
//
// 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 {
// Debugger configuration object, used to configure the underlying
// debugger used by the server.
Debugger debugger.Config
// Listener is used to serve requests.
Listener net.Listener
// ProcessArgs are the arguments to launch a new process.
ProcessArgs []string
// AcceptMulti configures the server to accept multiple connection.
// Note that the server API is not reentrant and clients will have to coordinate.
AcceptMulti bool
// APIVersion selects which version of the API to serve (default: 1).
APIVersion int
// CheckLocalConnUser is true if the debugger should check that local
// connections come from the same user that started the headless server
CheckLocalConnUser bool
// DisconnectChan will be closed by the server when the client disconnects
DisconnectChan chan<- struct{}
}
@@ -0,0 +1,169 @@
package dap
import (
"bytes"
"errors"
"fmt"
"sort"
"strings"
"github.com/go-delve/delve/pkg/config"
"github.com/google/go-dap"
)
func (s *Session) delveCmd(goid, frame int, cmdstr string) (string, error) {
vals := strings.SplitN(strings.TrimSpace(cmdstr), " ", 2)
cmdname := vals[0]
var args string
if len(vals) > 1 {
args = strings.TrimSpace(vals[1])
}
for _, cmd := range debugCommands(s) {
for _, alias := range cmd.aliases {
if alias == cmdname {
return cmd.cmdFn(goid, frame, args)
}
}
}
return "", errNoCmd
}
type cmdfunc func(goid, frame int, args string) (string, error)
type command struct {
aliases []string
helpMsg string
cmdFn cmdfunc
}
const (
msgHelp = `Prints the help message.
dlv help [command]
Type "help" followed by the name of a command for more information about it.`
msgConfig = `Changes configuration parameters.
dlv config -list
Show all configuration parameters.
dlv config -list <parameter>
Show value of a configuration parameter.
dlv config <parameter> <value>
Changes the value of a configuration parameter.
dlv config substitutePath <from> <to>
dlv config substitutePath <from>
dlv config substitutePath -clear
Adds or removes a path substitution rule. If -clear is used all substitutePath rules are removed.
See also Documentation/cli/substitutepath.md.
dlv config showPprofLabels <label>
dlv config showPprofLabels -clear <label>
dlv config showPprofLabels -clear
Adds or removes a label key to show in the callstack view. If -clear is used without an argument,
all labels are removed.`
msgSources = `Print list of source files.
dlv sources [<regex>]
If regex is specified only the source files matching it will be returned.`
)
// debugCommands returns a list of commands with default commands defined.
func debugCommands(s *Session) []command {
return []command{
{aliases: []string{"help", "h"}, cmdFn: s.helpMessage, helpMsg: msgHelp},
{aliases: []string{"config"}, cmdFn: s.evaluateConfig, helpMsg: msgConfig},
{aliases: []string{"sources", "s"}, cmdFn: s.sources, helpMsg: msgSources},
}
}
var errNoCmd = errors.New("command not available")
func (s *Session) helpMessage(_, _ int, args string) (string, error) {
var buf bytes.Buffer
if args != "" {
for _, cmd := range debugCommands(s) {
for _, alias := range cmd.aliases {
if alias == args {
return cmd.helpMsg, nil
}
}
}
return "", errNoCmd
}
fmt.Fprintln(&buf, "The following commands are available:")
for _, cmd := range debugCommands(s) {
h := cmd.helpMsg
if idx := strings.Index(h, "\n"); idx >= 0 {
h = h[:idx]
}
if len(cmd.aliases) > 1 {
fmt.Fprintf(&buf, " dlv %s (alias: %s) \t %s\n", cmd.aliases[0], strings.Join(cmd.aliases[1:], " | "), h)
} else {
fmt.Fprintf(&buf, " dlv %s \t %s\n", cmd.aliases[0], h)
}
}
fmt.Fprintln(&buf)
fmt.Fprintln(&buf, "Type 'dlv help' followed by a command for full documentation.")
return buf.String(), nil
}
func (s *Session) evaluateConfig(_, _ int, expr string) (string, error) {
argv := config.Split2PartsBySpace(expr)
name := argv[0]
if name == "-list" {
if len(argv) > 1 {
return config.ConfigureListByName(&s.args, argv[1], "cfgName"), nil
}
return listConfig(&s.args), nil
}
updated, res, err := configureSet(&s.args, expr)
if err != nil {
return "", err
}
if updated {
// Send invalidated events for areas that are affected by configuration changes.
switch name {
case "showGlobalVariables", "showRegisters":
// Variable data has become invalidated.
s.send(&dap.InvalidatedEvent{
Event: *newEvent("invalidated"),
Body: dap.InvalidatedEventBody{
Areas: []dap.InvalidatedAreas{"variables"},
},
})
case "goroutineFilters", "hideSystemGoroutines", "showPprofLabels":
// Thread related data has become invalidated.
s.send(&dap.InvalidatedEvent{
Event: *newEvent("invalidated"),
Body: dap.InvalidatedEventBody{
Areas: []dap.InvalidatedAreas{"threads"},
},
})
}
res += "\nUpdated"
}
return res, nil
}
func (s *Session) sources(_, _ int, filter string) (string, error) {
sources, err := s.debugger.Sources(filter)
if err != nil {
return "", err
}
sort.Strings(sources)
return strings.Join(sources, "\n"), nil
}
@@ -0,0 +1,131 @@
package dap
import (
"bytes"
"errors"
"fmt"
"strings"
"github.com/go-delve/delve/pkg/config"
)
func listConfig(args *launchAttachArgs) string {
var buf bytes.Buffer
config.ConfigureList(&buf, args, "cfgName")
return buf.String()
}
func configureSet(sargs *launchAttachArgs, args string) (bool, string, error) {
v := config.Split2PartsBySpace(args)
cfgname := v[0]
var rest string
if len(v) == 2 {
rest = v[1]
}
field := config.ConfigureFindFieldByName(sargs, cfgname, "cfgName")
if !field.CanAddr() {
return false, "", fmt.Errorf("%q is not a configuration parameter", cfgname)
}
if cfgname == "substitutePath" {
err := configureSetSubstitutePath(sargs, rest)
if err != nil {
return false, "", err
}
// Print the updated client to server and server to client maps.
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
}
if cfgname == "showPprofLabels" {
err := configureSetShowPprofLabels(sargs, rest)
if err != nil {
return false, "", err
}
// Print the updated labels
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
}
err := config.ConfigureSetSimple(rest, cfgname, field)
if err != nil {
return false, "", err
}
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
}
func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
if strings.TrimSpace(rest) == "-clear" {
args.substitutePathClientToServer = args.substitutePathClientToServer[:0]
args.substitutePathServerToClient = args.substitutePathServerToClient[:0]
return nil
}
argv := config.SplitQuotedFields(rest, '"')
if len(argv) == 2 && argv[0] == "-clear" {
argv = argv[1:]
}
switch len(argv) {
case 0:
// do nothing, let caller show the current list of substitute path rules
return nil
case 1: // delete substitute-path rule
for i := range args.substitutePathClientToServer {
if args.substitutePathClientToServer[i][0] == argv[0] {
copy(args.substitutePathClientToServer[i:], args.substitutePathClientToServer[i+1:])
args.substitutePathClientToServer = args.substitutePathClientToServer[:len(args.substitutePathClientToServer)-1]
copy(args.substitutePathServerToClient[i:], args.substitutePathServerToClient[i+1:])
args.substitutePathServerToClient = args.substitutePathServerToClient[:len(args.substitutePathServerToClient)-1]
return nil
}
}
return fmt.Errorf("could not find rule for %q", argv[0])
case 2: // add substitute-path rule
for i := range args.substitutePathClientToServer {
if args.substitutePathClientToServer[i][0] == argv[0] {
args.substitutePathClientToServer[i][1] = argv[1]
args.substitutePathServerToClient[i][0] = argv[1]
return nil
}
}
args.substitutePathClientToServer = append(args.substitutePathClientToServer, [2]string{argv[0], argv[1]})
args.substitutePathServerToClient = append(args.substitutePathServerToClient, [2]string{argv[1], argv[0]})
default:
return errors.New("too many arguments to \"config substitutePath\"")
}
return nil
}
func configureSetShowPprofLabels(args *launchAttachArgs, rest string) error {
if strings.TrimSpace(rest) == "-clear" {
args.ShowPprofLabels = args.ShowPprofLabels[:0]
return nil
}
delete := false
argv := config.SplitQuotedFields(rest, '"')
if len(argv) == 2 && argv[0] == "-clear" {
argv = argv[1:]
delete = true
}
switch len(argv) {
case 0:
// do nothing, let caller show the current list of labels
return nil
case 1:
if delete {
for i := range args.ShowPprofLabels {
if args.ShowPprofLabels[i] == argv[0] {
copy(args.ShowPprofLabels[i:], args.ShowPprofLabels[i+1:])
args.ShowPprofLabels = args.ShowPprofLabels[:len(args.ShowPprofLabels)-1]
return nil
}
}
return fmt.Errorf("could not find label %q", argv[0])
} else {
args.ShowPprofLabels = append(args.ShowPprofLabels, argv[0])
}
default:
return errors.New("too many arguments to \"config showPprofLabels\"")
}
return nil
}
@@ -0,0 +1,340 @@
package dap
import (
"testing"
)
func TestListConfig(t *testing.T) {
type args struct {
args *launchAttachArgs
}
tests := []struct {
name string
args args
want string
}{
{
name: "empty",
args: args{
args: &launchAttachArgs{},
},
want: formatConfig(0, false, false, "", []string{}, false, [][2]string{}),
},
{
name: "default values",
args: args{
args: &defaultArgs,
},
want: formatConfig(50, false, false, "", []string{}, false, [][2]string{}),
},
{
name: "custom values",
args: args{
args: &launchAttachArgs{
StackTraceDepth: 35,
ShowGlobalVariables: true,
GoroutineFilters: "SomeFilter",
ShowPprofLabels: []string{"SomeLabel"},
substitutePathClientToServer: [][2]string{{"hello", "world"}},
substitutePathServerToClient: [][2]string{{"world", "hello"}},
},
},
want: formatConfig(35, true, false, "SomeFilter", []string{"SomeLabel"}, false, [][2]string{{"hello", "world"}}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := listConfig(tt.args.args); got != tt.want {
t.Errorf("listConfig() = %v, want %v", got, tt.want)
}
})
}
}
func TestConfigureSetSubstitutePath(t *testing.T) {
type args struct {
args *launchAttachArgs
rest string
}
tests := []struct {
name string
args args
wantRules [][2]string
wantErr bool
}{
// Test add rule.
{
name: "add rule",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{},
substitutePathServerToClient: [][2]string{},
},
rest: "/path/to/client/dir /path/to/server/dir",
},
wantRules: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
wantErr: false,
},
{
name: "add rule (multiple)",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
},
substitutePathServerToClient: [][2]string{
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
},
},
rest: "/path/to/client/dir/c /path/to/server/dir/b",
},
wantRules: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
},
wantErr: false,
},
{
name: "add rule from empty string",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{},
substitutePathServerToClient: [][2]string{},
},
rest: `"" /path/to/client/dir`,
},
wantRules: [][2]string{{"", "/path/to/client/dir"}},
wantErr: false,
},
{
name: "add rule to empty string",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{},
substitutePathServerToClient: [][2]string{},
},
rest: `/path/to/client/dir ""`,
},
wantRules: [][2]string{{"/path/to/client/dir", ""}},
wantErr: false,
},
{
name: "add rule from empty string(multiple)",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
},
substitutePathServerToClient: [][2]string{
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
},
},
rest: `"" /path/to/client/dir/c`,
},
wantRules: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
{"", "/path/to/client/dir/c"},
},
wantErr: false,
},
{
name: "add rule to empty string(multiple)",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
},
substitutePathServerToClient: [][2]string{
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
},
},
rest: `/path/to/client/dir/c ""`,
},
wantRules: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
{"/path/to/client/dir/c", ""},
},
wantErr: false,
},
// Test modify rule.
{
name: "modify rule",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", "/path/to/client/dir"}},
},
rest: "/path/to/client/dir /new/path/to/server/dir",
},
wantRules: [][2]string{{"/path/to/client/dir", "/new/path/to/server/dir"}},
wantErr: false,
},
{
name: "modify rule with from as empty string",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{{"", "/path/to/server/dir"}},
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", ""}},
},
rest: `"" /new/path/to/server/dir`,
},
wantRules: [][2]string{{"", "/new/path/to/server/dir"}},
wantErr: false,
},
{
name: "modify rule with to as empty string",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", ""}},
substitutePathServerToClient: [][2]string{{"", "/path/to/client/dir"}},
},
rest: `/path/to/client/dir ""`,
},
wantRules: [][2]string{{"/path/to/client/dir", ""}},
wantErr: false,
},
{
name: "modify rule (multiple)",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
},
substitutePathServerToClient: [][2]string{
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
{"/path/to/server/dir/b", "/path/to/client/dir/c"},
},
},
rest: "/path/to/client/dir/b /new/path",
},
wantRules: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/new/path"},
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
},
wantErr: false,
},
{
name: "modify rule with from as empty string(multiple)",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"", "/path/to/server/dir/b"},
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
},
substitutePathServerToClient: [][2]string{
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
{"/path/to/server/dir/b", ""},
{"/path/to/server/dir/b", "/path/to/client/dir/c"},
},
},
rest: `"" /new/path`,
},
wantRules: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"", "/new/path"},
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
},
wantErr: false,
},
{
name: "modify rule with to as empty string(multiple)",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
},
substitutePathServerToClient: [][2]string{
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
{"/path/to/server/dir/b", "/path/to/client/dir/c"},
},
},
rest: `/path/to/client/dir/b ""`,
},
wantRules: [][2]string{
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
{"/path/to/client/dir/b", ""},
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
},
wantErr: false,
},
// Test delete rule.
{
name: "delete rule",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", "/path/to/client/dir"}},
},
rest: "/path/to/client/dir",
},
wantRules: [][2]string{},
wantErr: false,
},
{
name: "delete rule, empty string",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{{"", "/path/to/server/dir"}},
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", ""}},
},
rest: `""`,
},
wantRules: [][2]string{},
wantErr: false,
},
// Test invalid input.
{
name: "error on delete nonexistent rule",
args: args{
args: &launchAttachArgs{
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", "/path/to/client/dir"}},
},
rest: "/path/to/server/dir",
},
wantRules: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := configureSetSubstitutePath(tt.args.args, tt.args.rest)
if (err != nil) != tt.wantErr {
t.Errorf("configureSetSubstitutePath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(tt.args.args.substitutePathClientToServer) != len(tt.wantRules) {
t.Errorf("configureSetSubstitutePath() got substitutePathClientToServer=%v, want %d rules", tt.args.args.substitutePathClientToServer, len(tt.wantRules))
return
}
gotClient2Server := tt.args.args.substitutePathClientToServer
gotServer2Client := tt.args.args.substitutePathServerToClient
for i, rule := range tt.wantRules {
if gotClient2Server[i][0] != rule[0] || gotClient2Server[i][1] != rule[1] {
t.Errorf("configureSetSubstitutePath() got substitutePathClientToServer[%d]=%#v,\n want %#v rules", i, gotClient2Server[i], rule)
}
if gotServer2Client[i][1] != rule[0] || gotServer2Client[i][0] != rule[1] {
reverseRule := [2]string{rule[1], rule[0]}
t.Errorf("configureSetSubstitutePath() got substitutePathServerToClient[%d]=%#v,\n want %#v rules", i, gotClient2Server[i], reverseRule)
}
}
})
}
}
@@ -0,0 +1,648 @@
// Package daptest provides a sample client with utilities
// for DAP mode testing.
package daptest
//go:generate go run ./gen/main.go -o ./resp.go
import (
"bufio"
"encoding/json"
"fmt"
"log"
"net"
"path/filepath"
"reflect"
"regexp"
"testing"
"github.com/google/go-dap"
)
// Client is a debugger service client that uses Debug Adaptor Protocol.
// It does not (yet?) implement service.Client interface.
// All client methods are synchronous.
type Client struct {
conn net.Conn
reader *bufio.Reader
// seq is used to track the sequence number of each
// requests that the client sends to the server
seq int
}
// NewClient creates a new Client over a TCP connection.
// Call Close() to close the connection.
func NewClient(addr string) *Client {
fmt.Println("Connecting to server at:", addr)
conn, err := net.Dial("tcp", addr)
if err != nil {
log.Fatal("dialing:", err)
}
return NewClientFromConn(conn)
}
// NewClientFromConn creates a new Client with the given TCP connection.
// Call Close to close the connection.
func NewClientFromConn(conn net.Conn) *Client {
c := &Client{conn: conn, reader: bufio.NewReader(conn)}
c.seq = 1 // match VS Code numbering
return c
}
// Close closes the client connection.
func (c *Client) Close() {
c.conn.Close()
}
func (c *Client) send(request dap.Message) {
dap.WriteProtocolMessage(c.conn, request)
}
func (c *Client) ReadMessage() (dap.Message, error) {
return dap.ReadProtocolMessage(c.reader)
}
func (c *Client) ExpectMessage(t *testing.T) dap.Message {
t.Helper()
m, err := dap.ReadProtocolMessage(c.reader)
if err != nil {
t.Fatal(err)
}
return m
}
func (c *Client) ExpectInvisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
t.Helper()
er := c.ExpectErrorResponse(t)
if er.Body.Error != nil && er.Body.Error.ShowUser {
t.Errorf("\ngot %#v\nwant ShowUser=false", er)
}
return er
}
func (c *Client) ExpectVisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
t.Helper()
er := c.ExpectErrorResponse(t)
if er.Body.Error == nil || !er.Body.Error.ShowUser {
t.Errorf("\ngot %#v\nwant ShowUser=true", er)
}
return er
}
func (c *Client) ExpectErrorResponseWith(t *testing.T, id int, message string, showUser bool) *dap.ErrorResponse {
t.Helper()
er := c.ExpectErrorResponse(t)
if er.Body.Error == nil {
t.Errorf("got nil, want Id=%d Format=%q ShowUser=%v", id, message, showUser)
return er
}
if matched, _ := regexp.MatchString(message, er.Body.Error.Format); !matched || er.Body.Error.Id != id || er.Body.Error.ShowUser != showUser {
t.Errorf("got %#v, want Id=%d Format=%q ShowUser=%v", er, id, message, showUser)
}
return er
}
func (c *Client) ExpectInitializeResponseAndCapabilities(t *testing.T) *dap.InitializeResponse {
t.Helper()
initResp := c.ExpectInitializeResponse(t)
wantCapabilities := dap.Capabilities{
// the values set by dap.(*Server).onInitializeRequest.
SupportsConfigurationDoneRequest: true,
SupportsConditionalBreakpoints: true,
SupportsDelayedStackTraceLoading: true,
SupportsExceptionInfoRequest: true,
SupportsSetVariable: true,
SupportsFunctionBreakpoints: true,
SupportsInstructionBreakpoints: true,
SupportsEvaluateForHovers: true,
SupportsClipboardContext: true,
SupportsSteppingGranularity: true,
SupportsLogPoints: true,
SupportsDisassembleRequest: true,
}
if !reflect.DeepEqual(initResp.Body, wantCapabilities) {
t.Errorf("capabilities in initializeResponse: got %+v, want %v", pretty(initResp.Body), pretty(wantCapabilities))
}
return initResp
}
func pretty(v interface{}) string {
s, _ := json.MarshalIndent(v, "", "\t")
return string(s)
}
func (c *Client) ExpectNotYetImplementedErrorResponse(t *testing.T) *dap.ErrorResponse {
t.Helper()
return c.ExpectErrorResponseWith(t, 7777, "Not yet implemented", false)
}
func (c *Client) ExpectUnsupportedCommandErrorResponse(t *testing.T) *dap.ErrorResponse {
t.Helper()
return c.ExpectErrorResponseWith(t, 9999, "Unsupported command", false)
}
func (c *Client) ExpectCapabilitiesEventSupportTerminateDebuggee(t *testing.T) *dap.CapabilitiesEvent {
t.Helper()
e := c.ExpectCapabilitiesEvent(t)
if !e.Body.Capabilities.SupportTerminateDebuggee {
t.Errorf("\ngot %#v\nwant SupportTerminateDebuggee=true", e.Body.Capabilities.SupportTerminateDebuggee)
}
return e
}
func (c *Client) ExpectOutputEventRegex(t *testing.T, want string) *dap.OutputEvent {
t.Helper()
e := c.ExpectOutputEvent(t)
if matched, _ := regexp.MatchString(want, e.Body.Output); !matched {
t.Errorf("\ngot %#v\nwant Output=%q", e, want)
}
return e
}
const ProcessExited = `Process [0-9]+ has exited with status %s\n`
func (c *Client) ExpectOutputEventProcessExited(t *testing.T, status int) *dap.OutputEvent {
t.Helper()
// We sometimes fail to return the correct exit status on Linux, so allow -1 here as well.
return c.ExpectOutputEventRegex(t, fmt.Sprintf(ProcessExited, fmt.Sprintf("(%d|-1)", status)))
}
func (c *Client) ExpectOutputEventProcessExitedAnyStatus(t *testing.T) *dap.OutputEvent {
t.Helper()
return c.ExpectOutputEventRegex(t, fmt.Sprintf(ProcessExited, `-?\d+`))
}
func (c *Client) ExpectOutputEventDetaching(t *testing.T) *dap.OutputEvent {
t.Helper()
return c.ExpectOutputEventRegex(t, `Detaching\n`)
}
func (c *Client) ExpectOutputEventDetachingKill(t *testing.T) *dap.OutputEvent {
t.Helper()
return c.ExpectOutputEventRegex(t, `Detaching and terminating target process\n`)
}
func (c *Client) ExpectOutputEventDetachingNoKill(t *testing.T) *dap.OutputEvent {
t.Helper()
return c.ExpectOutputEventRegex(t, `Detaching without terminating target process\n`)
}
func (c *Client) ExpectOutputEventTerminating(t *testing.T) *dap.OutputEvent {
t.Helper()
return c.ExpectOutputEventRegex(t, `Terminating process [0-9]+\n`)
}
const ClosingClient = "Closing client session, but leaving multi-client DAP server at .+:[0-9]+ with debuggee %s\n"
func (c *Client) ExpectOutputEventClosingClient(t *testing.T, status string) *dap.OutputEvent {
t.Helper()
return c.ExpectOutputEventRegex(t, fmt.Sprintf(ClosingClient, status))
}
func (c *Client) CheckStopLocation(t *testing.T, thread int, name string, line interface{}) {
t.Helper()
c.StackTraceRequest(thread, 0, 20)
st := c.ExpectStackTraceResponse(t)
if len(st.Body.StackFrames) < 1 {
t.Errorf("\ngot %#v\nwant len(stackframes) => 1", st)
} else {
switch line := line.(type) {
case int:
if line != -1 && st.Body.StackFrames[0].Line != line {
t.Errorf("\ngot %#v\nwant Line=%d", st, line)
}
case []int:
found := false
for _, line := range line {
if st.Body.StackFrames[0].Line == line {
found = true
}
}
if !found {
t.Errorf("\ngot %#v\nwant Line=%v", st, line)
}
}
if st.Body.StackFrames[0].Name != name {
t.Errorf("\ngot %#v\nwant Name=%q", st, name)
}
}
}
// InitializeRequest sends an 'initialize' request.
func (c *Client) InitializeRequest() {
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
request.Arguments = dap.InitializeRequestArguments{
AdapterID: "go",
PathFormat: "path",
LinesStartAt1: true,
ColumnsStartAt1: true,
SupportsVariableType: true,
SupportsVariablePaging: true,
SupportsRunInTerminalRequest: true,
Locale: "en-us",
}
c.send(request)
}
// InitializeRequestWithArgs sends an 'initialize' request with specified arguments.
func (c *Client) InitializeRequestWithArgs(args dap.InitializeRequestArguments) {
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
request.Arguments = args
c.send(request)
}
func toRawMessage(in interface{}) json.RawMessage {
out, _ := json.Marshal(in)
return out
}
// LaunchRequest sends a 'launch' request with the specified args.
func (c *Client) LaunchRequest(mode, program string, stopOnEntry bool) {
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
request.Arguments = toRawMessage(map[string]interface{}{
"request": "launch",
"mode": mode,
"program": program,
"stopOnEntry": stopOnEntry,
})
c.send(request)
}
// LaunchRequestWithArgs takes a map of untyped implementation-specific
// arguments to send a 'launch' request. This version can be used to
// test for values of unexpected types or unspecified values.
func (c *Client) LaunchRequestWithArgs(arguments map[string]interface{}) {
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
request.Arguments = toRawMessage(arguments)
c.send(request)
}
// AttachRequest sends an 'attach' request with the specified
// arguments.
func (c *Client) AttachRequest(arguments map[string]interface{}) {
request := &dap.AttachRequest{Request: *c.newRequest("attach")}
request.Arguments = toRawMessage(arguments)
c.send(request)
}
// DisconnectRequest sends a 'disconnect' request.
func (c *Client) DisconnectRequest() {
request := &dap.DisconnectRequest{Request: *c.newRequest("disconnect")}
c.send(request)
}
// DisconnectRequestWithKillOption sends a 'disconnect' request with an option to specify
// `terminateDebuggee`.
func (c *Client) DisconnectRequestWithKillOption(kill bool) {
request := &dap.DisconnectRequest{Request: *c.newRequest("disconnect")}
request.Arguments = &dap.DisconnectArguments{
TerminateDebuggee: kill,
}
c.send(request)
}
// SetBreakpointsRequest sends a 'setBreakpoints' request.
func (c *Client) SetBreakpointsRequest(file string, lines []int) {
c.SetBreakpointsRequestWithArgs(file, lines, nil, nil, nil)
}
// SetBreakpointsRequestWithArgs sends a 'setBreakpoints' request with an option to
// specify conditions, hit conditions, and log messages.
func (c *Client) SetBreakpointsRequestWithArgs(file string, lines []int, conditions, hitConditions, logMessages map[int]string) {
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setBreakpoints")}
request.Arguments = dap.SetBreakpointsArguments{
Source: dap.Source{
Name: filepath.Base(file),
Path: file,
},
Breakpoints: make([]dap.SourceBreakpoint, len(lines)),
}
for i, l := range lines {
request.Arguments.Breakpoints[i].Line = l
if cond, ok := conditions[l]; ok {
request.Arguments.Breakpoints[i].Condition = cond
}
if hitCond, ok := hitConditions[l]; ok {
request.Arguments.Breakpoints[i].HitCondition = hitCond
}
if logMessage, ok := logMessages[l]; ok {
request.Arguments.Breakpoints[i].LogMessage = logMessage
}
}
c.send(request)
}
// SetExceptionBreakpointsRequest sends a 'setExceptionBreakpoints' request.
func (c *Client) SetExceptionBreakpointsRequest() {
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setExceptionBreakpoints")}
c.send(request)
}
// ConfigurationDoneRequest sends a 'configurationDone' request.
func (c *Client) ConfigurationDoneRequest() {
request := &dap.ConfigurationDoneRequest{Request: *c.newRequest("configurationDone")}
c.send(request)
}
// ContinueRequest sends a 'continue' request.
func (c *Client) ContinueRequest(thread int) {
request := &dap.ContinueRequest{Request: *c.newRequest("continue")}
request.Arguments.ThreadId = thread
c.send(request)
}
// NextRequest sends a 'next' request.
func (c *Client) NextRequest(thread int) {
request := &dap.NextRequest{Request: *c.newRequest("next")}
request.Arguments.ThreadId = thread
c.send(request)
}
// NextInstructionRequest sends a 'next' request with granularity 'instruction'.
func (c *Client) NextInstructionRequest(thread int) {
request := &dap.NextRequest{Request: *c.newRequest("next")}
request.Arguments.ThreadId = thread
request.Arguments.Granularity = "instruction"
c.send(request)
}
// StepInRequest sends a 'stepIn' request.
func (c *Client) StepInRequest(thread int) {
request := &dap.StepInRequest{Request: *c.newRequest("stepIn")}
request.Arguments.ThreadId = thread
c.send(request)
}
// StepInInstructionRequest sends a 'stepIn' request with granularity 'instruction'.
func (c *Client) StepInInstructionRequest(thread int) {
request := &dap.StepInRequest{Request: *c.newRequest("stepIn")}
request.Arguments.ThreadId = thread
request.Arguments.Granularity = "instruction"
c.send(request)
}
// StepOutRequest sends a 'stepOut' request.
func (c *Client) StepOutRequest(thread int) {
request := &dap.StepOutRequest{Request: *c.newRequest("stepOut")}
request.Arguments.ThreadId = thread
c.send(request)
}
// StepOutInstructionRequest sends a 'stepOut' request with granularity 'instruction'.
func (c *Client) StepOutInstructionRequest(thread int) {
request := &dap.StepOutRequest{Request: *c.newRequest("stepOut")}
request.Arguments.ThreadId = thread
request.Arguments.Granularity = "instruction"
c.send(request)
}
// PauseRequest sends a 'pause' request.
func (c *Client) PauseRequest(threadId int) {
request := &dap.PauseRequest{Request: *c.newRequest("pause")}
request.Arguments.ThreadId = threadId
c.send(request)
}
// ThreadsRequest sends a 'threads' request.
func (c *Client) ThreadsRequest() {
request := &dap.ThreadsRequest{Request: *c.newRequest("threads")}
c.send(request)
}
// StackTraceRequest sends a 'stackTrace' request.
func (c *Client) StackTraceRequest(threadID, startFrame, levels int) {
request := &dap.StackTraceRequest{Request: *c.newRequest("stackTrace")}
request.Arguments.ThreadId = threadID
request.Arguments.StartFrame = startFrame
request.Arguments.Levels = levels
c.send(request)
}
// ScopesRequest sends a 'scopes' request.
func (c *Client) ScopesRequest(frameID int) {
request := &dap.ScopesRequest{Request: *c.newRequest("scopes")}
request.Arguments.FrameId = frameID
c.send(request)
}
// VariablesRequest sends a 'variables' request.
func (c *Client) VariablesRequest(variablesReference int) {
request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
request.Arguments.VariablesReference = variablesReference
c.send(request)
}
// IndexedVariablesRequest sends a 'variables' request.
func (c *Client) IndexedVariablesRequest(variablesReference, start, count int) {
request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
request.Arguments.VariablesReference = variablesReference
request.Arguments.Filter = "indexed"
request.Arguments.Start = start
request.Arguments.Count = count
c.send(request)
}
// NamedVariablesRequest sends a 'variables' request.
func (c *Client) NamedVariablesRequest(variablesReference int) {
request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
request.Arguments.VariablesReference = variablesReference
request.Arguments.Filter = "named"
c.send(request)
}
// TerminateRequest sends a 'terminate' request.
func (c *Client) TerminateRequest() {
c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")})
}
// RestartRequest sends a 'restart' request.
func (c *Client) RestartRequest() {
c.send(&dap.RestartRequest{Request: *c.newRequest("restart")})
}
// SetFunctionBreakpointsRequest sends a 'setFunctionBreakpoints' request.
func (c *Client) SetFunctionBreakpointsRequest(breakpoints []dap.FunctionBreakpoint) {
c.send(&dap.SetFunctionBreakpointsRequest{
Request: *c.newRequest("setFunctionBreakpoints"),
Arguments: dap.SetFunctionBreakpointsArguments{
Breakpoints: breakpoints,
},
})
}
// SetInstructionBreakpointsRequest sends a 'setInstructionBreakpoints' request.
func (c *Client) SetInstructionBreakpointsRequest(breakpoints []dap.InstructionBreakpoint) {
c.send(&dap.SetInstructionBreakpointsRequest{
Request: *c.newRequest("setInstructionBreakpoints"),
Arguments: dap.SetInstructionBreakpointsArguments{
Breakpoints: breakpoints,
},
})
}
// StepBackRequest sends a 'stepBack' request.
func (c *Client) StepBackRequest() {
c.send(&dap.StepBackRequest{Request: *c.newRequest("stepBack")})
}
// ReverseContinueRequest sends a 'reverseContinue' request.
func (c *Client) ReverseContinueRequest() {
c.send(&dap.ReverseContinueRequest{Request: *c.newRequest("reverseContinue")})
}
// SetVariableRequest sends a 'setVariable' request.
func (c *Client) SetVariableRequest(variablesRef int, name, value string) {
request := &dap.SetVariableRequest{Request: *c.newRequest("setVariable")}
request.Arguments.VariablesReference = variablesRef
request.Arguments.Name = name
request.Arguments.Value = value
c.send(request)
}
// RestartFrameRequest sends a 'restartFrame' request.
func (c *Client) RestartFrameRequest() {
c.send(&dap.RestartFrameRequest{Request: *c.newRequest("restartFrame")})
}
// GotoRequest sends a 'goto' request.
func (c *Client) GotoRequest() {
c.send(&dap.GotoRequest{Request: *c.newRequest("goto")})
}
// SetExpressionRequest sends a 'setExpression' request.
func (c *Client) SetExpressionRequest() {
c.send(&dap.SetExpressionRequest{Request: *c.newRequest("setExpression")})
}
// SourceRequest sends a 'source' request.
func (c *Client) SourceRequest() {
c.send(&dap.SourceRequest{Request: *c.newRequest("source")})
}
// TerminateThreadsRequest sends a 'terminateThreads' request.
func (c *Client) TerminateThreadsRequest() {
c.send(&dap.TerminateThreadsRequest{Request: *c.newRequest("terminateThreads")})
}
// EvaluateRequest sends a 'evaluate' request.
func (c *Client) EvaluateRequest(expr string, fid int, context string) {
request := &dap.EvaluateRequest{Request: *c.newRequest("evaluate")}
request.Arguments.Expression = expr
request.Arguments.FrameId = fid
request.Arguments.Context = context
c.send(request)
}
// StepInTargetsRequest sends a 'stepInTargets' request.
func (c *Client) StepInTargetsRequest() {
c.send(&dap.StepInTargetsRequest{Request: *c.newRequest("stepInTargets")})
}
// GotoTargetsRequest sends a 'gotoTargets' request.
func (c *Client) GotoTargetsRequest() {
c.send(&dap.GotoTargetsRequest{Request: *c.newRequest("gotoTargets")})
}
// CompletionsRequest sends a 'completions' request.
func (c *Client) CompletionsRequest() {
c.send(&dap.CompletionsRequest{Request: *c.newRequest("completions")})
}
// ExceptionInfoRequest sends a 'exceptionInfo' request.
func (c *Client) ExceptionInfoRequest(threadID int) {
request := &dap.ExceptionInfoRequest{Request: *c.newRequest("exceptionInfo")}
request.Arguments.ThreadId = threadID
c.send(request)
}
// LoadedSourcesRequest sends a 'loadedSources' request.
func (c *Client) LoadedSourcesRequest() {
c.send(&dap.LoadedSourcesRequest{Request: *c.newRequest("loadedSources")})
}
// DataBreakpointInfoRequest sends a 'dataBreakpointInfo' request.
func (c *Client) DataBreakpointInfoRequest() {
c.send(&dap.DataBreakpointInfoRequest{Request: *c.newRequest("dataBreakpointInfo")})
}
// SetDataBreakpointsRequest sends a 'setDataBreakpoints' request.
func (c *Client) SetDataBreakpointsRequest() {
c.send(&dap.SetDataBreakpointsRequest{Request: *c.newRequest("setDataBreakpoints")})
}
// ReadMemoryRequest sends a 'readMemory' request.
func (c *Client) ReadMemoryRequest() {
c.send(&dap.ReadMemoryRequest{Request: *c.newRequest("readMemory")})
}
// DisassembleRequest sends a 'disassemble' request.
func (c *Client) DisassembleRequest(memoryReference string, instructionOffset, instructionCount int) {
c.send(&dap.DisassembleRequest{
Request: *c.newRequest("disassemble"),
Arguments: dap.DisassembleArguments{
MemoryReference: memoryReference,
Offset: 0,
InstructionOffset: instructionOffset,
InstructionCount: instructionCount,
ResolveSymbols: false,
},
})
}
// CancelRequest sends a 'cancel' request.
func (c *Client) CancelRequest() {
c.send(&dap.CancelRequest{Request: *c.newRequest("cancel")})
}
// BreakpointLocationsRequest sends a 'breakpointLocations' request.
func (c *Client) BreakpointLocationsRequest() {
c.send(&dap.BreakpointLocationsRequest{Request: *c.newRequest("breakpointLocations")})
}
// ModulesRequest sends a 'modules' request.
func (c *Client) ModulesRequest() {
c.send(&dap.ModulesRequest{Request: *c.newRequest("modules")})
}
// UnknownRequest triggers dap.DecodeProtocolMessageFieldError.
func (c *Client) UnknownRequest() {
request := c.newRequest("unknown")
c.send(request)
}
// UnknownEvent triggers dap.DecodeProtocolMessageFieldError.
func (c *Client) UnknownEvent() {
event := &dap.Event{}
event.Type = "event"
event.Seq = -1
event.Event = "unknown"
c.send(event)
}
// BadRequest triggers an unmarshal error.
func (c *Client) BadRequest() {
content := []byte("{malformedString}")
contentLengthHeaderFmt := "Content-Length: %d\r\n\r\n"
header := fmt.Sprintf(contentLengthHeaderFmt, len(content))
c.conn.Write([]byte(header))
c.conn.Write(content)
}
// KnownEvent passes decode checks, but delve has no 'case' to
// handle it. This behaves the same way a new request type
// added to go-dap, but not to delve.
func (c *Client) KnownEvent() {
event := &dap.Event{}
event.Type = "event"
event.Seq = -1
event.Event = "terminated"
c.send(event)
}
func (c *Client) newRequest(command string) *dap.Request {
request := &dap.Request{}
request.Type = "request"
request.Command = command
request.Seq = c.seq
c.seq++
return request
}
@@ -0,0 +1,117 @@
// Binary gen generates service/dap/daptest/responses.go.
package main
import (
"bytes"
"flag"
"fmt"
"go/format"
"go/types"
"os"
"text/template"
"golang.org/x/tools/go/packages"
_ "github.com/google/go-dap"
)
var oFlag = flag.String("o", "", "output file name")
var tmpl = template.Must(template.New("assert").Parse(`package daptest
// Code generated by go generate; DO NOT EDIT.
// The code generator program is in ./gen directory.
import (
"testing"
"github.com/google/go-dap"
)
{{range .}}
// Expect{{.}} reads a protocol message from the connection
// and fails the test if the read message is not *{{.}}.
func (c *Client) Expect{{.}}(t *testing.T) *dap.{{.}} {
t.Helper()
m := c.ExpectMessage(t)
return c.Check{{.}}(t, m)
}
// Check{{.}} fails the test if m is not *{{.}}.
func (c *Client) Check{{.}}(t *testing.T, m dap.Message) *dap.{{.}} {
t.Helper(){{if or (or (eq . "StepInResponse") (eq . "StepOutResponse")) (eq . "NextResponse") }}
_, ok := m.(*dap.ContinuedEvent)
if !ok {
t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
}
m = c.ExpectMessage(t){{else}}{{if (eq . "ConfigurationDoneResponse") }}
oe, ok := m.(*dap.OutputEvent)
if !ok {
t.Fatalf("got %#v, want *dap.OutputEvent", m)
}
if oe.Body.Output != "Type 'dlv help' for list of commands.\n" {
t.Fatalf("got %#v, want Output=%q", m, "Type 'dlv help' for list of commands.\n")
}
m = c.ExpectMessage(t){{end}}{{end}}
r, ok := m.(*dap.{{.}})
if !ok {
t.Fatalf("got %#v, want *dap.{{.}}", m)
}
return r
}{{end}}
`))
func main() {
flag.Parse()
if *oFlag == "" {
fmt.Fprintf(os.Stderr, "-o must be provided\n")
}
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedTypes,
}, "github.com/google/go-dap")
if err != nil {
fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1)
}
if len(pkgs) != 1 || pkgs[0].Types == nil {
fmt.Fprintf(os.Stderr, "invalid package was loaded: %#v\n", pkgs)
os.Exit(1)
}
messages := []string{}
scope := pkgs[0].Types.Scope()
for _, name := range scope.Names() {
// Find only types that are embedding go-dap.Response message.
obj := scope.Lookup(name)
if !obj.Exported() {
continue // skip unexported
}
u, ok := obj.Type().Underlying().(*types.Struct)
if !ok {
continue
}
for i := 0; i < u.NumFields(); i++ {
if f := u.Field(i); f.Embedded() && (f.Type().String() == "github.com/google/go-dap.Response" ||
f.Type().String() == "github.com/google/go-dap.Event") {
messages = append(messages, obj.Name())
break
}
}
}
buf := &bytes.Buffer{}
if err := tmpl.Execute(buf, messages); err != nil {
fmt.Fprintf(os.Stderr, "Failed to generate: %v\n", err)
os.Exit(1)
}
formatted, err := format.Source(buf.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "Generated invalid go code: %v\n", err)
os.Exit(1)
}
if err := os.WriteFile(*oFlag, formatted, 0o644); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write: %v\n", err)
os.Exit(1)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
package dap
// Unique identifiers for messages returned for errors from requests.
// These values are not mandated by DAP (other than the uniqueness
// requirement), so each implementation is free to choose their own.
const (
UnsupportedCommand int = 9999
InternalError int = 8888
NotYetImplemented int = 7777
// Where applicable and for consistency only,
// values below are inspired the original vscode-go debug adaptor.
FailedToLaunch = 3000
FailedToAttach = 3001
FailedToInitialize = 3002
UnableToSetBreakpoints = 2002
UnableToDisplayThreads = 2003
UnableToProduceStackTrace = 2004
UnableToListLocals = 2005
UnableToListArgs = 2006
UnableToListGlobals = 2007
UnableToLookupVariable = 2008
UnableToEvaluateExpression = 2009
UnableToHalt = 2010
UnableToGetExceptionInfo = 2011
UnableToSetVariable = 2012
UnableToDisassemble = 2013
UnableToListRegisters = 2014
UnableToRunDlvCommand = 2015
// Add more codes as we support more requests
NoDebugIsRunning = 3000
DebuggeeIsRunning = 4000
DisconnectError = 5000
)
@@ -0,0 +1,75 @@
package dap
import "github.com/go-delve/delve/pkg/proc"
const startHandle = 1000
// handlesMap maps arbitrary values to unique sequential ids.
// This provides convenient abstraction of references, offering
// opacity and allowing simplification of complex identifiers.
// Based on
// https://github.com/microsoft/vscode-debugadapter-node/blob/master/adapter/src/handles.ts
type handlesMap struct {
nextHandle int
handleToVal map[int]interface{}
}
type fullyQualifiedVariable struct {
*proc.Variable
// A way to load this variable by either using all names in the hierarchic
// sequence above this variable (most readable when referenced by the UI)
// if available or a special expression based on:
// https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#loading-more-of-a-variable
// Empty if the variable cannot or should not be reloaded.
fullyQualifiedNameOrExpr string
// True if this represents variable scope
isScope bool
// startIndex is the index of the first child for an array or slice.
// This variable represents a chunk of the array, slice or map.
startIndex int
}
func newHandlesMap() *handlesMap {
return &handlesMap{startHandle, make(map[int]interface{})}
}
func (hs *handlesMap) reset() {
hs.nextHandle = startHandle
hs.handleToVal = make(map[int]interface{})
}
func (hs *handlesMap) create(value interface{}) int {
next := hs.nextHandle
hs.nextHandle++
hs.handleToVal[next] = value
return next
}
func (hs *handlesMap) get(handle int) (interface{}, bool) {
v, ok := hs.handleToVal[handle]
return v, ok
}
type variablesHandlesMap struct {
m *handlesMap
}
func newVariablesHandlesMap() *variablesHandlesMap {
return &variablesHandlesMap{newHandlesMap()}
}
func (hs *variablesHandlesMap) create(value *fullyQualifiedVariable) int {
return hs.m.create(value)
}
func (hs *variablesHandlesMap) get(handle int) (*fullyQualifiedVariable, bool) {
v, ok := hs.m.get(handle)
if !ok {
return nil, false
}
return v.(*fullyQualifiedVariable), true
}
func (hs *variablesHandlesMap) reset() {
hs.m.reset()
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,313 @@
package dap
import (
"encoding/json"
"errors"
"fmt"
)
// Launch debug sessions support the following modes:
//
// -- [DEFAULT] "debug" - builds and launches debugger for specified program (similar to 'dlv debug')
//
// Required args: program
// Optional args with default: output, cwd, noDebug
// Optional args: buildFlags, args
//
// -- "test" - builds and launches debugger for specified test (similar to 'dlv test')
//
// same args as above
//
// -- "exec" - launches debugger for precompiled binary (similar to 'dlv exec')
//
// Required args: program
// Optional args with default: cwd, noDebug
// Optional args: args
//
// -- "replay" - replays a trace generated by mozilla rr. Mozilla rr must be installed.
//
// Required args: traceDirPath
// Optional args: args
//
// -- "core" - examines a core dump (only supports linux and windows core dumps).
//
// Required args: program, coreFilePath
// Optional args: args
//
// TODO(hyangah): change this to 'validateLaunchMode' that checks
// all the required/optional fields mentioned above.
func isValidLaunchMode(mode string) bool {
switch mode {
case "exec", "debug", "test", "replay", "core":
return true
}
return false
}
// Default values for Launch/Attach configs.
// Used to initialize configuration variables before decoding
// arguments in launch/attach requests.
var (
defaultLaunchAttachCommonConfig = LaunchAttachCommonConfig{
Backend: "default",
StackTraceDepth: 50,
}
defaultLaunchConfig = LaunchConfig{
Mode: "debug",
LaunchAttachCommonConfig: defaultLaunchAttachCommonConfig,
}
defaultAttachConfig = AttachConfig{
Mode: "local",
LaunchAttachCommonConfig: defaultLaunchAttachCommonConfig,
}
)
// LaunchConfig is the collection of launch request attributes recognized by DAP implementation.
type LaunchConfig struct {
// Acceptable values are:
// "debug": compiles your program with optimizations disabled, starts and attaches to it.
// "test": compiles your unit test program with optimizations disabled, starts and attaches to it.
// "exec": executes a precompiled binary and begins a debug session.
// "replay": replays an rr trace.
// "core": examines a core dump.
//
// Default is "debug".
Mode string `json:"mode,omitempty"`
// Path to the program folder (or any go file within that folder)
// when in `debug` or `test` mode, and to the pre-built binary file
// to debug in `exec` mode.
// If it is not an absolute path, it will be interpreted as a path
// relative to Delve's working directory.
// Required when mode is `debug`, `test`, `exec`, and `core`.
Program string `json:"program,omitempty"`
// Command line arguments passed to the debugged program.
// Relative paths used in Args will be interpreted as paths relative
// to `cwd`.
Args []string `json:"args,omitempty"`
// Working directory of the program being debugged.
// If a relative path is provided, it will be interpreted as
// a relative path to Delve's working directory. This is
// similar to `dlv --wd` flag.
//
// If not specified or empty, Delve's working directory is
// used by default. But for `test` mode, Delve tries to find
// the test's package source directory and run tests from there.
// This matches the behavior of `dlv test` and `go test`.
Cwd string `json:"cwd,omitempty"`
// Build flags, to be passed to the Go compiler.
// Relative paths used in BuildFlags will be interpreted as paths
// relative to Delve's current working directory.
//
// It should be a string like `dlv --build-flags`, or
// an array of strings that is augmented when invoking the go build or
// test command through os/exec.Command API.
// For example, both forms are acceptable.
// "buildFlags": "-tags=integration -ldflags='-X main.Hello=World'"
// or
// "buildFlags": ["-tags=integration", "-ldflags=-X main.Hello=World"]
// Using other types is an error.
BuildFlags BuildFlags `json:"buildFlags,omitempty"`
// Output path for the binary of the debuggee.
// Relative path is interpreted as the path relative to
// the Delve's current working directory.
// This is deleted after the debug session ends.
Output string `json:"output,omitempty"`
// NoDebug is used to run the program without debugging.
NoDebug bool `json:"noDebug,omitempty"`
// TraceDirPath is the trace directory path for replay mode.
// Relative path is interpreted as a path relative to Delve's
// current working directory.
// This is required for "replay" mode but unused in other modes.
TraceDirPath string `json:"traceDirPath,omitempty"`
// CoreFilePath is the core file path for core mode.
//
// This is required for "core" mode but unused in other modes.
CoreFilePath string `json:"coreFilePath,omitempty"`
// DlvCwd is the new working directory for Delve server.
// If specified, the server will change its working
// directory to the specified directory using os.Chdir.
// Any other launch attributes with relative paths interpreted
// using Delve's working directory will use this new directory.
// When Delve needs to build the program (in debug/test modes),
// it will run the go command from this directory as well.
//
// If a relative path is provided as DlvCwd, it will be
// interpreted as a path relative to Delve's current working
// directory.
DlvCwd string `json:"dlvCwd,omitempty"`
// Env specifies optional environment variables for Delve server
// in addition to the environment variables Delve initially
// started with.
// Variables with 'nil' values can be used to unset the named
// environment variables.
// Values are interpreted verbatim. Variable substitution or
// reference to other environment variables is not supported.
Env map[string]*string `json:"env,omitempty"`
// The output mode specifies how to handle the program's output.
OutputMode string `json:"outputMode,omitempty"`
LaunchAttachCommonConfig
}
// LaunchAttachCommonConfig is the attributes common in both launch/attach requests.
type LaunchAttachCommonConfig struct {
// Automatically stop program after launch or attach.
StopOnEntry bool `json:"stopOnEntry,omitempty"`
// Backend used for debugging. See `dlv backend` for allowed values.
// Default is "default".
Backend string `json:"backend,omitempty"`
// Maximum depth of stack trace to return.
// Default is 50.
StackTraceDepth int `json:"stackTraceDepth,omitempty"`
// Boolean value to indicate whether global package variables
// should be shown in the variables pane or not.
ShowGlobalVariables bool `json:"showGlobalVariables,omitempty"`
// Boolean value to indicate whether registers should be shown
// in the variables pane or not.
ShowRegisters bool `json:"showRegisters,omitempty"`
// Boolean value to indicate whether system goroutines
// should be hidden from the call stack view.
HideSystemGoroutines bool `json:"hideSystemGoroutines,omitempty"`
// String value to indicate which system goroutines should be
// shown in the call stack view. See filtering documentation:
// https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md#goroutines
GoroutineFilters string `json:"goroutineFilters,omitempty"`
// Array of string values indicating the keys of pprof labels to show as a
// goroutine name in the threads view. If the array has one element, only
// that label's value will be shown; otherwise, each of the labels will be
// shown as "key:value". To show all labels, specify the single element "*".
ShowPprofLabels []string `json:"showPprofLabels,omitempty"`
// An array of mappings from a local path (client) to the remote path (debugger).
// This setting is useful when working in a file system with symbolic links,
// running remote debugging, or debugging an executable compiled externally.
// The debug adapter will replace the local path with the remote path in all of the calls.
// See also Documentation/cli/substitutepath.md.
SubstitutePath []SubstitutePath `json:"substitutePath,omitempty"`
}
// SubstitutePath defines a mapping from a local path to the remote path.
// Both 'from' and 'to' must be specified and non-null.
// Empty values can be used to add or remove absolute path prefixes when mapping.
// For example, mapping with empty 'to' can be used to work with binaries with trimmed paths.
type SubstitutePath struct {
// The local path to be replaced when passing paths to the debugger.
From string `json:"from,omitempty"`
// The remote path to be replaced when passing paths back to the client.
To string `json:"to,omitempty"`
}
func (m *SubstitutePath) UnmarshalJSON(data []byte) error {
// use custom unmarshal to check if both from/to are set.
type tmpType struct {
From *string
To *string
}
var tmp tmpType
if err := json.Unmarshal(data, &tmp); err != nil {
if _, ok := err.(*json.UnmarshalTypeError); ok {
return fmt.Errorf(`cannot use %s as 'substitutePath' of type {"from":string, "to":string}`, data)
}
return err
}
if tmp.From == nil || tmp.To == nil {
return errors.New("'substitutePath' requires both 'from' and 'to' entries")
}
*m = SubstitutePath{*tmp.From, *tmp.To}
return nil
}
// AttachConfig is the collection of attach request attributes recognized by DAP implementation.
// 'processId' and 'waitFor' are mutually exclusive, and can't be specified at the same time.
type AttachConfig struct {
// Acceptable values are:
// "local": attaches to the local process with the given ProcessID.
// "remote": expects the debugger to already be running to "attach" to an in-progress debug session.
//
// Default is "local".
Mode string `json:"mode"`
// The numeric ID of the process to be debugged.
ProcessID int `json:"processId,omitempty"`
// Wait for a process with a name beginning with this prefix.
AttachWaitFor string `json:"waitFor,omitempty"`
LaunchAttachCommonConfig
}
// unmarshalLaunchAttachArgs wraps unmarshaling of launch/attach request's
// arguments attribute. Upon unmarshal failure, it returns an error massaged
// to be suitable for end-users.
func unmarshalLaunchAttachArgs(input json.RawMessage, config interface{}) error {
if err := json.Unmarshal(input, config); err != nil {
if uerr, ok := err.(*json.UnmarshalTypeError); ok {
// Format json.UnmarshalTypeError error string in our own way. E.g.,
// "json: cannot unmarshal number into Go struct field LaunchArgs.substitutePath of type dap.SubstitutePath"
// => "cannot unmarshal number into 'substitutePath' of type {from:string, to:string}"
// "json: cannot unmarshal number into Go struct field LaunchArgs.program of type string" (go1.16)
// => "cannot unmarshal number into 'program' of type string"
typ := uerr.Type.String()
if uerr.Field == "substitutePath" {
typ = `{"from":string, "to":string}`
}
return fmt.Errorf("cannot unmarshal %v into %q of type %v", uerr.Value, uerr.Field, typ)
}
return err
}
return nil
}
func prettyPrint(config interface{}) string {
pretty, err := json.MarshalIndent(config, "", "\t")
if err != nil {
return fmt.Sprintf("%#v", config)
}
return string(pretty)
}
// BuildFlags is either string or []string.
type BuildFlags struct {
value interface{}
}
func (s *BuildFlags) UnmarshalJSON(b []byte) error {
if v := string(b); v == "" || v == "null" {
s.value = nil
return nil
}
var strs []string
if err := json.Unmarshal(b, &strs); err == nil {
s.value = strs
return nil
}
var str string
if err := json.Unmarshal(b, &str); err != nil {
s.value = nil
if uerr, ok := err.(*json.UnmarshalTypeError); ok {
return fmt.Errorf(`cannot unmarshal %v into "buildFlags" of type []string or string`, uerr.Value)
}
return err
}
s.value = str
return nil
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
package debugger
import (
"fmt"
"os"
"syscall"
)
func init() {
attachErrorMessage = attachErrorMessageLinux
}
//lint:file-ignore ST1005 errors here can be capitalized
func attachErrorMessageLinux(pid int, err error) error {
fallbackerr := fmt.Errorf("could not attach to pid %d: %s", pid, err)
if serr, ok := err.(syscall.Errno); ok {
switch serr {
case syscall.EPERM:
bs, err := os.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: 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 {
return fallbackerr
}
if fi.Sys().(*syscall.Stat_t).Uid != uint32(os.Getuid()) {
return fmt.Errorf("Could not attach to pid %d: current user does not own the process", pid)
}
}
}
return fallbackerr
}
@@ -0,0 +1,99 @@
package debugger
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/go-delve/delve/pkg/gobuild"
protest "github.com/go-delve/delve/pkg/proc/test"
"github.com/go-delve/delve/service/api"
)
func TestDebugger_LaunchNoMain(t *testing.T) {
fixturesDir := protest.FindFixturesDir()
nomaindir := filepath.Join(fixturesDir, "nomaindir")
debugname := "debug"
exepath := filepath.Join(nomaindir, debugname)
defer os.Remove(exepath)
if err := gobuild.GoBuild(debugname, []string{nomaindir}, fmt.Sprintf("-o %s", exepath)); err != nil {
t.Fatalf("go build error %v", err)
}
d := new(Debugger)
_, err := d.Launch([]string{exepath}, ".")
if err == nil {
t.Fatalf("expected error but none was generated")
}
if err != api.ErrNotExecutable {
t.Fatalf("expected error \"%v\" got \"%v\"", api.ErrNotExecutable, err)
}
}
func TestDebugger_LaunchInvalidFormat(t *testing.T) {
fixturesDir := protest.FindFixturesDir()
buildtestdir := filepath.Join(fixturesDir, "buildtest")
debugname := "debug"
switchOS := map[string]string{
"darwin": "linux",
"windows": "linux",
"freebsd": "windows",
"linux": "windows",
}
if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" {
t.Setenv("GOARCH", "amd64")
}
if runtime.GOARCH == "ppc64le" && runtime.GOOS == "linux" {
t.Setenv("GOARCH", "amd64")
}
t.Setenv("GOOS", switchOS[runtime.GOOS])
exepath := filepath.Join(buildtestdir, debugname)
if err := gobuild.GoBuild(debugname, []string{buildtestdir}, fmt.Sprintf("-o %s", exepath)); err != nil {
t.Fatalf("go build error %v", err)
}
defer os.Remove(exepath)
d := new(Debugger)
_, err := d.Launch([]string{exepath}, ".")
if err == nil {
t.Fatalf("expected error but none was generated")
}
if err != api.ErrNotExecutable {
t.Fatalf("expected error %q got \"%v\"", api.ErrNotExecutable, err)
}
}
func TestDebugger_LaunchCurrentDir(t *testing.T) {
fixturesDir := protest.FindFixturesDir()
testDir := filepath.Join(fixturesDir, "buildtest")
debugname := "debug"
exepath := filepath.Join(testDir, debugname)
originalPath, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
defer os.Chdir(originalPath)
defer func() {
if err := os.Remove(exepath); err != nil {
t.Fatalf("error removing executable %v", err)
}
}()
if err := gobuild.GoBuild(debugname, []string{testDir}, fmt.Sprintf("-o %s", exepath)); err != nil {
t.Fatalf("go build error %v", err)
}
os.Chdir(testDir)
d := new(Debugger)
d.config = &Config{}
_, err = d.Launch([]string{debugname}, ".")
if err == nil {
t.Fatal("expected error but none was generated")
}
if err != nil && !strings.Contains(err.Error(), "unknown backend") {
t.Fatal(err)
}
}
@@ -0,0 +1,102 @@
//go:build !windows
package debugger
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/creack/pty"
"github.com/go-delve/delve/pkg/gobuild"
protest "github.com/go-delve/delve/pkg/proc/test"
"github.com/go-delve/delve/service/api"
)
func TestDebugger_LaunchNoExecutablePerm(t *testing.T) {
fixturesDir := protest.FindFixturesDir()
buildtestdir := filepath.Join(fixturesDir, "buildtest")
debugname := "debug"
switchOS := map[string]string{
"darwin": "linux",
"windows": "linux",
"freebsd": "windows",
"linux": "windows",
}
if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" {
t.Setenv("GOARCH", "amd64")
}
if runtime.GOARCH == "ppc64le" && runtime.GOOS == "linux" {
t.Setenv("GOARCH", "amd64")
}
t.Setenv("GOOS", switchOS[runtime.GOOS])
exepath := filepath.Join(buildtestdir, debugname)
defer os.Remove(exepath)
if err := gobuild.GoBuild(debugname, []string{buildtestdir}, fmt.Sprintf("-o %s", exepath)); err != nil {
t.Fatalf("go build error %v", err)
}
if err := os.Chmod(exepath, 0644); err != nil {
t.Fatal(err)
}
d := new(Debugger)
_, err := d.Launch([]string{exepath}, ".")
if err == nil {
t.Fatalf("expected error but none was generated")
}
if err != api.ErrNotExecutable {
t.Fatalf("expected error %q got \"%v\"", api.ErrNotExecutable, err)
}
}
func TestDebugger_LaunchWithTTY(t *testing.T) {
if os.Getenv("CI") == "true" {
if _, err := exec.LookPath("lsof"); err != nil {
t.Skip("skipping test in CI, system does not contain lsof")
}
}
// Ensure no env meddling is leftover from previous tests.
t.Setenv("GOOS", runtime.GOOS)
t.Setenv("GOARCH", runtime.GOARCH)
p, tty, err := pty.Open()
if err != nil {
t.Fatal(err)
}
defer p.Close()
defer tty.Close()
fixturesDir := protest.FindFixturesDir()
buildtestdir := filepath.Join(fixturesDir, "buildtest")
debugname := "debugtty"
exepath := filepath.Join(buildtestdir, debugname)
if err := gobuild.GoBuild(debugname, []string{buildtestdir}, fmt.Sprintf("-o %s", exepath)); err != nil {
t.Fatalf("go build error %v", err)
}
defer os.Remove(exepath)
var backend string
protest.DefaultTestBackend(&backend)
conf := &Config{TTY: tty.Name(), Backend: backend}
pArgs := []string{exepath}
d, err := New(conf, pArgs)
if err != nil {
t.Fatal(err)
}
openFileCmd, wantTTYName := "lsof", tty.Name()
if runtime.GOOS == "freebsd" {
openFileCmd = "fstat"
wantTTYName = strings.TrimPrefix(wantTTYName, "/dev/")
}
cmd := exec.Command(openFileCmd, "-p", fmt.Sprintf("%d", d.ProcessPid()))
result, err := cmd.CombinedOutput()
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(result, []byte(wantTTYName)) {
t.Fatalf("process open file list does not contain expected tty %q", wantTTYName)
}
}
@@ -0,0 +1,3 @@
// Package sameuser provides utilities for checking users of a local connection.
// Only works in Linux.
package sameuser
@@ -0,0 +1,9 @@
//go:build !linux
package sameuser
import "net"
func CanAccept(_, _, _ net.Addr) bool {
return true
}
@@ -0,0 +1,131 @@
package sameuser
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"net"
"os"
"strings"
"github.com/go-delve/delve/pkg/logflags"
)
// for testing
var (
uid = os.Getuid()
readFile = os.ReadFile
)
type errConnectionNotFound struct {
filename string
}
func (e *errConnectionNotFound) Error() string {
return fmt.Sprintf("connection not found in %s", e.filename)
}
func sameUserForHexLocalAddr(filename, localAddr, remoteAddr string) (bool, error) {
b, err := readFile(filename)
if err != nil {
return false, err
}
for _, line := range strings.Split(strings.TrimSpace(string(b)), "\n") {
// The format contains whitespace padding (%4d, %5u), so we use
// fmt.Sscanf instead of splitting on whitespace.
var (
sl int
readLocalAddr, readRemoteAddr string
state int
queue, timer string
retransmit int
remoteUID uint
)
// Note that we must use %d where the kernel format uses %4d or %5u:
// - %4d fails to parse for large number of entries (len(sl) > 4)
// - %u is not understood by the fmt package (%U is something else)
// - %5d cuts off longer uids (e.g. 149098 on gLinux)
n, err := fmt.Sscanf(line, "%d: %s %s %02X %s %s %08X %d",
&sl, &readLocalAddr, &readRemoteAddr, &state, &queue, &timer, &retransmit, &remoteUID)
if n != 8 || err != nil {
continue // invalid line (e.g. header line)
}
if readLocalAddr != remoteAddr || readRemoteAddr != localAddr {
// this check is deliberately crossed, the (readLocalAddr,
// readRemoteAddr) pair is from the point of view of the client, the
// (localAddr, remoteAddr) is from the point of view of the server.
continue
}
same := uid == int(remoteUID)
if !same && logflags.Any() {
log.Printf("connection from different user (remote: %d, local: %d) detected: %v", remoteUID, uid, line)
}
return same, nil
}
return false, &errConnectionNotFound{filename}
}
func addrToHex4(addr *net.TCPAddr) string {
// For details about the format, see the kernel side implementation:
// https://elixir.bootlin.com/linux/v5.2.2/source/net/ipv4/tcp_ipv4.c#L2375
b := addr.IP.To4()
return fmt.Sprintf("%02X%02X%02X%02X:%04X", b[3], b[2], b[1], b[0], addr.Port)
}
func addrToHex6(addr *net.TCPAddr) string {
a16 := addr.IP.To16()
// For details about the format, see the kernel side implementation:
// https://elixir.bootlin.com/linux/v5.2.2/source/net/ipv6/tcp_ipv6.c#L1792
words := make([]uint32, 4)
if err := binary.Read(bytes.NewReader(a16), binary.LittleEndian, words); err != nil {
panic(err)
}
return fmt.Sprintf("%08X%08X%08X%08X:%04X", words[0], words[1], words[2], words[3], addr.Port)
}
func sameUserForRemoteAddr4(localAddr, remoteAddr *net.TCPAddr) (bool, error) {
r, err := sameUserForHexLocalAddr("/proc/net/tcp", addrToHex4(localAddr), addrToHex4(remoteAddr))
if _, isNotFound := err.(*errConnectionNotFound); isNotFound {
// See Issue #1835
r, err2 := sameUserForHexLocalAddr("/proc/net/tcp6", "0000000000000000FFFF0000"+addrToHex4(localAddr), "0000000000000000FFFF0000"+addrToHex4(remoteAddr))
if err2 == nil {
return r, nil
}
}
return r, err
}
func sameUserForRemoteAddr6(localAddr, remoteAddr *net.TCPAddr) (bool, error) {
return sameUserForHexLocalAddr("/proc/net/tcp6", addrToHex6(localAddr), addrToHex6(remoteAddr))
}
func sameUserForRemoteAddr(localAddr, remoteAddr *net.TCPAddr) (bool, error) {
if remoteAddr.IP.To4() == nil {
return sameUserForRemoteAddr6(localAddr, remoteAddr)
}
return sameUserForRemoteAddr4(localAddr, remoteAddr)
}
func CanAccept(listenAddr, localAddr, remoteAddr net.Addr) bool {
laddr, ok := listenAddr.(*net.TCPAddr)
if !ok || !laddr.IP.IsLoopback() {
return true
}
remoteAddrTCP := remoteAddr.(*net.TCPAddr)
localAddrTCP := localAddr.(*net.TCPAddr)
same, err := sameUserForRemoteAddr(localAddrTCP, remoteAddrTCP)
if err != nil {
log.Printf("cannot check remote address: %v", err)
}
if !same {
if logflags.Any() {
log.Printf("closing connection from different user (%v): connections to localhost are only accepted from the same UNIX user for security reasons", remoteAddrTCP)
} else {
fmt.Fprintf(os.Stderr, "closing connection from different user (%v): connections to localhost are only accepted from the same UNIX user for security reasons\n", remoteAddrTCP)
}
return false
}
return true
}
@@ -0,0 +1,66 @@
package sameuser
import (
"net"
"testing"
)
func TestSameUserForRemoteAddr(t *testing.T) {
uid = 149098
var proc string
readFile = func(string) ([]byte, error) {
return []byte(proc), nil
}
for _, tt := range []struct {
name string
proc string
localAddr, remoteAddr *net.TCPAddr
want bool
}{
{
name: "ipv4-same",
proc: ` sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
21: 0100007F:E682 0100007F:0FC8 01 00000000:00000000 00:00000000 00000000 149098 0 8420541 2 0000000000000000 20 0 0 10 -1 `,
localAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4040},
remoteAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 59010},
want: true,
},
{
name: "ipv4-not-found",
proc: ` sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
21: 0100007F:E682 0100007F:0FC8 01 00000000:00000000 00:00000000 00000000 149098 0 8420541 2 0000000000000000 20 0 0 10 -1 `,
localAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4040},
remoteAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 2342},
want: false,
},
{
name: "ipv4-different-uid",
proc: ` sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
21: 0100007F:E682 0100007F:0FC8 01 00000000:00000000 00:00000000 00000000 149097 0 8420541 2 0000000000000000 20 0 0 10 -1 `,
localAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4040},
remoteAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 59010},
want: false,
},
{
name: "ipv6-same",
proc: ` sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
5: 00000000000000000000000001000000:D3E4 00000000000000000000000001000000:0FC8 01 00000000:00000000 00:00000000 00000000 149098 0 8425526 2 0000000000000000 20 0 0 10 -1
6: 00000000000000000000000001000000:0FC8 00000000000000000000000001000000:D3E4 01 00000000:00000000 00:00000000 00000000 149098 0 8424744 1 0000000000000000 20 0 0 10 -1`,
localAddr: &net.TCPAddr{IP: net.ParseIP("::1"), Port: 4040},
remoteAddr: &net.TCPAddr{IP: net.ParseIP("::1"), Port: 54244},
want: true,
},
} {
t.Run(tt.name, func(t *testing.T) {
proc = tt.proc
// The returned error is for reporting only.
same, _ := sameUserForRemoteAddr(tt.localAddr, tt.remoteAddr)
if got, want := same, tt.want; got != want {
t.Errorf("sameUserForRemoteAddr(%v, %v) = %v, want %v", tt.localAddr, tt.remoteAddr, got, want)
}
})
}
}
@@ -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()
}
@@ -0,0 +1,320 @@
package rpc1
import (
"errors"
"fmt"
"log"
"net/rpc"
"net/rpc/jsonrpc"
"sync"
"github.com/go-delve/delve/service/api"
)
// RPCClient is a RPC service.Client.
type RPCClient struct {
addr string
client *rpc.Client
haltMu sync.Mutex
haltReq bool
}
var errAPIUnsupported = errors.New("unsupported")
// NewClient creates a new RPCClient.
func NewClient(addr string) *RPCClient {
client, err := jsonrpc.Dial("tcp", addr)
if err != nil {
log.Fatal("dialing:", err)
}
return &RPCClient{
addr: addr,
client: client,
}
}
func (c *RPCClient) ProcessPid() int {
var pid int
c.call("ProcessPid", nil, &pid)
return pid
}
func (c *RPCClient) Detach(kill bool) error {
return c.call("Detach", kill, nil)
}
func (c *RPCClient) Restart() error {
return c.call("Restart", nil, nil)
}
func (c *RPCClient) GetState() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("State", nil, state)
return state, err
}
func (c *RPCClient) Continue() <-chan *api.DebuggerState {
ch := make(chan *api.DebuggerState)
c.haltMu.Lock()
c.haltReq = false
c.haltMu.Unlock()
go func() {
for {
c.haltMu.Lock()
if c.haltReq {
c.haltMu.Unlock()
close(ch)
return
}
c.haltMu.Unlock()
state := new(api.DebuggerState)
err := c.call("Command", &api.DebuggerCommand{Name: api.Continue}, state)
if err != nil {
state.Err = err
}
if state.Exited {
// Error types apparently cannot be marshalled by Go correctly. Must reset error here.
//lint:ignore ST1005 backwards compatibility
state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus)
}
ch <- state
if err != nil || state.Exited {
close(ch)
return
}
isbreakpoint := false
istracepoint := true
for i := range state.Threads {
if state.Threads[i].Breakpoint != nil {
isbreakpoint = true
istracepoint = istracepoint && state.Threads[i].Breakpoint.Tracepoint
}
}
if !isbreakpoint || !istracepoint {
close(ch)
return
}
}
}()
return ch
}
func (c *RPCClient) Next() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("Command", &api.DebuggerCommand{Name: api.Next}, state)
return state, err
}
func (c *RPCClient) Step() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("Command", &api.DebuggerCommand{Name: api.Step}, state)
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)
return state, err
}
func (c *RPCClient) ReverseStepInstruction() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("Command", &api.DebuggerCommand{Name: api.ReverseStepInstruction}, state)
return state, err
}
func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) {
state := new(api.DebuggerState)
cmd := &api.DebuggerCommand{
Name: api.SwitchThread,
ThreadID: threadID,
}
err := c.call("Command", cmd, state)
return state, err
}
func (c *RPCClient) SwitchGoroutine(goroutineID int) (*api.DebuggerState, error) {
state := new(api.DebuggerState)
cmd := &api.DebuggerCommand{
Name: api.SwitchGoroutine,
GoroutineID: int64(goroutineID),
}
err := c.call("Command", cmd, state)
return state, err
}
func (c *RPCClient) Halt() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
c.haltMu.Lock()
c.haltReq = true
c.haltMu.Unlock()
err := c.call("Command", &api.DebuggerCommand{Name: api.Halt}, state)
return state, err
}
func (c *RPCClient) GetBreakpoint(id int) (*api.Breakpoint, error) {
breakpoint := new(api.Breakpoint)
err := c.call("GetBreakpoint", id, breakpoint)
return breakpoint, err
}
func (c *RPCClient) GetBreakpointByName(name string) (*api.Breakpoint, error) {
breakpoint := new(api.Breakpoint)
err := c.call("GetBreakpointByName", name, breakpoint)
return breakpoint, err
}
func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) {
newBreakpoint := new(api.Breakpoint)
err := c.call("CreateBreakpoint", breakPoint, &newBreakpoint)
return newBreakpoint, err
}
func (c *RPCClient) ListBreakpoints() ([]*api.Breakpoint, error) {
var breakpoints []*api.Breakpoint
err := c.call("ListBreakpoints", nil, &breakpoints)
return breakpoints, err
}
func (c *RPCClient) ClearBreakpoint(id int) (*api.Breakpoint, error) {
bp := new(api.Breakpoint)
err := c.call("ClearBreakpoint", id, bp)
return bp, err
}
func (c *RPCClient) ClearBreakpointByName(name string) (*api.Breakpoint, error) {
bp := new(api.Breakpoint)
err := c.call("ClearBreakpointByName", name, bp)
return bp, err
}
func (c *RPCClient) AmendBreakpoint(bp *api.Breakpoint) error {
err := c.call("AmendBreakpoint", bp, nil)
return err
}
func (c *RPCClient) CancelNext() error {
return errAPIUnsupported
}
func (c *RPCClient) ListThreads() ([]*api.Thread, error) {
var threads []*api.Thread
err := c.call("ListThreads", nil, &threads)
return threads, err
}
func (c *RPCClient) GetThread(id int) (*api.Thread, error) {
thread := new(api.Thread)
err := c.call("GetThread", id, &thread)
return thread, err
}
func (c *RPCClient) EvalVariable(scope api.EvalScope, symbol string) (*api.Variable, error) {
v := new(api.Variable)
err := c.call("EvalSymbol", EvalSymbolArgs{scope, symbol}, v)
return v, err
}
func (c *RPCClient) SetVariable(scope api.EvalScope, symbol, value string) error {
var unused int
return c.call("SetSymbol", SetSymbolArgs{scope, symbol, value}, &unused)
}
func (c *RPCClient) ListSources(filter string) ([]string, error) {
var sources []string
err := c.call("ListSources", filter, &sources)
return sources, err
}
func (c *RPCClient) ListFunctions(filter string) ([]string, error) {
var funcs []string
err := c.call("ListFunctions", filter, &funcs)
return funcs, err
}
func (c *RPCClient) ListTypes(filter string) ([]string, error) {
var types []string
err := c.call("ListTypes", filter, &types)
return types, err
}
func (c *RPCClient) ListPackageVariables(filter string) ([]api.Variable, error) {
var vars []api.Variable
err := c.call("ListPackageVars", filter, &vars)
return vars, err
}
func (c *RPCClient) ListPackageVariablesFor(threadID int, filter string) ([]api.Variable, error) {
var vars []api.Variable
err := c.call("ListThreadPackageVars", &ThreadListArgs{Id: threadID, Filter: filter}, &vars)
return vars, err
}
func (c *RPCClient) ListLocalVariables(scope api.EvalScope) ([]api.Variable, error) {
var vars []api.Variable
err := c.call("ListLocalVars", scope, &vars)
return vars, err
}
func (c *RPCClient) ListRegisters() (string, error) {
var regs string
err := c.call("ListRegisters", nil, &regs)
return regs, err
}
func (c *RPCClient) ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error) {
var vars []api.Variable
err := c.call("ListFunctionArgs", scope, &vars)
return vars, err
}
func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
var goroutines []*api.Goroutine
err := c.call("ListGoroutines", nil, &goroutines)
return goroutines, err
}
func (c *RPCClient) Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error) {
var locations []api.Stackframe
err := c.call("StacktraceGoroutine", &StacktraceGoroutineArgs{Id: goroutineId, Depth: depth, Full: full}, &locations)
return locations, err
}
func (c *RPCClient) AttachedToExistingProcess() bool {
var answer bool
c.call("AttachedToExistingProcess", nil, &answer)
return answer
}
func (c *RPCClient) FindLocation(scope api.EvalScope, loc string) ([]api.Location, error) {
var answer []api.Location
err := c.call("FindLocation", FindLocationArgs{scope, loc}, &answer)
return answer, err
}
// DisassembleRange disassembles code between startPC and endPC
func (c *RPCClient) DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) {
var r api.AsmInstructions
err := c.call("Disassemble", DisassembleRequest{scope, startPC, endPC, flavour}, &r)
return r, err
}
// DisassemblePC disassembles function containing pc
func (c *RPCClient) DisassemblePC(scope api.EvalScope, pc uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) {
var r api.AsmInstructions
err := c.call("Disassemble", DisassembleRequest{scope, pc, 0, flavour}, &r)
return r, err
}
func (c *RPCClient) call(method string, args, reply interface{}) error {
return c.client.Call("RPCServer."+method, args, reply)
}
@@ -0,0 +1,5 @@
// Package rpc1 implements version 1 of Delve's API and is only
// kept for backwards compatibility.
// client.go is the old client code used by Delve's frontend (delve/cmd/dlv) and
// is preserved for backwards compatibility with integration tests.
package rpc1
@@ -0,0 +1,330 @@
package rpc1
import (
"errors"
"fmt"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/debugger"
)
var defaultLoadConfig = proc.LoadConfig{
FollowPointers: true,
MaxVariableRecurse: 1,
MaxStringLen: 64,
MaxArrayValues: 64,
MaxStructFields: -1,
}
type RPCServer struct {
// config is all the information necessary to start the debugger and server.
config *service.Config
// debugger is a debugger service.
debugger *debugger.Debugger
}
func NewServer(config *service.Config, debugger *debugger.Debugger) *RPCServer {
return &RPCServer{config, debugger}
}
func (s *RPCServer) ProcessPid(arg1 interface{}, pid *int) error {
*pid = s.debugger.ProcessPid()
return nil
}
func (s *RPCServer) Detach(kill bool, ret *int) error {
return s.debugger.Detach(kill)
}
func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
if s.config.Debugger.AttachPid != 0 {
return errors.New("cannot restart process Delve did not create")
}
_, err := s.debugger.Restart(false, "", false, nil, [3]string{}, false)
return err
}
func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
st, err := s.debugger.State(false)
if err != nil {
return err
}
*state = *st
return nil
}
func (s *RPCServer) Command(command *api.DebuggerCommand, cb service.RPCCallback) {
st, err := s.debugger.Command(command, cb.SetupDoneChan(), cb.DisconnectChan())
cb.Return(st, err)
}
func (s *RPCServer) GetBreakpoint(id int, breakpoint *api.Breakpoint) error {
bp := s.debugger.FindBreakpoint(id)
if bp == nil {
return fmt.Errorf("no breakpoint with id %d", id)
}
*breakpoint = *bp
return nil
}
func (s *RPCServer) GetBreakpointByName(name string, breakpoint *api.Breakpoint) error {
bp := s.debugger.FindBreakpointByName(name)
if bp == nil {
return fmt.Errorf("no breakpoint with name %s", name)
}
*breakpoint = *bp
return nil
}
type StacktraceGoroutineArgs struct {
Id int
Depth int
Full bool
}
func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations *[]api.Stackframe) error {
var loadcfg *proc.LoadConfig = nil
if args.Full {
loadcfg = &defaultLoadConfig
}
locs, err := s.debugger.Stacktrace(int64(args.Id), args.Depth, 0)
if err != nil {
return err
}
*locations, err = s.debugger.ConvertStacktrace(locs, loadcfg)
return err
}
func (s *RPCServer) ListBreakpoints(arg interface{}, breakpoints *[]*api.Breakpoint) error {
*breakpoints = s.debugger.Breakpoints(false)
return nil
}
func (s *RPCServer) CreateBreakpoint(bp, newBreakpoint *api.Breakpoint) error {
if err := api.ValidBreakpointName(bp.Name); err != nil {
return err
}
createdbp, err := s.debugger.CreateBreakpoint(bp, "", nil, false)
if err != nil {
return err
}
*newBreakpoint = *createdbp
return nil
}
func (s *RPCServer) ClearBreakpoint(id int, breakpoint *api.Breakpoint) error {
bp := s.debugger.FindBreakpoint(id)
if bp == nil {
return fmt.Errorf("no breakpoint with id %d", id)
}
deleted, err := s.debugger.ClearBreakpoint(bp)
if err != nil {
return err
}
*breakpoint = *deleted
return nil
}
func (s *RPCServer) ClearBreakpointByName(name string, breakpoint *api.Breakpoint) error {
bp := s.debugger.FindBreakpointByName(name)
if bp == nil {
return fmt.Errorf("no breakpoint with name %s", name)
}
deleted, err := s.debugger.ClearBreakpoint(bp)
if err != nil {
return err
}
*breakpoint = *deleted
return nil
}
func (s *RPCServer) AmendBreakpoint(amend *api.Breakpoint, unused *int) error {
*unused = 0
if err := api.ValidBreakpointName(amend.Name); err != nil {
return err
}
return s.debugger.AmendBreakpoint(amend)
}
func (s *RPCServer) ListThreads(arg interface{}, threads *[]*api.Thread) (err error) {
pthreads, err := s.debugger.Threads()
if err != nil {
return err
}
s.debugger.LockTarget()
defer s.debugger.UnlockTarget()
*threads = api.ConvertThreads(pthreads, s.debugger.ConvertThreadBreakpoint)
return nil
}
func (s *RPCServer) GetThread(id int, thread *api.Thread) error {
t, err := s.debugger.FindThread(id)
if err != nil {
return err
}
if t == nil {
return fmt.Errorf("no thread with id %d", id)
}
s.debugger.LockTarget()
defer s.debugger.UnlockTarget()
*thread = *api.ConvertThread(t, s.debugger.ConvertThreadBreakpoint(t))
return nil
}
func (s *RPCServer) ListPackageVars(filter string, variables *[]api.Variable) error {
vars, err := s.debugger.PackageVariables(filter, defaultLoadConfig)
if err != nil {
return err
}
*variables = api.ConvertVars(vars)
return nil
}
type ThreadListArgs struct {
Id int
Filter string
}
func (s *RPCServer) ListThreadPackageVars(args *ThreadListArgs, variables *[]api.Variable) error {
vars, err := s.debugger.PackageVariables(args.Filter, defaultLoadConfig)
if err != nil {
return err
}
*variables = api.ConvertVars(vars)
return nil
}
func (s *RPCServer) ListRegisters(arg interface{}, registers *string) error {
state, err := s.debugger.State(false)
if err != nil {
return err
}
dregs, err := s.debugger.ThreadRegisters(state.CurrentThread.ID)
if err != nil {
return err
}
regs := api.Registers(api.ConvertRegisters(dregs, s.debugger.DwarfRegisterToString, false))
*registers = regs.String()
return nil
}
func (s *RPCServer) ListLocalVars(scope api.EvalScope, variables *[]api.Variable) error {
vars, err := s.debugger.LocalVariables(scope.GoroutineID, scope.Frame, scope.DeferredCall, defaultLoadConfig)
if err != nil {
return err
}
*variables = api.ConvertVars(vars)
return nil
}
func (s *RPCServer) ListFunctionArgs(scope api.EvalScope, variables *[]api.Variable) error {
vars, err := s.debugger.FunctionArguments(scope.GoroutineID, scope.Frame, scope.DeferredCall, defaultLoadConfig)
if err != nil {
return err
}
*variables = api.ConvertVars(vars)
return nil
}
type EvalSymbolArgs struct {
Scope api.EvalScope
Symbol string
}
func (s *RPCServer) EvalSymbol(args EvalSymbolArgs, variable *api.Variable) error {
v, err := s.debugger.EvalVariableInScope(args.Scope.GoroutineID, args.Scope.Frame, args.Scope.DeferredCall, args.Symbol, defaultLoadConfig)
if err != nil {
return err
}
*variable = *api.ConvertVar(v)
return nil
}
type SetSymbolArgs struct {
Scope api.EvalScope
Symbol string
Value string
}
func (s *RPCServer) SetSymbol(args SetSymbolArgs, unused *int) error {
*unused = 0
return s.debugger.SetVariableInScope(args.Scope.GoroutineID, args.Scope.Frame, args.Scope.DeferredCall, args.Symbol, args.Value)
}
func (s *RPCServer) ListSources(filter string, sources *[]string) error {
ss, err := s.debugger.Sources(filter)
if err != nil {
return err
}
*sources = ss
return nil
}
func (s *RPCServer) ListFunctions(filter string, followCalls int, funcs *[]string) error {
fns, err := s.debugger.Functions(filter, followCalls)
if err != nil {
return err
}
*funcs = fns
return nil
}
func (s *RPCServer) ListTypes(filter string, types *[]string) error {
tps, err := s.debugger.Types(filter)
if err != nil {
return err
}
*types = tps
return nil
}
func (s *RPCServer) ListGoroutines(arg interface{}, goroutines *[]*api.Goroutine) error {
gs, _, err := s.debugger.Goroutines(0, 0)
if err != nil {
return err
}
s.debugger.LockTarget()
s.debugger.UnlockTarget()
*goroutines = api.ConvertGoroutines(s.debugger.Target(), gs)
return nil
}
func (s *RPCServer) AttachedToExistingProcess(arg interface{}, answer *bool) error {
if s.config.Debugger.AttachPid != 0 {
*answer = true
}
return nil
}
type FindLocationArgs struct {
Scope api.EvalScope
Loc string
}
func (s *RPCServer) FindLocation(args FindLocationArgs, answer *[]api.Location) error {
var err error
*answer, _, err = s.debugger.FindLocation(args.Scope.GoroutineID, args.Scope.Frame, args.Scope.DeferredCall, args.Loc, false, nil)
return err
}
type DisassembleRequest struct {
Scope api.EvalScope
StartPC, EndPC uint64
Flavour api.AssemblyFlavour
}
func (s *RPCServer) Disassemble(args DisassembleRequest, answer *api.AsmInstructions) error {
var err error
insts, err := s.debugger.Disassemble(args.Scope.GoroutineID, args.StartPC, args.EndPC)
if err != nil {
return err
}
*answer = make(api.AsmInstructions, len(insts))
for i := range insts {
(*answer)[i] = api.ConvertAsmInstruction(insts[i], s.debugger.AsmInstructionText(&insts[i], proc.AssemblyFlavour(args.Flavour)))
}
return nil
}
@@ -0,0 +1,587 @@
package rpc2
import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"time"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
)
// RPCClient is a RPC service.Client.
type RPCClient struct {
client *rpc.Client
retValLoadCfg *api.LoadConfig
}
// Ensure the implementation satisfies the interface.
var _ service.Client = &RPCClient{}
// NewClient creates a new RPCClient.
func NewClient(addr string) *RPCClient {
client, err := jsonrpc.Dial("tcp", addr)
if err != nil {
log.Fatal("dialing:", err)
}
return newFromRPCClient(client)
}
func newFromRPCClient(client *rpc.Client) *RPCClient {
c := &RPCClient{client: client}
c.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 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)
return out.Pid
}
func (c *RPCClient) BuildID() string {
out := new(BuildIDOut)
c.call("BuildID", BuildIDIn{}, out)
return out.BuildID
}
func (c *RPCClient) LastModified() time.Time {
out := new(LastModifiedOut)
c.call("LastModified", LastModifiedIn{}, out)
return out.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(rebuild bool) ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut)
err := c.call("Restart", RestartIn{"", false, nil, false, rebuild, [3]string{}}, out)
return out.DiscardedBreakpoints, err
}
func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut)
err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild, newRedirects}, out)
return out.DiscardedBreakpoints, err
}
func (c *RPCClient) GetState() (*api.DebuggerState, error) {
var out StateOut
err := c.call("State", StateIn{NonBlocking: false}, &out)
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) DirectionCongruentContinue() <-chan *api.DebuggerState {
return c.continueDir(api.DirectionCongruentContinue)
}
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: cmd, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
state := out.State
if err != nil {
state.Err = err
}
if state.Exited {
// Error types apparently cannot be marshalled by Go correctly. Must reset error here.
//lint:ignore ST1005 backwards compatibility
state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus)
}
ch <- &state
if err != nil || state.Exited {
close(ch)
return
}
isbreakpoint := false
istracepoint := true
for i := range state.Threads {
if state.Threads[i].Breakpoint != nil {
isbreakpoint = true
istracepoint = istracepoint && (state.Threads[i].Breakpoint.Tracepoint || state.Threads[i].Breakpoint.TraceReturn)
}
}
if !isbreakpoint || !istracepoint {
close(ch)
return
}
}
}()
return ch
}
func (c *RPCClient) Next() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.Next, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
return &out.State, err
}
func (c *RPCClient) ReverseNext() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.ReverseNext, 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, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
return &out.State, err
}
func (c *RPCClient) ReverseStep() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStep, 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, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
return &out.State, err
}
func (c *RPCClient) ReverseStepOut() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStepOut, ReturnInfoLoadConfig: c.retValLoadCfg}, &out)
return &out.State, err
}
func (c *RPCClient) Call(goroutineID int64, expr string, unsafe bool) (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.Call, ReturnInfoLoadConfig: c.retValLoadCfg, Expr: expr, UnsafeCall: unsafe, GoroutineID: goroutineID}, &out)
return &out.State, err
}
func (c *RPCClient) StepInstruction(skipCalls bool) (*api.DebuggerState, error) {
var out CommandOut
name := api.StepInstruction
if skipCalls {
name = api.NextInstruction
}
err := c.call("Command", api.DebuggerCommand{Name: name}, &out)
return &out.State, err
}
func (c *RPCClient) ReverseStepInstruction(skipCalls bool) (*api.DebuggerState, error) {
var out CommandOut
name := api.ReverseStepInstruction
if skipCalls {
name = api.ReverseNextInstruction
}
err := c.call("Command", api.DebuggerCommand{Name: name}, &out)
return &out.State, err
}
func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) {
var out CommandOut
cmd := api.DebuggerCommand{
Name: api.SwitchThread,
ThreadID: threadID,
}
err := c.call("Command", cmd, &out)
return &out.State, err
}
func (c *RPCClient) SwitchGoroutine(goroutineID int64) (*api.DebuggerState, error) {
var out CommandOut
cmd := api.DebuggerCommand{
Name: api.SwitchGoroutine,
GoroutineID: goroutineID,
}
err := c.call("Command", cmd, &out)
return &out.State, err
}
func (c *RPCClient) Halt() (*api.DebuggerState, error) {
var out CommandOut
err := c.call("Command", api.DebuggerCommand{Name: api.Halt}, &out)
return &out.State, err
}
func (c *RPCClient) GetBufferedTracepoints() ([]api.TracepointResult, error) {
var out GetBufferedTracepointsOut
err := c.call("GetBufferedTracepoints", GetBufferedTracepointsIn{}, &out)
return out.TracepointResults, err
}
func (c *RPCClient) GetBreakpoint(id int) (*api.Breakpoint, error) {
var out GetBreakpointOut
err := c.call("GetBreakpoint", GetBreakpointIn{id, ""}, &out)
return &out.Breakpoint, err
}
func (c *RPCClient) GetBreakpointByName(name string) (*api.Breakpoint, error) {
var out GetBreakpointOut
err := c.call("GetBreakpoint", GetBreakpointIn{0, name}, &out)
return &out.Breakpoint, err
}
// CreateBreakpoint will send a request to the RPC server to create a breakpoint.
// Please refer to the documentation for `Debugger.CreateBreakpoint` for a description of how
// the requested breakpoint parameters are interpreted and used:
// https://pkg.go.dev/github.com/go-delve/delve/service/debugger#Debugger.CreateBreakpoint
func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) {
var out CreateBreakpointOut
err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, "", nil, false}, &out)
return &out.Breakpoint, err
}
// CreateBreakpointWithExpr is like CreateBreakpoint but will also set a
// location expression to be used to restore the breakpoint after it is
// disabled.
func (c *RPCClient) CreateBreakpointWithExpr(breakPoint *api.Breakpoint, locExpr string, substitutePathRules [][2]string, suspended bool) (*api.Breakpoint, error) {
var out CreateBreakpointOut
err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, locExpr, substitutePathRules, suspended}, &out)
return &out.Breakpoint, err
}
func (c *RPCClient) CreateEBPFTracepoint(fnName string) error {
var out CreateEBPFTracepointOut
return c.call("CreateEBPFTracepoint", CreateEBPFTracepointIn{FunctionName: fnName}, &out)
}
func (c *RPCClient) CreateWatchpoint(scope api.EvalScope, expr string, wtype api.WatchType) (*api.Breakpoint, error) {
var out CreateWatchpointOut
err := c.call("CreateWatchpoint", CreateWatchpointIn{scope, expr, wtype}, &out)
return out.Breakpoint, err
}
func (c *RPCClient) ListBreakpoints(all bool) ([]*api.Breakpoint, error) {
var out ListBreakpointsOut
err := c.call("ListBreakpoints", ListBreakpointsIn{all}, &out)
return out.Breakpoints, err
}
func (c *RPCClient) ClearBreakpoint(id int) (*api.Breakpoint, error) {
var out ClearBreakpointOut
err := c.call("ClearBreakpoint", ClearBreakpointIn{id, ""}, &out)
return out.Breakpoint, err
}
func (c *RPCClient) ClearBreakpointByName(name string) (*api.Breakpoint, error) {
var out ClearBreakpointOut
err := c.call("ClearBreakpoint", ClearBreakpointIn{0, name}, &out)
return out.Breakpoint, err
}
func (c *RPCClient) ToggleBreakpoint(id int) (*api.Breakpoint, error) {
var out ToggleBreakpointOut
err := c.call("ToggleBreakpoint", ToggleBreakpointIn{id, ""}, &out)
return out.Breakpoint, err
}
func (c *RPCClient) ToggleBreakpointByName(name string) (*api.Breakpoint, error) {
var out ToggleBreakpointOut
err := c.call("ToggleBreakpoint", ToggleBreakpointIn{0, name}, &out)
return out.Breakpoint, err
}
func (c *RPCClient) AmendBreakpoint(bp *api.Breakpoint) error {
out := new(AmendBreakpointOut)
err := c.call("AmendBreakpoint", AmendBreakpointIn{*bp}, out)
return err
}
func (c *RPCClient) CancelNext() error {
var out CancelNextOut
return c.call("CancelNext", CancelNextIn{}, &out)
}
func (c *RPCClient) ListThreads() ([]*api.Thread, error) {
var out ListThreadsOut
err := c.call("ListThreads", ListThreadsIn{}, &out)
return out.Threads, err
}
func (c *RPCClient) GetThread(id int) (*api.Thread, error) {
var out GetThreadOut
err := c.call("GetThread", GetThreadIn{id}, &out)
return out.Thread, err
}
func (c *RPCClient) EvalVariable(scope api.EvalScope, expr string, cfg api.LoadConfig) (*api.Variable, error) {
var out EvalOut
err := c.call("Eval", EvalIn{scope, expr, &cfg}, &out)
return out.Variable, err
}
func (c *RPCClient) SetVariable(scope api.EvalScope, symbol, value string) error {
out := new(SetOut)
return c.call("Set", SetIn{scope, symbol, value}, out)
}
func (c *RPCClient) ListSources(filter string) ([]string, error) {
sources := new(ListSourcesOut)
err := c.call("ListSources", ListSourcesIn{filter}, sources)
return sources.Sources, err
}
func (c *RPCClient) ListFunctions(filter string, TraceFollow int) ([]string, error) {
funcs := new(ListFunctionsOut)
err := c.call("ListFunctions", ListFunctionsIn{filter, TraceFollow}, funcs)
return funcs.Funcs, err
}
func (c *RPCClient) ListTypes(filter string) ([]string, error) {
types := new(ListTypesOut)
err := c.call("ListTypes", ListTypesIn{filter}, types)
return types.Types, err
}
func (c *RPCClient) ListPackageVariables(filter string, cfg api.LoadConfig) ([]api.Variable, error) {
var out ListPackageVarsOut
err := c.call("ListPackageVars", ListPackageVarsIn{filter, cfg}, &out)
return out.Variables, err
}
func (c *RPCClient) ListPackagesBuildInfo(filter string, includeFiles bool) ([]api.PackageBuildInfo, error) {
var out ListPackagesBuildInfoOut
err := c.call("ListPackagesBuildInfo", ListPackagesBuildInfoIn{Filter: filter, IncludeFiles: includeFiles}, &out)
return out.List, err
}
func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) {
var out ListLocalVarsOut
err := c.call("ListLocalVars", ListLocalVarsIn{scope, cfg}, &out)
return out.Variables, err
}
func (c *RPCClient) ListThreadRegisters(threadID int, includeFp bool) (api.Registers, error) {
out := new(ListRegistersOut)
err := c.call("ListRegisters", ListRegistersIn{ThreadID: threadID, IncludeFp: includeFp, Scope: nil}, out)
return out.Regs, err
}
func (c *RPCClient) ListScopeRegisters(scope api.EvalScope, includeFp bool) (api.Registers, error) {
out := new(ListRegistersOut)
err := c.call("ListRegisters", ListRegistersIn{ThreadID: 0, IncludeFp: includeFp, Scope: &scope}, out)
return out.Regs, err
}
func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) {
var out ListFunctionArgsOut
err := c.call("ListFunctionArgs", ListFunctionArgsIn{scope, cfg}, &out)
return out.Args, err
}
func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, error) {
var out ListGoroutinesOut
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, nil, api.GoroutineGroupingOptions{}, nil}, &out)
return out.Goroutines, out.Nextg, err
}
func (c *RPCClient) ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions, scope *api.EvalScope) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error) {
if group == nil {
group = &api.GoroutineGroupingOptions{}
}
var out ListGoroutinesOut
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, filters, *group, scope}, &out)
return out.Goroutines, out.Groups, out.Nextg, out.TooManyGroups, err
}
func (c *RPCClient) Stacktrace(goroutineId int64, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error) {
var out StacktraceOut
err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, false, opts, cfg}, &out)
return out.Locations, err
}
func (c *RPCClient) Ancestors(goroutineID int64, numAncestors int, depth int) ([]api.Ancestor, error) {
var out AncestorsOut
err := c.call("Ancestors", AncestorsIn{goroutineID, numAncestors, depth}, &out)
return out.Ancestors, err
}
func (c *RPCClient) AttachedToExistingProcess() bool {
out := new(AttachedToExistingProcessOut)
c.call("AttachedToExistingProcess", AttachedToExistingProcessIn{}, out)
return out.Answer
}
func (c *RPCClient) FindLocation(scope api.EvalScope, loc string, findInstructions bool, substitutePathRules [][2]string) ([]api.Location, string, error) {
var out FindLocationOut
err := c.call("FindLocation", FindLocationIn{scope, loc, !findInstructions, substitutePathRules}, &out)
return out.Locations, out.SubstituteLocExpr, err
}
// DisassembleRange disassembles code between startPC and endPC
func (c *RPCClient) DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) {
var out DisassembleOut
err := c.call("Disassemble", DisassembleIn{scope, startPC, endPC, flavour}, &out)
return out.Disassemble, err
}
// DisassemblePC disassembles function containing pc
func (c *RPCClient) DisassemblePC(scope api.EvalScope, pc uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) {
var out DisassembleOut
err := c.call("Disassemble", DisassembleIn{scope, pc, 0, flavour}, &out)
return out.Disassemble, err
}
// 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) FunctionReturnLocations(fnName string) ([]uint64, error) {
var out FunctionReturnLocationsOut
err := c.call("FunctionReturnLocations", FunctionReturnLocationsIn{fnName}, &out)
return out.Addrs, err
}
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) ListDynamicLibraries() ([]api.Image, error) {
var out ListDynamicLibrariesOut
c.call("ListDynamicLibraries", ListDynamicLibrariesIn{}, &out)
return out.List, nil
}
func (c *RPCClient) ExamineMemory(address uint64, count int) ([]byte, bool, error) {
out := &ExaminedMemoryOut{}
err := c.call("ExamineMemory", ExamineMemoryIn{Length: count, Address: address}, out)
if err != nil {
return nil, false, err
}
return out.Mem, out.IsLittleEndian, nil
}
func (c *RPCClient) StopRecording() error {
return c.call("StopRecording", StopRecordingIn{}, &StopRecordingOut{})
}
func (c *RPCClient) CoreDumpStart(dest string) (api.DumpState, error) {
out := &DumpStartOut{}
err := c.call("DumpStart", DumpStartIn{Destination: dest}, out)
return out.State, err
}
func (c *RPCClient) CoreDumpWait(msec int) api.DumpState {
out := &DumpWaitOut{}
_ = c.call("DumpWait", DumpWaitIn{Wait: msec}, out)
return out.State
}
func (c *RPCClient) CoreDumpCancel() error {
out := &DumpCancelOut{}
return c.call("DumpCancel", DumpCancelIn{}, out)
}
// ListTargets returns the current list of debug targets.
func (c *RPCClient) ListTargets() ([]api.Target, error) {
out := &ListTargetsOut{}
err := c.call("ListTargets", ListTargetsIn{}, out)
return out.Targets, err
}
// FollowExec enabled or disabled follow exec mode. When follow exec is
// enabled Delve will automatically attach to new subprocesses with a
// command line matched by regex, if regex is nil all new subprocesses are
// automatically debugged.
func (c *RPCClient) FollowExec(v bool, regex string) error {
out := &FollowExecOut{}
err := c.call("FollowExec", FollowExecIn{Enable: v, Regex: regex}, out)
return err
}
// FollowExecEnabled returns true if follow exec mode is enabled.
func (c *RPCClient) FollowExecEnabled() bool {
out := &FollowExecEnabledOut{}
_ = c.call("FollowExecEnabled", FollowExecEnabledIn{}, out)
return out.Enabled
}
func (c *RPCClient) SetDebugInfoDirectories(v []string) error {
return c.call("DebugInfoDirectories", DebugInfoDirectoriesIn{Set: true, List: v}, &DebugInfoDirectoriesOut{})
}
func (c *RPCClient) GetDebugInfoDirectories() ([]string, error) {
out := &DebugInfoDirectoriesOut{}
err := c.call("DebugInfoDirectories", DebugInfoDirectoriesIn{Set: false, List: nil}, out)
return out.List, err
}
func (c *RPCClient) call(method string, args, reply interface{}) error {
return c.client.Call("RPCServer."+method, args, reply)
}
func (c *RPCClient) CallAPI(method string, args, reply interface{}) error {
return c.call(method, args, reply)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,15 @@
package service
// RPCCallback is used by RPC methods to return their result asynchronously.
type RPCCallback interface {
Return(out interface{}, err error)
// SetupDoneChan returns a channel that should be closed to signal that the
// asynchronous method has completed setup and the server is ready to
// receive other requests.
SetupDoneChan() chan struct{}
// DisconnectChan returns a channel that should be closed to signal that
// the client that initially issued the command has been disconnected.
DisconnectChan() chan struct{}
}
@@ -0,0 +1,501 @@
package rpccommon
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"os"
"reflect"
"runtime"
"sync"
"unicode"
"unicode/utf8"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/version"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/dap"
"github.com/go-delve/delve/service/debugger"
"github.com/go-delve/delve/service/internal/sameuser"
"github.com/go-delve/delve/service/rpc1"
"github.com/go-delve/delve/service/rpc2"
)
// ServerImpl implements a JSON-RPC server that can switch between two
// versions of the API.
type ServerImpl struct {
// config is all the information necessary to start the debugger and server.
config *service.Config
// listener is used to serve JSON-RPC.
listener net.Listener
// stopChan is used to stop the listener goroutine.
stopChan chan struct{}
// debugger is the debugger service.
debugger *debugger.Debugger
// s1 is APIv1 server.
s1 *rpc1.RPCServer
// s2 is APIv2 server.
s2 *rpc2.RPCServer
// maps of served methods, one for each supported API.
methodMaps []map[string]*methodType
log logflags.Logger
}
type RPCCallback struct {
s *ServerImpl
sending *sync.Mutex
codec rpc.ServerCodec
req rpc.Request
setupDone chan struct{}
disconnectChan chan struct{}
}
var _ service.RPCCallback = &RPCCallback{}
// RPCServer implements the RPC method calls common to all versions of the API.
type RPCServer struct {
s *ServerImpl
}
type methodType struct {
method reflect.Method
Rcvr reflect.Value
ArgType reflect.Type
ReplyType reflect.Type
Synchronous bool
}
// NewServer creates a new RPCServer.
func NewServer(config *service.Config) *ServerImpl {
logger := logflags.RPCLogger()
if config.APIVersion < 2 {
logger.Info("Using API v1")
}
if config.Debugger.Foreground {
// Print listener address
logflags.WriteAPIListeningMessage(config.Listener.Addr())
logger.Debug("API server pid = ", os.Getpid())
}
return &ServerImpl{
config: config,
listener: config.Listener,
stopChan: make(chan struct{}),
log: logger,
}
}
// Stop stops the JSON-RPC server.
func (s *ServerImpl) Stop() error {
s.log.Debug("stopping")
close(s.stopChan)
if s.config.AcceptMulti {
s.listener.Close()
}
if s.debugger.IsRunning() {
s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil, nil)
}
kill := s.config.Debugger.AttachPid == 0
return s.debugger.Detach(kill)
}
// Run starts a debugger and exposes it with an JSON-RPC server. The debugger
// itself can be stopped with the `detach` API.
func (s *ServerImpl) Run() error {
var err error
if s.config.APIVersion < 2 {
s.config.APIVersion = 1
}
if s.config.APIVersion > 2 {
return errors.New("unknown API version")
}
// Create and start the debugger
config := s.config.Debugger
if s.debugger, err = debugger.New(&config, s.config.ProcessArgs); err != nil {
return err
}
s.s1 = rpc1.NewServer(s.config, s.debugger)
s.s2 = rpc2.NewServer(s.config, s.debugger)
rpcServer := &RPCServer{s}
s.methodMaps = make([]map[string]*methodType, 2)
s.methodMaps[0] = map[string]*methodType{}
s.methodMaps[1] = map[string]*methodType{}
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()
for {
c, err := s.listener.Accept()
if err != nil {
select {
case <-s.stopChan:
// We were supposed to exit, do nothing and return
return
default:
panic(err)
}
}
if s.config.CheckLocalConnUser {
if !sameuser.CanAccept(s.listener.Addr(), c.LocalAddr(), c.RemoteAddr()) {
c.Close()
continue
}
}
go s.serveConnectionDemux(c)
if !s.config.AcceptMulti {
break
}
}
}()
return nil
}
type bufReadWriteCloser struct {
*bufio.Reader
io.WriteCloser
}
func (s *ServerImpl) serveConnectionDemux(c io.ReadWriteCloser) {
conn := &bufReadWriteCloser{bufio.NewReader(c), c}
b, err := conn.Peek(1)
if err != nil {
s.log.Warnf("error determining new connection protocol: %v", err)
return
}
if b[0] == 'C' { // C is for DAP's Content-Length
s.log.Debugf("serving DAP on new connection")
ds := dap.NewSession(conn, &dap.Config{Config: s.config, StopTriggered: s.stopChan}, s.debugger)
go ds.ServeDAPCodec()
} else {
s.log.Debugf("serving JSON-RPC on new connection")
go s.serveJSONCodec(conn)
}
}
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
// Is this an exported - upper case - name?
func isExported(name string) bool {
ch, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(ch)
}
// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
// Fills methods map with the methods of receiver that should be made
// available through the RPC interface.
// These are all the public methods of rcvr that have one of those
// 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, log logflags.Logger) {
typ := reflect.TypeOf(rcvr)
rcvrv := reflect.ValueOf(rcvr)
sname := reflect.Indirect(rcvrv).Type().Name()
if sname == "" {
log.Debugf("rpc.Register: no service name for type %s", typ)
return
}
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
mname := method.Name
mtype := method.Type
// method must be exported
if method.PkgPath != "" {
continue
}
// Method needs three ins: (receive, *args, *reply) or (receiver, *args, *RPCCallback)
if mtype.NumIn() != 3 {
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.Warn(mname, "argument type not exported:", argType)
continue
}
replyType := mtype.In(2)
synchronous := replyType.String() != "service.RPCCallback"
if synchronous {
// Second arg must be a pointer.
if replyType.Kind() != reflect.Ptr {
log.Warn("method", mname, "reply type not a pointer:", replyType)
continue
}
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) {
log.Warn("method", mname, "reply type not exported:", replyType)
continue
}
// Method needs one out.
if mtype.NumOut() != 1 {
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.Warn("method", mname, "returns", returnType.String(), "not error")
continue
}
} else if mtype.NumOut() != 0 {
// Method needs zero outs.
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}
}
}
func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
clientDisconnectChan := make(chan struct{})
defer func() {
close(clientDisconnectChan)
if !s.config.AcceptMulti && s.config.DisconnectChan != nil {
close(s.config.DisconnectChan)
}
}()
sending := new(sync.Mutex)
codec := jsonrpc.NewServerCodec(conn)
var req rpc.Request
var resp rpc.Response
for {
req = rpc.Request{}
err := codec.ReadRequestHeader(&req)
if err != nil {
if err != io.EOF {
s.log.Error("rpc:", err)
}
break
}
mtype, ok := s.methodMaps[s.config.APIVersion-1][req.ServiceMethod]
if !ok {
s.log.Errorf("rpc: can't find method %s", req.ServiceMethod)
s.sendResponse(sending, &req, &rpc.Response{}, nil, codec, fmt.Sprintf("unknown method: %s", req.ServiceMethod))
continue
}
var argv, replyv reflect.Value
// Decode the argument value.
argIsValue := false // if true, need to indirect before calling.
if mtype.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(mtype.ArgType.Elem())
} else {
argv = reflect.New(mtype.ArgType)
argIsValue = true
}
// argv guaranteed to be a pointer now.
if err = codec.ReadRequestBody(argv.Interface()); err != nil {
return
}
if argIsValue {
argv = argv.Elem()
}
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
var errInter interface{}
func() {
defer func() {
if ierr := recover(); ierr != nil {
errInter = newInternalError(ierr, 2)
}
}()
returnValues = function.Call([]reflect.Value{mtype.Rcvr, argv, replyv})
errInter = returnValues[0].Interface()
}()
errmsg := ""
if errInter != nil {
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)
if req.ServiceMethod == "RPCServer.Detach" && s.config.DisconnectChan != nil {
close(s.config.DisconnectChan)
s.config.DisconnectChan = nil
}
} 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, make(chan struct{}), clientDisconnectChan}
go func() {
defer func() {
if ierr := recover(); ierr != nil {
ctl.Return(nil, newInternalError(ierr, 2))
}
}()
function.Call([]reflect.Value{mtype.Rcvr, argv, reflect.ValueOf(ctl)})
}()
<-ctl.setupDone
}
}
codec.Close()
}
// A value sent as a placeholder for the server's response value when the server
// receives an invalid request. It is never decoded by the client since the Response
// contains an error when it is used.
var invalidRequest = struct{}{}
func (s *ServerImpl) sendResponse(sending *sync.Mutex, req *rpc.Request, resp *rpc.Response, reply interface{}, codec rpc.ServerCodec, errmsg string) {
resp.ServiceMethod = req.ServiceMethod
if errmsg != "" {
resp.Error = errmsg
reply = invalidRequest
}
resp.Seq = req.Seq
sending.Lock()
defer sending.Unlock()
err := codec.WriteResponse(resp, reply)
if err != nil {
s.log.Error("writing response:", err)
}
}
func (cb *RPCCallback) Return(out interface{}, err error) {
select {
case <-cb.setupDone:
// already closed
default:
close(cb.setupDone)
}
errmsg := ""
if err != nil {
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)
}
if cb.hasDisconnected() {
return
}
cb.s.sendResponse(cb.sending, &cb.req, &resp, out, cb.codec, errmsg)
}
func (cb *RPCCallback) DisconnectChan() chan struct{} {
return cb.disconnectChan
}
func (cb *RPCCallback) hasDisconnected() bool {
select {
case <-cb.disconnectChan:
return true
default:
}
return false
}
func (cb *RPCCallback) SetupDoneChan() chan struct{} {
return cb.setupDone
}
// GetVersion returns the version of delve as well as the API version
// currently served.
func (s *RPCServer) GetVersion(args api.GetVersionIn, out *api.GetVersionOut) error {
out.DelveVersion = version.DelveVersion.String()
out.APIVersion = s.s.config.APIVersion
return s.s.debugger.GetVersion(out)
}
// SetApiVersion changes version of the API being served.
func (s *RPCServer) SetApiVersion(args api.SetAPIVersionIn, out *api.SetAPIVersionOut) error {
if args.APIVersion < 2 {
args.APIVersion = 1
}
if args.APIVersion > 2 {
return errors.New("unknown API version")
}
s.s.config.APIVersion = args.APIVersion
return nil
}
type internalError struct {
Err interface{}
Stack []internalErrorFrame
}
type internalErrorFrame struct {
Pc uintptr
Func string
File string
Line int
}
func newInternalError(ierr interface{}, skip int) *internalError {
r := &internalError{ierr, nil}
for i := skip; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
fname := "<unknown>"
fn := runtime.FuncForPC(pc)
if fn != nil {
fname = fn.Name()
}
r.Stack = append(r.Stack, internalErrorFrame{pc, fname, file, line})
}
return r
}
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)
}
return out.String()
}
@@ -0,0 +1,8 @@
package service
// Server represents a server for a remote client
// to connect to.
type Server interface {
Run() error
Stop() error
}
@@ -0,0 +1,142 @@
package service_test
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/rpc1"
"github.com/go-delve/delve/service/rpc2"
)
func assertNoError(err error, t *testing.T, s string) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
fname := filepath.Base(file)
t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
}
}
func assertError(err error, t *testing.T, s string) {
if err == nil {
_, file, line, _ := runtime.Caller(1)
fname := filepath.Base(file)
t.Fatalf("failed assertion at %s:%d: %s (no error)\n", fname, line, s)
}
if strings.Contains(err.Error(), "Internal debugger error") {
_, file, line, _ := runtime.Caller(1)
fname := filepath.Base(file)
t.Fatalf("failed assertion at %s:%d: %s internal debugger error: %v\n", fname, line, s, err)
}
}
func init() {
runtime.GOMAXPROCS(2)
}
type nextTest struct {
begin, end int
}
func testProgPath(t *testing.T, name string) string {
fp, err := filepath.Abs(fmt.Sprintf("_fixtures/%s.go", name))
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(fp); err != nil {
fp, err = filepath.Abs(fmt.Sprintf("../../_fixtures/%s.go", name))
if err != nil {
t.Fatal(err)
}
}
sympath, err := filepath.EvalSymlinks(fp)
if err == nil {
fp = strings.ReplaceAll(sympath, "\\", "/")
}
return fp
}
type BreakpointLister interface {
ListBreakpoints() ([]*api.Breakpoint, error)
}
func countBreakpoints(t *testing.T, c interface{}) int {
var bps []*api.Breakpoint
var err error
switch c := c.(type) {
case *rpc2.RPCClient:
bps, err = c.ListBreakpoints(false)
case *rpc1.RPCClient:
bps, err = c.ListBreakpoints()
}
assertNoError(err, t, "ListBreakpoints()")
bpcount := 0
for _, bp := range bps {
if bp.ID >= 0 {
bpcount++
}
}
return bpcount
}
type locationFinder1 interface {
FindLocation(api.EvalScope, string) ([]api.Location, error)
}
type locationFinder2 interface {
FindLocation(api.EvalScope, string, bool, [][2]string) ([]api.Location, string, error)
}
func findLocationHelper(t *testing.T, c interface{}, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
var locs []api.Location
var err error
switch c := c.(type) {
case locationFinder1:
locs, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc)
case locationFinder2:
locs, _, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc, false, nil)
default:
t.Errorf("unexpected type %T passed to findLocationHelper", c)
}
t.Logf("FindLocation(\"%s\") → %v\n", loc, locs)
if shouldErr {
if err == nil {
t.Fatalf("Resolving location <%s> didn't return an error: %v", loc, locs)
}
} else {
if err != nil {
t.Fatalf("Error resolving location <%s>: %v", loc, err)
}
}
if (count >= 0) && (len(locs) != count) {
t.Fatalf("Wrong number of breakpoints returned for location <%s> (got %d, expected %d)", loc, len(locs), count)
}
if checkAddr != 0 && checkAddr != locs[0].PC {
t.Fatalf("Wrong address returned for location <%s> (got %#x, expected %#x)", loc, locs[0].PC, checkAddr)
}
addrs := make([]uint64, len(locs))
for i := range locs {
addrs[i] = locs[i].PC
}
return addrs
}
func getCurinstr(d3 api.AsmInstructions) *api.AsmInstruction {
for i := range d3 {
if d3[i].AtPC {
return &d3[i]
}
}
return nil
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff