2015-08-07 16:50:14 +00:00
|
|
|
package debugger
|
|
|
|
|
|
|
|
import (
|
|
|
|
"debug/gosym"
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/derekparker/delve/service/api"
|
|
|
|
)
|
|
|
|
|
|
|
|
const maxFindLocationCandidates = 5
|
|
|
|
|
|
|
|
type LocationSpec interface {
|
2015-09-05 22:23:59 +00:00
|
|
|
Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error)
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type NormalLocationSpec struct {
|
|
|
|
Base string
|
|
|
|
FuncBase *FuncLocationSpec
|
|
|
|
LineOffset int
|
|
|
|
}
|
|
|
|
|
|
|
|
type RegexLocationSpec struct {
|
|
|
|
FuncRegex string
|
|
|
|
}
|
|
|
|
|
|
|
|
type AddrLocationSpec struct {
|
|
|
|
Addr uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
type OffsetLocationSpec struct {
|
|
|
|
Offset int
|
|
|
|
}
|
|
|
|
|
|
|
|
type LineLocationSpec struct {
|
|
|
|
Line int
|
|
|
|
}
|
|
|
|
|
|
|
|
type FuncLocationSpec struct {
|
|
|
|
PackageName string
|
|
|
|
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 '/':
|
2015-09-08 20:00:38 +00:00
|
|
|
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)
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case '*':
|
|
|
|
rest = rest[1:]
|
|
|
|
addr, err := strconv.ParseInt(rest, 0, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, malformed(err.Error())
|
|
|
|
}
|
|
|
|
if addr == 0 {
|
|
|
|
return nil, malformed("can not set breakpoint at address 0x0")
|
|
|
|
}
|
|
|
|
return &AddrLocationSpec{uint64(addr)}, nil
|
|
|
|
|
|
|
|
default:
|
2015-09-08 20:00:38 +00:00
|
|
|
return parseLocationSpecDefault(locStr, rest)
|
|
|
|
}
|
|
|
|
}
|
2015-08-07 16:50:14 +00:00
|
|
|
|
2015-09-08 20:00:38 +00:00
|
|
|
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)
|
|
|
|
}
|
2015-08-07 16:50:14 +00:00
|
|
|
|
2015-09-08 20:00:38 +00:00
|
|
|
v := strings.SplitN(rest, ":", 2)
|
2015-08-07 16:50:14 +00:00
|
|
|
|
2015-09-08 20:00:38 +00:00
|
|
|
if len(v) == 1 {
|
|
|
|
n, err := strconv.ParseInt(v[0], 0, 64)
|
|
|
|
if err == nil {
|
|
|
|
return &LineLocationSpec{int(n)}, nil
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
2015-09-08 20:00:38 +00:00
|
|
|
}
|
2015-08-07 16:50:14 +00:00
|
|
|
|
2015-09-08 20:00:38 +00:00
|
|
|
spec := &NormalLocationSpec{}
|
2015-08-07 16:50:14 +00:00
|
|
|
|
2015-09-08 20:00:38 +00:00
|
|
|
spec.Base = v[0]
|
|
|
|
spec.FuncBase = parseFuncLocationSpec(spec.Base)
|
2015-08-07 16:50:14 +00:00
|
|
|
|
2015-09-08 20:00:38 +00:00
|
|
|
if len(v) < 2 {
|
|
|
|
spec.LineOffset = -1
|
2015-08-07 16:50:14 +00:00
|
|
|
return spec, nil
|
|
|
|
}
|
2015-09-08 20:00:38 +00:00
|
|
|
|
|
|
|
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
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
v := strings.Split(in, ".")
|
|
|
|
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
|
2015-10-17 07:31:07 +00:00
|
|
|
} else if strings.Index(r, "/") >= 0 {
|
|
|
|
spec.PackageName = r
|
2015-08-07 16:50:14 +00:00
|
|
|
} else {
|
|
|
|
spec.PackageOrReceiverName = r
|
|
|
|
}
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
spec.BaseName = v[2]
|
|
|
|
spec.ReceiverName = stripReceiverDecoration(v[1])
|
|
|
|
spec.PackageName = stripReceiverDecoration(v[0])
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil
|
2015-10-17 07:31:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if strings.Index(spec.BaseName, "/") >= 0 || strings.Index(spec.ReceiverName, "/") >= 0 {
|
|
|
|
return nil
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 != "" && spec.PackageName != sym.PackageName() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if spec.PackageOrReceiverName != "" && spec.PackageOrReceiverName != sym.PackageName() && spec.PackageOrReceiverName != recv {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2015-09-05 22:23:59 +00:00
|
|
|
func (loc *RegexLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
2015-08-07 16:50:14 +00:00
|
|
|
funcs := d.process.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.process.FindFunctionLocation(matches[i], true, 0)
|
|
|
|
if err == nil {
|
|
|
|
r = append(r, api.Location{PC: addr})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
2015-09-05 22:23:59 +00:00
|
|
|
func (loc *AddrLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
2015-08-07 16:50:14 +00:00
|
|
|
return []api.Location{{PC: loc.Addr}}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (loc *NormalLocationSpec) FileMatch(path string) bool {
|
2015-09-08 20:00:38 +00:00
|
|
|
if len(loc.Base) < len(path)-1 {
|
|
|
|
return strings.HasSuffix(path, loc.Base) && (path[len(path)-len(loc.Base)-1] == filepath.Separator)
|
|
|
|
} else {
|
|
|
|
return loc.Base == path
|
|
|
|
}
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
|
|
|
|
2015-07-29 14:24:52 +00:00
|
|
|
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, ", "))
|
|
|
|
}
|
|
|
|
|
2015-09-05 22:23:59 +00:00
|
|
|
func (loc *NormalLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
2015-08-07 16:50:14 +00:00
|
|
|
funcs := d.process.Funcs()
|
|
|
|
files := d.process.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) {
|
|
|
|
candidates = append(candidates, f.Name)
|
|
|
|
if len(candidates) >= maxFindLocationCandidates {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch len(candidates) {
|
|
|
|
case 1:
|
|
|
|
var addr uint64
|
|
|
|
var err error
|
|
|
|
if candidates[0][0] == '/' {
|
|
|
|
if loc.LineOffset < 0 {
|
|
|
|
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
|
|
|
|
}
|
|
|
|
addr, err = d.process.FindFileLocation(candidates[0], loc.LineOffset)
|
|
|
|
} else {
|
|
|
|
if loc.LineOffset < 0 {
|
|
|
|
addr, err = d.process.FindFunctionLocation(candidates[0], true, 0)
|
|
|
|
} else {
|
|
|
|
addr, err = d.process.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:
|
2015-07-29 14:24:52 +00:00
|
|
|
return nil, AmbiguousLocationError{Location: locStr, CandidatesString: candidates}
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-05 22:23:59 +00:00
|
|
|
func (loc *OffsetLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
|
|
|
file, line, fn := d.process.PCToLine(pc)
|
2015-08-28 20:06:29 +00:00
|
|
|
if fn == nil {
|
|
|
|
return nil, fmt.Errorf("could not determine current location")
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
2015-08-28 20:06:29 +00:00
|
|
|
addr, err := d.process.FindFileLocation(file, line+loc.Offset)
|
2015-08-07 16:50:14 +00:00
|
|
|
return []api.Location{{PC: addr}}, err
|
|
|
|
}
|
|
|
|
|
2015-09-05 22:23:59 +00:00
|
|
|
func (loc *LineLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
|
|
|
file, _, fn := d.process.PCToLine(pc)
|
2015-08-28 20:06:29 +00:00
|
|
|
if fn == nil {
|
|
|
|
return nil, fmt.Errorf("could not determine current location")
|
2015-08-07 16:50:14 +00:00
|
|
|
}
|
2015-08-28 20:06:29 +00:00
|
|
|
addr, err := d.process.FindFileLocation(file, loc.Line)
|
2015-08-07 16:50:14 +00:00
|
|
|
return []api.Location{{PC: addr}}, err
|
|
|
|
}
|