go-delve/delve · error

escape check for %s failed, variable unreadable: %v

Error message

escape check for %s failed, variable unreadable: %v

What it means

allPointers walks an actual argument variable to check pointer escape, but if the variable itself (or a child) is Unreadable — memory could not be read, or the value couldn't be loaded — the escape check cannot proceed and this error is returned, aborting the call injection.

Source

Thrown at pkg/proc/fncall.go:737

func funcCallArgRegABI(fn *Function, bi *BinaryInfo, entry reader.Variable, argname string, typ godwarf.Type, pargFrameSize *int64) (*funcCallArg, error) {
	// Conservatively calculate the full stack argument space for ABI0.
	*pargFrameSize = alignAddr(*pargFrameSize, typ.Align())
	*pargFrameSize += typ.Size()

	isret, _ := entry.Val(dwarf.AttrVarParam).(bool)
	return &funcCallArg{name: argname, typ: typ, dwarfEntry: entry.Tree, isret: isret}, nil
}

// alignAddr rounds up addr to a multiple of align. Align must be a power of 2.
func alignAddr(addr, align int64) int64 {
	return (addr + align - 1) &^ (align - 1)
}

// allPointers calls f on every pointer contained in v
func allPointers(v *Variable, name string, f func(addr uint64, name string) error) error {
	if v.Unreadable != nil {
		return fmt.Errorf("escape check for %s failed, variable unreadable: %v", name, v.Unreadable)
	}
	switch v.Kind {
	case reflect.Ptr, reflect.UnsafePointer:
		var w *Variable
		if len(v.Children) == 1 {
			// this branch is here to support pointers constructed with typecasts from ints or the '&' operator
			w = &v.Children[0]
		} else {
			w = v.maybeDereference()
		}
		return f(w.Addr, name)
	case reflect.Chan, reflect.String, reflect.Slice:
		return f(v.Base, name)
	case reflect.Map:
		sv := v.clone()
		sv.RealType = godwarf.ResolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
		sv = sv.maybeDereference()
		return f(sv.Addr, name)

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Print the argument first to confirm it is readable before calling
  2. Fix/refresh the pointer so it refers to valid live memory
  3. Disable the escape check if the value is provably safe but unreadable to the checker

Example fix

// before
call Use(p) // p unreadable -> escape check fails
// after
print p       // verify readable first
call Use(*p)
Defensive patterns

Strategy: validation

Validate before calling

v, err := scope.EvalExpression(expr, loadCfg)
if err != nil || v.Unreadable != nil {
    return errors.New("argument not readable; fix pointer/memory before call injection")
}

Type guard

func readable(v *proc.Variable) bool { return v != nil && v.Unreadable == nil }

Try / catch

if err != nil && strings.Contains(err.Error(), "variable unreadable") {
    // inspect the argument first, or retry after the target advances to valid state
}

Prevention

When it happens

Trigger: Calling a function whose argument's memory is unreadable at check time: freed/unmapped memory, invalid pointer constructed by cast, register-based value not yet materialized, or unreadable children during the recursive walk.

Common situations: Arguments built from stale pointers, values living in registers of a stopped thread that can't be read, or corrupted heap during debugging of a crashing program.

Related errors


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