diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index 3f999946..d6eff4ea 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -34,6 +34,7 @@ import (
 	"github.com/go-delve/delve/pkg/internal/gosym"
 	"github.com/go-delve/delve/pkg/logflags"
 	"github.com/go-delve/delve/pkg/proc/debuginfod"
+	"github.com/go-delve/delve/pkg/proc/evalop"
 	"github.com/hashicorp/golang-lru/simplelru"
 )
 
@@ -110,7 +111,8 @@ type BinaryInfo struct {
 	// Go 1.17 register ABI is enabled.
 	regabi bool
 
-	logger logflags.Logger
+	debugPinnerFn *Function
+	logger        logflags.Logger
 }
 
 var (
@@ -2574,13 +2576,23 @@ func (bi *BinaryInfo) LookupFunc() map[string][]*Function {
 }
 
 func (bi *BinaryInfo) lookupOneFunc(name string) *Function {
+	if name == evalop.DebugPinnerFunctionName && bi.debugPinnerFn != nil {
+		return bi.debugPinnerFn
+	}
 	fns := bi.LookupFunc()[name]
 	if fns == nil {
 		return nil
 	}
+	if name == evalop.DebugPinnerFunctionName {
+		bi.debugPinnerFn = fns[0]
+	}
 	return fns[0]
 }
 
+func (bi *BinaryInfo) hasDebugPinner() bool {
+	return bi.lookupOneFunc(evalop.DebugPinnerFunctionName) != nil
+}
+
 // loadDebugInfoMapsCompileUnit loads entry from a single compile unit.
 func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) {
 	hasAttrGoPkgName := goversion.ProducerAfterOrEqual(cu.producer, 1, 13)
diff --git a/pkg/proc/dwarf_export_test.go b/pkg/proc/dwarf_export_test.go
index 7add16c7..3431f93b 100644
--- a/pkg/proc/dwarf_export_test.go
+++ b/pkg/proc/dwarf_export_test.go
@@ -30,3 +30,14 @@ func NewCompositeMemory(p *Target, pieces []op.Piece, base uint64) (*compositeMe
 func IsJNZ(inst archInst) bool {
 	return inst.(*x86Inst).Op == x86asm.JNE
 }
+
+// HasDebugPinner returns true if the target has runtime.debugPinner.
+func (bi *BinaryInfo) HasDebugPinner() bool {
+	return bi.hasDebugPinner()
+}
+
+// DebugPinCount returns the number of addresses pinned during the last
+// function call injection.
+func DebugPinCount() int {
+	return debugPinCount
+}
diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go
index 08133d65..da4679fe 100644
--- a/pkg/proc/eval.go
+++ b/pkg/proc/eval.go
@@ -186,9 +186,17 @@ func GoroutineScope(t *Target, thread Thread) (*EvalScope, error) {
 	return FrameToScope(t, thread.ProcessMemory(), g, threadID, locations...), nil
 }
 
+func (scope *EvalScope) evalopFlags() evalop.Flags {
+	flags := evalop.Flags(0)
+	if scope.BinInfo.hasDebugPinner() {
+		flags |= evalop.HasDebugPinner
+	}
+	return flags
+}
+
 // EvalExpression returns the value of the given expression.
 func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
-	ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, false)
+	ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, scope.evalopFlags())
 	if err != nil {
 		return nil, err
 	}
@@ -642,7 +650,7 @@ func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error {
 
 // SetVariable sets the value of the named variable
 func (scope *EvalScope) SetVariable(name, value string) error {
-	ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, name, value)
+	ops, err := evalop.CompileSet(scopeToEvalLookup{scope}, name, value, scope.evalopFlags())
 	if err != nil {
 		return err
 	}
@@ -828,9 +836,13 @@ type evalStack struct {
 	scope               *EvalScope
 	curthread           Thread
 	lastRetiredFncall   *functionCallState
+	debugPinner         *Variable
 }
 
 func (s *evalStack) push(v *Variable) {
+	if v == nil {
+		panic(errors.New("internal debugger error, nil pushed onto variables stack"))
+	}
 	s.stack = append(s.stack, v)
 }
 
@@ -944,7 +956,7 @@ func (stack *evalStack) run() {
 		stack.executeOp()
 		// If the instruction we just executed requests the call injection
 		// protocol by setting callInjectionContinue we switch to it.
-		if stack.callInjectionContinue {
+		if stack.callInjectionContinue && stack.err == nil {
 			scope.callCtx.injectionThread = nil
 			return
 		}
@@ -959,25 +971,35 @@ func (stack *evalStack) run() {
 	// injections before returning.
 
 	if len(stack.fncalls) > 0 {
+		fncallLog("undoing calls (%v)", stack.err)
 		fncall := stack.fncallPeek()
 		if fncall == stack.lastRetiredFncall {
 			stack.err = fmt.Errorf("internal debugger error: could not undo injected call during error recovery, original error: %v", stack.err)
 			return
 		}
 		if fncall.undoInjection != nil {
-			// setTargetExecuted is set if evalop.CallInjectionSetTarget has been
-			// executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
-			// call in evalop.CallInjectionSetTarget before continuing.
-			switch scope.BinInfo.Arch.Name {
-			case "amd64":
-				regs, _ := curthread.Registers()
-				setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize()))
-				setPC(curthread, fncall.undoInjection.oldpc)
-			case "arm64", "ppc64le":
-				setLR(curthread, fncall.undoInjection.oldlr)
-				setPC(curthread, fncall.undoInjection.oldpc)
-			default:
-				panic("not implemented")
+			if fncall.undoInjection.doComplete2 {
+				// doComplete2 is set if CallInjectionComplete{DoPinning: true} has been
+				// executed but CallInjectionComplete2 hasn't.
+				regs, err := curthread.Registers()
+				if err == nil {
+					callInjectionComplete2(scope, scope.BinInfo, fncall, regs, curthread)
+				}
+			} else {
+				// undoInjection is set if evalop.CallInjectionSetTarget has been
+				// executed but evalop.CallInjectionComplete hasn't, we must undo the callOP
+				// call in evalop.CallInjectionSetTarget before continuing.
+				switch scope.BinInfo.Arch.Name {
+				case "amd64":
+					regs, _ := curthread.Registers()
+					setSP(curthread, regs.SP()+uint64(scope.BinInfo.Arch.PtrSize()))
+					setPC(curthread, fncall.undoInjection.oldpc)
+				case "arm64", "ppc64le":
+					setLR(curthread, fncall.undoInjection.oldlr)
+					setPC(curthread, fncall.undoInjection.oldpc)
+				default:
+					panic("not implemented")
+				}
 			}
 		}
 		stack.lastRetiredFncall = fncall
@@ -1137,6 +1159,11 @@ func (stack *evalStack) executeOp() {
 	case *evalop.Pop:
 		stack.pop()
 
+	case *evalop.Roll:
+		rolled := stack.stack[len(stack.stack)-op.N-1]
+		copy(stack.stack[len(stack.stack)-op.N-1:], stack.stack[len(stack.stack)-op.N:])
+		stack.stack[len(stack.stack)-1] = rolled
+
 	case *evalop.BuiltinCall:
 		vars := make([]*Variable, len(op.Args))
 		for i := len(op.Args) - 1; i >= 0; i-- {
@@ -1159,9 +1186,29 @@ func (stack *evalStack) executeOp() {
 		stack.err = funcCallCopyOneArg(scope, fncall, actualArg, &fncall.formalArgs[op.ArgNum], curthread)
 
 	case *evalop.CallInjectionComplete:
-		stack.fncallPeek().undoInjection = nil
+		fncall := stack.fncallPeek()
+		fncall.doPinning = op.DoPinning
+		if op.DoPinning {
+			fncall.undoInjection.doComplete2 = true
+		} else {
+			fncall.undoInjection = nil
+		}
 		stack.callInjectionContinue = true
 
+	case *evalop.CallInjectionComplete2:
+		fncall := stack.fncallPeek()
+		if len(fncall.addrsToPin) != 0 {
+			stack.err = fmt.Errorf("internal debugger error: CallInjectionComplete2 called when there still are addresses to pin")
+		}
+		fncall.undoInjection = nil
+		regs, err := curthread.Registers()
+		if err == nil {
+			callInjectionComplete2(scope, scope.BinInfo, fncall, regs, curthread)
+			stack.callInjectionContinue = true
+		} else {
+			stack.err = err
+		}
+
 	case *evalop.CallInjectionStartSpecial:
 		stack.callInjectionContinue = scope.callInjectionStartSpecial(stack, op, curthread)
 
@@ -1173,6 +1220,26 @@ func (stack *evalStack) executeOp() {
 		rhv := stack.pop()
 		stack.err = scope.setValue(lhv, rhv, exprToString(op.Rhe))
 
+	case *evalop.PushPinAddress:
+		debugPinCount++
+		fncall := stack.fncallPeek()
+		addrToPin := fncall.addrsToPin[len(fncall.addrsToPin)-1]
+		fncall.addrsToPin = fncall.addrsToPin[:len(fncall.addrsToPin)-1]
+		typ, err := scope.BinInfo.findType("unsafe.Pointer")
+		if ptyp, ok := typ.(*godwarf.PtrType); err == nil && ok {
+			v := newVariable("", 0, typ, scope.BinInfo, scope.Mem)
+			v.Children = []Variable{*(newVariable("", uint64(addrToPin), ptyp.Type, scope.BinInfo, scope.Mem))}
+			stack.push(v)
+		} else {
+			stack.err = fmt.Errorf("can not pin address: %v", err)
+		}
+
+	case *evalop.SetDebugPinner:
+		stack.debugPinner = stack.pop()
+
+	case *evalop.PushDebugPinner:
+		stack.push(stack.debugPinner)
+
 	default:
 		stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op)
 	}
@@ -1273,7 +1340,7 @@ func (stack *evalStack) pushIdent(scope *EvalScope, name string) (found bool) {
 }
 
 func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
-	ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t)
+	ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, t, scope.evalopFlags())
 	if err != nil {
 		return nil, err
 	}
@@ -1289,9 +1356,14 @@ func exprToString(t ast.Expr) string {
 }
 
 func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
-	x := stack.peek()
-	if op.Pop {
-		stack.pop()
+	var x *Variable
+
+	switch op.When {
+	case evalop.JumpIfTrue, evalop.JumpIfFalse, evalop.JumpIfAllocStringChecksFail:
+		x = stack.peek()
+		if op.Pop {
+			stack.pop()
+		}
 	}
 
 	var v bool
@@ -1308,6 +1380,17 @@ func (scope *EvalScope) evalJump(op *evalop.Jump, stack *evalStack) {
 			return
 		}
 		return
+	case evalop.JumpAlways:
+		stack.opidx = op.Target - 1
+		return
+	case evalop.JumpIfPinningDone:
+		fncall := stack.fncallPeek()
+		if len(fncall.addrsToPin) == 0 {
+			stack.opidx = op.Target - 1
+		}
+		return
+	default:
+		panic("internal error, bad jump condition")
 	}
 
 	if x.Kind != reflect.Bool {
diff --git a/pkg/proc/evalop/evalcompile.go b/pkg/proc/evalop/evalcompile.go
index 660c7879..14cb5304 100644
--- a/pkg/proc/evalop/evalcompile.go
+++ b/pkg/proc/evalop/evalcompile.go
@@ -18,7 +18,8 @@ import (
 )
 
 var (
-	ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
+	ErrFuncCallNotAllowed   = errors.New("function calls not allowed without using 'call'")
+	DebugPinnerFunctionName = "runtime.debugPinnerV1"
 )
 
 type compileCtx struct {
@@ -26,6 +27,8 @@ type compileCtx struct {
 	ops        []Op
 	allowCalls bool
 	curCall    int
+	flags      Flags
+	firstCall  bool
 }
 
 type evalLookup interface {
@@ -33,14 +36,24 @@ type evalLookup interface {
 	HasBuiltin(string) bool
 }
 
+// Flags describes flags used to control Compile and CompileAST
+type Flags uint8
+
+const (
+	CanSet         Flags = 1 << iota // Assignment is allowed
+	HasDebugPinner                   // runtime.debugPinner is available
+)
+
 // CompileAST compiles the expression t into a list of instructions.
-func CompileAST(lookup evalLookup, t ast.Expr) ([]Op, error) {
-	ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
+func CompileAST(lookup evalLookup, t ast.Expr, flags Flags) ([]Op, error) {
+	ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
 	err := ctx.compileAST(t)
 	if err != nil {
 		return nil, err
 	}
 
+	ctx.compileDebugUnpin()
+
 	err = ctx.depthCheck(1)
 	if err != nil {
 		return ctx.ops, err
@@ -50,18 +63,18 @@ func CompileAST(lookup evalLookup, t ast.Expr) ([]Op, error) {
 
 // Compile compiles the expression expr into a list of instructions.
 // If canSet is true expressions like "x = y" are also accepted.
-func Compile(lookup evalLookup, expr string, canSet bool) ([]Op, error) {
+func Compile(lookup evalLookup, expr string, flags Flags) ([]Op, error) {
 	t, err := parser.ParseExpr(expr)
 	if err != nil {
-		if canSet {
+		if flags&CanSet != 0 {
 			eqOff, isAs := isAssignment(err)
 			if isAs {
-				return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:])
+				return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:], flags)
 			}
 		}
 		return nil, err
 	}
-	return CompileAST(lookup, t)
+	return CompileAST(lookup, t, flags)
 }
 
 func isAssignment(err error) (int, bool) {
@@ -74,7 +87,7 @@ func isAssignment(err error) (int, bool) {
 
 // CompileSet compiles the expression setting lhexpr to rhexpr into a list of
 // instructions.
-func CompileSet(lookup evalLookup, lhexpr, rhexpr string) ([]Op, error) {
+func CompileSet(lookup evalLookup, lhexpr, rhexpr string, flags Flags) ([]Op, error) {
 	lhe, err := parser.ParseExpr(lhexpr)
 	if err != nil {
 		return nil, err
@@ -84,7 +97,7 @@ func CompileSet(lookup evalLookup, lhexpr, rhexpr string) ([]Op, error) {
 		return nil, err
 	}
 
-	ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
+	ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
 	err = ctx.compileAST(rhe)
 	if err != nil {
 		return nil, err
@@ -120,13 +133,17 @@ func (ctx *compileCtx) compileAllocLiteralString() {
 		&PushLen{},
 		&PushNil{},
 		&PushConst{constant.MakeBool(false)},
-	})
+	}, true)
 
 	ctx.pushOp(&ConvertAllocToString{})
 	jmp.Target = len(ctx.ops)
 }
 
-func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op) {
+func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op, doPinning bool) {
+	if doPinning {
+		ctx.compileGetDebugPinner()
+	}
+
 	id := ctx.curCall
 	ctx.curCall++
 	ctx.pushOp(&CallInjectionStartSpecial{
@@ -136,11 +153,40 @@ func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args
 	ctx.pushOp(&CallInjectionSetTarget{id: id})
 
 	for i := range args {
-		ctx.pushOp(args[i])
+		if args[i] != nil {
+			ctx.pushOp(args[i])
+		}
 		ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i})
 	}
 
-	ctx.pushOp(&CallInjectionComplete{id: id})
+	doPinning = doPinning && (ctx.flags&HasDebugPinner != 0)
+
+	ctx.pushOp(&CallInjectionComplete{id: id, DoPinning: doPinning})
+
+	if doPinning {
+		ctx.compilePinningLoop(id)
+	}
+}
+
+func (ctx *compileCtx) compileGetDebugPinner() {
+	if ctx.firstCall && ctx.flags&HasDebugPinner != 0 {
+		ctx.compileSpecialCall(DebugPinnerFunctionName, []ast.Expr{}, []Op{}, false)
+		ctx.pushOp(&SetDebugPinner{})
+		ctx.firstCall = false
+	}
+}
+
+func (ctx *compileCtx) compileDebugUnpin() {
+	if !ctx.firstCall && ctx.flags&HasDebugPinner != 0 {
+		ctx.compileSpecialCall("runtime.(*Pinner).Unpin", []ast.Expr{
+			&ast.Ident{Name: "debugPinner"},
+		}, []Op{
+			&PushDebugPinner{},
+		}, false)
+		ctx.pushOp(&Pop{})
+		ctx.pushOp(&PushNil{})
+		ctx.pushOp(&SetDebugPinner{})
+	}
 }
 
 func (ctx *compileCtx) pushOp(op Op) {
@@ -172,6 +218,8 @@ func (ctx *compileCtx) depthCheck(endDepth int) error {
 		}
 	}
 
+	debugPinnerSeen := false
+
 	for i, op := range ctx.ops {
 		npop, npush := op.depthCheck()
 		if depth[i] < npop {
@@ -179,8 +227,15 @@ func (ctx *compileCtx) depthCheck(endDepth int) error {
 		}
 		d := depth[i] - npop + npush
 		checkAndSet(i+1, d)
-		if jmp, _ := op.(*Jump); jmp != nil {
-			checkAndSet(jmp.Target, d)
+		switch op := op.(type) {
+		case *Jump:
+			checkAndSet(op.Target, d)
+		case *CallInjectionStartSpecial:
+			debugPinnerSeen = true
+		case *CallInjectionComplete:
+			if op.DoPinning && !debugPinnerSeen {
+				err = fmt.Errorf("internal debugger error: pinning call injection seen before call to %s at instrution %d", DebugPinnerFunctionName, i)
+			}
 		}
 		if err != nil {
 			return err
@@ -521,6 +576,16 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
 	id := ctx.curCall
 	ctx.curCall++
 
+	if ctx.flags&HasDebugPinner != 0 {
+		return ctx.compileFunctionCallWithPinning(node, id)
+	}
+
+	return ctx.compileFunctionCallNoPinning(node, id)
+}
+
+// compileFunctionCallNoPinning compiles a function call when runtime.debugPinner is
+// not available in the target.
+func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int) error {
 	oldAllowCalls := ctx.allowCalls
 	oldOps := ctx.ops
 	ctx.allowCalls = false
@@ -570,6 +635,61 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
 	return nil
 }
 
+// compileFunctionCallWithPinning compiles a function call when runtime.debugPinner
+// is available in the target.
+func (ctx *compileCtx) compileFunctionCallWithPinning(node *ast.CallExpr, id int) error {
+	ctx.compileGetDebugPinner()
+
+	err := ctx.compileAST(node.Fun)
+	if err != nil {
+		return err
+	}
+
+	for i, arg := range node.Args {
+		err := ctx.compileAST(arg)
+		if isStringLiteral(arg) {
+			ctx.compileAllocLiteralString()
+		}
+		if err != nil {
+			return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", exprToString(arg), i+1, exprToString(node.Fun), err)
+		}
+	}
+
+	ctx.pushOp(&Roll{len(node.Args)})
+	ctx.pushOp(&CallInjectionStart{HasFunc: true, id: id, Node: node})
+	ctx.pushOp(&Pop{})
+	ctx.pushOp(&CallInjectionSetTarget{id: id})
+
+	for i := len(node.Args) - 1; i >= 0; i-- {
+		arg := node.Args[i]
+		ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg})
+	}
+
+	ctx.pushOp(&CallInjectionComplete{id: id, DoPinning: true})
+
+	ctx.compilePinningLoop(id)
+
+	return nil
+}
+
+func (ctx *compileCtx) compilePinningLoop(id int) {
+	loopStart := len(ctx.ops)
+	jmp := &Jump{When: JumpIfPinningDone}
+	ctx.pushOp(jmp)
+	ctx.pushOp(&PushPinAddress{})
+	ctx.compileSpecialCall("runtime.(*Pinner).Pin", []ast.Expr{
+		&ast.Ident{Name: "debugPinner"},
+		&ast.Ident{Name: "pinAddress"},
+	}, []Op{
+		&PushDebugPinner{},
+		nil,
+	}, false)
+	ctx.pushOp(&Pop{})
+	ctx.pushOp(&Jump{When: JumpAlways, Target: loopStart})
+	jmp.Target = len(ctx.ops)
+	ctx.pushOp(&CallInjectionComplete2{id: id})
+}
+
 func Listing(depth []int, ops []Op) string {
 	if depth == nil {
 		depth = make([]int, len(ops)+1)
diff --git a/pkg/proc/evalop/ops.go b/pkg/proc/evalop/ops.go
index 43ae96c5..d64c0995 100644
--- a/pkg/proc/evalop/ops.go
+++ b/pkg/proc/evalop/ops.go
@@ -164,8 +164,13 @@ type Jump struct {
 }
 
 func (jmpif *Jump) depthCheck() (npop, npush int) {
-	if jmpif.Pop {
-		return 1, 0
+	switch jmpif.When {
+	case JumpIfTrue, JumpIfFalse, JumpIfAllocStringChecksFail:
+		if jmpif.Pop {
+			return 1, 0
+		} else {
+			return 1, 1
+		}
 	}
 	return 0, 0
 }
@@ -177,6 +182,8 @@ const (
 	JumpIfFalse JumpCond = iota
 	JumpIfTrue
 	JumpIfAllocStringChecksFail
+	JumpAlways
+	JumpIfPinningDone
 )
 
 // Binary pops two variables from the stack, applies the specified binary
@@ -200,6 +207,13 @@ type Pop struct {
 
 func (*Pop) depthCheck() (npop, npush int) { return 1, 0 }
 
+// Roll removes the n-th element of the stack and pushes it back in at the top
+type Roll struct {
+	N int
+}
+
+func (*Roll) depthCheck() (npop, npush int) { return 1, 1 }
+
 // BuiltinCall pops len(Args) argument from the stack, calls the specified
 // builtin on them and pushes the result back on the stack.
 type BuiltinCall struct {
@@ -240,11 +254,30 @@ type CallInjectionCopyArg struct {
 func (*CallInjectionCopyArg) depthCheck() (npop, npush int) { return 1, 0 }
 
 // CallInjectionComplete resumes target execution so that the injected call can run.
+// If DoPinning is true it stops after the call is completed without undoing
+// the call injection frames so that address pinning for the return value
+// can be performed, see CallInjectionComplete2.
 type CallInjectionComplete struct {
+	id        int
+	DoPinning bool
+}
+
+func (op *CallInjectionComplete) depthCheck() (npop, npush int) {
+	if op.DoPinning {
+		return 0, 0
+	} else {
+		return 0, 1
+	}
+}
+
+// CallInjectionComplete2 if DoPinning was passed to CallInjectionComplete
+// this will finish the call injection protocol and push the evaluation
+// result on the stack.
+type CallInjectionComplete2 struct {
 	id int
 }
 
-func (*CallInjectionComplete) depthCheck() (npop, npush int) { return 0, 1 }
+func (*CallInjectionComplete2) depthCheck() (npop, npush int) { return 0, 1 }
 
 // CallInjectionStartSpecial starts call injection for a function with a
 // name and arguments known at compile time.
@@ -272,3 +305,22 @@ type SetValue struct {
 }
 
 func (*SetValue) depthCheck() (npop, npush int) { return 2, 0 }
+
+// SetDebugPinner pops one variable from the stack and uses it as the saved debug pinner.
+type SetDebugPinner struct {
+}
+
+func (*SetDebugPinner) depthCheck() (npop, npush int) { return 1, 0 }
+
+// PushDebugPinner pushes the debug pinner on the stack.
+type PushDebugPinner struct {
+}
+
+func (*PushDebugPinner) depthCheck() (npop, npush int) { return 0, 1 }
+
+// PushPinAddress pushes an address to pin on the stack (as an
+// unsafe.Pointer) and removes it from the list of addresses to pin.
+type PushPinAddress struct {
+}
+
+func (*PushPinAddress) depthCheck() (npop, npush int) { return 0, 1 }
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index f045c3ab..02d4803f 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -8,6 +8,7 @@ import (
 	"go/ast"
 	"go/constant"
 	"reflect"
+	"slices"
 	"sort"
 	"strconv"
 	"strings"
@@ -42,6 +43,9 @@ import (
 //  - evalop.CallInjectionSetTarget
 //  - evalCallInjectionCopyArg
 //  - evalCallInjectionComplete
+//
+// When the target has runtime.debugPinner then evalCallInjectionPinPointer
+// must be also called in a loop until it returns false.
 
 const (
 	debugCallFunctionNamePrefix1 = "debugCall"
@@ -89,12 +93,20 @@ type functionCallState struct {
 	// it contains information on how to undo a function call injection without running it
 	undoInjection *undoInjection
 
+	// hasDebugPinner is true if the target has runtime.debugPinner
+	hasDebugPinner bool
+	// doPinning is true if this call injection should pin the results
+	doPinning bool
+	// addrsToPin addresses from return variables that should be pinned
+	addrsToPin []uint64
+
 	protocolReg   uint64
 	debugCallName string
 }
 
 type undoInjection struct {
 	oldpc, oldlr uint64
+	doComplete2  bool
 }
 
 type callContext struct {
@@ -123,10 +135,14 @@ type callInjection struct {
 	endCallInjection func()
 }
 
+//lint:ignore U1000 this variable is only used by tests
+var debugPinCount int
+
 // EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
 // Because this can only be done in the current goroutine, unlike
 // EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
 func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
+	debugPinCount = 0
 	t := grp.Selected
 	bi := t.BinInfo()
 	if !t.SupportsFunctionCalls() {
@@ -172,7 +188,7 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa
 		return err
 	}
 
-	ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, true)
+	ops, err := evalop.Compile(scopeToEvalLookup{scope}, expr, scope.evalopFlags()|evalop.CanSet)
 	if err != nil {
 		return err
 	}
@@ -186,7 +202,7 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa
 	}
 
 	stack.eval(scope, ops)
-	if stack.callInjectionContinue {
+	if stack.callInjectionContinue && stack.err == nil {
 		return grp.Continue()
 	}
 
@@ -289,11 +305,19 @@ func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, st
 		return
 	}
 
+	for _, v := range stack.stack {
+		if v.Flags&(VariableFakeAddress|VariableCPURegister|variableSaved) != 0 || v.Unreadable != nil || v.DwarfType == nil || v.RealType == nil || v.Addr == 0 {
+			continue
+		}
+		saveVariable(v)
+	}
+
 	fncall := functionCallState{
-		expr:          op.Node,
-		savedRegs:     regs,
-		protocolReg:   protocolReg,
-		debugCallName: dbgcallfn.Name,
+		expr:           op.Node,
+		savedRegs:      regs,
+		protocolReg:    protocolReg,
+		debugCallName:  dbgcallfn.Name,
+		hasDebugPinner: scope.BinInfo.hasDebugPinner(),
 	}
 
 	if op.HasFunc {
@@ -365,10 +389,18 @@ func (scope *EvalScope) evalCallInjectionStart(op *evalop.CallInjectionStart, st
 	p.fncallForG[scope.g.ID].startThreadID = thread.ThreadID()
 
 	stack.fncallPush(&fncall)
-	stack.push(newConstant(constant.MakeBool(fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0), scope.Mem))
+	stack.push(newConstant(constant.MakeBool(!fncall.hasDebugPinner && (fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0)), scope.Mem))
 	stack.callInjectionContinue = true
 }
 
+func saveVariable(v *Variable) {
+	v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size()))
+	v.Flags |= variableSaved
+	if cachemem, ok := v.mem.(*memCache); ok {
+		v.Unreadable = cachemem.load()
+	}
+}
+
 func funcCallFinish(scope *EvalScope, stack *evalStack) {
 	fncall := stack.fncallPop()
 	if fncall.err != nil {
@@ -697,7 +729,7 @@ func allPointers(v *Variable, name string, f func(addr uint64, name string) erro
 		return fmt.Errorf("escape check for %s failed, variable unreadable: %v", name, v.Unreadable)
 	}
 	switch v.Kind {
-	case reflect.Ptr:
+	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
@@ -713,6 +745,12 @@ func allPointers(v *Variable, name string, f func(addr uint64, name string) erro
 		sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
 		sv = sv.maybeDereference()
 		return f(sv.Addr, name)
+	case reflect.Interface:
+		sv := v.clone()
+		sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.InterfaceType).TypedefType))
+		sv = sv.maybeDereference()
+		sv.Kind = reflect.Struct
+		return allPointers(sv, name, f)
 	case reflect.Struct:
 		t := v.RealType.(*godwarf.StructType)
 		for _, field := range t.Field {
@@ -732,6 +770,10 @@ func allPointers(v *Variable, name string, f func(addr uint64, name string) erro
 		if err := f(v.funcvalAddr(), name); err != nil {
 			return err
 		}
+	case reflect.Complex64, reflect.Complex128, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Bool, reflect.Float32, reflect.Float64:
+		// nothing to do
+	default:
+		panic(fmt.Errorf("not implemented: %s", v.Kind))
 	}
 
 	return nil
@@ -864,29 +906,32 @@ func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
 			return (v.Flags & VariableReturnArgument) != 0
 		})
 
-		loadValues(fncall.retvars, callScope.callCtx.retLoadCfg)
+		if !fncall.doPinning {
+			loadValues(fncall.retvars, callScope.callCtx.retLoadCfg)
+		}
 		for _, v := range fncall.retvars {
 			v.Flags |= VariableFakeAddress
 		}
 
-		// Store the stack span of the currently running goroutine (which in Go >=
-		// 1.15 might be different from the original injection goroutine) so that
-		// later on we can use it to perform the escapeCheck
-		if threadg, _ := GetG(thread); threadg != nil {
-			callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
-		}
-		if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" {
-			oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize()))
-			if err != nil {
-				fncall.err = fmt.Errorf("could not restore LR: %v", err)
-				break
-			}
-			if err = setLR(thread, oldlr); err != nil {
-				fncall.err = fmt.Errorf("could not restore LR: %v", err)
-				break
+		if fncall.doPinning {
+			stack.callInjectionContinue = false
+			for _, v := range fncall.retvars {
+				saveVariable(v)
+				allPointers(v, "", func(addr uint64, _ string) error {
+					if addr != 0 && pointerEscapes(addr, callScope.g.stack, callScope.callCtx.stacks) {
+						fncall.addrsToPin = append(fncall.addrsToPin, addr)
+					}
+					return nil
+				})
 			}
+			slices.Sort(fncall.addrsToPin)
+			fncall.addrsToPin = slices.Compact(fncall.addrsToPin)
+
+			return false // will continue with evalop.CallInjectionComplete2
 		}
 
+		callInjectionComplete2(callScope, bi, fncall, regs, thread)
+
 	case debugCallRegReadPanic: // 2
 		// read panic value from stack
 		stack.callInjectionContinue = true
@@ -913,9 +958,29 @@ func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
 	return false
 }
 
+func callInjectionComplete2(callScope *EvalScope, bi *BinaryInfo, fncall *functionCallState, regs Registers, thread Thread) {
+	// Store the stack span of the currently running goroutine (which in Go >=
+	// 1.15 might be different from the original injection goroutine) so that
+	// later on we can use it to perform the escapeCheck
+	if threadg, _ := GetG(thread); threadg != nil {
+		callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
+	}
+	if bi.Arch.Name == "arm64" || bi.Arch.Name == "ppc64le" {
+		oldlr, err := readUintRaw(thread.ProcessMemory(), regs.SP(), int64(bi.Arch.PtrSize()))
+		if err != nil {
+			fncall.err = fmt.Errorf("could not restore LR: %v", err)
+			return
+		}
+		if err = setLR(thread, oldlr); err != nil {
+			fncall.err = fmt.Errorf("could not restore LR: %v", err)
+			return
+		}
+	}
+}
+
 func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTarget, stack *evalStack, thread Thread) {
 	fncall := stack.fncallPeek()
-	if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 {
+	if !fncall.hasDebugPinner && (fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0) {
 		funcCallEvalFuncExpr(scope, stack, fncall)
 	}
 	stack.pop() // target function, consumed by funcCallEvalFuncExpr either above or in evalop.CallInjectionStart
@@ -944,7 +1009,7 @@ func (scope *EvalScope) evalCallInjectionSetTarget(op *evalop.CallInjectionSetTa
 	if fncall.receiver != nil {
 		err := funcCallCopyOneArg(scope, fncall, fncall.receiver, &fncall.formalArgs[0], thread)
 		if err != nil {
-			stack.err = err
+			stack.err = fmt.Errorf("could not set call receiver: %v", err)
 			return
 		}
 		fncall.formalArgs = fncall.formalArgs[1:]
@@ -995,6 +1060,9 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64
 func (scope *EvalScope) callInjectionStartSpecial(stack *evalStack, op *evalop.CallInjectionStartSpecial, curthread Thread) bool {
 	fnv, err := scope.findGlobalInternal(op.FnName)
 	if fnv == nil {
+		if err == nil {
+			err = fmt.Errorf("function %s not found", op.FnName)
+		}
 		stack.err = err
 		return false
 	}
@@ -1013,6 +1081,9 @@ func (scope *EvalScope) callInjectionStartSpecial(stack *evalStack, op *evalop.C
 func (scope *EvalScope) convertAllocToString(stack *evalStack) {
 	mallocv := stack.pop()
 	v := stack.pop()
+
+	mallocv.loadValue(loadFullValue)
+
 	if mallocv.Unreadable != nil {
 		stack.err = mallocv.Unreadable
 		return
@@ -1058,7 +1129,7 @@ func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool {
 // callInjectionProtocol is the function called from Continue to progress
 // the injection protocol for all threads.
 // Returns true if a call injection terminated
-func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
+func callInjectionProtocol(t *Target, trapthread Thread, threads []Thread) (done bool, err error) {
 	if len(t.fncallForG) == 0 {
 		// we aren't injecting any calls, no need to check the threads.
 		return false, nil
@@ -1072,6 +1143,9 @@ func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
 		if err != nil {
 			continue
 		}
+		if (thread.ThreadID() != trapthread.ThreadID()) && !thread.SoftExc() {
+			continue
+		}
 		if !isCallInjectionStop(t, thread, loc) {
 			continue
 		}
@@ -1180,7 +1254,10 @@ func debugCallProtocolReg(archName string, version int) (uint64, bool) {
 // runtimeWhitelist is a list of functions in the runtime that we can call
 // (through call injection) even if they are optimized.
 var runtimeWhitelist = map[string]bool{
-	"runtime.mallocgc": true,
+	"runtime.mallocgc":             true,
+	evalop.DebugPinnerFunctionName: true,
+	"runtime.(*Pinner).Unpin":      true,
+	"runtime.(*Pinner).Pin":        true,
 }
 
 // runtimeOptimizedWorkaround modifies the input DIE so that arguments and
diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go
index 5f2ad383..530ca5c0 100644
--- a/pkg/proc/mem.go
+++ b/pkg/proc/mem.go
@@ -43,14 +43,25 @@ func (m *memCache) contains(addr uint64, size int) bool {
 	return addr >= m.cacheAddr && end <= m.cacheAddr+uint64(len(m.cache))
 }
 
+func (m *memCache) load() error {
+	if m.loaded {
+		return nil
+	}
+	_, err := m.mem.ReadMemory(m.cache, m.cacheAddr)
+	if err != nil {
+		return err
+	}
+	m.loaded = true
+	return nil
+}
+
 func (m *memCache) ReadMemory(data []byte, addr uint64) (n int, err error) {
 	if m.contains(addr, len(data)) {
 		if !m.loaded {
-			_, err := m.mem.ReadMemory(m.cache, m.cacheAddr)
+			err := m.load()
 			if err != nil {
 				return 0, err
 			}
-			m.loaded = true
 		}
 		copy(data, m.cache[addr-m.cacheAddr:])
 		return len(data), nil
diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go
index cbc9389b..b6ee2e2f 100644
--- a/pkg/proc/target_exec.go
+++ b/pkg/proc/target_exec.go
@@ -159,7 +159,7 @@ func (grp *TargetGroup) Continue() error {
 					log.Debugf("\t%d PC=%#x", th.ThreadID(), regs.PC())
 				}
 			}
-			callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, threads)
+			callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, trapthread, threads)
 			callInjectionDone = callInjectionDone || callInjectionDoneThis
 			if callInjectionDoneThis {
 				dbp.StopReason = StopCallReturned
diff --git a/pkg/proc/types.go b/pkg/proc/types.go
index bcfc6107..eaa2b771 100644
--- a/pkg/proc/types.go
+++ b/pkg/proc/types.go
@@ -206,7 +206,10 @@ func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type)
 	if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
 		kindv = _type.loadFieldNamed("Kind_")
 	}
-	if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
+	if kindv == nil {
+		return 0, 0, false, fmt.Errorf("unreadable interace type (no kind field)")
+	}
+	if kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
 		return 0, 0, false, fmt.Errorf("unreadable interface type: %v", kindv.Unreadable)
 	}
 	typeKind, _ = constant.Uint64Val(kindv.Value)
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 95c1222d..1c3c19fd 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -89,6 +89,8 @@ const (
 	// variableTrustLen means that when this variable is loaded its length
 	// should be trusted and used instead of MaxArrayValues
 	variableTrustLen
+
+	variableSaved
 )
 
 // Variable represents a variable. It contains the address, name,
@@ -1235,7 +1237,7 @@ func (v *Variable) maybeDereference() *Variable {
 
 	switch t := v.RealType.(type) {
 	case *godwarf.PtrType:
-		if v.Addr == 0 && len(v.Children) == 1 && v.loaded {
+		if (v.Addr == 0 || v.Flags&VariableFakeAddress != 0) && len(v.Children) == 1 && v.loaded {
 			// fake pointer variable constructed by casting an integer to a pointer type
 			return &v.Children[0]
 		}
@@ -1469,7 +1471,7 @@ func convertToEface(srcv, dstv *Variable) error {
 	}
 	typeAddr, typeKind, runtimeTypeFound, err := dwarfToRuntimeType(srcv.bi, srcv.mem, srcv.RealType)
 	if err != nil {
-		return err
+		return fmt.Errorf("can not convert value of type %s to %s: %v", srcv.DwarfType.String(), dstv.DwarfType.String(), err)
 	}
 	if !runtimeTypeFound || typeKind&kindDirectIface == 0 {
 		return &typeConvErr{srcv.DwarfType, dstv.RealType}
diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go
index 0fe452da..2e2ad1c2 100644
--- a/pkg/proc/variables_test.go
+++ b/pkg/proc/variables_test.go
@@ -912,14 +912,14 @@ func getEvalExpressionTestCases() []varTest {
 		{"bytearray[0] * bytearray[0]", false, "144", "144", "uint8", nil},
 
 		// function call / typecast errors
-		{"unknownthing(1, 2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
-		{"(unknownthing)(1, 2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
+		{"unknownthing(1, 2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
+		{"(unknownthing)(1, 2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
 		{"afunc(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
 		{"(afunc)(2)", false, "", "", "", errors.New("function calls not allowed without using 'call'")},
-		{"(*afunc)(2)", false, "", "", "", errors.New("expression \"afunc\" (func()) can not be dereferenced")},
-		{"unknownthing(2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
-		{"(*unknownthing)(2)", false, "", "", "", errors.New("could not find symbol value for unknownthing")},
-		{"(*strings.Split)(2)", false, "", "", "", errors.New("could not find symbol value for strings")},
+		{"(*afunc)(2)", false, "", "", "", errors.New("*")},
+		{"unknownthing(2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
+		{"(*unknownthing)(2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for unknownthing")},
+		{"(*strings.Split)(2)", false, "", "", "", altErrors("function calls not allowed without using 'call'", "could not find symbol value for strings")},
 
 		// pretty printing special types
 		{"tim1", false, `time.Time(1977-05-25T18:00:00Z)…`, `time.Time(1977-05-25T18:00:00Z)…`, "time.Time", nil},
@@ -977,6 +977,18 @@ func getEvalExpressionTestCases() []varTest {
 	return testcases
 }
 
+func altErrors(errs ...string) *altError {
+	return &altError{errs}
+}
+
+type altError struct {
+	errs []string
+}
+
+func (err *altError) Error() string {
+	return "[multiple alternatives]"
+}
+
 func TestEvalExpression(t *testing.T) {
 	testcases := getEvalExpressionTestCases()
 	protest.AllowRecording(t)
@@ -1000,8 +1012,22 @@ func TestEvalExpression(t *testing.T) {
 					if err == nil {
 						t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name)
 					}
-					if tc.err.Error() != err.Error() {
-						t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
+					switch e := tc.err.(type) {
+					case *altError:
+						ok := false
+						for _, tgtErr := range e.errs {
+							if tgtErr == err.Error() {
+								ok = true
+								break
+							}
+						}
+						if !ok {
+							t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
+						}
+					default:
+						if tc.err.Error() != "*" && tc.err.Error() != err.Error() {
+							t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
+						}
 					}
 				}
 			})
@@ -1272,9 +1298,10 @@ func TestIssue1075(t *testing.T) {
 }
 
 type testCaseCallFunction struct {
-	expr string   // call expression to evaluate
-	outs []string // list of return parameters in this format: ::
-	err  error    // if not nil should return an error
+	expr     string   // call expression to evaluate
+	outs     []string // list of return parameters in this format: ::
+	err      error    // if not nil should return an error
+	pinCount int      // where debugPinner is supported this is the number of pins created during the function call injection
 }
 
 func TestCallFunction(t *testing.T) {
@@ -1286,126 +1313,126 @@ func TestCallFunction(t *testing.T) {
 	var testcases = []testCaseCallFunction{
 		// Basic function call injection tests
 
-		{"call1(one, two)", []string{":int:3"}, nil},
-		{"call1(one+two, 4)", []string{":int:7"}, nil},
-		{"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil},
-		{`stringsJoin(nil, "")`, []string{`:string:""`}, nil},
-		{`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil},
-		{`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil},
-		{`stringsJoin(s1, comma)`, nil, errors.New(`could not find symbol value for s1`)},
-		{`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")},
-		{`noreturncall(2)`, nil, nil},
+		{"call1(one, two)", []string{":int:3"}, nil, 0},
+		{"call1(one+two, 4)", []string{":int:7"}, nil, 0},
+		{"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil, 0},
+		{`stringsJoin(nil, "")`, []string{`:string:""`}, nil, 0},
+		{`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 1},
+		{`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil, 2},
+		{`stringsJoin(s1, comma)`, nil, errors.New(`could not find symbol value for s1`), 1},
+		{`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string"), 1},
+		{`noreturncall(2)`, nil, nil, 0},
 
 		// Expression tests
-		{`square(2) + 1`, []string{":int:5"}, nil},
-		{`intcallpanic(1) + 1`, []string{":int:2"}, nil},
-		{`intcallpanic(0) + 1`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil},
-		{`onetwothree(5)[1] + 2`, []string{":int:9"}, nil},
+		{`square(2) + 1`, []string{":int:5"}, nil, 0},
+		{`intcallpanic(1) + 1`, []string{":int:2"}, nil, 0},
+		{`intcallpanic(0) + 1`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil, 0},
+		{`onetwothree(5)[1] + 2`, []string{":int:9"}, nil, 1},
 
 		// Call types tests (methods, function pointers, etc.)
 		// The following set of calls was constructed using https://docs.google.com/document/d/1bMwCey-gmqZVTpRax-ESeVuZGmjwbocYs1iHplK-cjo/pub as a reference
 
-		{`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value
+		{`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 1}, // direct call of a method with value receiver / on a value
 
-		{`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil},  // direct call of a method with pointer receiver / on a value
-		{`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil},  // direct call of a method with value receiver / on a pointer
-		{`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil}, // direct call of a method with pointer receiver / on a pointer
+		{`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil, 1},  // direct call of a method with pointer receiver / on a value
+		{`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 1},  // direct call of a method with value receiver / on a pointer
+		{`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 1}, // direct call of a method with pointer receiver / on a pointer
 
-		{`vable_pa.VRcvr(6)`, []string{`:string:"6 + 6 = 12"`}, nil}, // indirect call of method on interface / containing value with value method
-		{`pable_pa.PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil},  // indirect call of method on interface / containing pointer with value method
-		{`vable_a.VRcvr(5)`, []string{`:string:"5 + 3 = 8"`}, nil},   // indirect call of method on interface / containing pointer with pointer method
+		{`vable_pa.VRcvr(6)`, []string{`:string:"6 + 6 = 12"`}, nil, 1}, // indirect call of method on interface / containing value with value method
+		{`pable_pa.PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 1},  // indirect call of method on interface / containing pointer with value method
+		{`vable_a.VRcvr(5)`, []string{`:string:"5 + 3 = 8"`}, nil, 1},   // indirect call of method on interface / containing pointer with pointer method
 
-		{`pa.nonexistent()`, nil, errors.New("pa has no member nonexistent")},
-		{`a.nonexistent()`, nil, errors.New("a has no member nonexistent")},
-		{`vable_pa.nonexistent()`, nil, errors.New("vable_pa has no member nonexistent")},
-		{`vable_a.nonexistent()`, nil, errors.New("vable_a has no member nonexistent")},
-		{`pable_pa.nonexistent()`, nil, errors.New("pable_pa has no member nonexistent")},
+		{`pa.nonexistent()`, nil, errors.New("pa has no member nonexistent"), 0},
+		{`a.nonexistent()`, nil, errors.New("a has no member nonexistent"), 0},
+		{`vable_pa.nonexistent()`, nil, errors.New("vable_pa has no member nonexistent"), 0},
+		{`vable_a.nonexistent()`, nil, errors.New("vable_a has no member nonexistent"), 0},
+		{`pable_pa.nonexistent()`, nil, errors.New("pable_pa has no member nonexistent"), 0},
 
-		{`fn2glob(10, 20)`, []string{":int:30"}, nil},               // indirect call of func value / set to top-level func
-		{`fn2clos(11)`, []string{`:string:"1 + 6 + 11 = 18"`}, nil}, // indirect call of func value / set to func literal
-		{`fn2clos(12)`, []string{`:string:"2 + 6 + 12 = 20"`}, nil},
-		{`fn2valmeth(13)`, []string{`:string:"13 + 6 = 19"`}, nil}, // indirect call of func value / set to value method
-		{`fn2ptrmeth(14)`, []string{`:string:"14 - 6 = 8"`}, nil},  // indirect call of func value / set to pointer method
+		{`fn2glob(10, 20)`, []string{":int:30"}, nil, 0},               // indirect call of func value / set to top-level func
+		{`fn2clos(11)`, []string{`:string:"1 + 6 + 11 = 18"`}, nil, 1}, // indirect call of func value / set to func literal
+		{`fn2clos(12)`, []string{`:string:"2 + 6 + 12 = 20"`}, nil, 1},
+		{`fn2valmeth(13)`, []string{`:string:"13 + 6 = 19"`}, nil, 1}, // indirect call of func value / set to value method
+		{`fn2ptrmeth(14)`, []string{`:string:"14 - 6 = 8"`}, nil, 1},  // indirect call of func value / set to pointer method
 
-		{"fn2nil()", nil, errors.New("nil pointer dereference")},
+		{"fn2nil()", nil, errors.New("nil pointer dereference"), 0},
 
-		{"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil},
+		{"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil, 1},
 
-		{"x.CallMe()", nil, nil},
-		{"x2.CallMe(5)", []string{":int:25"}, nil},
+		{"x.CallMe()", nil, nil, 0},
+		{"x2.CallMe(5)", []string{":int:25"}, nil, 0},
 
-		{"\"delve\".CallMe()", nil, errors.New("\"delve\" (type string) is not a struct")},
+		{"\"delve\".CallMe()", nil, errors.New("\"delve\" (type string) is not a struct"), 0},
 
 		// Nested function calls tests
 
-		{`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil},
-		{`onetwothree(intcallpanic(0))`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil},
-		{`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil},
-		{`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int")},
+		{`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil, 1},
+		{`onetwothree(intcallpanic(0))`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil, 0},
+		{`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil, 1},
+		{`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int"), 1},
 
 		// Variable setting tests
-		{`pa2 = getAStructPtr(8); pa2`, []string{`pa2:*main.astruct:*main.astruct {X: 8}`}, nil},
+		{`pa2 = getAStructPtr(8); pa2`, []string{`pa2:*main.astruct:*main.astruct {X: 8}`}, nil, 1},
 
 		// Escape tests
 
-		{"escapeArg(&a2)", nil, errors.New("cannot use &a2 as argument pa2 in function main.escapeArg: stack object passed to escaping pointer: pa2")},
+		{"escapeArg(&a2)", nil, errors.New("cannot use &a2 as argument pa2 in function main.escapeArg: stack object passed to escaping pointer: pa2"), 0},
 
 		// Issue 1577
-		{"1+2", []string{`::3`}, nil},
-		{`"de"+"mo"`, []string{`::"demo"`}, nil},
+		{"1+2", []string{`::3`}, nil, 0},
+		{`"de"+"mo"`, []string{`::"demo"`}, nil, 0},
 
 		// Issue 3176
-		{`ref.String()[0]`, []string{`:byte:98`}, nil},
-		{`ref.String()[20]`, nil, errors.New("index out of bounds")},
+		{`ref.String()[0]`, []string{`:byte:98`}, nil, 1},
+		{`ref.String()[20]`, nil, errors.New("index out of bounds"), 1},
 	}
 
 	var testcases112 = []testCaseCallFunction{
 		// string allocation requires trusted argument order, which we don't have in Go 1.11
-		{`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil},
-		{`str = "a new string"; str`, []string{`str:string:"a new string"`}, nil},
+		{`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
+		{`str = "a new string"; str`, []string{`str:string:"a new string"`}, nil, 1},
 
 		// support calling optimized functions
-		{`strings.Join(nil, "")`, []string{`:string:""`}, nil},
-		{`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil},
-		{`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string")},
-		{`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil},
-		{`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil},
-		{`strings.LastIndexByte(stringslice[1], 'o')`, []string{":int:2"}, nil},
-		{`d.Base.Method()`, []string{`:int:4`}, nil},
-		{`d.Method()`, []string{`:int:4`}, nil},
+		{`strings.Join(nil, "")`, []string{`:string:""`}, nil, 0},
+		{`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 1},
+		{`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string"), 1},
+		{`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
+		{`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil, 0},
+		{`strings.LastIndexByte(stringslice[1], 'o')`, []string{":int:2"}, nil, 0},
+		{`d.Base.Method()`, []string{`:int:4`}, nil, 0},
+		{`d.Method()`, []string{`:int:4`}, nil, 0},
 	}
 
 	var testcases113 = []testCaseCallFunction{
-		{`curriedAdd(2)(3)`, []string{`:int:5`}, nil},
+		{`curriedAdd(2)(3)`, []string{`:int:5`}, nil, 1},
 
 		// Method calls on a value returned by a function
 
-		{`getAStruct(3).VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value
+		{`getAStruct(3).VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 1}, // direct call of a method with value receiver / on a value
 
-		{`getAStruct(3).PRcvr(2)`, nil, errors.New("cannot use getAStruct(3).PRcvr as argument pa in function main.(*astruct).PRcvr: stack object passed to escaping pointer: pa")}, // direct call of a method with pointer receiver / on a value
-		{`getAStructPtr(6).VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil},  // direct call of a method with value receiver / on a pointer
-		{`getAStructPtr(6).PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil}, // direct call of a method with pointer receiver / on a pointer
+		{`getAStruct(3).PRcvr(2)`, nil, errors.New("could not set call receiver: cannot use getAStruct(3).PRcvr as argument pa in function main.(*astruct).PRcvr: stack object passed to escaping pointer: pa"), 0}, // direct call of a method with pointer receiver / on a value
+		{`getAStructPtr(6).VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 2},  // direct call of a method with value receiver / on a pointer
+		{`getAStructPtr(6).PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 2}, // direct call of a method with pointer receiver / on a pointer
 
-		{`getVRcvrableFromAStruct(3).VRcvr(6)`, []string{`:string:"6 + 3 = 9"`}, nil},     // indirect call of method on interface / containing value with value method
-		{`getPRcvrableFromAStructPtr(6).PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil},  // indirect call of method on interface / containing pointer with value method
-		{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method
+		{`getVRcvrableFromAStruct(3).VRcvr(6)`, []string{`:string:"6 + 3 = 9"`}, nil, 3},     // indirect call of method on interface / containing value with value method
+		{`getPRcvrableFromAStructPtr(6).PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 3},  // indirect call of method on interface / containing pointer with value method
+		{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil, 3}, // indirect call of method on interface / containing pointer with pointer method
 	}
 
 	var testcasesBefore114After112 = []testCaseCallFunction{
-		{`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`)},
+		{`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`), 1},
 	}
 
 	var testcases114 = []testCaseCallFunction{
-		{`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`)},
+		{`strings.Join(s1, comma)`, nil, errors.New(`could not find symbol value for s1`), 1},
 	}
 
 	var testcases117 = []testCaseCallFunction{
-		{`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil},
-		{`regabistacktest2(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`, []string{":int:3", ":int:5", ":int:7", ":int:9", ":int:11", ":int:13", ":int:15", ":int:17", ":int:19", ":int:11"}, nil},
-		{`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil},
-		{`issue3364.String()`, []string{`:string:"1 2"`}, nil},
-		{`regabistacktest3(rast3, 5)`, []string{`:[10]string:[10]string ["onetwo","twothree","threefour","fourfive","fivesix","sixseven","sevenheight","heightnine","nineten","tenone"]`, ":uint8:15"}, nil},
-		{`floatsum(1, 2)`, []string{":float64:3"}, nil},
+		{`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil, 10},
+		{`regabistacktest2(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`, []string{":int:3", ":int:5", ":int:7", ":int:9", ":int:11", ":int:13", ":int:15", ":int:17", ":int:19", ":int:11"}, nil, 0},
+		{`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil, 1},
+		{`issue3364.String()`, []string{`:string:"1 2"`}, nil, 1},
+		{`regabistacktest3(rast3, 5)`, []string{`:[10]string:[10]string ["onetwo","twothree","threefour","fourfive","fivesix","sixseven","sevenheight","heightnine","nineten","tenone"]`, ":uint8:15"}, nil, 10},
+		{`floatsum(1, 2)`, []string{":float64:3"}, nil, 0},
 	}
 
 	withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
@@ -1447,7 +1474,7 @@ func TestCallFunction(t *testing.T) {
 		}
 
 		// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
-		testCallFunction(t, grp, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
+		testCallFunction(t, grp, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil, 0})
 	})
 }
 
@@ -1463,6 +1490,12 @@ func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, grp *proc.Targe
 }
 
 func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc testCaseCallFunction) {
+	t.Run(tc.expr, func(t *testing.T) {
+		testCallFunctionIntl(t, grp, p, tc)
+	})
+}
+
+func testCallFunctionIntl(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc testCaseCallFunction) {
 	const unsafePrefix = "-unsafe "
 
 	var callExpr, varExpr string
@@ -1493,7 +1526,11 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te
 	}
 
 	if err != nil {
-		t.Fatalf("call %q: error %q", tc.expr, err.Error())
+		if strings.HasPrefix(err.Error(), "internal debugger error") {
+			t.Fatalf("call %q: error %s", tc.expr, err.Error())
+		} else {
+			t.Fatalf("call %q: error %q", tc.expr, err.Error())
+		}
 	}
 
 	retvalsVar := p.CurrentThread().Common().ReturnValues(pnormalLoadConfig)
@@ -1538,6 +1575,13 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te
 			t.Fatalf("call %q, output parameter %d: expected value %q, got %q", tc.expr, i, tgtValue, cvs)
 		}
 	}
+
+	if p.BinInfo().HasDebugPinner() {
+		t.Logf("\t(pins = %d)", proc.DebugPinCount())
+		if proc.DebugPinCount() != tc.pinCount {
+			t.Fatalf("call %q, expected pin count %d, got %d", tc.expr, tc.pinCount, proc.DebugPinCount())
+		}
+	}
 }
 
 func TestIssue1531(t *testing.T) {