Repository: go-delve/delve

Issue:
[dap/launch] race condition causes nil pointer deref when using `--backend=rr`
(It appears the issue template is broken for me right now and not showing up, I'll do my best to edit it back in)

dlv version
```sh
$ dlv version
Delve Debugger
Version: 1.24.1
Build: $Id: 7c2184d58d69f0717b8620fdebc56d34174f9945 $

$ go version
go version go1.24.1 linux/amd64
```
(dlv, go, and rr have been installed from nixpkgs@26b2bef8b3c73a0931af73d902af2d806588f6bb)

When running dlv as dap server (`dlv dap`) and sending a launch config using `rr` as backend
example config: 
```json
{
	"mode": "test",
	"program": "/REDACTED/internal/merge/model",
	"args": [
		"-rapid.checks=5"
	],
	"cwd": "/REDACTED/internal/merge/model",
	"buildFlags": {},
	"output": "//REDACTED/internal/merge/model/__debug_bin1638317219",
	"traceDirPath": "/REDACTED/internal/merge/model/testdata/rr/",
	"dlvCwd": "/REDACTED/internal/merge/model",
	"env": {
		"GOPATH": "/REDACTED/go"
	},
	"backend": "rr",
	"stackTraceDepth": 50
}
```
delve will panic trying to deref debugger.target inside of https://github.com/go-delve/delve/blob/4dc820ee3c3364152aff34976927f051d800d6ca/service/dap/server.go#L1163C23-L1163C42

I have tracked the cause to a simple race condition. for backend RR, [setting the target group is delayed ](https://github.com/go-delve/delve/blob/4dc820ee3c3364152aff34976927f051d800d6ca/service/debugger/debugger.go#L316) to wait for the asynchronous recording to finish. [The  comment above the goroutine](https://github.com/go-delve/delve/blob/4dc820ee3c3364152aff34976927f051d800d6ca/service/debugger/debugger.go#L301) points this out as intentional and safe due to the mutex, however, [`Target()`](https://github.com/go-delve/delve/blob/4dc820ee3c3364152aff34976927f051d800d6ca/service/debugger/debugger.go#L2222) and [`TargetGroup()`](https://github.com/go-delve/delve/blob/4dc820ee3c3364152aff34976927f051d800d6ca/service/debugger/debugger.go#L2226) don't respect the mutex before accessing `d.target`.

I'm unsure whether the public getters are intentionally not blocked by the mutex, however this directly leads to the nil deref, since the recording, especially of more complicated tests (i.e. rapid as in my example above) can take a while, making the recording goroutine lose the race.

I have confirmed that simply adding a Lock and Unlock to the start up of the dap server fixes the issue for me.
```diff
diff --git a/service/dap/server.go b/service/dap/server.go
index 46ce6a0d..365504a1 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -1157,6 +1157,8 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
                s.debugger, err = debugger.New(&s.config.Debugger, s.config.ProcessArgs)
 
                if s.debugger != nil {

+                       s.debugger.LockTarget()
+                       // Unlock immediatey, otherwise ProcessPid() will hang waiting for the mutex
+                       s.debugger.UnlockTarget()
                         s.send(&dap.ProcessEvent{
                                Event: *newEvent("process"),
                                Body: dap.ProcessEventBody{
					SystemProcessId: s.debugger.ProcessPid(),
```

I'm not certain if this fix is durable enough, since it implicitly assumes that if d.target is not yet initialized, it will be once the first holder of the TargetMutex releases it and that it will never become invalid again (athough this might be a reasonable internal contract). I will create a basic pull request using the diff above and then test whether adding the mutex to the Target() and TargetGroup() methods also works

Discussion:
@BronzeDeer: On further inspection there actually are quite a few public methods that access d.target but don't lock the mutex first:

Target(), TargetGroup(), CreateWatchpoint(), Command()[1], DwarfRegisterToString(), GetVersion()[2], DumpTarget()[3], GetBufferedTracepoints(), 

[1] Command() handling its "halt" branch without the target mutex, where it is explicitly checking for the state of the recording, this might also be a pattern that is preferable to the current solution for the dap launch.
[2] GetVersion() indirectly against this race on `d.target` by only accessing d.target if not running and not a recording, however I don't know if this will exclude all other races on `d.target` in the future
[3] DumpTarget() is protected, but has a TODO note from @aarzilli about delayed unlocking of the mutex

However, given the generally established pattern inside the public methods, maybe the methods above are simply oversights? I'm happy to change the PR to adding all missing mutex locks if that is preferred
@aarzilli: Note that debugger methods are called only by the loop in `service/rpccommon/server.go` and in `service/dap/server.go` which already take care of not running multiple requests concurrently in most cases. It doesn't make sense to make Debugger thread safe in the traditional sense because, due to kernel API constraints on anything except macOS, there can be only one of it in use at any given time per process.

Further constraints are that Debugger.target itself is only ever changed by Launch and Restart.

CreateWatchpoint is definitely missing a lock,  the rest are unconvincing to me. In particular if you acquire the lock in the halt section of Command you will actually create a deadlock, GetBufferedTracepoints too.@BronzeDeer: > Note that debugger methods are called only by the loop in `service/rpccommon/server.go` and in `service/dap/server.go` which already take care of not running multiple requests concurrently in most cases. It doesn't make sense to make Debugger thread safe in the traditional sense because, due to kernel API constraints on anything except macOS, there can be only one of it in use at any given time per process.
> 
> Further constraints are that Debugger.target itself is only ever changed by Launch and Restart.
> 
> CreateWatchpoint is definitely missing a lock, the rest are unconvincing to me. In particular if you acquire the lock in the halt section of Command you will actually create a deadlock, GetBufferedTracepoints too.

Thank you. This insight was very much what I was looking for, I'm not familiar with delve's internal architecture and my overview of the methods was definitiely a naive one without the necessary context to sort the methods by what deliberate holds no mutex and what was an oversight (Since I have no idea what target even is in details and what the lifecycle of the struct is, I only stumbled in here because my time-travel debugging in VSC kept crashing on launch while running the debugger directly worked.

I'll create a separate PR for the mutex for "CreateWatchpoint()"

Solution patch:
diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go
index d181387a..3d0ef859 100644
--- a/pkg/proc/core/core.go
+++ b/pkg/proc/core/core.go
@@ -319,18 +319,6 @@ func (t *thread) ProcessMemory() proc.MemoryReadWriter {
 	return t.p
 }
 
-// Location returns the location of this thread based on
-// the value of the instruction pointer register.
-func (t *thread) Location() (*proc.Location, error) {
-	regs, err := t.Registers()
-	if err != nil {
-		return nil, err
-	}
-	pc := regs.PC()
-	f, l, fn := t.p.bi.PCToLine(pc)
-	return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
-}
-
 // Breakpoint returns the current breakpoint this thread is stopped at.
 // For core files this always returns an empty BreakpointState struct, as
 // there are no breakpoints when debugging core files.
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index 9b4f8c85..856d8b05 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -815,7 +815,7 @@ func funcCallStep(callScope *EvalScope, stack *evalStack, thread Thread) bool {
 	regval := bi.Arch.RegistersToDwarfRegisters(0, regs).Uint64Val(fncall.protocolReg)
 
 	if logflags.FnCall() {
-		loc, _ := thread.Location()
+		loc, _ := ThreadLocation(thread)
 		var pc uint64
 		var fnname string
 		if loc != nil {
@@ -1151,7 +1151,7 @@ func callInjectionProtocol(t *Target, trapthread Thread, threads []Thread) (done
 		t.currentThread = currentThread
 	}()
 	for _, thread := range threads {
-		loc, err := thread.Location()
+		loc, err := ThreadLocation(thread)
 		if err != nil {
 			continue
 		}
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index 35d7091d..b58d0387 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -1523,22 +1523,6 @@ func (t *gdbThread) ProcessMemory() proc.MemoryReadWriter {
 	return t.p
 }
 
-// Location returns the current location of this thread.
-func (t *gdbThread) Location() (*proc.Location, error) {
-	regs, err := t.Registers()
-	if err != nil {
-		return nil, err
-	}
-	if pcreg, ok := regs.(*gdbRegisters).regs[regs.(*gdbRegisters).regnames.PC]; !ok {
-		t.p.conn.log.Errorf("thread %d could not find RIP register", t.ID)
-	} else if len(pcreg.value) < t.p.bi.Arch.PtrSize() {
-		t.p.conn.log.Errorf("thread %d bad length for RIP register: %d", t.ID, len(pcreg.value))
-	}
-	pc := regs.PC()
-	f, l, fn := t.p.bi.PCToLine(pc)
-	return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
-}
-
 // Breakpoint returns the current active breakpoint for this thread.
 func (t *gdbThread) Breakpoint() *proc.BreakpointState {
 	return &t.CurrentBreakpoint
diff --git a/pkg/proc/native/threads.go b/pkg/proc/native/threads.go
index daf38d71..56ae4529 100644
--- a/pkg/proc/native/threads.go
+++ b/pkg/proc/native/threads.go
@@ -77,18 +77,6 @@ func (procgrp *processGroup) stepInstruction(t *nativeThread) (err error) {
 	return nil
 }
 
-// Location returns the threads location, including the file:line
-// of the corresponding source code, the function we're in
-// and the current instruction address.
-func (t *nativeThread) Location() (*proc.Location, error) {
-	pc, err := t.PC()
-	if err != nil {
-		return nil, err
-	}
-	f, l, fn := t.dbp.bi.PCToLine(pc)
-	return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
-}
-
 // BinInfo returns information on the binary.
 func (t *nativeThread) BinInfo() *proc.BinaryInfo {
 	return t.dbp.bi
diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go
index dc716457..8839279a 100644
--- a/pkg/proc/target_exec.go
+++ b/pkg/proc/target_exec.go
@@ -444,7 +444,7 @@ func stepInstructionOut(grp *TargetGroup, dbp *Target, curthread Thread, fnname1
 		if err := grp.procgrp.StepInstruction(curthread.ThreadID()); err != nil {
 			return err
 		}
-		loc, err := curthread.Location()
+		loc, err := ThreadLocation(curthread)
 		var locFnName string
 		if loc.Fn != nil && !loc.Fn.cu.image.Stripped() {
 			locFnName = loc.Fn.Name
@@ -995,7 +995,7 @@ func stepIntoCallback(curthread Thread, p *Target) (bool, error) {
 	}
 
 	var fn *Function
-	if loc, _ := curthread.Location(); loc != nil {
+	if loc, _ := ThreadLocation(curthread); loc != nil {
 		fn = loc.Fn
 	}
 	g, _ := GetG(curthread)
@@ -1675,7 +1675,7 @@ func (t *Target) handleHardcodedBreakpoints(grp *TargetGroup, trapthread Thread,
 			continue
 		}
 
-		loc, err := thread.Location()
+		loc, err := ThreadLocation(thread)
 		if err != nil || loc.Fn == nil {
 			continue
 		}
diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go
index d349c30a..4fb78b3b 100644
--- a/pkg/proc/threads.go
+++ b/pkg/proc/threads.go
@@ -8,7 +8,6 @@ import (
 
 // Thread represents a thread.
 type Thread interface {
-	Location() (*Location, error)
 	// Breakpoint will return the breakpoint that this thread is stopped at or
 	// nil if the thread is not stopped at any breakpoint.
 	Breakpoint() *BreakpointState
@@ -103,3 +102,16 @@ func setClosureReg(thread Thread, newClosureReg uint64) error {
 func setLR(thread Thread, newLR uint64) error {
 	return thread.SetReg(thread.BinInfo().Arch.LRRegNum, op.DwarfRegisterFromUint64(newLR))
 }
+
+// ThreadLocation returns the threads location, including the file:line
+// of the corresponding source code, the function we're in
+// and the current instruction address.
+func ThreadLocation(thread Thread) (*Location, error) {
+	regs, err := thread.Registers()
+	if err != nil {
+		return nil, err
+	}
+	pc := regs.PC()
+	f, l, fn := thread.BinInfo().PCToLine(pc)
+	return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil
+}
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 6be6c1e3..d457b3fe 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -43,6 +43,8 @@ const (
 	maxMapBucketsFactor = 100 // Maximum numbers of map buckets to read for every requested map entry when loading variables through (*EvalScope).LocalVariables and (*EvalScope).FunctionArguments.
 
 	maxGoroutineUserCurrentDepth = 30 // Maximum depth used by (*G).UserCurrent to search its location
+
+	maxGoroutinesLabelEntries = 1_000 // Maximum number of label entries for a goroutine's label map
 )
 
 type floatSpecial uint8
@@ -257,7 +259,8 @@ func GetG(thread Thread) (*G, error) {
 	if thread.Common().g != nil {
 		return thread.Common().g, nil
 	}
-	if loc, _ := thread.Location(); loc != nil && loc.Fn != nil && loc.Fn.Name == "runtime.clone" {
+	loc, _ := ThreadLocation(thread)
+	if loc != nil && loc.Fn != nil && loc.Fn.Name == "runtime.clone" {
 		// When threads are executing runtime.clone the value of TLS is unreliable.
 		return nil, nil
 	}
@@ -296,7 +299,7 @@ func GetG(thread Thread) (*G, error) {
 		g.SystemStack = true
 	}
 	g.Thread = thread
-	if loc, err := thread.Location(); err == nil {
+	if err == nil {
 		g.CurrentLoc = *loc
 	}
 	thread.Common().g = g
@@ -354,7 +357,7 @@ func GoroutinesInfo(dbp *Target, start, count int) ([]*G, int, error) {
 			continue
 		}
 		if thg, allocated := threadg[g.ID]; allocated {
-			loc, err := thg.Thread.Location()
+			loc, err := ThreadLocation(thg.Thread)
 			if err != nil {
 				return nil, -1, err
 			}
@@ -584,6 +587,13 @@ func (g *G) Labels() map[string]string {
 				case reflect.Struct:
 					labelMap, _ = labelMap.structMember("list")
 					if labelMap != nil {
+						// ensure the labelMap.Len is a valid value to prevent infinite loops
+						if labelMap.Len < 0 {
+							labelMap.Len = 0
+						}
+						if labelMap.Len > maxGoroutinesLabelEntries {
+							labelMap.Len = maxGoroutinesLabelEntries
+						}
 						for i := int64(0); i < labelMap.Len; i++ {
 							v, err := labelMap.sliceAccess(int(i))
 							if err != nil {
diff --git a/service/api/conversions.go b/service/api/conversions.go
index fb90e414..14bf025a 100644
--- a/service/api/conversions.go
+++ b/service/api/conversions.go
@@ -97,7 +97,7 @@ func ConvertThread(th proc.Thread, bp *Breakpoint) *Thread {
 		gid      int64
 	)
 
-	loc, err := th.Location()
+	loc, err := proc.ThreadLocation(th)
 	if err == nil {
 		pc = loc.PC
 		file = loc.File
diff --git a/service/dap/server.go b/service/dap/server.go
index 46ce6a0d..5421d8b8 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -1157,10 +1157,14 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
 		s.debugger, err = debugger.New(&s.config.Debugger, s.config.ProcessArgs)
 
 		if s.debugger != nil {
+			s.debugger.LockTarget()
+			cmdline := s.debugger.Target().CmdLine
+			s.debugger.UnlockTarget()
+
 			s.send(&dap.ProcessEvent{
 				Event: *newEvent("process"),
 				Body: dap.ProcessEventBody{
-					Name:            s.debugger.Target().CmdLine,
+					Name:            cmdline,
 					SystemProcessId: s.debugger.ProcessPid(),
 					IsLocalProcess:  true,
 					StartMethod:     "launch",
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index ba88e44c..ba73428c 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -989,6 +989,9 @@ func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
 
 // CreateWatchpoint creates a watchpoint on the specified expression.
 func (d *Debugger) CreateWatchpoint(goid int64, frame, deferredCall int, expr string, wtype api.WatchType) (*api.Breakpoint, error) {
+	d.targetMutex.Lock()
+	defer d.targetMutex.Unlock()
+
 	p := d.target.Selected
 
 	s, err := proc.ConvertEvalScope(p, goid, frame, deferredCall)
@@ -1890,7 +1893,7 @@ func (d *Debugger) CurrentPackage() (string, error) {
 	if _, err := d.target.Valid(); err != nil {
 		return "", err
 	}
-	loc, err := d.target.Selected.CurrentThread().Location()
+	loc, err := proc.ThreadLocation(d.target.Selected.CurrentThread())
 	if err != nil {
 		return "", err
 	}
@@ -2228,7 +2231,7 @@ func (d *Debugger) TargetGroup() *proc.TargetGroup {
 }
 
 func (d *Debugger) BuildID() string {
-	loc, err := d.target.Selected.CurrentThread().Location()
+	loc, err := proc.ThreadLocation(d.target.Selected.CurrentThread())
 	if err != nil {
 		return ""
 	}



Test patch:
diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go
index 3dc3d1e2..23fdd6dd 100644
--- a/pkg/proc/gdbserial/rr_test.go
+++ b/pkg/proc/gdbserial/rr_test.go
@@ -73,7 +73,7 @@ func TestRestartAfterExit(t *testing.T) {
 		p := grp.Selected
 		setFunctionBreakpoint(p, t, "main.main")
 		assertNoError(grp.Continue(), t, "Continue")
-		loc, err := p.CurrentThread().Location()
+		loc, err := proc.ThreadLocation(p.CurrentThread())
 		assertNoError(err, t, "CurrentThread().Location()")
 		err = grp.Continue()
 		if !errors.As(err, &proc.ErrProcessExited{}) {
@@ -83,7 +83,7 @@ func TestRestartAfterExit(t *testing.T) {
 		assertNoError(grp.Restart(""), t, "Restart")
 
 		assertNoError(grp.Continue(), t, "Continue (after restart)")
-		loc2, err := p.CurrentThread().Location()
+		loc2, err := proc.ThreadLocation(p.CurrentThread())
 		assertNoError(err, t, "CurrentThread().Location() (after restart)")
 		if loc2.Line != loc.Line {
 			t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
@@ -101,13 +101,13 @@ func TestRestartDuringStop(t *testing.T) {
 		p := grp.Selected
 		setFunctionBreakpoint(p, t, "main.main")
 		assertNoError(grp.Continue(), t, "Continue")
-		loc, err := p.CurrentThread().Location()
+		loc, err := proc.ThreadLocation(p.CurrentThread())
 		assertNoError(err, t, "CurrentThread().Location()")
 
 		assertNoError(grp.Restart(""), t, "Restart")
 
 		assertNoError(grp.Continue(), t, "Continue (after restart)")
-		loc2, err := p.CurrentThread().Location()
+		loc2, err := proc.ThreadLocation(p.CurrentThread())
 		assertNoError(err, t, "CurrentThread().Location() (after restart)")
 		if loc2.Line != loc.Line {
 			t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
@@ -143,7 +143,7 @@ func TestReverseBreakpointCounts(t *testing.T) {
 		p := grp.Selected
 		endbp := setFileBreakpoint(p, t, fixture, 28)
 		assertNoError(grp.Continue(), t, "Continue()")
-		loc, _ := p.CurrentThread().Location()
+		loc, _ := proc.ThreadLocation(p.CurrentThread())
 		if loc.PC != endbp.Addr {
 			t.Fatalf("did not reach end of main.main function: %s:%d (%#x)", loc.File, loc.Line, loc.PC)
 		}
@@ -156,7 +156,7 @@ func TestReverseBreakpointCounts(t *testing.T) {
 	countLoop:
 		for {
 			assertNoError(grp.Continue(), t, "Continue()")
-			loc, _ := p.CurrentThread().Location()
+			loc, _ := proc.ThreadLocation(p.CurrentThread())
 			switch loc.PC {
 			case startbp.Addr:
 				break countLoop
@@ -188,7 +188,7 @@ func getPosition(grp *proc.TargetGroup, t *testing.T) (when string, loc *proc.Lo
 	var err error
 	when, err = grp.When()
 	assertNoError(err, t, "When")
-	loc, err = grp.Selected.CurrentThread().Location()
+	loc, err = proc.ThreadLocation(grp.Selected.CurrentThread())
 	assertNoError(err, t, "Location")
 	return
 }
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 9ecfec7e..e8de4107 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -1627,7 +1627,7 @@ func TestStepIntoFunction(t *testing.T) {
 		// Step into function
 		assertNoError(grp.Step(), t, "Step() returned an error")
 		// We should now be inside the function.
-		loc, err := p.CurrentThread().Location()
+		loc, err := proc.ThreadLocation(p.CurrentThread())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -2462,7 +2462,7 @@ func TestNextInDeferReturn(t *testing.T) {
 		// point where the target program panics.
 		setFunctionBreakpoint(p, t, "main.sampleFunction")
 		for i := 0; i < 20; i++ {
-			loc, err := p.CurrentThread().Location()
+			loc, err := proc.ThreadLocation(p.CurrentThread())
 			assertNoError(err, t, "CurrentThread().Location()")
 			t.Logf("at %#x %s:%d", loc.PC, loc.File, loc.Line)
 			if loc.Fn != nil && loc.Fn.Name == "main.sampleFunction" {
@@ -3184,7 +3184,7 @@ func TestIssue1008(t *testing.T) {
 	withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 		setFunctionBreakpoint(p, t, "main.main")
 		assertNoError(grp.Continue(), t, "Continue()")
-		loc, err := p.CurrentThread().Location()
+		loc, err := proc.ThreadLocation(p.CurrentThread())
 		assertNoError(err, t, "CurrentThread().Location()")
 		t.Logf("location %v\n", loc)
 		if !strings.HasSuffix(loc.File, "/main.go") {
@@ -5176,7 +5176,7 @@ func TestFollowExec(t *testing.T) {
 				if grp.Selected.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 1 {
 					t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint)
 				}
-				loc, err := grp.Selected.CurrentThread().Location()
+				loc, err := proc.ThreadLocation(grp.Selected.CurrentThread())
 				assertNoError(err, t, "Location")
 				if loc.Fn.Name != "main.traceme1" {
 					t.Fatalf("wrong stop location %#v", loc)
@@ -5188,7 +5188,7 @@ func TestFollowExec(t *testing.T) {
 				if p.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 3 {
 					t.Fatalf("wrong breakpoint %#v", p.CurrentThread().Breakpoint().Breakpoint)
 				}
-				loc, err := p.CurrentThread().Location()
+				loc, err := proc.ThreadLocation(p.CurrentThread())
 				assertNoError(err, t, "Location")
 				if loc.Fn.Name != "main.traceme3" {
 					t.Fatalf("wrong stop location %#v", loc)
@@ -5208,7 +5208,7 @@ func TestFollowExec(t *testing.T) {
 						t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint)
 					}
 					pids[tgt.Pid()]++
-					loc, err := tgt.CurrentThread().Location()
+					loc, err := proc.ThreadLocation(tgt.CurrentThread())
 					assertNoError(err, t, "Location")
 					if loc.Fn.Name != "main.traceme2" {
 						t.Fatalf("wrong stop location %#v", loc)
@@ -5268,7 +5268,7 @@ func TestStepShadowConcurrentBreakpoint(t *testing.T) {
 		for {
 			t.Logf("stop (%d %d):", stacktraceme1calls, stacktraceme2calls)
 			for _, th := range p.ThreadList() {
-				loc, _ := th.Location()
+				loc, _ := proc.ThreadLocation(th)
 				t.Logf("\t%s:%d\n", loc.File, loc.Line)
 				bp := th.Breakpoint().Breakpoint
 				if bp != nil && bp.Addr == break2.Addr {
diff --git a/pkg/proc/variables_fuzz_test.go b/pkg/proc/variables_fuzz_test.go
index 9f7aac1b..6431981d 100644
--- a/pkg/proc/variables_fuzz_test.go
+++ b/pkg/proc/variables_fuzz_test.go
@@ -136,7 +136,7 @@ func doFuzzEvalExpressionSetup(f *testing.F) {
 	assertNoError(err, f, "Stacktrace")
 
 	mem := c.Memory()
-	loc, _ := c.CurrentThread().Location()
+	loc, _ := proc.ThreadLocation(c.CurrentThread())
 	tmem := &tracingMem{make(map[uint64]int), mem}
 
 	scope := &proc.EvalScope{Location: *loc, Regs: frames[0].Regs, Mem: tmem, BinInfo: c.BinInfo()}