delve/service/api/conversions.go

470 lines
12 KiB
Go
Raw Normal View History

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
}
2023-11-06 13:55:44 +00:00
// 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()
}
2016-01-10 08:57:52 +00:00
// 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 {
2016-01-10 08:57:52 +00:00
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)
}
2016-01-10 08:57:52 +00:00
// 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
}
2016-01-10 08:57:52 +00:00
// 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
2018-03-20 10:05:35 +00:00
// 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(),
}
}
2016-01-10 08:57:52 +00:00
// ConvertGoroutine converts from proc.G to api.Goroutine.
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
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{
2016-01-10 08:57:52 +00:00
ID: g.ID,
CurrentLoc: ConvertLocation(g.CurrentLoc),
UserCurrentLoc: ConvertLocation(g.UserCurrent()),
GoStatementLoc: ConvertLocation(g.Go()),
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
StartLoc: ConvertLocation(g.StartLoc(tgt)),
ThreadID: tid,
WaitSince: g.WaitSince,
WaitReason: g.WaitReason,
2020-01-20 20:02:12 +00:00
Labels: g.Labels(),
Status: g.Status,
}
}
// ConvertGoroutines converts from []*proc.G to []*api.Goroutine.
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
func ConvertGoroutines(tgt *proc.Target, gs []*proc.G) []*Goroutine {
goroutines := make([]*Goroutine, len(gs))
for i := range gs {
Go 1.17 support branch (#2451) * proc: support new Go 1.17 panic/defer mechanism Go 1.17 will create wrappers for deferred calls that take arguments. Change defer reading code so that wrappers are automatically unwrapped. Also the deferred function is called directly by runtime.gopanic, without going through runtime.callN which means that sometimes when a panic happens the stack is either: 0. deferred function call 1. deferred call wrapper 2. runtime.gopanic or: 0. deferred function call 1. runtime.gopanic instead of always being: 0. deferred function call 1. runtime.callN 2. runtime.gopanic the isPanicCall check is changed accordingly. * test: miscellaneous minor test fixes for Go 1.17 * proc: resolve inlined calls when stepping out of runtime.breakpoint Calls to runtime.Breakpoint are inlined in Go 1.17 when inlining is enabled, resolve inlined calls in stepInstructionOut. * proc: add support for debugCallV2 with regabi This change adds support for the new debug call protocol which had to change for the new register ABI introduced in Go 1.17. Summary of changes: - Abstracts over the debug call version depending on the Go version found in the binary. - Uses R12 instead of RAX as the debug protocol register when the binary is from Go 1.17 or later. - Creates a variable directly from the DWARF entry for function arguments to support passing arguments however the ABI expects. - Computes a very conservative stack frame size for the call when injecting a call into a Go process whose version is >=1.17. Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com> Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com> * TeamCity: enable tests on go-tip * goversion: version compatibility bump * TeamCity: fix go-tip builds on macOS/arm64 Co-authored-by: Michael Anthony Knyszek <mknyszek@google.com>
2021-07-08 15:47:53 +00:00
goroutines[i] = ConvertGoroutine(tgt, gs[i])
}
return goroutines
}
2016-01-10 08:57:52 +00:00
// 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),
}
}
2016-02-06 06:00:48 +00:00
// ConvertAsmInstruction converts from proc.AsmInstruction to api.AsmInstruction.
2016-02-06 06:00:48 +00:00
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
}
2023-04-27 20:39:33 +00:00
// 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())),
}
}