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, errView on GitHub (pinned to a23773e6c3)
Solutions
- Trace a plain Go function whose prologue uses the standard sp-relative CFA rule
- Place the uprobe at FirstPCAfterPrologue where the CFA rule is still RuleCFA, instead of fn.Entry
- Rebuild the target without hand-written asm in the traced path
- 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
- Only use --ebpf tracing on standard compiled Go functions
- Place uprobes at FirstPCAfterPrologue where CFA is still sp-offset based
- Avoid tracing functions in packages containing custom asm prologues
- If tracing cgo/asm-heavy code, select the stopping tracepoint backend instead
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
- no FDE for uprobe PC %#x: %w
- cannot establish frame at uprobe PC %#x: %w
- could not retrieve CFA for current PC
- CFA becomes undefined at PC %#x: %v
- ErrStackUnderflow
AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31).
Data as JSON: /api/errors/c730a063858247a9.
Report an issue: GitHub.