delve/service/api/conversions.go
Alessandro Arzilli f3b149bda7 proc: support debugging plugins (#1414)
This change splits the BinaryInfo object into a slice of Image objects
containing information about the base executable and each loaded shared
library (note: go plugins are shared libraries).

Delve backens are supposed to call BinaryInfo.AddImage whenever they
detect that a new shared library has been loaded.

Member fields of BinaryInfo that are used to speed up access to dwarf
(Functions, packageVars, consts, etc...) remain part of BinaryInfo and
are updated to reference the correct image object. This simplifies this
change.

This approach has a few shortcomings:

1. Multiple shared libraries can define functions or globals with the
   same name and we have no way to disambiguate between them.

2. We don't have a way to handle library unloading.

Both of those affect C shared libraries much more than they affect go
plugins. Go plugins can't be unloaded at all and a lot of name
collisions are prevented by import paths.

There's only one problem that is concerning: if two plugins both import
the same package they will end up with multiple definition for the same
function.
For example if two plugins use fmt.Printf the final in-memory image
(and therefore our BinaryInfo object) will end up with two copies of
fmt.Printf at different memory addresses. If a user types
  break fmt.Printf
a breakpoint should be created at *both* locations.
Allowing this is a relatively complex change that should be done in a
different PR than this.

For this reason I consider this approach an acceptable and sustainable
stopgap.

Updates #865
2019-05-08 14:06:38 -07:00

323 lines
7.2 KiB
Go

package api
import (
"bytes"
"fmt"
"go/constant"
"go/printer"
"go/token"
"reflect"
"strconv"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/proc"
)
// ConvertBreakpoint converts from a proc.Breakpoint to
// an api.Breakpoint.
func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
b := &Breakpoint{
Name: bp.Name,
ID: bp.ID,
FunctionName: bp.FunctionName,
File: bp.File,
Line: bp.Line,
Addr: bp.Addr,
Tracepoint: bp.Tracepoint,
TraceReturn: bp.TraceReturn,
Stacktrace: bp.Stacktrace,
Goroutine: bp.Goroutine,
Variables: bp.Variables,
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
TotalHitCount: bp.TotalHitCount,
}
b.HitCount = map[string]uint64{}
for idx := range bp.HitCount {
b.HitCount[strconv.Itoa(idx)] = bp.HitCount[idx]
}
var buf bytes.Buffer
printer.Fprint(&buf, token.NewFileSet(), bp.Cond)
b.Cond = buf.String()
return b
}
// ConvertThread converts a proc.Thread into an
// api thread.
func ConvertThread(th proc.Thread) *Thread {
var (
function *Function
file string
line int
pc uint64
gid int
)
loc, err := th.Location()
if err == nil {
pc = loc.PC
file = loc.File
line = loc.Line
function = ConvertFunction(loc.Fn)
}
var bp *Breakpoint
if b := th.Breakpoint(); b.Active {
bp = ConvertBreakpoint(b.Breakpoint)
}
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,
}
}
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,
DeclLine: v.DeclLine,
}
r.Type = prettyTypeName(v.DwarfType)
r.RealType = prettyTypeName(v.RealType)
if v.Unreadable != nil {
r.Unreadable = v.Unreadable.Error()
}
if v.Value != nil {
switch v.Kind {
case reflect.Float32:
r.Value = convertFloatValue(v, 32)
case reflect.Float64:
r.Value = convertFloatValue(v, 64)
case reflect.String, reflect.Func:
r.Value = constant.StringVal(v.Value)
default:
if cd := v.ConstDescr(); cd != "" {
r.Value = fmt.Sprintf("%s (%s)", cd, v.Value.String())
} else {
r.Value = v.Value.String()
}
}
}
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
}
// 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(g *proc.G) *Goroutine {
th := g.Thread
tid := 0
if th != nil {
tid = th.ThreadID()
}
r := &Goroutine{
ID: g.ID,
CurrentLoc: ConvertLocation(g.CurrentLoc),
UserCurrentLoc: ConvertLocation(g.UserCurrent()),
GoStatementLoc: ConvertLocation(g.Go()),
StartLoc: ConvertLocation(g.StartLoc()),
ThreadID: tid,
}
if g.Unreadable != nil {
r.Unreadable = g.Unreadable.Error()
}
return r
}
// ConvertLocation converts from proc.Location to api.Location.
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{
cfg.FollowPointers,
cfg.MaxVariableRecurse,
cfg.MaxStringLen,
cfg.MaxArrayValues,
cfg.MaxStructFields,
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{
cfg.FollowPointers,
cfg.MaxVariableRecurse,
cfg.MaxStringLen,
cfg.MaxArrayValues,
cfg.MaxStructFields,
}
}
// ConvertRegisters converts proc.Register to api.Register for a slice.
func ConvertRegisters(in []proc.Register) (out []Register) {
out = make([]Register, len(in))
for i := range in {
out[i] = Register{in[i].Name, in[i].Value}
}
return
}
// ConvertCheckpoint converts proc.Chekcpoint to api.Checkpoint.
func ConvertCheckpoint(in proc.Checkpoint) (out Checkpoint) {
return Checkpoint(in)
}
func ConvertImage(image *proc.Image) Image {
return Image{Path: image.Path, Address: image.StaticBase}
}