go-delve/delve · error

unexpected CFA rule %d at uprobe PC %#x

Error message

unexpected CFA rule %d at uprobe PC %#x

What it means

The frame context at the uprobe PC uses a CFA rule other than frame.RuleCFA (e.g. an expression-based or register-based rule). Delve's eBPF backend only supports the simple CFA-offset rule, since it must compute the caller stack pointer arithmetically to capture function arguments, so it rejects anything else.

Source

Thrown at pkg/proc/breakpoints.go:713

// This offset is used as inputRegs.CFA when evaluating DWARF locations for
// input parameters so that DW_OP_fbreg expressions yield RSP-relative offsets
// directly — see the comment in setEBPFTracepointOnFunc for the full derivation.
func uprobeEntryPointCFA(t *Target, fn *Function) (uint64, int64, error) {
	uprobePC, err := FirstPCAfterPrologue(t, fn, false)
	if err != nil {
		return 0, 0, err
	}
	bi := t.BinInfo()
	fde, err := bi.frameEntries.FDEForPC(uprobePC)
	if err != nil {
		return 0, 0, fmt.Errorf("no FDE for uprobe PC %#x: %w", uprobePC, err)
	}
	framectx, err := fde.EstablishFrame(uprobePC)
	if err != nil {
		return 0, 0, fmt.Errorf("cannot establish frame at uprobe PC %#x: %w", uprobePC, err)
	}
	if framectx.CFA.Rule != frame.RuleCFA {
		return 0, 0, fmt.Errorf("unexpected CFA rule %d at uprobe PC %#x", framectx.CFA.Rule, uprobePC)
	}
	return uprobePC, framectx.CFA.Offset, nil
}

// SetWatchpoint sets a data breakpoint at addr and stores it in the
// process wide break point table.
func (t *Target) SetWatchpoint(logicalID int, scope *EvalScope, expr string, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
	if (wtype&WatchWrite == 0) && (wtype&WatchRead == 0) {
		return nil, errors.New("at least one of read and write must be set for watchpoint")
	}

	n, err := parser.ParseExpr(expr)
	if err != nil {
		return nil, err
	}
	xv, err := scope.evalAST(n)
	if err != nil {
		return nil, err

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Trace a plain Go function whose prologue uses the standard sp-relative CFA rule
  2. Place the uprobe at FirstPCAfterPrologue where the CFA rule is still RuleCFA, instead of fn.Entry
  3. Rebuild the target without hand-written asm in the traced path
  4. Use the regular (stopping) tracepoint backend which does not need CFA computation

Example fix

// before
bp, cfa, err := uprobeEntryPointCFA(t, fn.Entry)
// after
pc, err := FirstPCAfterPrologue(t.ProcessInternal(), fn, true)
bp, cfa, err := uprobeEntryPointCFA(t, pc)
Defensive patterns

Strategy: validation

Validate before calling

fde, _ := bi.frameEntries.FDEForPC(pc)
ctx, err := fde.EstablishFrame(pc)
usable := err == nil && ctx.CFA.Rule == frame.RuleCFA

Type guard

func cfaRuleSupported(ctx frame.FrameContext) bool { return ctx.CFA.Rule == frame.RuleCFA }

Prevention

When it happens

Trigger: setEBPFTracepointOnFunc on a function whose DWARF CFI describes CFA via an expression or register rule instead of a fixed offset from the stack pointer — typical of optimized/epilogue-dense code or hand-written assembly CFI.

Common situations: Tracing functions in binaries built with nonstandard unwind metadata (cgo, asm with .cfi_def_cfa_register/.cfi_def_cfa_expression); uprobe PC placed at an instruction where the CFA rule switches (push-based prologue transitions).

Related errors


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