
* proc: refactor BinaryInfo part of proc.Process to own type The data structures and associated code used by proc.Process to implement target.BinaryInfo will also be useful to support a backend for examining core dumps, split this part of proc.Process to a different type. * proc: compile support for all executable formats unconditionally So far we only compiled in support for loading the executable format supported by the host operating system. Once support for core files is introduced it is however useful to support loading in all executable formats, there is no reason why it shouldn't be possible to examine a linux coredump on windows, or viceversa. * proc: bugfix: do not resume threads on detach if killing * Replace BinaryInfo interface with BinInfo() method returning proc.BinaryInfo
412 lines
9.8 KiB
Go
412 lines
9.8 KiB
Go
package debugger
|
|
|
|
import (
|
|
"debug/gosym"
|
|
"fmt"
|
|
"go/constant"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/derekparker/delve/pkg/proc"
|
|
"github.com/derekparker/delve/service/api"
|
|
)
|
|
|
|
const maxFindLocationCandidates = 5
|
|
|
|
type LocationSpec interface {
|
|
Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error)
|
|
}
|
|
|
|
type NormalLocationSpec struct {
|
|
Base string
|
|
FuncBase *FuncLocationSpec
|
|
LineOffset int
|
|
}
|
|
|
|
type RegexLocationSpec struct {
|
|
FuncRegex string
|
|
}
|
|
|
|
type AddrLocationSpec struct {
|
|
AddrExpr string
|
|
}
|
|
|
|
type OffsetLocationSpec struct {
|
|
Offset int
|
|
}
|
|
|
|
type LineLocationSpec struct {
|
|
Line int
|
|
}
|
|
|
|
type FuncLocationSpec struct {
|
|
PackageName string
|
|
AbsolutePackage bool
|
|
ReceiverName string
|
|
PackageOrReceiverName string
|
|
BaseName string
|
|
}
|
|
|
|
func parseLocationSpec(locStr string) (LocationSpec, error) {
|
|
rest := locStr
|
|
|
|
malformed := func(reason string) error {
|
|
return fmt.Errorf("Malformed breakpoint location \"%s\" at %d: %s", locStr, len(locStr)-len(rest), reason)
|
|
}
|
|
|
|
if len(rest) <= 0 {
|
|
return nil, malformed("empty string")
|
|
}
|
|
|
|
switch rest[0] {
|
|
case '+', '-':
|
|
offset, err := strconv.Atoi(rest)
|
|
if err != nil {
|
|
return nil, malformed(err.Error())
|
|
}
|
|
return &OffsetLocationSpec{offset}, nil
|
|
|
|
case '/':
|
|
if rest[len(rest)-1] == '/' {
|
|
rx, rest := readRegex(rest[1:])
|
|
if len(rest) < 0 {
|
|
return nil, malformed("non-terminated regular expression")
|
|
}
|
|
if len(rest) > 1 {
|
|
return nil, malformed("no line offset can be specified for regular expression locations")
|
|
}
|
|
return &RegexLocationSpec{rx}, nil
|
|
} else {
|
|
return parseLocationSpecDefault(locStr, rest)
|
|
}
|
|
|
|
case '*':
|
|
return &AddrLocationSpec{rest[1:]}, nil
|
|
|
|
default:
|
|
return parseLocationSpecDefault(locStr, rest)
|
|
}
|
|
}
|
|
|
|
func parseLocationSpecDefault(locStr, rest string) (LocationSpec, error) {
|
|
malformed := func(reason string) error {
|
|
return fmt.Errorf("Malformed breakpoint location \"%s\" at %d: %s", locStr, len(locStr)-len(rest), reason)
|
|
}
|
|
|
|
v := strings.Split(rest, ":")
|
|
if len(v) > 2 {
|
|
// On Windows, path may contain ":", so split only on last ":"
|
|
v = []string{strings.Join(v[0:len(v)-1], ":"), v[len(v)-1]}
|
|
}
|
|
|
|
if len(v) == 1 {
|
|
n, err := strconv.ParseInt(v[0], 0, 64)
|
|
if err == nil {
|
|
return &LineLocationSpec{int(n)}, nil
|
|
}
|
|
}
|
|
|
|
spec := &NormalLocationSpec{}
|
|
|
|
spec.Base = v[0]
|
|
spec.FuncBase = parseFuncLocationSpec(spec.Base)
|
|
|
|
if len(v) < 2 {
|
|
spec.LineOffset = -1
|
|
return spec, nil
|
|
}
|
|
|
|
rest = v[1]
|
|
|
|
var err error
|
|
spec.LineOffset, err = strconv.Atoi(rest)
|
|
if err != nil || spec.LineOffset < 0 {
|
|
return nil, malformed("line offset negative or not a number")
|
|
}
|
|
|
|
return spec, nil
|
|
}
|
|
|
|
func readRegex(in string) (rx string, rest string) {
|
|
out := make([]rune, 0, len(in))
|
|
escaped := false
|
|
for i, ch := range in {
|
|
if escaped {
|
|
if ch == '/' {
|
|
out = append(out, '/')
|
|
} else {
|
|
out = append(out, '\\')
|
|
out = append(out, ch)
|
|
}
|
|
escaped = false
|
|
} else {
|
|
switch ch {
|
|
case '\\':
|
|
escaped = true
|
|
case '/':
|
|
return string(out), in[i:]
|
|
default:
|
|
out = append(out, ch)
|
|
}
|
|
}
|
|
}
|
|
return string(out), ""
|
|
}
|
|
|
|
func parseFuncLocationSpec(in string) *FuncLocationSpec {
|
|
var v []string
|
|
pathend := strings.LastIndex(in, "/")
|
|
if pathend < 0 {
|
|
v = strings.Split(in, ".")
|
|
} else {
|
|
v = strings.Split(in[pathend:], ".")
|
|
if len(v) > 0 {
|
|
v[0] = in[:pathend] + v[0]
|
|
}
|
|
}
|
|
|
|
var spec FuncLocationSpec
|
|
switch len(v) {
|
|
case 1:
|
|
spec.BaseName = v[0]
|
|
|
|
case 2:
|
|
spec.BaseName = v[1]
|
|
r := stripReceiverDecoration(v[0])
|
|
if r != v[0] {
|
|
spec.ReceiverName = r
|
|
} else if strings.Index(r, "/") >= 0 {
|
|
spec.PackageName = r
|
|
} else {
|
|
spec.PackageOrReceiverName = r
|
|
}
|
|
|
|
case 3:
|
|
spec.BaseName = v[2]
|
|
spec.ReceiverName = stripReceiverDecoration(v[1])
|
|
spec.PackageName = v[0]
|
|
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
if strings.HasPrefix(spec.PackageName, "/") {
|
|
spec.PackageName = spec.PackageName[1:]
|
|
spec.AbsolutePackage = true
|
|
}
|
|
|
|
if strings.Index(spec.BaseName, "/") >= 0 || strings.Index(spec.ReceiverName, "/") >= 0 {
|
|
return nil
|
|
}
|
|
|
|
return &spec
|
|
}
|
|
|
|
func stripReceiverDecoration(in string) string {
|
|
if len(in) < 3 {
|
|
return in
|
|
}
|
|
if (in[0] != '(') || (in[1] != '*') || (in[len(in)-1] != ')') {
|
|
return in
|
|
}
|
|
|
|
return in[2 : len(in)-1]
|
|
}
|
|
|
|
func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
|
|
if spec.BaseName != sym.BaseName() {
|
|
return false
|
|
}
|
|
|
|
recv := stripReceiverDecoration(sym.ReceiverName())
|
|
if spec.ReceiverName != "" && spec.ReceiverName != recv {
|
|
return false
|
|
}
|
|
if spec.PackageName != "" {
|
|
if spec.AbsolutePackage {
|
|
if spec.PackageName != sym.PackageName() {
|
|
return false
|
|
}
|
|
} else {
|
|
if !partialPathMatch(spec.PackageName, sym.PackageName()) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
if spec.PackageOrReceiverName != "" && !partialPathMatch(spec.PackageOrReceiverName, sym.PackageName()) && spec.PackageOrReceiverName != recv {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
|
funcs := d.target.BinInfo().Funcs()
|
|
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r := make([]api.Location, 0, len(matches))
|
|
for i := range matches {
|
|
addr, err := d.target.FindFunctionLocation(matches[i], true, 0)
|
|
if err == nil {
|
|
r = append(r, api.Location{PC: addr})
|
|
}
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
|
if scope == nil {
|
|
addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
|
}
|
|
return []api.Location{{PC: uint64(addr)}}, nil
|
|
} else {
|
|
v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{true, 0, 0, 0, 0})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if v.Unreadable != nil {
|
|
return nil, v.Unreadable
|
|
}
|
|
switch v.Kind {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
addr, _ := constant.Uint64Val(v.Value)
|
|
return []api.Location{{PC: addr}}, nil
|
|
case reflect.Func:
|
|
_, _, fn := d.target.BinInfo().PCToLine(uint64(v.Base))
|
|
pc, err := d.target.FirstPCAfterPrologue(fn, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []api.Location{{PC: uint64(pc)}}, nil
|
|
default:
|
|
return nil, fmt.Errorf("wrong expression kind: %v", v.Kind)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (loc *NormalLocationSpec) FileMatch(path string) bool {
|
|
return partialPathMatch(loc.Base, path)
|
|
}
|
|
|
|
func partialPathMatch(expr, path string) bool {
|
|
if runtime.GOOS == "windows" {
|
|
// Accept `expr` which is case-insensitive and slash-insensitive match to `path`
|
|
expr = strings.ToLower(filepath.ToSlash(expr))
|
|
path = strings.ToLower(filepath.ToSlash(path))
|
|
}
|
|
if len(expr) < len(path)-1 {
|
|
return strings.HasSuffix(path, expr) && (path[len(path)-len(expr)-1] == '/')
|
|
} else {
|
|
return expr == path
|
|
}
|
|
}
|
|
|
|
type AmbiguousLocationError struct {
|
|
Location string
|
|
CandidatesString []string
|
|
CandidatesLocation []api.Location
|
|
}
|
|
|
|
func (ale AmbiguousLocationError) Error() string {
|
|
var candidates []string
|
|
if ale.CandidatesLocation != nil {
|
|
for i := range ale.CandidatesLocation {
|
|
candidates = append(candidates, ale.CandidatesLocation[i].Function.Name)
|
|
}
|
|
|
|
} else {
|
|
candidates = ale.CandidatesString
|
|
}
|
|
return fmt.Sprintf("Location \"%s\" ambiguous: %s…", ale.Location, strings.Join(candidates, ", "))
|
|
}
|
|
|
|
func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
|
funcs := d.target.BinInfo().Funcs()
|
|
files := d.target.BinInfo().Sources()
|
|
|
|
candidates := []string{}
|
|
for file := range files {
|
|
if loc.FileMatch(file) {
|
|
candidates = append(candidates, file)
|
|
if len(candidates) >= maxFindLocationCandidates {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if loc.FuncBase != nil {
|
|
for _, f := range funcs {
|
|
if f.Sym == nil {
|
|
continue
|
|
}
|
|
if loc.FuncBase.Match(f.Sym) {
|
|
if loc.Base == f.Name {
|
|
// if an exact match for the function name is found use it
|
|
candidates = []string{f.Name}
|
|
break
|
|
}
|
|
if len(candidates) < maxFindLocationCandidates {
|
|
candidates = append(candidates, f.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch len(candidates) {
|
|
case 1:
|
|
var addr uint64
|
|
var err error
|
|
if filepath.IsAbs(candidates[0]) {
|
|
if loc.LineOffset < 0 {
|
|
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
|
|
}
|
|
addr, err = d.target.FindFileLocation(candidates[0], loc.LineOffset)
|
|
} else {
|
|
if loc.LineOffset < 0 {
|
|
addr, err = d.target.FindFunctionLocation(candidates[0], true, 0)
|
|
} else {
|
|
addr, err = d.target.FindFunctionLocation(candidates[0], false, loc.LineOffset)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []api.Location{{PC: addr}}, nil
|
|
|
|
case 0:
|
|
return nil, fmt.Errorf("Location \"%s\" not found", locStr)
|
|
default:
|
|
return nil, AmbiguousLocationError{Location: locStr, CandidatesString: candidates}
|
|
}
|
|
}
|
|
|
|
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
|
if scope == nil {
|
|
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
|
}
|
|
file, line, fn := d.target.BinInfo().PCToLine(scope.PC)
|
|
if fn == nil {
|
|
return nil, fmt.Errorf("could not determine current location")
|
|
}
|
|
addr, err := d.target.FindFileLocation(file, line+loc.Offset)
|
|
return []api.Location{{PC: addr}}, err
|
|
}
|
|
|
|
func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
|
if scope == nil {
|
|
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
|
}
|
|
file, _, fn := d.target.BinInfo().PCToLine(scope.PC)
|
|
if fn == nil {
|
|
return nil, fmt.Errorf("could not determine current location")
|
|
}
|
|
addr, err := d.target.FindFileLocation(file, loc.Line)
|
|
return []api.Location{{PC: addr}}, err
|
|
}
|