{"record":{"id":"015bdecd0a82c4f7","repo":"go-delve/delve","slug":"runtime-copystack-has-too-many-return-instructions","errorCode":null,"errorMessage":"runtime.copystack has too many return instructions","messagePattern":"runtime\\.copystack has too many return instructions","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/proc/stackwatch.go","lineNumber":104,"sourceCode":"\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tretbreaklet2 := retbp2.Breaklets[len(retbp2.Breaklets)-1]\n\t\t\t\tretbreaklet2.watchpoint = watchpoint\n\t\t\t\tretbreaklet2.callback = woos\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Stack Resize Sentinel\n\n\tretpcs, err := findRetPC(t, \"runtime.copystack\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(retpcs) > 1 {\n\t\treturn errors.New(\"runtime.copystack has too many return instructions\")\n\t}\n\n\trszbp, err := t.SetBreakpoint(0, retpcs[0], StackResizeBreakpoint, sameGCond)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trszbreaklet := rszbp.Breaklets[len(rszbp.Breaklets)-1]\n\trszbreaklet.watchpoint = watchpoint\n\trszbreaklet.callback = func(th Thread, _ *Target) (bool, error) {\n\t\tadjustStackWatchpoint(t, th, watchpoint)\n\t\treturn false, nil // we never want this breakpoint to be shown to the user\n\t}\n\n\treturn nil\n}\n\n// clearStackWatchBreakpoints clears all accessory breakpoints for","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/go-delve/delve/blob/a23773e6c31361e43246bc43a424ee009679b174/pkg/proc/stackwatch.go#L86-L122","documentation":"To keep stack watchpoints correct across goroutine stack growth/shrink, Delve sets a StackResizeBreakpoint at the (expected single) return instruction of runtime.copystack. findRetPC scans that function's disassembly for return instructions; if more than one RET PC is found, the code cannot know which one to instrument and aborts with 'runtime.copystack has too many return instructions'. The layout of runtime.copystack is compiler-version dependent, so a Go toolchain that emits multiple ret instructions in copystack breaks this assumption.","triggerScenarios":"Setting a stack-allocated variable watchpoint (SetWatchpoint on a variable that lives on the goroutine stack) while the debugged binary's runtime.copystack disassembles to more than one return instruction.","commonSituations":"Using an unusual or very new/old Go toolchain whose runtime.copystack code shape differs from what this Delve version expects; instrumented/patched runtimes; certain optimization settings changing the function's epilogue layout.","solutions":["Upgrade or downgrade Delve to a version matched to your Go toolchain, so findRetPC's expectations of runtime.copystack match the compiled runtime","Rebuild the program with a standard, commonly-used Go release that produces the single-return copystack layout Delve expects","As a workaround, watch the variable's address manually via a hardware watchpoint on a non-stack variable or re-check the value manually instead of using stack watchpoints","Check the Go version pinning in your build (go.mod toolchain directive, CI images) and align it with what your Delve version was tested against"],"exampleFix":"// before: mismatched toolchain\n// go 1.99 toolchain producing multi-ret copystack\ngo build -gcflags=\"all=-N -l\" -o app\n// after: pin a toolchain known to work with this Delve\n// go.mod: toolchain go1.22\ngo build -gcflags=\"all=-N -l\" -o app","handlingStrategy":"try-catch","validationCode":"// pre-check copystack's ret count before creating stack watchpoints\npcs, err := findRetPC(target, \"runtime.copystack\") // via debugger tooling\nif err != nil || len(pcs) != 1 {\n    return fmt.Errorf(\"stack watchpoints unavailable: copystack has %d ret PCs\", len(pcs))\n}","typeGuard":null,"tryCatchPattern":"wp, err := client.CreateWatchpoint(scope, expr, WatchWrite)\nif err != nil {\n    if strings.Contains(err.Error(), \"too many return instructions\") {\n        // fall back to manual polling of the variable instead of a stack watchpoint\n        return pollVariableInstead(expr)\n    }\n    return err\n}","preventionTips":["Use a Go toolchain version known to work with your Delve version","Keep Delve updated alongside Go upgrades","Prefer watchpoints on heap-allocated variables when debugging nonstandard runtimes","Test watchpoint creation once at session start so the failure is caught early"],"tags":["debugger","watchpoints","go-runtime","breakpoints"],"backgroundTag":"copystack-too-many-ret-instructions","analyzedSha":"a23773e6c31361e43246bc43a424ee009679b174","analyzedAt":"2026-08-31T15:12:45.221Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}