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 }