mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 14:36:47 +08:00 
			
		
		
		
	Show the location expression that will be used to set a suspended breakpoint in the breakpoints list. Also change 'target' called without arguments to print a better error message and 'target follow-exec' without the last argument to print the state of follow-exec.
		
			
				
	
	
		
			471 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			471 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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,
 | 
						|
	}
 | 
						|
 | 
						|
	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 informations 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())),
 | 
						|
	}
 | 
						|
}
 |