go-delve/delve · error

runtime.copystack has too many return instructions

Error message

runtime.copystack has too many return instructions

What it means

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.

Source

Thrown at pkg/proc/stackwatch.go:104

				if err != nil {
					return err
				}
				retbreaklet2 := retbp2.Breaklets[len(retbp2.Breaklets)-1]
				retbreaklet2.watchpoint = watchpoint
				retbreaklet2.callback = woos
				break
			}
		}
	}

	// Stack Resize Sentinel

	retpcs, err := findRetPC(t, "runtime.copystack")
	if err != nil {
		return err
	}
	if len(retpcs) > 1 {
		return errors.New("runtime.copystack has too many return instructions")
	}

	rszbp, err := t.SetBreakpoint(0, retpcs[0], StackResizeBreakpoint, sameGCond)
	if err != nil {
		return err
	}

	rszbreaklet := rszbp.Breaklets[len(rszbp.Breaklets)-1]
	rszbreaklet.watchpoint = watchpoint
	rszbreaklet.callback = func(th Thread, _ *Target) (bool, error) {
		adjustStackWatchpoint(t, th, watchpoint)
		return false, nil // we never want this breakpoint to be shown to the user
	}

	return nil
}

// clearStackWatchBreakpoints clears all accessory breakpoints for

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Upgrade or downgrade Delve to a version matched to your Go toolchain, so findRetPC's expectations of runtime.copystack match the compiled runtime
  2. Rebuild the program with a standard, commonly-used Go release that produces the single-return copystack layout Delve expects
  3. 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
  4. 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

Example fix

// before: mismatched toolchain
// go 1.99 toolchain producing multi-ret copystack
go build -gcflags="all=-N -l" -o app
// after: pin a toolchain known to work with this Delve
// go.mod: toolchain go1.22
go build -gcflags="all=-N -l" -o app
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check copystack's ret count before creating stack watchpoints
pcs, err := findRetPC(target, "runtime.copystack") // via debugger tooling
if err != nil || len(pcs) != 1 {
    return fmt.Errorf("stack watchpoints unavailable: copystack has %d ret PCs", len(pcs))
}

Try / catch

wp, err := client.CreateWatchpoint(scope, expr, WatchWrite)
if err != nil {
    if strings.Contains(err.Error(), "too many return instructions") {
        // fall back to manual polling of the variable instead of a stack watchpoint
        return pollVariableInstead(expr)
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31). Data as JSON: /api/errors/015bdecd0a82c4f7. Report an issue: GitHub.