github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/target_exec.go (about) 1 package proc 2 3 import ( 4 "bytes" 5 "debug/dwarf" 6 "errors" 7 "fmt" 8 "go/ast" 9 "go/token" 10 "path/filepath" 11 "strings" 12 13 "golang.org/x/arch/ppc64/ppc64asm" 14 15 "github.com/go-delve/delve/pkg/astutil" 16 "github.com/go-delve/delve/pkg/dwarf/reader" 17 ) 18 19 const maxSkipAutogeneratedWrappers = 5 // maximum recursion depth for skipAutogeneratedWrappers 20 21 // ErrNoSourceForPC is returned when the given address 22 // does not correspond with a source file location. 23 type ErrNoSourceForPC struct { 24 pc uint64 25 } 26 27 func (err *ErrNoSourceForPC) Error() string { 28 return fmt.Sprintf("no source for PC %#x", err.pc) 29 } 30 31 // Next resumes the processes in the group, continuing the selected target 32 // until the next source line. 33 func (grp *TargetGroup) Next() (err error) { 34 if _, err := grp.Valid(); err != nil { 35 return err 36 } 37 if grp.HasSteppingBreakpoints() { 38 return fmt.Errorf("next while nexting") 39 } 40 41 if err = next(grp.Selected, false, false); err != nil { 42 grp.Selected.ClearSteppingBreakpoints() 43 return 44 } 45 46 return grp.Continue() 47 } 48 49 // Continue continues execution of the debugged 50 // processes. It will continue until it hits a breakpoint 51 // or is otherwise stopped. 52 func (grp *TargetGroup) Continue() error { 53 if grp.numValid() == 0 { 54 _, err := grp.targets[0].Valid() 55 return err 56 } 57 for _, dbp := range grp.targets { 58 if isvalid, _ := dbp.Valid(); !isvalid { 59 continue 60 } 61 for _, thread := range dbp.ThreadList() { 62 thread.Common().CallReturn = false 63 thread.Common().returnValues = nil 64 } 65 dbp.Breakpoints().WatchOutOfScope = nil 66 dbp.clearHardcodedBreakpoints() 67 } 68 grp.cctx.CheckAndClearManualStopRequest() 69 defer func() { 70 // Make sure we clear internal breakpoints if we simultaneously receive a 71 // manual stop request and hit a breakpoint. 72 if grp.cctx.CheckAndClearManualStopRequest() { 73 grp.finishManualStop() 74 } 75 }() 76 for { 77 if grp.cctx.CheckAndClearManualStopRequest() { 78 grp.finishManualStop() 79 return nil 80 } 81 for _, dbp := range grp.targets { 82 dbp.ClearCaches() 83 } 84 trapthread, stopReason, contOnceErr := grp.procgrp.ContinueOnce(grp.cctx) 85 var traptgt *Target 86 if trapthread != nil { 87 traptgt = grp.TargetForThread(trapthread.ThreadID()) 88 if traptgt == nil { 89 return fmt.Errorf("could not find target for thread %d", trapthread.ThreadID()) 90 } 91 } else { 92 traptgt = grp.targets[0] 93 } 94 traptgt.StopReason = stopReason 95 96 it := ValidTargets{Group: grp} 97 for it.Next() { 98 for _, thread := range it.ThreadList() { 99 if thread.Breakpoint().Breakpoint != nil { 100 thread.Breakpoint().Breakpoint.checkCondition(it.Target, thread, thread.Breakpoint()) 101 } 102 } 103 } 104 105 if contOnceErr != nil { 106 // Attempt to refresh status of current thread/current goroutine, see 107 // Issue #2078. 108 // Errors are ignored because depending on why ContinueOnce failed this 109 // might very well not work. 110 _ = grp.setCurrentThreads(traptgt, trapthread) 111 if pe, ok := contOnceErr.(ErrProcessExited); ok { 112 traptgt.exitStatus = pe.Status 113 } 114 return contOnceErr 115 } 116 if stopReason == StopLaunched { 117 it.Reset() 118 for it.Next() { 119 it.Target.ClearSteppingBreakpoints() 120 } 121 } 122 123 var callInjectionDone bool 124 var callErr error 125 var hcbpErr error 126 it.Reset() 127 for it.Next() { 128 dbp := it.Target 129 threads := dbp.ThreadList() 130 callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, threads) 131 callInjectionDone = callInjectionDone || callInjectionDoneThis 132 if callInjectionDoneThis { 133 dbp.StopReason = StopCallReturned 134 } 135 if callErrThis != nil && callErr == nil { 136 callErr = callErrThis 137 } 138 hcbpErrThis := dbp.handleHardcodedBreakpoints(trapthread, threads) 139 if hcbpErrThis != nil && hcbpErr == nil { 140 hcbpErr = hcbpErrThis 141 } 142 } 143 // callErr and hcbpErr check delayed until after pickCurrentThread, which 144 // must always happen, otherwise the debugger could be left in an 145 // inconsistent state. 146 147 it = ValidTargets{Group: grp} 148 for it.Next() { 149 var th Thread = nil 150 if it.Target == traptgt { 151 th = trapthread 152 } 153 err := pickCurrentThread(it.Target, th) 154 if err != nil { 155 return err 156 } 157 } 158 grp.pickCurrentTarget(traptgt) 159 dbp := grp.Selected 160 161 if callErr != nil { 162 return callErr 163 } 164 if hcbpErr != nil { 165 return hcbpErr 166 } 167 168 curthread := dbp.CurrentThread() 169 curbp := curthread.Breakpoint() 170 171 switch { 172 case curbp.Active && curbp.Stepping: 173 if curbp.SteppingInto { 174 // See description of proc.(*Process).next for the meaning of StepBreakpoints 175 if err := conditionErrors(grp); err != nil { 176 return err 177 } 178 if grp.GetDirection() == Backward { 179 if err := dbp.ClearSteppingBreakpoints(); err != nil { 180 return err 181 } 182 return grp.StepInstruction() 183 } 184 } else { 185 curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread) 186 if err := dbp.ClearSteppingBreakpoints(); err != nil { 187 return err 188 } 189 dbp.StopReason = StopNextFinished 190 return conditionErrors(grp) 191 } 192 case curbp.Active: 193 onNextGoroutine, err := onNextGoroutine(dbp, curthread, dbp.Breakpoints()) 194 if err != nil { 195 return err 196 } 197 if onNextGoroutine && 198 (!isTraceOrTraceReturn(curbp.Breakpoint) || grp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) { 199 err := dbp.ClearSteppingBreakpoints() 200 if err != nil { 201 return err 202 } 203 } 204 if curbp.LogicalID() == unrecoveredPanicID { 205 dbp.ClearSteppingBreakpoints() 206 } 207 if curbp.LogicalID() != hardcodedBreakpointID { 208 dbp.StopReason = StopBreakpoint 209 } 210 if curbp.Breakpoint.WatchType != 0 { 211 dbp.StopReason = StopWatchpoint 212 } 213 return conditionErrors(grp) 214 default: 215 // not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat 216 } 217 if callInjectionDone { 218 // a call injection was finished, don't let a breakpoint with a failed 219 // condition or a step breakpoint shadow this. 220 return conditionErrors(grp) 221 } 222 } 223 } 224 225 func (grp *TargetGroup) finishManualStop() { 226 for _, dbp := range grp.targets { 227 if isvalid, _ := dbp.Valid(); !isvalid { 228 continue 229 } 230 dbp.StopReason = StopManual 231 dbp.clearHardcodedBreakpoints() 232 if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { 233 dbp.ClearSteppingBreakpoints() 234 } 235 } 236 } 237 238 // setCurrentThreads switches traptgt to trapthread, then for each target in 239 // the group if its current thread exists it refreshes the current 240 // goroutine, otherwise it switches it to a randomly selected thread. 241 func (grp *TargetGroup) setCurrentThreads(traptgt *Target, trapthread Thread) error { 242 var err error 243 if traptgt != nil && trapthread != nil { 244 err = traptgt.SwitchThread(trapthread.ThreadID()) 245 } 246 for _, tgt := range grp.targets { 247 if isvalid, _ := tgt.Valid(); !isvalid { 248 continue 249 } 250 if _, ok := tgt.FindThread(tgt.currentThread.ThreadID()); ok { 251 tgt.selectedGoroutine, _ = GetG(tgt.currentThread) 252 } else { 253 threads := tgt.ThreadList() 254 if len(threads) > 0 { 255 err1 := tgt.SwitchThread(threads[0].ThreadID()) 256 if err1 != nil && err == nil { 257 err = err1 258 } 259 } 260 } 261 } 262 return err 263 } 264 265 func isTraceOrTraceReturn(bp *Breakpoint) bool { 266 if bp.Logical == nil { 267 return false 268 } 269 return bp.Logical.Tracepoint || bp.Logical.TraceReturn 270 } 271 272 func conditionErrors(grp *TargetGroup) error { 273 var condErr error 274 for _, dbp := range grp.targets { 275 if isvalid, _ := dbp.Valid(); !isvalid { 276 continue 277 } 278 for _, th := range dbp.ThreadList() { 279 if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil { 280 if condErr == nil { 281 condErr = bp.CondError 282 } else { 283 return fmt.Errorf("multiple errors evaluating conditions") 284 } 285 } 286 } 287 } 288 return condErr 289 } 290 291 // pick a new dbp.currentThread, with the following priority: 292 // 293 // - a thread with an active stepping breakpoint 294 // - a thread with an active breakpoint, prioritizing trapthread 295 // - trapthread if it is not nil 296 // - the previous current thread if it still exists 297 // - a randomly selected thread 298 func pickCurrentThread(dbp *Target, trapthread Thread) error { 299 threads := dbp.ThreadList() 300 for _, th := range threads { 301 if bp := th.Breakpoint(); bp.Active && bp.Stepping { 302 return dbp.SwitchThread(th.ThreadID()) 303 } 304 } 305 if trapthread != nil { 306 if bp := trapthread.Breakpoint(); bp.Active { 307 return dbp.SwitchThread(trapthread.ThreadID()) 308 } 309 } 310 for _, th := range threads { 311 if bp := th.Breakpoint(); bp.Active { 312 return dbp.SwitchThread(th.ThreadID()) 313 } 314 } 315 if trapthread != nil { 316 return dbp.SwitchThread(trapthread.ThreadID()) 317 } 318 if _, ok := dbp.FindThread(dbp.currentThread.ThreadID()); ok { 319 dbp.selectedGoroutine, _ = GetG(dbp.currentThread) 320 return nil 321 } 322 if len(threads) > 0 { 323 return dbp.SwitchThread(threads[0].ThreadID()) 324 } 325 return nil 326 } 327 328 // pickCurrentTarget picks a new current target, with the following property: 329 // 330 // - a target with an active stepping breakpoint 331 // - a target with StopReason == StopCallReturned 332 // - a target with an active breakpoint, prioritizing traptgt 333 // - traptgt 334 func (grp *TargetGroup) pickCurrentTarget(traptgt *Target) { 335 if len(grp.targets) == 1 { 336 grp.Selected = grp.targets[0] 337 return 338 } 339 for _, dbp := range grp.targets { 340 if isvalid, _ := dbp.Valid(); !isvalid { 341 continue 342 } 343 bp := dbp.currentThread.Breakpoint() 344 if bp.Active && bp.Stepping { 345 grp.Selected = dbp 346 return 347 } 348 } 349 for _, dbp := range grp.targets { 350 if isvalid, _ := dbp.Valid(); !isvalid { 351 continue 352 } 353 if dbp.StopReason == StopCallReturned { 354 grp.Selected = dbp 355 return 356 } 357 } 358 359 if traptgt.currentThread.Breakpoint().Active { 360 grp.Selected = traptgt 361 return 362 } 363 for _, dbp := range grp.targets { 364 if isvalid, _ := dbp.Valid(); !isvalid { 365 continue 366 } 367 bp := dbp.currentThread.Breakpoint() 368 if bp.Active { 369 grp.Selected = dbp 370 return 371 } 372 } 373 grp.Selected = traptgt 374 } 375 376 func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmInstruction, error) { 377 regs, err := thread.Registers() 378 if err != nil { 379 return nil, err 380 } 381 pc := regs.PC() + uint64(off) 382 return disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()), true) 383 } 384 385 // stepInstructionOut repeatedly calls StepInstruction until the current 386 // function is neither fnname1 or fnname2. 387 // This function is used to step out of runtime.Breakpoint as well as 388 // runtime.debugCallV1. 389 func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string) error { 390 defer dbp.ClearCaches() 391 for { 392 if err := curthread.StepInstruction(); err != nil { 393 return err 394 } 395 loc, err := curthread.Location() 396 var locFnName string 397 if loc.Fn != nil && !loc.Fn.cu.image.Stripped() { 398 locFnName = loc.Fn.Name 399 // Calls to runtime.Breakpoint are inlined in some versions of Go when 400 // inlining is enabled. Here we attempt to resolve any inlining. 401 dwarfTree, _ := loc.Fn.cu.image.getDwarfTree(loc.Fn.offset) 402 if dwarfTree != nil { 403 inlstack := reader.InlineStack(dwarfTree, loc.PC) 404 if len(inlstack) > 0 { 405 if locFnName2, ok := inlstack[0].Val(dwarf.AttrName).(string); ok { 406 locFnName = locFnName2 407 } 408 } 409 } 410 } 411 if err != nil || loc.Fn == nil || (locFnName != fnname1 && locFnName != fnname2) { 412 g, _ := GetG(curthread) 413 selg := dbp.SelectedGoroutine() 414 if g != nil && selg != nil && g.ID == selg.ID { 415 selg.CurrentLoc = *loc 416 } 417 return curthread.SetCurrentBreakpoint(true) 418 } 419 } 420 } 421 422 // Step resumes the processes in the group, continuing the selected target 423 // until the next source line. Will step into functions. 424 func (grp *TargetGroup) Step() (err error) { 425 if _, err := grp.Valid(); err != nil { 426 return err 427 } 428 if grp.HasSteppingBreakpoints() { 429 return fmt.Errorf("next while nexting") 430 } 431 432 if err = next(grp.Selected, true, false); err != nil { 433 _ = grp.Selected.ClearSteppingBreakpoints() 434 return err 435 } 436 437 if bpstate := grp.Selected.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && grp.GetDirection() == Backward { 438 grp.Selected.ClearSteppingBreakpoints() 439 return grp.StepInstruction() 440 } 441 442 return grp.Continue() 443 } 444 445 // sameGoroutineCondition returns an expression that evaluates to true when 446 // the current goroutine is g. 447 func sameGoroutineCondition(bi *BinaryInfo, g *G, threadID int) ast.Expr { 448 if g == nil { 449 if len(bi.Images[0].compileUnits) == 0 { 450 // It's unclear what the right behavior is here. We are probably 451 // debugging a process without debug info, this means we can't properly 452 // create a same goroutine condition (we don't have a description for the 453 // runtime.g type). If we don't set the condition then 'next' (and step, 454 // stepout) will work for single-threaded programs (in limited 455 // circumstances) but fail in presence of any concurrency. 456 // If we set a thread ID condition even single threaded programs can fail 457 // due to goroutine migration, but sometimes it will work even with 458 // concurrency. 459 return nil 460 } 461 return astutil.Eql(astutil.PkgVar("runtime", "threadid"), astutil.Int(int64(threadID))) 462 } 463 return astutil.Eql(astutil.Sel(astutil.PkgVar("runtime", "curg"), "goid"), astutil.Int(int64(g.ID))) 464 } 465 466 func frameoffCondition(frame *Stackframe) ast.Expr { 467 return astutil.Eql(astutil.PkgVar("runtime", "frameoff"), astutil.Int(frame.FrameOffset())) 468 } 469 470 // StepOut resumes the processes in the group, continuing the selected target 471 // until until the current goroutine exits the function currently being 472 // executed or a deferred function is executed 473 func (grp *TargetGroup) StepOut() error { 474 backward := grp.GetDirection() == Backward 475 if _, err := grp.Valid(); err != nil { 476 return err 477 } 478 if grp.HasSteppingBreakpoints() { 479 return fmt.Errorf("next while nexting") 480 } 481 482 dbp := grp.Selected 483 selg := dbp.SelectedGoroutine() 484 curthread := dbp.CurrentThread() 485 486 topframe, retframe, err := topframe(dbp, selg, curthread) 487 if err != nil { 488 return err 489 } 490 491 success := false 492 defer func() { 493 if !success { 494 dbp.ClearSteppingBreakpoints() 495 } 496 }() 497 498 if topframe.Inlined { 499 if err := next(dbp, false, true); err != nil { 500 return err 501 } 502 503 success = true 504 return grp.Continue() 505 } 506 507 sameGCond := sameGoroutineCondition(dbp.BinInfo(), selg, curthread.ThreadID()) 508 509 if backward { 510 if err := stepOutReverse(dbp, topframe, retframe, sameGCond); err != nil { 511 return err 512 } 513 514 success = true 515 return grp.Continue() 516 } 517 518 deferpc, err := setDeferBreakpoint(dbp, nil, topframe, sameGCond, false) 519 if err != nil { 520 return err 521 } 522 523 if topframe.Ret == 0 && deferpc == 0 { 524 return errors.New("nothing to stepout to") 525 } 526 527 if topframe.Ret != 0 { 528 topframe, retframe := skipAutogeneratedWrappersOut(grp.Selected, selg, curthread, &topframe, &retframe) 529 retFrameCond := astutil.And(sameGCond, frameoffCondition(retframe)) 530 bp, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, retframe.Current.PC, NextBreakpoint, retFrameCond)) 531 if err != nil { 532 return err 533 } 534 if bp != nil { 535 configureReturnBreakpoint(dbp.BinInfo(), bp, topframe, retFrameCond) 536 } 537 } 538 539 if bp := curthread.Breakpoint(); bp.Breakpoint == nil { 540 curthread.SetCurrentBreakpoint(false) 541 } 542 543 success = true 544 return grp.Continue() 545 } 546 547 // StepInstruction will continue the current thread for exactly 548 // one instruction. This method affects only the thread 549 // associated with the selected goroutine. All other 550 // threads will remain stopped. 551 func (grp *TargetGroup) StepInstruction() (err error) { 552 dbp := grp.Selected 553 thread := dbp.CurrentThread() 554 g := dbp.SelectedGoroutine() 555 if g != nil { 556 if g.Thread == nil { 557 // Step called on parked goroutine 558 if _, err := dbp.SetBreakpoint(0, g.PC, NextBreakpoint, 559 sameGoroutineCondition(dbp.BinInfo(), dbp.SelectedGoroutine(), thread.ThreadID())); err != nil { 560 return err 561 } 562 return grp.Continue() 563 } 564 thread = g.Thread 565 } 566 dbp.ClearCaches() 567 if ok, err := dbp.Valid(); !ok { 568 return err 569 } 570 err = thread.StepInstruction() 571 if err != nil { 572 return err 573 } 574 thread.Breakpoint().Clear() 575 err = thread.SetCurrentBreakpoint(false) 576 if err != nil { 577 return err 578 } 579 if tg, _ := GetG(thread); tg != nil { 580 dbp.selectedGoroutine = tg 581 } 582 dbp.StopReason = StopNextFinished 583 return nil 584 } 585 586 // Set breakpoints at every line, and the return address. Also look for 587 // a deferred function and set a breakpoint there too. 588 // If stepInto is true it will also set breakpoints inside all 589 // functions called on the current source line, for non-absolute CALLs 590 // a breakpoint of kind StepBreakpoint is set on the CALL instruction, 591 // Continue will take care of setting a breakpoint to the destination 592 // once the CALL is reached. 593 // 594 // Regardless of stepInto the following breakpoints will be set: 595 // - a breakpoint on the first deferred function with NextDeferBreakpoint 596 // kind, the list of all the addresses to deferreturn calls in this function 597 // and condition checking that we remain on the same goroutine 598 // - a breakpoint on each line of the function, with a condition checking 599 // that we stay on the same stack frame and goroutine. 600 // - a breakpoint on the return address of the function, with a condition 601 // checking that we move to the previous stack frame and stay on the same 602 // goroutine. 603 // 604 // The breakpoint on the return address is *not* set if the current frame is 605 // an inlined call. For inlined calls topframe.Current.Fn is the function 606 // where the inlining happened and the second set of breakpoints will also 607 // cover the "return address". 608 // 609 // If inlinedStepOut is true this function implements the StepOut operation 610 // for an inlined function call. Everything works the same as normal except 611 // when removing instructions belonging to inlined calls we also remove all 612 // instructions belonging to the current inlined call. 613 func next(dbp *Target, stepInto, inlinedStepOut bool) error { 614 backward := dbp.recman.GetDirection() == Backward 615 selg := dbp.SelectedGoroutine() 616 curthread := dbp.CurrentThread() 617 topframe, retframe, err := topframe(dbp, selg, curthread) 618 if err != nil { 619 return err 620 } 621 622 if topframe.Current.Fn == nil { 623 return &ErrNoSourceForPC{topframe.Current.PC} 624 } 625 626 if backward && retframe.Current.Fn == nil { 627 return &ErrNoSourceForPC{retframe.Current.PC} 628 } 629 630 // sanity check 631 if inlinedStepOut && !topframe.Inlined { 632 panic("next called with inlinedStepOut but topframe was not inlined") 633 } 634 635 success := false 636 defer func() { 637 if !success { 638 dbp.ClearSteppingBreakpoints() 639 } 640 }() 641 642 ext := filepath.Ext(topframe.Current.File) 643 csource := ext != ".go" && ext != ".s" 644 var regs Registers 645 if selg != nil && selg.Thread != nil { 646 regs, err = selg.Thread.Registers() 647 if err != nil { 648 return err 649 } 650 } 651 652 sameGCond := sameGoroutineCondition(dbp.BinInfo(), selg, curthread.ThreadID()) 653 654 var firstPCAfterPrologue uint64 655 656 if backward { 657 firstPCAfterPrologue, err = FirstPCAfterPrologue(dbp, topframe.Current.Fn, false) 658 if err != nil { 659 return err 660 } 661 if firstPCAfterPrologue == topframe.Current.PC { 662 // We don't want to step into the prologue so we just execute a reverse step out instead 663 if err := stepOutReverse(dbp, topframe, retframe, sameGCond); err != nil { 664 return err 665 } 666 667 success = true 668 return nil 669 } 670 671 topframe.Ret, err = findCallInstrForRet(dbp, dbp.Memory(), topframe.Ret, retframe.Current.Fn) 672 if err != nil { 673 return err 674 } 675 } 676 677 text, err := disassemble(dbp.Memory(), regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false) 678 if err != nil && stepInto { 679 return err 680 } 681 682 sameFrameCond := astutil.And(sameGCond, frameoffCondition(&topframe)) 683 684 if stepInto && !backward { 685 err := setStepIntoBreakpoints(dbp, topframe.Current.Fn, text, topframe, sameGCond) 686 if err != nil { 687 return err 688 } 689 } 690 691 if !backward && !topframe.Current.Fn.cu.image.Stripped() { 692 _, err = setDeferBreakpoint(dbp, text, topframe, sameGCond, stepInto) 693 if err != nil { 694 return err 695 } 696 } 697 698 // Add breakpoints on all the lines in the current function 699 pcs, err := topframe.Current.Fn.AllPCs(topframe.Current.File, topframe.Current.Line) 700 if err != nil { 701 return err 702 } 703 704 if backward { 705 // Ensure that pcs contains firstPCAfterPrologue when reverse stepping. 706 found := false 707 for _, pc := range pcs { 708 if pc == firstPCAfterPrologue { 709 found = true 710 break 711 } 712 } 713 if !found { 714 pcs = append(pcs, firstPCAfterPrologue) 715 } 716 } 717 718 if !stepInto { 719 // Removing any PC range belonging to an inlined call 720 frame := topframe 721 if inlinedStepOut { 722 frame = retframe 723 } 724 pcs, err = removeInlinedCalls(pcs, frame) 725 if err != nil { 726 return err 727 } 728 } 729 730 if !csource { 731 var covered bool 732 for i := range pcs { 733 if topframe.Current.Fn.Entry <= pcs[i] && pcs[i] < topframe.Current.Fn.End { 734 covered = true 735 break 736 } 737 } 738 739 if !covered { 740 fn := dbp.BinInfo().PCToFunc(topframe.Ret) 741 if selg != nil && fn != nil && fn.Name == "runtime.goexit" { 742 return nil 743 } 744 } 745 } 746 747 for _, pc := range pcs { 748 if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, pc, NextBreakpoint, sameFrameCond)); err != nil { 749 dbp.ClearSteppingBreakpoints() 750 return err 751 } 752 } 753 754 if stepInto && backward { 755 err := setStepIntoBreakpointsReverse(dbp, text, topframe, sameGCond) 756 if err != nil { 757 return err 758 } 759 } 760 761 if !topframe.Inlined { 762 topframe, retframe := skipAutogeneratedWrappersOut(dbp, selg, curthread, &topframe, &retframe) 763 retFrameCond := astutil.And(sameGCond, frameoffCondition(retframe)) 764 765 // Add a breakpoint on the return address for the current frame. 766 // For inlined functions there is no need to do this, the set of PCs 767 // returned by the AllPCsBetween call above already cover all instructions 768 // of the containing function. 769 bp, _ := dbp.SetBreakpoint(0, retframe.Current.PC, NextBreakpoint, retFrameCond) 770 // Return address could be wrong, if we are unable to set a breakpoint 771 // there it's ok. 772 if bp != nil { 773 configureReturnBreakpoint(dbp.BinInfo(), bp, topframe, retFrameCond) 774 } 775 } 776 777 if bp := curthread.Breakpoint(); bp.Breakpoint == nil { 778 curthread.SetCurrentBreakpoint(false) 779 } 780 success = true 781 return nil 782 } 783 784 func setStepIntoBreakpoints(dbp *Target, curfn *Function, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr) error { 785 for _, instr := range text { 786 if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() { 787 continue 788 } 789 790 if instr.DestLoc != nil { 791 if err := setStepIntoBreakpoint(dbp, curfn, []AsmInstruction{instr}, sameGCond); err != nil { 792 return err 793 } 794 } else { 795 // Non-absolute call instruction, set a StepBreakpoint here 796 bp, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, instr.Loc.PC, StepBreakpoint, sameGCond)) 797 if err != nil { 798 return err 799 } 800 breaklet := bp.Breaklets[len(bp.Breaklets)-1] 801 breaklet.callback = stepIntoCallback 802 } 803 } 804 return nil 805 } 806 807 // stepIntoCallback is a callback called when a StepBreakpoint is hit, it 808 // disassembles the current instruction to figure out its destination and 809 // sets a breakpoint on it. 810 func stepIntoCallback(curthread Thread, p *Target) (bool, error) { 811 if p.recman.GetDirection() != Forward { 812 // This should never happen, step into breakpoints with callbacks are only 813 // set when moving forward and direction changes are forbidden while 814 // breakpoints are set. 815 return true, nil 816 } 817 818 text, err := disassembleCurrentInstruction(p, curthread, 0) 819 if err != nil { 820 return false, err 821 } 822 var fn *Function 823 if loc, _ := curthread.Location(); loc != nil { 824 fn = loc.Fn 825 } 826 g, _ := GetG(curthread) 827 // here we either set a breakpoint into the destination of the CALL 828 // instruction or we determined that the called function is hidden, 829 // either way we need to resume execution 830 if err = setStepIntoBreakpoint(p, fn, text, sameGoroutineCondition(p.BinInfo(), g, curthread.ThreadID())); err != nil { 831 return false, err 832 } 833 834 return false, nil 835 } 836 837 func setStepIntoBreakpointsReverse(dbp *Target, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr) error { 838 bpmap := dbp.Breakpoints() 839 // Set a breakpoint after every CALL instruction 840 for i, instr := range text { 841 if instr.Loc.File != topframe.Current.File || !instr.IsCall() || instr.DestLoc == nil || instr.DestLoc.Fn == nil { 842 continue 843 } 844 845 if instr.DestLoc.Fn.privateRuntime() { 846 continue 847 } 848 849 if nextIdx := i + 1; nextIdx < len(text) { 850 _, ok := bpmap.M[text[nextIdx].Loc.PC] 851 if !ok { 852 if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, text[nextIdx].Loc.PC, StepBreakpoint, sameGCond)); err != nil { 853 return err 854 } 855 } 856 } 857 } 858 return nil 859 } 860 861 func FindDeferReturnCalls(text []AsmInstruction) []uint64 { 862 const deferreturn = "runtime.deferreturn" 863 deferreturns := []uint64{} 864 865 // Find all runtime.deferreturn locations in the function 866 // See documentation of Breakpoint.DeferCond for why this is necessary 867 for _, instr := range text { 868 if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == deferreturn { 869 deferreturns = append(deferreturns, instr.Loc.PC) 870 } 871 } 872 return deferreturns 873 } 874 875 // Removes instructions belonging to inlined calls of topframe from pcs. 876 // If includeCurrentFn is true it will also remove all instructions 877 // belonging to the current function. 878 func removeInlinedCalls(pcs []uint64, topframe Stackframe) ([]uint64, error) { 879 // TODO(derekparker) it should be possible to still use some internal 880 // runtime information to do this. 881 if topframe.Call.Fn == nil || topframe.Call.Fn.cu.image.Stripped() { 882 return pcs, nil 883 } 884 dwarfTree, err := topframe.Call.Fn.cu.image.getDwarfTree(topframe.Call.Fn.offset) 885 if err != nil { 886 return pcs, err 887 } 888 for _, e := range reader.InlineStack(dwarfTree, 0) { 889 if e.Offset == topframe.Call.Fn.offset { 890 continue 891 } 892 for _, rng := range e.Ranges { 893 pcs = removePCsBetween(pcs, rng[0], rng[1]) 894 } 895 } 896 return pcs, nil 897 } 898 899 func removePCsBetween(pcs []uint64, start, end uint64) []uint64 { 900 out := pcs[:0] 901 for _, pc := range pcs { 902 if pc < start || pc >= end { 903 out = append(out, pc) 904 } 905 } 906 return out 907 } 908 909 func setStepIntoBreakpoint(dbp *Target, curfn *Function, text []AsmInstruction, cond ast.Expr) error { 910 if len(text) == 0 { 911 return nil 912 } 913 914 // If the current function is already a runtime function then 915 // setStepIntoBreakpoint is allowed to step into unexported runtime 916 // functions. 917 stepIntoUnexportedRuntime := curfn != nil && strings.HasPrefix(curfn.Name, "runtime.") 918 919 instr := text[0] 920 921 if instr.DestLoc == nil { 922 // Call destination couldn't be resolved because this was not the 923 // current instruction, therefore the step-into breakpoint can not be set. 924 return nil 925 } 926 927 pc := instr.DestLoc.PC 928 fn := instr.DestLoc.Fn 929 if dbp.BinInfo().Arch.Name == "ppc64le" && instr.Inst.OpcodeEquals(uint64(ppc64asm.BCLRL)) { 930 regs, err := dbp.CurrentThread().Registers() 931 if err != nil { 932 return err 933 } 934 lr := regs.LR() 935 fn = dbp.BinInfo().PCToFunc(lr) 936 } 937 938 // Skip unexported runtime functions 939 if !stepIntoUnexportedRuntime && fn != nil && fn.privateRuntime() { 940 return nil 941 } 942 943 //TODO(aarzilli): if we want to let users hide functions 944 // or entire packages from being stepped into with 'step' 945 // those extra checks should be done here. 946 947 // Skip InhibitStepInto functions for different arch. 948 if dbp.BinInfo().Arch.inhibitStepInto(dbp.BinInfo(), pc) { 949 return nil 950 } 951 952 fn, pc = skipAutogeneratedWrappersIn(dbp, fn, pc) 953 954 // We want to skip the function prologue but we should only do it if the 955 // destination address of the CALL instruction is the entry point of the 956 // function. 957 // Calls to runtime.duffzero and duffcopy inserted by the compiler can 958 // sometimes point inside the body of those functions, well after the 959 // prologue. 960 if fn != nil && fn.Entry == pc { 961 pc, _ = FirstPCAfterPrologue(dbp, fn, false) 962 } 963 964 // Set a breakpoint after the function's prologue 965 if _, err := allowDuplicateBreakpoint(dbp.SetBreakpoint(0, pc, NextBreakpoint, cond)); err != nil { 966 return err 967 } 968 969 return nil 970 } 971 972 func allowDuplicateBreakpoint(bp *Breakpoint, err error) (*Breakpoint, error) { 973 if err != nil { 974 //lint:ignore S1020 this is clearer 975 if _, isexists := err.(BreakpointExistsError); isexists { 976 return bp, nil 977 } 978 } 979 return bp, err 980 } 981 982 func isAutogenerated(loc Location) bool { 983 return loc.File == "<autogenerated>" && loc.Line == 1 984 } 985 986 func isAutogeneratedOrDeferReturn(loc Location) bool { 987 return isAutogenerated(loc) || (loc.Fn != nil && loc.Fn.Name == "runtime.deferreturn") 988 } 989 990 // skipAutogeneratedWrappersIn skips autogenerated wrappers when setting a 991 // step-into breakpoint. 992 // See genwrapper in: $GOROOT/src/cmd/compile/internal/gc/subr.go 993 func skipAutogeneratedWrappersIn(p Process, startfn *Function, startpc uint64) (*Function, uint64) { 994 if startfn == nil { 995 return nil, startpc 996 } 997 fn := startfn 998 for count := 0; count < maxSkipAutogeneratedWrappers; count++ { 999 if !fn.cu.isgo { 1000 // can't exit Go 1001 return startfn, startpc 1002 } 1003 text, err := Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End) 1004 if err != nil { 1005 break 1006 } 1007 if len(text) == 0 { 1008 break 1009 } 1010 if !isAutogenerated(text[0].Loc) { 1011 return fn, fn.Entry 1012 } 1013 tgtfns := []*Function{} 1014 // collect all functions called by the current destination function 1015 for _, instr := range text { 1016 switch { 1017 case instr.IsCall(): 1018 if instr.DestLoc == nil { 1019 return startfn, startpc 1020 } 1021 if p.BinInfo().Arch.inhibitStepInto(p.BinInfo(), instr.DestLoc.PC) { 1022 // ignored 1023 continue 1024 } 1025 if instr.DestLoc.Fn == nil { 1026 return startfn, startpc 1027 } 1028 // calls to non private runtime functions 1029 if !instr.DestLoc.Fn.privateRuntime() { 1030 tgtfns = append(tgtfns, instr.DestLoc.Fn) 1031 } 1032 case instr.IsJmp(): 1033 // unconditional jumps to a different function that isn't a private runtime function 1034 if instr.DestLoc != nil && instr.DestLoc.Fn != fn && !instr.DestLoc.Fn.privateRuntime() { 1035 tgtfns = append(tgtfns, instr.DestLoc.Fn) 1036 } 1037 } 1038 } 1039 if len(tgtfns) != 1 { 1040 // too many or not enough function calls 1041 break 1042 } 1043 1044 tgtfn := tgtfns[0] 1045 if strings.TrimSuffix(tgtfn.BaseName(), "-fm") != strings.TrimSuffix(fn.BaseName(), "-fm") { 1046 return startfn, startpc 1047 } 1048 fn = tgtfn 1049 } 1050 return startfn, startpc 1051 } 1052 1053 // skipAutogeneratedWrappersOut skip autogenerated wrappers when setting a 1054 // step out breakpoint. 1055 // See genwrapper in: $GOROOT/src/cmd/compile/internal/gc/subr.go 1056 // It also skips runtime.deferreturn frames (which are only ever on the stack on Go 1.18 or later) 1057 func skipAutogeneratedWrappersOut(tgt *Target, g *G, thread Thread, startTopframe, startRetframe *Stackframe) (topframe, retframe *Stackframe) { 1058 topframe, retframe = startTopframe, startRetframe 1059 if startTopframe.Ret == 0 { 1060 return 1061 } 1062 if !isAutogeneratedOrDeferReturn(startRetframe.Current) { 1063 return 1064 } 1065 retfn := thread.BinInfo().PCToFunc(startTopframe.Ret) 1066 if retfn == nil { 1067 return 1068 } 1069 if !retfn.cu.isgo { 1070 return 1071 } 1072 var err error 1073 var frames []Stackframe 1074 if g == nil { 1075 frames, err = ThreadStacktrace(tgt, thread, maxSkipAutogeneratedWrappers) 1076 } else { 1077 frames, err = GoroutineStacktrace(tgt, g, maxSkipAutogeneratedWrappers, 0) 1078 } 1079 if err != nil { 1080 return 1081 } 1082 for i := 1; i < len(frames); i++ { 1083 frame := frames[i] 1084 if frame.Current.Fn == nil { 1085 return 1086 } 1087 file, line := g.Thread.BinInfo().EntryLineForFunc(frame.Current.Fn) 1088 if !isAutogeneratedOrDeferReturn(Location{File: file, Line: line, Fn: frame.Current.Fn}) { 1089 return &frames[i-1], &frames[i] 1090 } 1091 } 1092 return 1093 } 1094 1095 // setDeferBreakpoint is a helper function used by next and StepOut to set a 1096 // breakpoint on the first deferred function. 1097 func setDeferBreakpoint(p *Target, text []AsmInstruction, topframe Stackframe, sameGCond ast.Expr, stepInto bool) (uint64, error) { 1098 // Set breakpoint on the most recently deferred function (if any) 1099 var deferpc uint64 1100 if topframe.TopmostDefer != nil && topframe.TopmostDefer.DwrapPC != 0 { 1101 _, _, deferfn := topframe.TopmostDefer.DeferredFunc(p) 1102 if deferfn != nil { 1103 var err error 1104 deferpc, err = FirstPCAfterPrologue(p, deferfn, false) 1105 if err != nil { 1106 return 0, err 1107 } 1108 } 1109 } 1110 if deferpc != 0 && deferpc != topframe.Current.PC { 1111 bp, err := allowDuplicateBreakpoint(p.SetBreakpoint(0, deferpc, NextDeferBreakpoint, sameGCond)) 1112 if err != nil { 1113 return 0, err 1114 } 1115 if bp != nil && stepInto { 1116 // If DeferReturns is set then the breakpoint will also be triggered when 1117 // called from runtime.deferreturn. We only do this for the step command, 1118 // not for next or stepout. 1119 for _, breaklet := range bp.Breaklets { 1120 if breaklet.Kind == NextDeferBreakpoint { 1121 breaklet.DeferReturns = FindDeferReturnCalls(text) 1122 break 1123 } 1124 } 1125 } 1126 } 1127 1128 return deferpc, nil 1129 } 1130 1131 // findCallInstrForRet returns the PC address of the CALL instruction 1132 // immediately preceding the instruction at ret. 1133 func findCallInstrForRet(p Process, mem MemoryReadWriter, ret uint64, fn *Function) (uint64, error) { 1134 text, err := disassemble(mem, nil, p.Breakpoints(), p.BinInfo(), fn.Entry, fn.End, false) 1135 if err != nil { 1136 return 0, err 1137 } 1138 var prevInstr AsmInstruction 1139 for _, instr := range text { 1140 if instr.Loc.PC == ret { 1141 return prevInstr.Loc.PC, nil 1142 } 1143 prevInstr = instr 1144 } 1145 return 0, fmt.Errorf("could not find CALL instruction for address %#x in %s", ret, fn.Name) 1146 } 1147 1148 // stepOutReverse sets a breakpoint on the CALL instruction that created the current frame, this is either: 1149 // - the CALL instruction immediately preceding the return address of the 1150 // current frame 1151 // - the return address of the current frame if the current frame was 1152 // created by a runtime.deferreturn run 1153 // - the return address of the runtime.gopanic frame if the current frame 1154 // was created by a panic 1155 // 1156 // This function is used to implement reversed StepOut 1157 func stepOutReverse(p *Target, topframe, retframe Stackframe, sameGCond ast.Expr) error { 1158 curthread := p.CurrentThread() 1159 selg := p.SelectedGoroutine() 1160 1161 if selg != nil && selg.Thread != nil { 1162 curthread = selg.Thread 1163 } 1164 1165 callerText, err := disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), retframe.Current.Fn.Entry, retframe.Current.Fn.End, false) 1166 if err != nil { 1167 return err 1168 } 1169 deferReturns := FindDeferReturnCalls(callerText) 1170 1171 var frames []Stackframe 1172 if selg == nil { 1173 frames, err = ThreadStacktrace(p, curthread, 3) 1174 } else { 1175 frames, err = GoroutineStacktrace(p, selg, 3, 0) 1176 } 1177 if err != nil { 1178 return err 1179 } 1180 1181 var callpc uint64 1182 1183 if ok, panicFrame := isPanicCall(frames); ok { 1184 if len(frames) < panicFrame+2 || frames[panicFrame+1].Current.Fn == nil { 1185 if panicFrame < len(frames) { 1186 return &ErrNoSourceForPC{frames[panicFrame].Current.PC} 1187 } else { 1188 return &ErrNoSourceForPC{frames[0].Current.PC} 1189 } 1190 } 1191 callpc, err = findCallInstrForRet(p, p.Memory(), frames[panicFrame].Ret, frames[panicFrame+1].Current.Fn) 1192 if err != nil { 1193 return err 1194 } 1195 } else { 1196 callpc, err = findCallInstrForRet(p, p.Memory(), topframe.Ret, retframe.Current.Fn) 1197 if err != nil { 1198 return err 1199 } 1200 1201 // check if the call instruction to this frame is a call to runtime.deferreturn 1202 if len(frames) > 0 { 1203 frames[0].Ret = callpc 1204 } 1205 if ok, pc := isDeferReturnCall(frames, deferReturns); ok && pc != 0 { 1206 callpc = pc 1207 } 1208 1209 } 1210 1211 _, err = allowDuplicateBreakpoint(p.SetBreakpoint(0, callpc, NextBreakpoint, sameGCond)) 1212 1213 return err 1214 } 1215 1216 // onNextGoroutine returns true if this thread is on the goroutine requested by the current 'next' command 1217 func onNextGoroutine(tgt *Target, thread Thread, breakpoints *BreakpointMap) (bool, error) { 1218 var breaklet *Breaklet 1219 breakletSearch: 1220 for i := range breakpoints.M { 1221 for _, blet := range breakpoints.M[i].Breaklets { 1222 if blet.Kind&steppingMask != 0 && blet.Cond != nil { 1223 breaklet = blet 1224 break breakletSearch 1225 } 1226 } 1227 } 1228 if breaklet == nil { 1229 return false, nil 1230 } 1231 // Internal breakpoint conditions can take multiple different forms: 1232 // Step into breakpoints: 1233 // runtime.curg.goid == X 1234 // Next or StepOut breakpoints: 1235 // runtime.curg.goid == X && runtime.frameoff == Y 1236 // Breakpoints that can be hit either by stepping on a line in the same 1237 // function or by returning from the function: 1238 // runtime.curg.goid == X && (runtime.frameoff == Y || runtime.frameoff == Z) 1239 // Here we are only interested in testing the runtime.curg.goid clause. 1240 w := onNextGoroutineWalker{tgt: tgt, thread: thread} 1241 ast.Walk(&w, breaklet.Cond) 1242 return w.ret, w.err 1243 } 1244 1245 type onNextGoroutineWalker struct { 1246 tgt *Target 1247 thread Thread 1248 ret bool 1249 err error 1250 } 1251 1252 func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor { 1253 if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL { 1254 x := exprToString(binx.X) 1255 if x == "runtime.curg.goid" || x == "runtime.threadid" { 1256 w.ret, w.err = evalBreakpointCondition(w.tgt, w.thread, n.(ast.Expr)) 1257 return nil 1258 } 1259 } 1260 return w 1261 } 1262 1263 func (t *Target) clearHardcodedBreakpoints() { 1264 threads := t.ThreadList() 1265 for _, thread := range threads { 1266 if thread.Breakpoint().Breakpoint != nil && thread.Breakpoint().LogicalID() == hardcodedBreakpointID { 1267 thread.Breakpoint().Active = false 1268 thread.Breakpoint().Breakpoint = nil 1269 } 1270 } 1271 } 1272 1273 // handleHardcodedBreakpoints looks for threads stopped at a hardcoded 1274 // breakpoint (i.e. a breakpoint instruction, like INT 3, hardcoded in the 1275 // program's text) and sets a fake breakpoint on them with logical id 1276 // hardcodedBreakpointID. 1277 // It checks trapthread and all threads that have SoftExc returning true. 1278 func (t *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread) error { 1279 mem := t.Memory() 1280 arch := t.BinInfo().Arch 1281 recorded, _ := t.recman.Recorded() 1282 1283 isHardcodedBreakpoint := func(thread Thread, pc uint64) uint64 { 1284 for _, bpinstr := range [][]byte{arch.BreakpointInstruction(), arch.AltBreakpointInstruction()} { 1285 if bpinstr == nil { 1286 continue 1287 } 1288 buf := make([]byte, len(bpinstr)) 1289 pc2 := pc 1290 if arch.BreakInstrMovesPC() { 1291 pc2 -= uint64(len(bpinstr)) 1292 } 1293 _, _ = mem.ReadMemory(buf, pc2) 1294 if bytes.Equal(buf, bpinstr) { 1295 return uint64(len(bpinstr)) 1296 } 1297 } 1298 return 0 1299 } 1300 1301 stepOverBreak := func(thread Thread, pc uint64) { 1302 if arch.BreakInstrMovesPC() { 1303 return 1304 } 1305 if recorded { 1306 return 1307 } 1308 if bpsize := isHardcodedBreakpoint(thread, pc); bpsize > 0 { 1309 setPC(thread, pc+uint64(bpsize)) 1310 } 1311 } 1312 1313 setHardcodedBreakpoint := func(thread Thread, loc *Location) { 1314 bpstate := thread.Breakpoint() 1315 hcbp := &Breakpoint{} 1316 bpstate.Active = true 1317 bpstate.Breakpoint = hcbp 1318 hcbp.FunctionName = loc.Fn.Name 1319 hcbp.File = loc.File 1320 hcbp.Line = loc.Line 1321 hcbp.Addr = loc.PC 1322 hcbp.Logical = &LogicalBreakpoint{} 1323 hcbp.Logical.Name = HardcodedBreakpoint 1324 hcbp.Breaklets = []*Breaklet{{Kind: UserBreakpoint, LogicalID: hardcodedBreakpointID}} 1325 t.StopReason = StopHardcodedBreakpoint 1326 } 1327 1328 for _, thread := range threads { 1329 if thread.Breakpoint().Breakpoint != nil { 1330 continue 1331 } 1332 if (thread.ThreadID() != trapthread.ThreadID()) && !thread.SoftExc() { 1333 continue 1334 } 1335 1336 loc, err := thread.Location() 1337 if err != nil || loc.Fn == nil { 1338 continue 1339 } 1340 1341 g, _ := GetG(thread) 1342 1343 switch { 1344 case loc.Fn.Name == "runtime.breakpoint": 1345 if recorded, _ := t.recman.Recorded(); recorded { 1346 setHardcodedBreakpoint(thread, loc) 1347 continue 1348 } 1349 stepOverBreak(thread, loc.PC) 1350 // In linux-arm64, PtraceSingleStep seems cannot step over BRK instruction 1351 // (linux-arm64 feature or kernel bug maybe). 1352 if !arch.BreakInstrMovesPC() { 1353 setPC(thread, loc.PC+uint64(arch.BreakpointSize())) 1354 } 1355 // Single-step current thread until we exit runtime.breakpoint and 1356 // runtime.Breakpoint. 1357 // On go < 1.8 it was sufficient to single-step twice on go1.8 a change 1358 // to the compiler requires 4 steps. 1359 if err := stepInstructionOut(t, thread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil { 1360 return err 1361 } 1362 setHardcodedBreakpoint(thread, loc) 1363 case g == nil || t.fncallForG[g.ID] == nil: 1364 if isHardcodedBreakpoint(thread, loc.PC) > 0 { 1365 stepOverBreak(thread, loc.PC) 1366 setHardcodedBreakpoint(thread, loc) 1367 } 1368 } 1369 } 1370 return nil 1371 }