github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/locspec/locations.go (about)

     1  package locspec
     2  
     3  import (
     4  	"fmt"
     5  	"go/constant"
     6  	"path"
     7  	"path/filepath"
     8  	"reflect"
     9  	"regexp"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/go-delve/delve/pkg/proc"
    15  	"github.com/go-delve/delve/service/api"
    16  )
    17  
    18  const maxFindLocationCandidates = 5
    19  
    20  // LocationSpec is an interface that represents a parsed location spec string.
    21  type LocationSpec interface {
    22  	// Find returns all locations that match the location spec.
    23  	Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, string, error)
    24  }
    25  
    26  // NormalLocationSpec represents a basic location spec.
    27  // This can be a file:line or func:line.
    28  type NormalLocationSpec struct {
    29  	Base       string
    30  	FuncBase   *FuncLocationSpec
    31  	LineOffset int
    32  }
    33  
    34  // RegexLocationSpec represents a regular expression
    35  // location expression such as /^myfunc$/.
    36  type RegexLocationSpec struct {
    37  	FuncRegex string
    38  }
    39  
    40  // AddrLocationSpec represents an address when used
    41  // as a location spec.
    42  type AddrLocationSpec struct {
    43  	AddrExpr string
    44  }
    45  
    46  // OffsetLocationSpec represents a location spec that
    47  // is an offset of the current location (file:line).
    48  type OffsetLocationSpec struct {
    49  	Offset int
    50  }
    51  
    52  // LineLocationSpec represents a line number in the current file.
    53  type LineLocationSpec struct {
    54  	Line int
    55  }
    56  
    57  // FuncLocationSpec represents a function in the target program.
    58  type FuncLocationSpec struct {
    59  	PackageName           string
    60  	AbsolutePackage       bool
    61  	ReceiverName          string
    62  	PackageOrReceiverName string
    63  	BaseName              string
    64  }
    65  
    66  // Parse will turn locStr into a parsed LocationSpec.
    67  func Parse(locStr string) (LocationSpec, error) {
    68  	rest := locStr
    69  
    70  	malformed := func(reason string) error {
    71  		//lint:ignore ST1005 backwards compatibility
    72  		return fmt.Errorf("Malformed breakpoint location %q at %d: %s", locStr, len(locStr)-len(rest), reason)
    73  	}
    74  
    75  	if len(rest) == 0 {
    76  		return nil, malformed("empty string")
    77  	}
    78  
    79  	switch rest[0] {
    80  	case '+', '-':
    81  		offset, err := strconv.Atoi(rest)
    82  		if err != nil {
    83  			return nil, malformed(err.Error())
    84  		}
    85  		return &OffsetLocationSpec{offset}, nil
    86  
    87  	case '/':
    88  		if rest[len(rest)-1] == '/' {
    89  			rx, rest := readRegex(rest[1:])
    90  			if len(rest) == 0 {
    91  				return nil, malformed("non-terminated regular expression")
    92  			}
    93  			if len(rest) > 1 {
    94  				return nil, malformed("no line offset can be specified for regular expression locations")
    95  			}
    96  			return &RegexLocationSpec{rx}, nil
    97  		} else {
    98  			return parseLocationSpecDefault(locStr, rest)
    99  		}
   100  
   101  	case '*':
   102  		return &AddrLocationSpec{AddrExpr: rest[1:]}, nil
   103  
   104  	default:
   105  		return parseLocationSpecDefault(locStr, rest)
   106  	}
   107  }
   108  
   109  func parseLocationSpecDefault(locStr, rest string) (LocationSpec, error) {
   110  	malformed := func(reason string) error {
   111  		//lint:ignore ST1005 backwards compatibility
   112  		return fmt.Errorf("Malformed breakpoint location %q at %d: %s", locStr, len(locStr)-len(rest), reason)
   113  	}
   114  
   115  	v := strings.Split(rest, ":")
   116  	if len(v) > 2 {
   117  		// On Windows, path may contain ":", so split only on last ":"
   118  		v = []string{strings.Join(v[0:len(v)-1], ":"), v[len(v)-1]}
   119  	}
   120  
   121  	if len(v) == 1 {
   122  		n, err := strconv.ParseInt(v[0], 0, 64)
   123  		if err == nil {
   124  			return &LineLocationSpec{int(n)}, nil
   125  		}
   126  	}
   127  
   128  	spec := &NormalLocationSpec{}
   129  
   130  	spec.Base = v[0]
   131  	spec.FuncBase = parseFuncLocationSpec(spec.Base)
   132  
   133  	if len(v) < 2 {
   134  		spec.LineOffset = -1
   135  		return spec, nil
   136  	}
   137  
   138  	rest = v[1]
   139  
   140  	var err error
   141  	spec.LineOffset, err = strconv.Atoi(rest)
   142  	if err != nil || spec.LineOffset < 0 {
   143  		return nil, malformed("line offset negative or not a number")
   144  	}
   145  
   146  	return spec, nil
   147  }
   148  
   149  func readRegex(in string) (rx string, rest string) {
   150  	out := make([]rune, 0, len(in))
   151  	escaped := false
   152  	for i, ch := range in {
   153  		if escaped {
   154  			if ch == '/' {
   155  				out = append(out, '/')
   156  			} else {
   157  				out = append(out, '\\', ch)
   158  			}
   159  			escaped = false
   160  		} else {
   161  			switch ch {
   162  			case '\\':
   163  				escaped = true
   164  			case '/':
   165  				return string(out), in[i:]
   166  			default:
   167  				out = append(out, ch)
   168  			}
   169  		}
   170  	}
   171  	return string(out), ""
   172  }
   173  
   174  func parseFuncLocationSpec(in string) *FuncLocationSpec {
   175  	var v []string
   176  	pathend := strings.LastIndex(in, "/")
   177  	if pathend < 0 {
   178  		v = strings.Split(in, ".")
   179  	} else {
   180  		v = strings.Split(in[pathend:], ".")
   181  		if len(v) > 0 {
   182  			v[0] = in[:pathend] + v[0]
   183  		}
   184  	}
   185  
   186  	var spec FuncLocationSpec
   187  	switch len(v) {
   188  	case 1:
   189  		spec.BaseName = v[0]
   190  
   191  	case 2:
   192  		spec.BaseName = v[1]
   193  		r := stripReceiverDecoration(v[0])
   194  		if r != v[0] {
   195  			spec.ReceiverName = r
   196  		} else if strings.Contains(r, "/") {
   197  			spec.PackageName = r
   198  		} else {
   199  			spec.PackageOrReceiverName = r
   200  		}
   201  
   202  	case 3:
   203  		spec.BaseName = v[2]
   204  		spec.ReceiverName = stripReceiverDecoration(v[1])
   205  		spec.PackageName = v[0]
   206  
   207  	default:
   208  		return nil
   209  	}
   210  
   211  	if strings.HasPrefix(spec.PackageName, "/") {
   212  		spec.PackageName = spec.PackageName[1:]
   213  		spec.AbsolutePackage = true
   214  	}
   215  
   216  	if strings.Contains(spec.BaseName, "/") || strings.Contains(spec.ReceiverName, "/") {
   217  		return nil
   218  	}
   219  
   220  	return &spec
   221  }
   222  
   223  func stripReceiverDecoration(in string) string {
   224  	if len(in) < 3 {
   225  		return in
   226  	}
   227  	if (in[0] != '(') || (in[1] != '*') || (in[len(in)-1] != ')') {
   228  		return in
   229  	}
   230  
   231  	return in[2 : len(in)-1]
   232  }
   233  
   234  // Match will return whether the provided function matches the location spec.
   235  func (spec *FuncLocationSpec) Match(sym *proc.Function, packageMap map[string][]string) bool {
   236  	if spec.BaseName != sym.BaseName() {
   237  		return false
   238  	}
   239  
   240  	recv := stripReceiverDecoration(sym.ReceiverName())
   241  	if spec.ReceiverName != "" && spec.ReceiverName != recv {
   242  		return false
   243  	}
   244  	if spec.PackageName != "" {
   245  		if spec.AbsolutePackage {
   246  			if spec.PackageName != sym.PackageName() {
   247  				return false
   248  			}
   249  		} else {
   250  			if !packageMatch(spec.PackageName, sym.PackageName(), packageMap) {
   251  				return false
   252  			}
   253  		}
   254  	}
   255  	if spec.PackageOrReceiverName != "" && !packageMatch(spec.PackageOrReceiverName, sym.PackageName(), packageMap) && spec.PackageOrReceiverName != recv {
   256  		return false
   257  	}
   258  	return true
   259  }
   260  
   261  func packageMatch(specPkg, symPkg string, packageMap map[string][]string) bool {
   262  	for _, pkg := range packageMap[specPkg] {
   263  		if partialPackageMatch(pkg, symPkg) {
   264  			return true
   265  		}
   266  	}
   267  	return partialPackageMatch(specPkg, symPkg)
   268  }
   269  
   270  // Find will search all functions in the target program and filter them via the
   271  // regex location spec. Only functions matching the regex will be returned.
   272  func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
   273  	if scope == nil {
   274  		//TODO(aarzilli): this needs only the list of function we should make it work
   275  		return nil, "", fmt.Errorf("could not determine location (scope is nil)")
   276  	}
   277  	funcs := scope.BinInfo.Functions
   278  	matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
   279  	if err != nil {
   280  		return nil, "", err
   281  	}
   282  	r := make([]api.Location, 0, len(matches))
   283  	for i := range matches {
   284  		addrs, _ := proc.FindFunctionLocation(t, matches[i], 0)
   285  		if len(addrs) > 0 {
   286  			r = append(r, addressesToLocation(addrs))
   287  		}
   288  	}
   289  	return r, "", nil
   290  }
   291  
   292  // Find returns the locations specified via the address location spec.
   293  func (loc *AddrLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
   294  	if scope == nil {
   295  		addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64)
   296  		if err != nil {
   297  			return nil, "", fmt.Errorf("could not determine current location (scope is nil)")
   298  		}
   299  		return []api.Location{{PC: uint64(addr)}}, "", nil
   300  	}
   301  
   302  	v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{FollowPointers: true})
   303  	if err != nil {
   304  		return nil, "", err
   305  	}
   306  	if v.Unreadable != nil {
   307  		return nil, "", v.Unreadable
   308  	}
   309  	switch v.Kind {
   310  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   311  		addr, _ := constant.Uint64Val(v.Value)
   312  		return []api.Location{{PC: addr}}, "", nil
   313  	case reflect.Func:
   314  		fn := scope.BinInfo.PCToFunc(uint64(v.Base))
   315  		pc, err := proc.FirstPCAfterPrologue(t, fn, false)
   316  		if err != nil {
   317  			return nil, "", err
   318  		}
   319  		return []api.Location{{PC: pc}}, v.Name, nil
   320  	default:
   321  		return nil, "", fmt.Errorf("wrong expression kind: %v", v.Kind)
   322  	}
   323  }
   324  
   325  // FileMatch is true if the path matches the location spec.
   326  func (loc *NormalLocationSpec) FileMatch(path string) bool {
   327  	return partialPathMatch(loc.Base, path)
   328  }
   329  
   330  func tryMatchRelativePathByProc(expr, debugname, file string) bool {
   331  	return len(expr) > 0 && expr[0] == '.' && file == path.Join(path.Dir(debugname), expr)
   332  }
   333  
   334  func partialPathMatch(expr, path string) bool {
   335  	if runtime.GOOS == "windows" {
   336  		// Accept `expr` which is case-insensitive and slash-insensitive match to `path`
   337  		expr = strings.ToLower(filepath.ToSlash(expr))
   338  		path = strings.ToLower(filepath.ToSlash(path))
   339  	}
   340  	return partialPackageMatch(expr, path)
   341  }
   342  
   343  func partialPackageMatch(expr, path string) bool {
   344  	if len(expr) < len(path)-1 {
   345  		return strings.HasSuffix(path, expr) && (path[len(path)-len(expr)-1] == '/')
   346  	}
   347  	return expr == path
   348  }
   349  
   350  // AmbiguousLocationError is returned when the location spec
   351  // should only return one location but returns multiple instead.
   352  type AmbiguousLocationError struct {
   353  	Location           string
   354  	CandidatesString   []string
   355  	CandidatesLocation []api.Location
   356  }
   357  
   358  func (ale AmbiguousLocationError) Error() string {
   359  	var candidates []string
   360  	if ale.CandidatesLocation != nil {
   361  		for i := range ale.CandidatesLocation {
   362  			candidates = append(candidates, ale.CandidatesLocation[i].Function.Name())
   363  		}
   364  
   365  	} else {
   366  		candidates = ale.CandidatesString
   367  	}
   368  	return fmt.Sprintf("Location %q ambiguous: %s…", ale.Location, strings.Join(candidates, ", "))
   369  }
   370  
   371  // Find will return a list of locations that match the given location spec.
   372  // This matches each other location spec that does not already have its own spec
   373  // implemented (such as regex, or addr).
   374  func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, string, error) {
   375  	limit := maxFindLocationCandidates
   376  	var candidateFiles []string
   377  	for _, sourceFile := range t.BinInfo().Sources {
   378  		substFile := sourceFile
   379  		if len(substitutePathRules) > 0 {
   380  			substFile = SubstitutePath(sourceFile, substitutePathRules)
   381  		}
   382  		if loc.FileMatch(substFile) || (len(processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, processArgs[0], substFile)) {
   383  			candidateFiles = append(candidateFiles, sourceFile)
   384  			if len(candidateFiles) >= limit {
   385  				break
   386  			}
   387  		}
   388  	}
   389  
   390  	limit -= len(candidateFiles)
   391  
   392  	var candidateFuncs []string
   393  	if loc.FuncBase != nil && limit > 0 {
   394  		candidateFuncs = loc.findFuncCandidates(t.BinInfo(), limit)
   395  	}
   396  
   397  	if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
   398  		if scope == nil {
   399  			return nil, "", fmt.Errorf("location %q not found", locStr)
   400  		}
   401  		// if no result was found this locations string could be an
   402  		// expression that the user forgot to prefix with '*', try treating it as
   403  		// such.
   404  		addrSpec := &AddrLocationSpec{AddrExpr: locStr}
   405  		locs, subst, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines, nil)
   406  		if err != nil {
   407  			return nil, "", fmt.Errorf("location %q not found", locStr)
   408  		}
   409  		return locs, subst, nil
   410  	} else if matching > 1 {
   411  		return nil, "", AmbiguousLocationError{Location: locStr, CandidatesString: append(candidateFiles, candidateFuncs...)}
   412  	}
   413  
   414  	// len(candidateFiles) + len(candidateFuncs) == 1
   415  	var addrs []uint64
   416  	var err error
   417  	if len(candidateFiles) == 1 {
   418  		if loc.LineOffset < 0 {
   419  			//lint:ignore ST1005 backwards compatibility
   420  			return nil, "", fmt.Errorf("Malformed breakpoint location, no line offset specified")
   421  		}
   422  		addrs, err = proc.FindFileLocation(t, candidateFiles[0], loc.LineOffset)
   423  		if includeNonExecutableLines {
   424  			if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
   425  				return []api.Location{{File: candidateFiles[0], Line: loc.LineOffset}}, "", nil
   426  			}
   427  		}
   428  	} else { // len(candidateFuncs) == 1
   429  		addrs, err = proc.FindFunctionLocation(t, candidateFuncs[0], loc.LineOffset)
   430  	}
   431  
   432  	if err != nil {
   433  		return nil, "", err
   434  	}
   435  	return []api.Location{addressesToLocation(addrs)}, "", nil
   436  }
   437  
   438  func (loc *NormalLocationSpec) findFuncCandidates(bi *proc.BinaryInfo, limit int) []string {
   439  	candidateFuncs := map[string]struct{}{}
   440  	// See if it matches generic functions first
   441  	for fname := range bi.LookupGenericFunc() {
   442  		if len(candidateFuncs) >= limit {
   443  			break
   444  		}
   445  		if !loc.FuncBase.Match(&proc.Function{Name: fname}, bi.PackageMap) {
   446  			continue
   447  		}
   448  		if loc.Base == fname {
   449  			return []string{fname}
   450  		}
   451  		candidateFuncs[fname] = struct{}{}
   452  	}
   453  	for _, fns := range bi.LookupFunc() {
   454  		f := fns[0]
   455  		if len(candidateFuncs) >= limit {
   456  			break
   457  		}
   458  		if !loc.FuncBase.Match(f, bi.PackageMap) {
   459  			continue
   460  		}
   461  		if loc.Base == f.Name {
   462  			// if an exact match for the function name is found use it
   463  			return []string{f.Name}
   464  		}
   465  		// If f is an instantiation of a generic function see if we should add its generic version instead.
   466  		if gn := f.NameWithoutTypeParams(); gn != "" {
   467  			if _, alreadyAdded := candidateFuncs[gn]; !alreadyAdded {
   468  				candidateFuncs[f.Name] = struct{}{}
   469  			}
   470  		} else {
   471  			candidateFuncs[f.Name] = struct{}{}
   472  		}
   473  	}
   474  	// convert candidateFuncs map into an array of its keys
   475  	r := make([]string, 0, len(candidateFuncs))
   476  	for s := range candidateFuncs {
   477  		r = append(r, s)
   478  	}
   479  	return r
   480  }
   481  
   482  // isAbs returns true if path looks like an absolute path.
   483  func isAbs(path string) bool {
   484  	// Unix-like absolute path
   485  	if strings.HasPrefix(path, "/") {
   486  		return true
   487  	}
   488  	return windowsAbsPath(path)
   489  }
   490  
   491  func windowsAbsPath(path string) bool {
   492  	// Windows UNC absolute path
   493  	if strings.HasPrefix(path, `\\`) {
   494  		return true
   495  	}
   496  	// DOS absolute paths
   497  	if len(path) < 3 || path[1] != ':' {
   498  		return false
   499  	}
   500  	return path[2] == '/' || path[2] == '\\'
   501  }
   502  
   503  func hasPathSeparatorSuffix(path string) bool {
   504  	return strings.HasSuffix(path, "/") || strings.HasSuffix(path, "\\")
   505  }
   506  
   507  func hasPathSeparatorPrefix(path string) bool {
   508  	return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\")
   509  }
   510  
   511  func pickSeparator(to string) string {
   512  	var sep byte
   513  	for i := range to {
   514  		if to[i] == '/' || to[i] == '\\' {
   515  			if sep == 0 {
   516  				sep = to[i]
   517  			} else if sep != to[i] {
   518  				return ""
   519  			}
   520  		}
   521  	}
   522  	return string(sep)
   523  }
   524  
   525  func joinPath(to, rest string) string {
   526  	sep := pickSeparator(to)
   527  
   528  	switch sep {
   529  	case "/":
   530  		rest = strings.ReplaceAll(rest, "\\", sep)
   531  	case "\\":
   532  		rest = strings.ReplaceAll(rest, "/", sep)
   533  	default:
   534  		sep = "/"
   535  	}
   536  
   537  	toEndsWithSlash := hasPathSeparatorSuffix(to)
   538  	restStartsWithSlash := hasPathSeparatorPrefix(rest)
   539  
   540  	switch {
   541  	case toEndsWithSlash && restStartsWithSlash:
   542  		return to[:len(to)-1] + rest
   543  	case toEndsWithSlash && !restStartsWithSlash:
   544  		return to + rest
   545  	case !toEndsWithSlash && restStartsWithSlash:
   546  		return to + rest
   547  	case !toEndsWithSlash && !restStartsWithSlash:
   548  		fallthrough
   549  	default:
   550  		return to + sep + rest
   551  	}
   552  }
   553  
   554  // SubstitutePath applies the specified path substitution rules to path.
   555  func SubstitutePath(path string, rules [][2]string) string {
   556  	// Look for evidence that we are dealing with windows somewhere, if we are use case-insensitive matching
   557  	caseInsensitive := windowsAbsPath(path)
   558  	if !caseInsensitive {
   559  		for i := range rules {
   560  			if windowsAbsPath(rules[i][0]) || windowsAbsPath(rules[i][1]) {
   561  				caseInsensitive = true
   562  				break
   563  			}
   564  		}
   565  	}
   566  	for _, r := range rules {
   567  		from, to := r[0], r[1]
   568  
   569  		// if we have an exact match, use it directly.
   570  		if path == from {
   571  			return to
   572  		}
   573  
   574  		match := false
   575  		var rest string
   576  		if from == "" {
   577  			match = !isAbs(path)
   578  			rest = path
   579  		} else {
   580  			if caseInsensitive {
   581  				match = strings.HasPrefix(strings.ToLower(path), strings.ToLower(from))
   582  				if match {
   583  					path = strings.ToLower(path)
   584  					from = strings.ToLower(from)
   585  				}
   586  			} else {
   587  				match = strings.HasPrefix(path, from)
   588  			}
   589  			if match {
   590  				// make sure the match ends on something that looks like a path separator boundary
   591  				rest = path[len(from):]
   592  				match = hasPathSeparatorSuffix(from) || hasPathSeparatorPrefix(rest)
   593  			}
   594  		}
   595  
   596  		if match {
   597  			if to == "" {
   598  				// make sure we return a relative path, regardless of whether 'from' consumed a final / or not
   599  				if hasPathSeparatorPrefix(rest) {
   600  					return rest[1:]
   601  				}
   602  				return rest
   603  			}
   604  
   605  			return joinPath(to, rest)
   606  		}
   607  	}
   608  	return path
   609  }
   610  
   611  func addressesToLocation(addrs []uint64) api.Location {
   612  	if len(addrs) == 0 {
   613  		return api.Location{}
   614  	}
   615  	return api.Location{PC: addrs[0], PCs: addrs}
   616  }
   617  
   618  // Find returns the location after adding the offset amount to the current line number.
   619  func (loc *OffsetLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
   620  	if scope == nil {
   621  		return nil, "", fmt.Errorf("could not determine current location (scope is nil)")
   622  	}
   623  	file, line, fn := scope.BinInfo.PCToLine(scope.PC)
   624  	if loc.Offset == 0 {
   625  		subst := ""
   626  		if fn != nil {
   627  			subst = fmt.Sprintf("%s:%d", file, line)
   628  		}
   629  		return []api.Location{{PC: scope.PC}}, subst, nil
   630  	}
   631  	if fn == nil {
   632  		return nil, "", fmt.Errorf("could not determine current location")
   633  	}
   634  	subst := fmt.Sprintf("%s:%d", file, line+loc.Offset)
   635  	addrs, err := proc.FindFileLocation(t, file, line+loc.Offset)
   636  	if includeNonExecutableLines {
   637  		if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
   638  			return []api.Location{{File: file, Line: line + loc.Offset}}, subst, nil
   639  		}
   640  	}
   641  	return []api.Location{addressesToLocation(addrs)}, subst, err
   642  }
   643  
   644  // Find will return the location at the given line in the current file.
   645  func (loc *LineLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
   646  	if scope == nil {
   647  		return nil, "", fmt.Errorf("could not determine current location (scope is nil)")
   648  	}
   649  	file, _, fn := scope.BinInfo.PCToLine(scope.PC)
   650  	if fn == nil {
   651  		return nil, "", fmt.Errorf("could not determine current location")
   652  	}
   653  	subst := fmt.Sprintf("%s:%d", file, loc.Line)
   654  	addrs, err := proc.FindFileLocation(t, file, loc.Line)
   655  	if includeNonExecutableLines {
   656  		if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
   657  			return []api.Location{{File: file, Line: loc.Line}}, subst, nil
   658  		}
   659  	}
   660  	return []api.Location{addressesToLocation(addrs)}, subst, err
   661  }
   662  
   663  func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) {
   664  	regex, err := regexp.Compile(filter)
   665  	if err != nil {
   666  		return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
   667  	}
   668  
   669  	funcs := []string{}
   670  	for _, f := range allFuncs {
   671  		if regex.MatchString(f.Name) {
   672  			funcs = append(funcs, f.Name)
   673  		}
   674  	}
   675  	return funcs, nil
   676  }