github.com/neilgarb/delve@v1.9.2-nobreaks/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, 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 \"%s\" 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 \"%s\" 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, error) { 273 funcs := scope.BinInfo.Functions 274 matches, err := regexFilterFuncs(loc.FuncRegex, funcs) 275 if err != nil { 276 return nil, err 277 } 278 r := make([]api.Location, 0, len(matches)) 279 for i := range matches { 280 addrs, _ := proc.FindFunctionLocation(t, matches[i], 0) 281 if len(addrs) > 0 { 282 r = append(r, addressesToLocation(addrs)) 283 } 284 } 285 return r, nil 286 } 287 288 // Find returns the locations specified via the address location spec. 289 func (loc *AddrLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { 290 if scope == nil { 291 addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64) 292 if err != nil { 293 return nil, fmt.Errorf("could not determine current location (scope is nil)") 294 } 295 return []api.Location{{PC: uint64(addr)}}, nil 296 } 297 298 v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{FollowPointers: true}) 299 if err != nil { 300 return nil, err 301 } 302 if v.Unreadable != nil { 303 return nil, v.Unreadable 304 } 305 switch v.Kind { 306 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 307 addr, _ := constant.Uint64Val(v.Value) 308 return []api.Location{{PC: addr}}, nil 309 case reflect.Func: 310 fn := scope.BinInfo.PCToFunc(uint64(v.Base)) 311 pc, err := proc.FirstPCAfterPrologue(t, fn, false) 312 if err != nil { 313 return nil, err 314 } 315 return []api.Location{{PC: pc}}, nil 316 default: 317 return nil, fmt.Errorf("wrong expression kind: %v", v.Kind) 318 } 319 } 320 321 // FileMatch is true if the path matches the location spec. 322 func (loc *NormalLocationSpec) FileMatch(path string) bool { 323 return partialPathMatch(loc.Base, path) 324 } 325 326 func tryMatchRelativePathByProc(expr, debugname, file string) bool { 327 return len(expr) > 0 && expr[0] == '.' && file == path.Join(path.Dir(debugname), expr) 328 } 329 330 func partialPathMatch(expr, path string) bool { 331 if runtime.GOOS == "windows" { 332 // Accept `expr` which is case-insensitive and slash-insensitive match to `path` 333 expr = strings.ToLower(filepath.ToSlash(expr)) 334 path = strings.ToLower(filepath.ToSlash(path)) 335 } 336 return partialPackageMatch(expr, path) 337 } 338 339 func partialPackageMatch(expr, path string) bool { 340 if len(expr) < len(path)-1 { 341 return strings.HasSuffix(path, expr) && (path[len(path)-len(expr)-1] == '/') 342 } 343 return expr == path 344 } 345 346 // AmbiguousLocationError is returned when the location spec 347 // should only return one location but returns multiple instead. 348 type AmbiguousLocationError struct { 349 Location string 350 CandidatesString []string 351 CandidatesLocation []api.Location 352 } 353 354 func (ale AmbiguousLocationError) Error() string { 355 var candidates []string 356 if ale.CandidatesLocation != nil { 357 for i := range ale.CandidatesLocation { 358 candidates = append(candidates, ale.CandidatesLocation[i].Function.Name()) 359 } 360 361 } else { 362 candidates = ale.CandidatesString 363 } 364 return fmt.Sprintf("Location \"%s\" ambiguous: %s…", ale.Location, strings.Join(candidates, ", ")) 365 } 366 367 // Find will return a list of locations that match the given location spec. 368 // This matches each other location spec that does not already have its own spec 369 // implemented (such as regex, or addr). 370 func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) { 371 limit := maxFindLocationCandidates 372 var candidateFiles []string 373 for _, sourceFile := range scope.BinInfo.Sources { 374 substFile := sourceFile 375 if len(substitutePathRules) > 0 { 376 substFile = SubstitutePath(sourceFile, substitutePathRules) 377 } 378 if loc.FileMatch(substFile) || (len(processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, processArgs[0], substFile)) { 379 candidateFiles = append(candidateFiles, sourceFile) 380 if len(candidateFiles) >= limit { 381 break 382 } 383 } 384 } 385 386 limit -= len(candidateFiles) 387 388 var candidateFuncs []string 389 if loc.FuncBase != nil && limit > 0 { 390 candidateFuncs = loc.findFuncCandidates(scope, limit) 391 } 392 393 if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 { 394 // if no result was found this locations string could be an 395 // expression that the user forgot to prefix with '*', try treating it as 396 // such. 397 addrSpec := &AddrLocationSpec{AddrExpr: locStr} 398 locs, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines, nil) 399 if err != nil { 400 return nil, fmt.Errorf("location \"%s\" not found", locStr) 401 } 402 return locs, nil 403 } else if matching > 1 { 404 return nil, AmbiguousLocationError{Location: locStr, CandidatesString: append(candidateFiles, candidateFuncs...)} 405 } 406 407 // len(candidateFiles) + len(candidateFuncs) == 1 408 var addrs []uint64 409 var err error 410 if len(candidateFiles) == 1 { 411 if loc.LineOffset < 0 { 412 //lint:ignore ST1005 backwards compatibility 413 return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified") 414 } 415 addrs, err = proc.FindFileLocation(t, candidateFiles[0], loc.LineOffset) 416 if includeNonExecutableLines { 417 if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine { 418 return []api.Location{{File: candidateFiles[0], Line: loc.LineOffset}}, nil 419 } 420 } 421 } else { // len(candidateFuncs) == 1 422 addrs, err = proc.FindFunctionLocation(t, candidateFuncs[0], loc.LineOffset) 423 } 424 425 if err != nil { 426 return nil, err 427 } 428 return []api.Location{addressesToLocation(addrs)}, nil 429 } 430 431 func (loc *NormalLocationSpec) findFuncCandidates(scope *proc.EvalScope, limit int) []string { 432 candidateFuncs := map[string]struct{}{} 433 // See if it matches generic functions first 434 for fname := range scope.BinInfo.LookupGenericFunc() { 435 if len(candidateFuncs) >= limit { 436 break 437 } 438 if !loc.FuncBase.Match(&proc.Function{Name: fname}, scope.BinInfo.PackageMap) { 439 continue 440 } 441 if loc.Base == fname { 442 return []string{fname} 443 } 444 candidateFuncs[fname] = struct{}{} 445 } 446 for _, f := range scope.BinInfo.LookupFunc { 447 if len(candidateFuncs) >= limit { 448 break 449 } 450 if !loc.FuncBase.Match(f, scope.BinInfo.PackageMap) { 451 continue 452 } 453 if loc.Base == f.Name { 454 // if an exact match for the function name is found use it 455 return []string{f.Name} 456 } 457 // If f is an instantiation of a generic function see if we should add its generic version instead. 458 if gn := f.NameWithoutTypeParams(); gn != "" { 459 if _, alreadyAdded := candidateFuncs[gn]; !alreadyAdded { 460 candidateFuncs[f.Name] = struct{}{} 461 } 462 } else { 463 candidateFuncs[f.Name] = struct{}{} 464 } 465 } 466 // convert candidateFuncs map into an array of its keys 467 r := make([]string, 0, len(candidateFuncs)) 468 for s := range candidateFuncs { 469 r = append(r, s) 470 } 471 return r 472 } 473 474 func crossPlatformPath(path string) string { 475 if runtime.GOOS == "windows" { 476 return strings.ToLower(path) 477 } 478 return path 479 } 480 481 // SubstitutePath applies the specified path substitution rules to path. 482 func SubstitutePath(path string, rules [][2]string) string { 483 path = crossPlatformPath(path) 484 // On windows paths returned from headless server are as c:/dir/dir 485 // though os.PathSeparator is '\\' 486 487 separator := "/" //make it default 488 if strings.Contains(path, "\\") { //dependent on the path 489 separator = "\\" 490 } 491 for _, r := range rules { 492 from := crossPlatformPath(r[0]) 493 to := r[1] 494 495 if !strings.HasSuffix(from, separator) { 496 from = from + separator 497 } 498 if !strings.HasSuffix(to, separator) { 499 to = to + separator 500 } 501 if strings.HasPrefix(path, from) { 502 return strings.Replace(path, from, to, 1) 503 } 504 } 505 return path 506 } 507 508 func addressesToLocation(addrs []uint64) api.Location { 509 if len(addrs) <= 0 { 510 return api.Location{} 511 } 512 return api.Location{PC: addrs[0], PCs: addrs} 513 } 514 515 // Find returns the location after adding the offset amount to the current line number. 516 func (loc *OffsetLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { 517 if scope == nil { 518 return nil, fmt.Errorf("could not determine current location (scope is nil)") 519 } 520 if loc.Offset == 0 { 521 return []api.Location{{PC: scope.PC}}, nil 522 } 523 file, line, fn := scope.BinInfo.PCToLine(scope.PC) 524 if fn == nil { 525 return nil, fmt.Errorf("could not determine current location") 526 } 527 addrs, err := proc.FindFileLocation(t, file, line+loc.Offset) 528 if includeNonExecutableLines { 529 if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine { 530 return []api.Location{{File: file, Line: line + loc.Offset}}, nil 531 } 532 } 533 return []api.Location{addressesToLocation(addrs)}, err 534 } 535 536 // Find will return the location at the given line in the current file. 537 func (loc *LineLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { 538 if scope == nil { 539 return nil, fmt.Errorf("could not determine current location (scope is nil)") 540 } 541 file, _, fn := scope.BinInfo.PCToLine(scope.PC) 542 if fn == nil { 543 return nil, fmt.Errorf("could not determine current location") 544 } 545 addrs, err := proc.FindFileLocation(t, file, loc.Line) 546 if includeNonExecutableLines { 547 if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine { 548 return []api.Location{{File: file, Line: loc.Line}}, nil 549 } 550 } 551 return []api.Location{addressesToLocation(addrs)}, err 552 } 553 554 func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) { 555 regex, err := regexp.Compile(filter) 556 if err != nil { 557 return nil, fmt.Errorf("invalid filter argument: %s", err.Error()) 558 } 559 560 funcs := []string{} 561 for _, f := range allFuncs { 562 if regex.MatchString(f.Name) { 563 funcs = append(funcs, f.Name) 564 } 565 } 566 return funcs, nil 567 }