Repository: go-delve/delve
Pull request #3103 (2022-08-12 14:52:56)

proc,service: change FindLocation to work with multiple targets
Changes FindLocation to support multiple targets and adds an AddrPid
member to api.Breakpoint so that clients can set breakpoints by address
when multiple targets are connected (but at them moment this field is
ignored).

Updates #1653
Updates #2551


aarzilli: gotip failures are due to #54411
derekparker: Sorry for the delay, will review more fully tomorrow morning my time.
aarzilli: No problem.

Patch:
diff --git a/_scripts/rtype-out.txt b/_scripts/rtype-out.txt
index 6716f0dc..8799bfa8 100644
--- a/_scripts/rtype-out.txt
+++ b/_scripts/rtype-out.txt
@@ -27,7 +27,7 @@ type g struct {
 	waitsince int64
 	waitreason waitReason (optional)
 	stack stack
-	atomicstatus anytype
+	atomicstatus uint32|runtime/internal/atomic.Uint32
 }
 
 type gobuf struct {
@@ -58,6 +58,10 @@ type moduledata struct {
 	types uintptr
 }
 
+type runtime/internal/atomic.Uint32 struct {
+	value uint32
+}
+
 type stack struct {
 	hi uintptr
 	lo uintptr
diff --git a/_scripts/rtype.go b/_scripts/rtype.go
index bb8076bc..5ba8b9c9 100644
--- a/_scripts/rtype.go
+++ b/_scripts/rtype.go
@@ -512,20 +512,44 @@ func report() {
 	}
 }
 
-// check parses the runtime package and checks that all the rules retrieved
-// from the 'proc' package pass.
-func check() {
-	pkgs2, err := packages.Load(&packages.Config{Mode: packages.LoadSyntax, Fset: fset}, "runtime")
+func lookupPackage(pkgmap map[string]*packages.Package, name string) *packages.Package {
+	if pkgmap[name] != nil {
+		return pkgmap[name]
+	}
+
+	pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadSyntax, Fset: fset}, name)
 	if err != nil {
 		log.Fatalf("could not load runtime package: %v", err)
 	}
+	packages.Visit(pkgs, func(pkg *packages.Package) bool {
+		if pkgmap[pkg.ID] == nil {
+			pkgmap[pkg.ID] = pkg
+		}
+		return true
+	}, nil)
+
+	return pkgmap[name]
+}
+
+func lookupTypeDef(pkgmap map[string]*packages.Package, typ string) types.Object {
+	dot := strings.Index(typ, ".")
+	if dot < 0 {
+		return lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(typ)
+	}
+
+	return lookupPackage(pkgmap, typ[:dot]).Types.Scope().Lookup(typ[dot+1:])
+}
 
+// check parses the runtime package and checks that all the rules retrieved
+// from the 'proc' package pass.
+func check() {
+	pkgmap := map[string]*packages.Package{}
 	allok := true
 
 	for _, rule := range checkVarTypeRules {
 		//TODO: implement
 		pos := fset.Position(rule.pos)
-		def := pkgs2[0].Types.Scope().Lookup(rule.V)
+		def := lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(rule.V)
 		if def == nil {
 			fmt.Fprintf(os.Stderr, "%s:%d: could not find variable %s\n", pos.Filename, pos.Line, rule.V)
 			allok = false
@@ -547,7 +571,7 @@ func check() {
 		rules := checkFieldTypeRules[S]
 		pos := fset.Position(rules[0].pos)
 
-		def := pkgs2[0].Types.Scope().Lookup(S)
+		def := lookupTypeDef(pkgmap, S)
 		if def == nil {
 			fmt.Fprintf(os.Stderr, "%s:%d: could not find struct %s\n", pos.Filename, pos.Line, S)
 			allok = false
@@ -594,7 +618,7 @@ func check() {
 	for _, C := range Cs {
 		rules := checkConstValRules[C]
 		pos := fset.Position(rules[0].pos)
-		def := pkgs2[0].Types.Scope().Lookup(C)
+		def := lookupPackage(pkgmap, "runtime").Types.Scope().Lookup(C)
 		if def == nil {
 			fmt.Fprintf(os.Stderr, "%s:%d: could not find constant %s\n", pos.Filename, pos.Line, C)
 			allok = false
diff --git a/pkg/locspec/locations.go b/pkg/locspec/locations.go
index 721398e6..1d6171cf 100644
--- a/pkg/locspec/locations.go
+++ b/pkg/locspec/locations.go
@@ -370,7 +370,7 @@ func (ale AmbiguousLocationError) Error() string {
 func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
 	limit := maxFindLocationCandidates
 	var candidateFiles []string
-	for _, sourceFile := range scope.BinInfo.Sources {
+	for _, sourceFile := range t.BinInfo().Sources {
 		substFile := sourceFile
 		if len(substitutePathRules) > 0 {
 			substFile = SubstitutePath(sourceFile, substitutePathRules)
@@ -387,10 +387,10 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
 
 	var candidateFuncs []string
 	if loc.FuncBase != nil && limit > 0 {
-		candidateFuncs = loc.findFuncCandidates(scope, limit)
+		candidateFuncs = loc.findFuncCandidates(t.BinInfo(), limit)
 	}
 
-	if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
+	if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 && scope != nil {
 		// if no result was found this locations string could be an
 		// expression that the user forgot to prefix with '*', try treating it as
 		// such.
@@ -428,14 +428,14 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
 	return []api.Location{addressesToLocation(addrs)}, nil
 }
 
-func (loc *NormalLocationSpec) findFuncCandidates(scope *proc.EvalScope, limit int) []string {
+func (loc *NormalLocationSpec) findFuncCandidates(bi *proc.BinaryInfo, limit int) []string {
 	candidateFuncs := map[string]struct{}{}
 	// See if it matches generic functions first
-	for fname := range scope.BinInfo.LookupGenericFunc() {
+	for fname := range bi.LookupGenericFunc() {
 		if len(candidateFuncs) >= limit {
 			break
 		}
-		if !loc.FuncBase.Match(&proc.Function{Name: fname}, scope.BinInfo.PackageMap) {
+		if !loc.FuncBase.Match(&proc.Function{Name: fname}, bi.PackageMap) {
 			continue
 		}
 		if loc.Base == fname {
@@ -443,11 +443,11 @@ func (loc *NormalLocationSpec) findFuncCandidates(scope *proc.EvalScope, limit i
 		}
 		candidateFuncs[fname] = struct{}{}
 	}
-	for _, f := range scope.BinInfo.LookupFunc {
+	for _, f := range bi.LookupFunc {
 		if len(candidateFuncs) >= limit {
 			break
 		}
-		if !loc.FuncBase.Match(f, scope.BinInfo.PackageMap) {
+		if !loc.FuncBase.Match(f, bi.PackageMap) {
 			continue
 		}
 		if loc.Base == f.Name {
diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go
index b000d566..0cc06df3 100644
--- a/pkg/proc/amd64_arch.go
+++ b/pkg/proc/amd64_arch.go
@@ -104,9 +104,9 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 		if rule.Offset == crosscall2SPOffsetBad {
 			switch bi.GOOS {
 			case "windows":
-				rule.Offset += crosscall2SPOffsetWindows
+				rule.Offset += crosscall2SPOffsetWindowsAMD64
 			default:
-				rule.Offset += crosscall2SPOffsetNonWindows
+				rule.Offset += crosscall2SPOffset
 			}
 		}
 		fctxt.CFA = rule
diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go
index dc592c70..22de6b16 100644
--- a/pkg/proc/arch.go
+++ b/pkg/proc/arch.go
@@ -149,7 +149,7 @@ func nameToDwarfFunc(n2d map[string]int) func(string) (int, bool) {
 
 // crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_amd64.s.
 const (
-	crosscall2SPOffsetBad        = 0x8
-	crosscall2SPOffsetWindows    = 0x118
-	crosscall2SPOffsetNonWindows = 0x58
+	crosscall2SPOffsetBad          = 0x8
+	crosscall2SPOffsetWindowsAMD64 = 0x118
+	crosscall2SPOffset             = 0x58
 )
diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go
index 4e44f3ae..ddd9797e 100644
--- a/pkg/proc/arm64_arch.go
+++ b/pkg/proc/arm64_arch.go
@@ -14,15 +14,25 @@ import (
 
 var arm64BreakInstruction = []byte{0x0, 0x0, 0x20, 0xd4}
 
+// Windows ARM64 expects a breakpoint to be compiled to the instruction BRK #0xF000.
+// See go.dev/issues/53837.
+var arm64WindowsBreakInstruction = []byte{0x0, 0x0, 0x3e, 0xd4}
+
 // ARM64Arch returns an initialized ARM64
 // struct.
 func ARM64Arch(goos string) *Arch {
+	var brk []byte
+	if goos == "windows" {
+		brk = arm64WindowsBreakInstruction
+	} else {
+		brk = arm64BreakInstruction
+	}
 	return &Arch{
 		Name:                             "arm64",
 		ptrSize:                          8,
 		maxInstructionLength:             4,
-		breakpointInstruction:            arm64BreakInstruction,
-		breakInstrMovesPC:                false,
+		breakpointInstruction:            brk,
+		breakInstrMovesPC:                goos == "windows",
 		derefTLS:                         false,
 		prologues:                        prologuesARM64,
 		fixFrameUnwindContext:            arm64FixFrameUnwindContext,
@@ -102,12 +112,7 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 	if a.crosscall2fn != nil && pc >= a.crosscall2fn.Entry && pc < a.crosscall2fn.End {
 		rule := fctxt.CFA
 		if rule.Offset == crosscall2SPOffsetBad {
-			switch bi.GOOS {
-			case "windows":
-				rule.Offset += crosscall2SPOffsetWindows
-			default:
-				rule.Offset += crosscall2SPOffsetNonWindows
-			}
+			rule.Offset += crosscall2SPOffset
 		}
 		fctxt.CFA = rule
 	}
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index aaa11a9a..43055abf 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -137,6 +137,7 @@ var (
 
 	supportedWindowsArch = map[_PEMachine]bool{
 		_IMAGE_FILE_MACHINE_AMD64: true,
+		_IMAGE_FILE_MACHINE_ARM64: true,
 	}
 
 	supportedDarwinArch = map[macho.Cpu]bool{
@@ -681,8 +682,18 @@ func loadBinaryInfo(bi *BinaryInfo, image *Image, path string, entryPoint uint64
 
 // GStructOffset returns the offset of the G
 // struct in thread local storage.
-func (bi *BinaryInfo) GStructOffset() uint64 {
-	return bi.gStructOffset
+func (bi *BinaryInfo) GStructOffset(mem MemoryReadWriter) (uint64, error) {
+	offset := bi.gStructOffset
+	if bi.GOOS == "windows" && bi.Arch.Name == "arm64" {
+		// The G struct offset from the TLS section is a pointer
+		// and the address must be dereferenced to find to actual G struct offset.
+		var err error
+		offset, err = readUintRaw(mem, offset, int64(bi.Arch.PtrSize()))
+		if err != nil {
+			return 0, err
+		}
+	}
+	return offset, nil
 }
 
 // LastModified returns the last modified time of the binary.
@@ -1284,7 +1295,7 @@ func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugIn
 		// Previous verrsions of delve looked for the build id in every debug info
 		// directory that contained the build-id substring. This behavior deviates
 		// from the ones specified by GDB but we keep it for backwards compatibility.
-		find(func(dir string) bool { return strings.Contains(dir, "build-id") }, fmt.Sprintf(".build-id/%s/%s.debug", bi.BuildID[:2], bi.BuildID[2:]))
+		find(func(dir string) bool { return strings.Contains(dir, "build-id") }, fmt.Sprintf("%s/%s.debug", bi.BuildID[:2], bi.BuildID[2:]))
 	}
 
 	if debugFilePath == "" {
@@ -1589,8 +1600,6 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
 	if err != nil {
 		return err
 	}
-
-	//TODO(aarzilli): actually test this when Go supports PIE buildmode on Windows.
 	opth := peFile.OptionalHeader.(*pe.OptionalHeader64)
 	if entryPoint != 0 {
 		image.StaticBase = entryPoint - opth.ImageBase
@@ -1618,15 +1627,40 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
 	wg.Add(2)
 	go bi.parseDebugFramePE(image, peFile, debugInfoBytes, wg)
 	go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, nil)
-
-	// Use ArbitraryUserPointer (0x28) as pointer to pointer
-	// to G struct per:
-	// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
-
-	bi.gStructOffset = 0x28
+	if image.index == 0 {
+		// determine g struct offset only when loading the executable file
+		wg.Add(1)
+		go bi.setGStructOffsetPE(entryPoint, peFile, wg)
+	}
 	return nil
 }
 
+func (bi *BinaryInfo) setGStructOffsetPE(entryPoint uint64, peFile *pe.File, wg *sync.WaitGroup) {
+	defer wg.Done()
+	switch _PEMachine(peFile.Machine) {
+	case _IMAGE_FILE_MACHINE_AMD64:
+		// Use ArbitraryUserPointer (0x28) as pointer to pointer
+		// to G struct per:
+		// https://golang.org/src/runtime/cgo/gcc_windows_amd64.c
+		bi.gStructOffset = 0x28
+	case _IMAGE_FILE_MACHINE_ARM64:
+		// Use runtime.tls_g as pointer to offset from R18 to G struct:
+		// https://golang.org/src/runtime/sys_windows_arm64.s:runtime·wintls
+		for _, s := range peFile.Symbols {
+			if s.Name == "runtime.tls_g" {
+				i := int(s.SectionNumber) - 1
+				if 0 <= i && i < len(peFile.Sections) {
+					sect := peFile.Sections[i]
+					if s.Value < sect.VirtualSize {
+						bi.gStructOffset = entryPoint + uint64(sect.VirtualAddress) + uint64(s.Value)
+					}
+				}
+				break
+			}
+		}
+	}
+}
+
 func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
 	f, err := os.OpenFile(path, 0, os.ModePerm)
 	if err != nil {
@@ -1808,6 +1842,8 @@ func (bi *BinaryInfo) macOSDebugFrameBugWorkaround() {
 
 // Do not call this function directly it isn't able to deal correctly with package paths
 func (bi *BinaryInfo) findType(name string) (godwarf.Type, error) {
+	name = strings.Replace(name, "interface{", "interface {", -1)
+	name = strings.Replace(name, "struct{", "struct {", -1)
 	ref, found := bi.types[name]
 	if !found {
 		return nil, reader.ErrTypeNotFound
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index 29f2aeae..f898eb4e 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -359,9 +359,11 @@ func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs
 		// store the MOV instruction.
 		// If the stub doesn't support memory allocation reloadRegisters will
 		// overwrite some existing memory to store the MOV.
-		if addr, err := p.conn.allocMemory(256); err == nil {
-			if _, err := p.conn.writeMemory(addr, p.loadGInstr()); err == nil {
-				p.loadGInstrAddr = addr
+		if ginstr, err := p.loadGInstr(); err == nil {
+			if addr, err := p.conn.allocMemory(256); err == nil {
+				if _, err := p.conn.writeMemory(addr, ginstr); err == nil {
+					p.loadGInstrAddr = addr
+				}
 			}
 		}
 	}
@@ -1553,7 +1555,7 @@ func (t *gdbThread) Blocked() bool {
 // loadGInstr returns the correct MOV instruction for the current
 // OS/architecture that can be executed to load the address of G from an
 // inferior's thread.
-func (p *gdbProcess) loadGInstr() []byte {
+func (p *gdbProcess) loadGInstr() ([]byte, error) {
 	var op []byte
 	switch p.bi.GOOS {
 	case "windows", "darwin", "freebsd":
@@ -1565,10 +1567,14 @@ func (p *gdbProcess) loadGInstr() []byte {
 	default:
 		panic("unsupported operating system attempting to find Goroutine on Thread")
 	}
+	offset, err := p.bi.GStructOffset(p.Memory())
+	if err != nil {
+		return nil, err
+	}
 	buf := &bytes.Buffer{}
 	buf.Write(op)
-	binary.Write(buf, binary.LittleEndian, uint32(p.bi.GStructOffset()))
-	return buf.Bytes()
+	binary.Write(buf, binary.LittleEndian, uint32(offset))
+	return buf.Bytes(), nil
 }
 
 func (p *gdbProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
@@ -1727,7 +1733,10 @@ func (t *gdbThread) readSomeRegisters(regNames ...string) error {
 // the MOV instruction used to load current G, executes this single
 // instruction and then puts everything back the way it was.
 func (t *gdbThread) reloadGAtPC() error {
-	movinstr := t.p.loadGInstr()
+	movinstr, err := t.p.loadGInstr()
+	if err != nil {
+		return err
+	}
 
 	if t.Blocked() {
 		t.regs.tls = 0
@@ -1760,7 +1769,7 @@ func (t *gdbThread) reloadGAtPC() error {
 	}
 
 	savedcode := make([]byte, len(movinstr))
-	_, err := t.p.ReadMemory(savedcode, pc)
+	_, err = t.p.ReadMemory(savedcode, pc)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/proc/native/dump_other.go b/pkg/proc/native/dump_other.go
index 0c4ce14a..e4ebf442 100644
--- a/pkg/proc/native/dump_other.go
+++ b/pkg/proc/native/dump_other.go
@@ -1,5 +1,5 @@
-//go:build (freebsd && amd64) || darwin
-// +build freebsd,amd64 darwin
+//go:build (freebsd && amd64) || darwin || (windows && arm64)
+// +build freebsd,amd64 darwin windows,arm64
 
 package native
 
diff --git a/pkg/proc/native/hwbreak_other.go b/pkg/proc/native/hwbreak_other.go
index 548f3336..26144830 100644
--- a/pkg/proc/native/hwbreak_other.go
+++ b/pkg/proc/native/hwbreak_other.go
@@ -1,5 +1,5 @@
-//go:build (linux && 386) || (darwin && arm64)
-// +build linux,386 darwin,arm64
+//go:build (linux && 386) || (darwin && arm64) || (windows && arm64)
+// +build linux,386 darwin,arm64 windows,arm64
 
 package native
 
diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go
index a7f0dd63..cfcc2878 100644
--- a/pkg/proc/native/proc_linux.go
+++ b/pkg/proc/native/proc_linux.go
@@ -726,8 +726,12 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
 		return fmt.Errorf("could not find function: %s", fnName)
 	}
 
+	offset, err := dbp.BinInfo().GStructOffset(dbp.Memory())
+	if err != nil {
+		return err
+	}
 	key := fn.Entry
-	err := dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, dbp.BinInfo().GStructOffset(), false)
+	err = dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, offset, false)
 	if err != nil {
 		return err
 	}
@@ -762,7 +766,7 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
 	}
 	addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...)
 	for _, addr := range addrs {
-		err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, dbp.BinInfo().GStructOffset(), true)
+		err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, offset, true)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/proc/native/registers_windows.go b/pkg/proc/native/registers_windows.go
index 428d1541..aa998f3f 100644
--- a/pkg/proc/native/registers_windows.go
+++ b/pkg/proc/native/registers_windows.go
@@ -1,12 +1,6 @@
 package native
 
-import (
-	"fmt"
-	"unsafe"
-
-	"github.com/go-delve/delve/pkg/dwarf/op"
-	"github.com/go-delve/delve/pkg/proc"
-)
+import "github.com/go-delve/delve/pkg/dwarf/op"
 
 // SetPC sets the RIP register to the value specified by `pc`.
 func (thread *nativeThread) setPC(pc uint64) error {
@@ -39,21 +33,3 @@ func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
 
 	return thread.setContext(context)
 }
-
-func registers(thread *nativeThread) (proc.Registers, error) {
-	context := newContext()
-
-	context.SetFlags(_CONTEXT_ALL)
-	err := thread.getContext(context)
-	if err != nil {
-		return nil, err
-	}
-
-	var threadInfo _THREAD_BASIC_INFORMATION
-	status := _NtQueryInformationThread(thread.os.hThread, _ThreadBasicInformation, uintptr(unsafe.Pointer(&threadInfo)), uint32(unsafe.Sizeof(threadInfo)), nil)
-	if !_NT_SUCCESS(status) {
-		return nil, fmt.Errorf("NtQueryInformationThread failed: it returns 0x%x", status)
-	}
-
-	return newRegisters(context, uint64(threadInfo.TebBaseAddress)), nil
-}
diff --git a/pkg/proc/native/support_sentinel.go b/pkg/proc/native/support_sentinel.go
index bfad9ad2..b8c7ba3e 100644
--- a/pkg/proc/native/support_sentinel.go
+++ b/pkg/proc/native/support_sentinel.go
@@ -1,6 +1,6 @@
 // This file is used to detect build on unsupported GOOS/GOARCH combinations.
 
-//go:build (!linux && !darwin && !windows && !freebsd) || (linux && !amd64 && !arm64 && !386) || (darwin && !amd64 && !arm64) || (windows && !amd64) || (freebsd && !amd64)
-// +build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64,!386 darwin,!amd64,!arm64 windows,!amd64 freebsd,!amd64
+//go:build !linux && !darwin && !windows && !freebsd
+// +build !linux,!darwin,!windows,!freebsd
 
-package your_operating_system_and_architecture_combination_is_not_supported_by_delve
+package your_operating_system_is_not_supported_by_delve
diff --git a/pkg/proc/native/support_sentinel_darwin.go b/pkg/proc/native/support_sentinel_darwin.go
new file mode 100644
index 00000000..6c829428
--- /dev/null
+++ b/pkg/proc/native/support_sentinel_darwin.go
@@ -0,0 +1,6 @@
+// This file is used to detect build on unsupported GOOS/GOARCH combinations.
+
+//go:build darwin && !amd64 && !arm64
+// +build darwin,!amd64,!arm64
+
+package your_darwin_architectur_is_not_supported_by_delve
diff --git a/pkg/proc/native/support_sentinel_freebsd.go b/pkg/proc/native/support_sentinel_freebsd.go
new file mode 100644
index 00000000..f7a1bff9
--- /dev/null
+++ b/pkg/proc/native/support_sentinel_freebsd.go
@@ -0,0 +1,6 @@
+// This file is used to detect build on unsupported GOOS/GOARCH combinations.
+
+//go:build freebsd && !amd64
+// +build freebsd,!amd64
+
+package your_freebsd_architecture_is_not_supported_by_delve
diff --git a/pkg/proc/native/support_sentinel_linux.go b/pkg/proc/native/support_sentinel_linux.go
new file mode 100644
index 00000000..1325f09c
--- /dev/null
+++ b/pkg/proc/native/support_sentinel_linux.go
@@ -0,0 +1,6 @@
+// This file is used to detect build on unsupported GOOS/GOARCH combinations.
+
+//go:build linux && !amd64 && !arm64 && !386
+// +build linux,!amd64,!arm64,!386
+
+package your_linux_architecture_is_not_supported_by_delve
diff --git a/pkg/proc/native/support_sentinel_windows.go b/pkg/proc/native/support_sentinel_windows.go
new file mode 100644
index 00000000..883c6648
--- /dev/null
+++ b/pkg/proc/native/support_sentinel_windows.go
@@ -0,0 +1,8 @@
+// This file is used to detect build on unsupported GOOS/GOARCH combinations.
+
+//go:build windows && !amd64 && !(arm64 && exp.winarm64)
+// +build windows
+// +build !amd64
+// +build !arm64 !exp.winarm64
+
+package your_windows_architecture_is_not_supported_by_delve
diff --git a/pkg/proc/native/syscall_windows_arm64.go b/pkg/proc/native/syscall_windows_arm64.go
new file mode 100644
index 00000000..8b550f32
--- /dev/null
+++ b/pkg/proc/native/syscall_windows_arm64.go
@@ -0,0 +1,23 @@
+package native
+
+import "github.com/go-delve/delve/pkg/proc/winutil"
+
+const (
+	_CONTEXT_ARM64               = 0x00400000
+	_CONTEXT_CONTROL             = (_CONTEXT_ARM64 | 0x1)
+	_CONTEXT_INTEGER             = (_CONTEXT_ARM64 | 0x2)
+	_CONTEXT_FLOATING_POINT      = (_CONTEXT_ARM64 | 0x4)
+	_CONTEXT_DEBUG_REGISTERS     = (_CONTEXT_ARM64 | 0x8)
+	_CONTEXT_ARM64_X18           = (_CONTEXT_ARM64 | 0x10)
+	_CONTEXT_FULL                = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_FLOATING_POINT)
+	_CONTEXT_ALL                 = (_CONTEXT_CONTROL | _CONTEXT_INTEGER | _CONTEXT_FLOATING_POINT | _CONTEXT_DEBUG_REGISTERS | _CONTEXT_ARM64_X18)
+	_CONTEXT_EXCEPTION_ACTIVE    = 0x8000000
+	_CONTEXT_SERVICE_ACTIVE      = 0x10000000
+	_CONTEXT_EXCEPTION_REQUEST   = 0x40000000
+	_CONTEXT_EXCEPTION_REPORTING = 0x80000000
+)
+
+// zsyscall_windows.go, an autogenerated file, wants to refer to the context
+// structure as _CONTEXT, but we need to have it in pkg/proc/winutil.CONTEXT
+// because it's also used on non-windows operating systems.
+type _CONTEXT = winutil.ARM64CONTEXT
diff --git a/pkg/proc/native/threads_windows_amd64.go b/pkg/proc/native/threads_windows_amd64.go
index e1b981b3..e5273f3f 100644
--- a/pkg/proc/native/threads_windows_amd64.go
+++ b/pkg/proc/native/threads_windows_amd64.go
@@ -2,6 +2,8 @@ package native
 
 import (
 	"errors"
+	"fmt"
+	"unsafe"
 
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/pkg/proc/amd64util"
@@ -12,8 +14,22 @@ func newContext() *winutil.AMD64CONTEXT {
 	return winutil.NewAMD64CONTEXT()
 }
 
-func newRegisters(context *winutil.AMD64CONTEXT, TebBaseAddress uint64) *winutil.AMD64Registers {
-	return winutil.NewAMD64Registers(context, TebBaseAddress)
+func registers(t *nativeThread) (proc.Registers, error) {
+	context := newContext()
+
+	context.SetFlags(_CONTEXT_ALL)
+	err := t.getContext(context)
+	if err != nil {
+		return nil, err
+	}
+
+	var threadInfo _THREAD_BASIC_INFORMATION
+	status := _NtQueryInformationThread(t.os.hThread, _ThreadBasicInformation, uintptr(unsafe.Pointer(&threadInfo)), uint32(unsafe.Sizeof(threadInfo)), nil)
+	if !_NT_SUCCESS(status) {
+		return nil, fmt.Errorf("NtQueryInformationThread failed: it returns 0x%x", status)
+	}
+
+	return winutil.NewAMD64Registers(context, uint64(threadInfo.TebBaseAddress)), nil
 }
 
 func (t *nativeThread) setContext(context *winutil.AMD64CONTEXT) error {
diff --git a/pkg/proc/native/threads_windows_arm64.go b/pkg/proc/native/threads_windows_arm64.go
new file mode 100644
index 00000000..cbd8948f
--- /dev/null
+++ b/pkg/proc/native/threads_windows_arm64.go
@@ -0,0 +1,38 @@
+package native
+
+import (
+	"github.com/go-delve/delve/pkg/proc"
+	"github.com/go-delve/delve/pkg/proc/winutil"
+)
+
+func newContext() *winutil.ARM64CONTEXT {
+	return winutil.NewARM64CONTEXT()
+}
+
+func registers(t *nativeThread) (proc.Registers, error) {
+	context := newContext()
+
+	context.SetFlags(_CONTEXT_ALL)
+	err := t.getContext(context)
+	if err != nil {
+		return nil, err
+	}
+
+	return winutil.NewARM64Registers(context, t.dbp.iscgo), nil
+}
+
+func newRegisters(context *winutil.ARM64CONTEXT, TebBaseAddress uint64, iscgo bool) *winutil.ARM64Registers {
+	return winutil.NewARM64Registers(context, iscgo)
+}
+
+func (t *nativeThread) setContext(context *winutil.ARM64CONTEXT) error {
+	return _SetThreadContext(t.os.hThread, context)
+}
+
+func (t *nativeThread) getContext(context *winutil.ARM64CONTEXT) error {
+	return _GetThreadContext(t.os.hThread, context)
+}
+
+func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
+	return t.setContext(savedRegs.(*winutil.ARM64Registers).Context)
+}
diff --git a/pkg/proc/target_group.go b/pkg/proc/target_group.go
index 2f45a882..71f9bd94 100644
--- a/pkg/proc/target_group.go
+++ b/pkg/proc/target_group.go
@@ -47,7 +47,8 @@ func NewGroup(t *Target) *TargetGroup {
 	}
 }
 
-// Targets returns a slice of targets in the group.
+// Targets returns a slice of all targets in the group, including the
+// ones that are no longer valid.
 func (grp *TargetGroup) Targets() []*Target {
 	return grp.targets
 }
@@ -60,7 +61,9 @@ func (grp *TargetGroup) Valid() (bool, error) {
 		if ok {
 			return true, nil
 		}
-		err0 = err
+		if err0 == nil {
+			err0 = err
+		}
 	}
 	return false, err0
 }
@@ -124,3 +127,25 @@ func (grp *TargetGroup) TargetForThread(thread Thread) *Target {
 	}
 	return nil
 }
+
+// ValidTargets iterates through all valid targets in Group.
+type ValidTargets struct {
+	*Target
+	Group *TargetGroup
+	start int
+}
+
+// Next moves to the next valid target, returns false if there aren't more
+// valid targets in the group.
+func (it *ValidTargets) Next() bool {
+	for i := it.start; i < len(it.Group.targets); i++ {
+		if ok, _ := it.Group.targets[i].Valid(); ok {
+			it.Target = it.Group.targets[i]
+			it.start = i + 1
+			return true
+		}
+	}
+	it.start = len(it.Group.targets)
+	it.Target = nil
+	return false
+}
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index d9d7ae84..5c48a14e 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -438,8 +438,12 @@ func getGVariable(thread Thread) (*Variable, error) {
 
 	gaddr, hasgaddr := regs.GAddr()
 	if !hasgaddr {
-		var err error
-		gaddr, err = readUintRaw(thread.ProcessMemory(), regs.TLS()+thread.BinInfo().GStructOffset(), int64(thread.BinInfo().Arch.PtrSize()))
+		bi := thread.BinInfo()
+		offset, err := bi.GStructOffset(thread.ProcessMemory())
+		if err != nil {
+			return nil, err
+		}
+		gaddr, err = readUintRaw(thread.ProcessMemory(), regs.TLS()+offset, int64(bi.Arch.PtrSize()))
 		if err != nil {
 			return nil, err
 		}
@@ -919,11 +923,12 @@ func (v *Variable) parseG() (*G, error) {
 	}
 
 	status := uint64(0)
-	if atomicStatus := v.loadFieldNamed("atomicstatus"); atomicStatus != nil {
+	if atomicStatus := v.loadFieldNamed("atomicstatus"); /* +rtype uint32|runtime/internal/atomic.Uint32 */ atomicStatus != nil {
 		if constant.Val(atomicStatus.Value) != nil {
 			status, _ = constant.Uint64Val(atomicStatus.Value)
 		} else {
-			vv := atomicStatus.fieldVariable("value")
+			atomicStatus := atomicStatus              // +rtype runtime/internal/atomic.Uint32
+			vv := atomicStatus.fieldVariable("value") // +rtype uint32
 			if vv == nil {
 				unreadable = true
 			} else {
diff --git a/pkg/proc/winutil/regs_arm64_arch.go b/pkg/proc/winutil/regs_arm64_arch.go
new file mode 100644
index 00000000..4c0b044c
--- /dev/null
+++ b/pkg/proc/winutil/regs_arm64_arch.go
@@ -0,0 +1,186 @@
+package winutil
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"unsafe"
+
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
+	"github.com/go-delve/delve/pkg/proc"
+)
+
+const (
+	ARM64_MAX_BREAKPOINTS = 8
+	ARM64_MAX_WATCHPOINTS = 2
+)
+
+// neon128 tracks the neon128 windows struct.
+type neon128 struct {
+	Low  uint64
+	High int64
+}
+
+// ARM64Registers represents CPU registers on an ARM64 processor.
+type ARM64Registers struct {
+	iscgo          bool
+	Regs           [31]uint64
+	Sp             uint64
+	Pc             uint64
+	FloatRegisters [32]neon128
+	Fpcr           uint32
+	Fpsr           uint32
+	Bcr            [ARM64_MAX_BREAKPOINTS]uint32
+	Bvr            [ARM64_MAX_BREAKPOINTS]uint64
+	Wcr            [ARM64_MAX_WATCHPOINTS]uint32
+	Wvr            [ARM64_MAX_WATCHPOINTS]uint64
+	Context        *ARM64CONTEXT
+}
+
+// NewARM64Registers creates a new ARM64Registers struct from a CONTEXT.
+func NewARM64Registers(context *ARM64CONTEXT, iscgo bool) *ARM64Registers {
+	regs := &ARM64Registers{
+		iscgo:          iscgo,
+		Regs:           context.Regs,
+		Sp:             context.Sp,
+		Pc:             context.Pc,
+		FloatRegisters: context.FloatRegisters,
+		Fpcr:           context.Fpcr,
+		Fpsr:           context.Fpsr,
+		Bcr:            context.Bcr,
+		Bvr:            context.Bvr,
+		Wcr:            context.Wcr,
+		Wvr:            context.Wvr,
+		Context:        context,
+	}
+
+	return regs
+}
+
+// Slice returns the registers as a list of (name, value) pairs.
+func (r *ARM64Registers) Slice(floatingPoint bool) ([]proc.Register, error) {
+	out := make([]proc.Register, 0, len(r.Regs)+len(r.FloatRegisters)+2)
+	for i, v := range r.Regs {
+		out = proc.AppendUint64Register(out, fmt.Sprintf("X%d", i), v)
+	}
+	out = proc.AppendUint64Register(out, "SP", r.Sp)
+	out = proc.AppendUint64Register(out, "PC", r.Pc)
+	if floatingPoint {
+		for i := range r.FloatRegisters {
+			var buf bytes.Buffer
+			binary.Write(&buf, binary.LittleEndian, r.FloatRegisters[i].Low)
+			binary.Write(&buf, binary.LittleEndian, r.FloatRegisters[i].High)
+			out = proc.AppendBytesRegister(out, fmt.Sprintf("V%d", i), buf.Bytes())
+		}
+		out = proc.AppendUint64Register(out, "Fpcr", uint64(r.Fpcr))
+		out = proc.AppendUint64Register(out, "Fpsr", uint64(r.Fpsr))
+	}
+	return out, nil
+}
+
+// PC returns the value of RIP register.
+func (r *ARM64Registers) PC() uint64 {
+	return r.Pc
+}
+
+// SP returns the value of RSP register.
+func (r *ARM64Registers) SP() uint64 {
+	return r.Sp
+}
+
+func (r *ARM64Registers) BP() uint64 {
+	return r.Regs[29]
+}
+
+// TLS returns the address of the thread local storage memory segment.
+func (r *ARM64Registers) TLS() uint64 {
+	if !r.iscgo {
+		return 0
+	}
+	return r.Regs[18]
+}
+
+// GAddr returns the address of the G variable if it is known, 0 and false
+// otherwise.
+func (r *ARM64Registers) GAddr() (uint64, bool) {
+	return r.Regs[28], !r.iscgo
+}
+
+// LR returns the link register.
+func (r *ARM64Registers) LR() uint64 {
+	return r.Regs[30]
+}
+
+// Copy returns a copy of these registers that is guaranteed not to change.
+func (r *ARM64Registers) Copy() (proc.Registers, error) {
+	rr := *r
+	rr.Context = NewARM64CONTEXT()
+	*(rr.Context) = *(r.Context)
+	return &rr, nil
+}
+
+// ARM64CONTEXT tracks the _ARM64_NT_CONTEXT of windows.
+type ARM64CONTEXT struct {
+	ContextFlags   uint32
+	Cpsr           uint32
+	Regs           [31]uint64
+	Sp             uint64
+	Pc             uint64
+	FloatRegisters [32]neon128
+	Fpcr           uint32
+	Fpsr           uint32
+	Bcr            [ARM64_MAX_BREAKPOINTS]uint32
+	Bvr            [ARM64_MAX_BREAKPOINTS]uint64
+	Wcr            [ARM64_MAX_WATCHPOINTS]uint32
+	Wvr            [ARM64_MAX_WATCHPOINTS]uint64
+}
+
+// NewARM64CONTEXT allocates Windows CONTEXT structure aligned to 16 bytes.
+func NewARM64CONTEXT() *ARM64CONTEXT {
+	var c *ARM64CONTEXT
+	buf := make([]byte, unsafe.Sizeof(*c)+15)
+	return (*ARM64CONTEXT)(unsafe.Pointer((uintptr(unsafe.Pointer(&buf[15]))) &^ 15))
+}
+
+func (ctx *ARM64CONTEXT) SetFlags(flags uint32) {
+	ctx.ContextFlags = flags
+}
+
+func (ctx *ARM64CONTEXT) SetPC(pc uint64) {
+	ctx.Pc = pc
+}
+
+func (ctx *ARM64CONTEXT) SetTrap(trap bool) {
+	const v = 0x200000
+	if trap {
+		ctx.Cpsr |= v
+	} else {
+		ctx.Cpsr &= ^uint32(v)
+	}
+}
+
+func (ctx *ARM64CONTEXT) SetReg(regNum uint64, reg *op.DwarfRegister) error {
+	switch regNum {
+	case regnum.ARM64_PC:
+		ctx.Pc = reg.Uint64Val
+		return nil
+	case regnum.ARM64_SP:
+		ctx.Sp = reg.Uint64Val
+		return nil
+	default:
+		switch {
+		case regNum >= regnum.ARM64_X0 && regNum <= regnum.ARM64_X0+30:
+			ctx.Regs[regNum-regnum.ARM64_X0] = reg.Uint64Val
+			return nil
+
+		case regNum >= regnum.ARM64_V0 && regNum <= regnum.ARM64_V0+30:
+			i := regNum - regnum.ARM64_V0
+			ctx.FloatRegisters[i].Low = reg.Uint64Val
+			ctx.FloatRegisters[i].High = 0
+			return nil
+		default:
+			return fmt.Errorf("changing register %d not implemented", regNum)
+		}
+	}
+}
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index 3fc3a16f..74164bfc 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -1758,6 +1758,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]
 	for _, loc := range locs {
 		requestedBp.Addr = loc.PC
 		requestedBp.Addrs = loc.PCs
+		requestedBp.AddrPid = loc.PCPids
 		if tracepoint {
 			requestedBp.LoadArgs = &ShortLoadConfig
 		}
diff --git a/service/api/conversions.go b/service/api/conversions.go
index fa5848b0..9c66fac4 100644
--- a/service/api/conversions.go
+++ b/service/api/conversions.go
@@ -54,7 +54,7 @@ func ConvertLogicalBreakpoint(lbp *proc.LogicalBreakpoint) *Breakpoint {
 }
 
 // ConvertPhysicalBreakpoints adds informations from physical breakpoints to an API breakpoint.
-func ConvertPhysicalBreakpoints(b *Breakpoint, bps []*proc.Breakpoint) {
+func ConvertPhysicalBreakpoints(b *Breakpoint, pids []int, bps []*proc.Breakpoint) {
 	if len(bps) == 0 {
 		return
 	}
@@ -63,8 +63,9 @@ func ConvertPhysicalBreakpoints(b *Breakpoint, bps []*proc.Breakpoint) {
 	b.WatchType = WatchType(bps[0].WatchType)
 
 	lg := false
-	for _, bp := range bps {
+	for i, bp := range bps {
 		b.Addrs = append(b.Addrs, bp.Addr)
+		b.AddrPid = append(b.AddrPid, pids[i])
 		if b.FunctionName != bp.FunctionName && b.FunctionName != "" {
 			if !lg {
 				b.FunctionName = removeTypeParams(b.FunctionName)
diff --git a/service/api/types.go b/service/api/types.go
index 913ff4ea..2e980ef9 100644
--- a/service/api/types.go
+++ b/service/api/types.go
@@ -80,6 +80,9 @@ type Breakpoint struct {
 	Addr uint64 `json:"addr"`
 	// Addrs is the list of addresses for this breakpoint.
 	Addrs []uint64 `json:"addrs"`
+	// AddrPid[i] is the PID associated with by Addrs[i], when debugging a
+	// single target process this is optional, otherwise it is mandatory.
+	AddrPid []int `json:"addrpid"`
 	// File is the source file for the breakpoint.
 	File string `json:"file"`
 	// Line is a line in File for the breakpoint.
@@ -192,6 +195,7 @@ type Location struct {
 	Line     int       `json:"line"`
 	Function *Function `json:"function,omitempty"`
 	PCs      []uint64  `json:"pcs,omitempty"`
+	PCPids   []int     `json:"pcpids,omitempty"`
 }
 
 // Stackframe describes one frame in a stack trace.
diff --git a/service/dap/server.go b/service/dap/server.go
index a282f964..acc6a283 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -1860,7 +1860,7 @@ func (s *Session) stoppedOnBreakpointGoroutineID(state *api.DebuggerState) (int6
 		return goid, nil
 	}
 	abp := api.ConvertLogicalBreakpoint(bp.Breakpoint.Logical)
-	api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp.Breakpoint})
+	api.ConvertPhysicalBreakpoints(abp, []int{0}, []*proc.Breakpoint{bp.Breakpoint})
 	return goid, abp
 }
 
@@ -3692,7 +3692,7 @@ func (msg *logMessage) evaluate(s *Session, goid int64) string {
 			evaluated[i] = fmt.Sprintf("{eval err: %e}", err)
 			continue
 		}
-		evaluated[i] = s.convertVariableToString(exprVar)
+		evaluated[i], _ = s.convertVariableWithOpts(exprVar, "", skipRef|showFullValue)
 	}
 	return fmt.Sprintf(msg.format, evaluated...)
 }
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 869bdd95..ba9d3a2b 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -3,11 +3,15 @@ package debugger
 import (
 	"bytes"
 	"debug/dwarf"
+	"debug/elf"
+	"debug/macho"
+	"debug/pe"
 	"errors"
 	"fmt"
 	"go/parser"
 	"go/token"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"regexp"
 	"runtime"
@@ -45,6 +49,9 @@ var (
 
 	// ErrCoreDumpNotSupported is returned when core dumping is not supported
 	ErrCoreDumpNotSupported = errors.New("core dumping not supported")
+
+	// ErrNotImplementedWithMultitarget is returned for operations that are not implemented with multiple targets
+	ErrNotImplementedWithMultitarget = errors.New("not implemented for multiple targets")
 )
 
 // Debugger service.
@@ -386,6 +393,10 @@ func (d *Debugger) FunctionReturnLocations(fnName string) ([]uint64, error) {
 	d.targetMutex.Lock()
 	defer d.targetMutex.Unlock()
 
+	if len(d.target.Targets()) > 1 {
+		return nil, ErrNotImplementedWithMultitarget
+	}
+
 	var (
 		p = d.target.Selected
 		g = p.SelectedGoroutine()
@@ -604,10 +615,11 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig, withBreakpointInfo bool) (
 		state.When, _ = d.target.When()
 	}
 
-	for _, t := range d.target.Targets() {
+	t := proc.ValidTargets{Group: d.target}
+	for t.Next() {
 		for _, bp := range t.Breakpoints().WatchOutOfScope {
 			abp := api.ConvertLogicalBreakpoint(bp.Logical)
-			api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
+			api.ConvertPhysicalBreakpoints(abp, []int{t.Pid()}, []*proc.Breakpoint{bp})
 			state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
 		}
 	}
@@ -674,8 +686,9 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
 		if runtime.GOOS == "windows" {
 			// Accept fileName which is case-insensitive and slash-insensitive match
 			fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
+			t := proc.ValidTargets{Group: d.target}
 		caseInsensitiveSearch:
-			for _, t := range d.target.Targets() {
+			for t.Next() {
 				for _, symFile := range t.BinInfo().Sources {
 					if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
 						fileName = symFile
@@ -689,6 +702,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
 		addrs, err = proc.FindFunctionLocation(d.target.Selected, requestedBp.FunctionName, requestedBp.Line)
 	case len(requestedBp.Addrs) > 0:
 		addrs = requestedBp.Addrs
+		//TODO(aarzilli): read requestedBp.AddrPid
 	default:
 		addrs = []uint64{requestedBp.Addr}
 	}
@@ -707,7 +721,18 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
 
 func (d *Debugger) convertBreakpoint(lbp *proc.LogicalBreakpoint) *api.Breakpoint {
 	abp := api.ConvertLogicalBreakpoint(lbp)
-	api.ConvertPhysicalBreakpoints(abp, d.findBreakpoint(lbp.LogicalID))
+	bps := []*proc.Breakpoint{}
+	pids := []int{}
+	t := proc.ValidTargets{Group: d.target}
+	for t.Next() {
+		for _, bp := range t.Breakpoints().M {
+			if bp.LogicalID() == lbp.LogicalID {
+				bps = append(bps, bp)
+				pids = append(pids, t.Pid())
+			}
+		}
+	}
+	api.ConvertPhysicalBreakpoints(abp, pids, bps)
 	return abp
 }
 
@@ -846,7 +871,7 @@ func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
 	d.targetMutex.Lock()
 	defer d.targetMutex.Unlock()
 	if len(d.target.Targets()) != 1 {
-		panic("multiple targets not implemented")
+		return ErrNotImplementedWithMultitarget
 	}
 	p := d.target.Selected
 	return p.SetEBPFTracepoint(fnName)
@@ -1045,23 +1070,22 @@ func isBpHitCondNotSatisfiable(bp *api.Breakpoint) bool {
 func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
 	d.targetMutex.Lock()
 	defer d.targetMutex.Unlock()
-	if len(d.target.Targets()) != 1 {
-		panic("multiple targets not implemented")
-	}
-	p := d.target.Selected
 
 	abps := []*api.Breakpoint{}
 	if all {
-		for _, bp := range p.Breakpoints().M {
-			var abp *api.Breakpoint
-			if bp.Logical != nil {
-				abp = api.ConvertLogicalBreakpoint(bp.Logical)
-			} else {
-				abp = &api.Breakpoint{}
+		t := proc.ValidTargets{Group: d.target}
+		for t.Next() {
+			for _, bp := range t.Breakpoints().M {
+				var abp *api.Breakpoint
+				if bp.Logical != nil {
+					abp = api.ConvertLogicalBreakpoint(bp.Logical)
+				} else {
+					abp = &api.Breakpoint{}
+				}
+				api.ConvertPhysicalBreakpoints(abp, []int{t.Pid()}, []*proc.Breakpoint{bp})
+				abp.VerboseDescr = bp.VerboseDescr()
+				abps = append(abps, abp)
 			}
-			api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
-			abp.VerboseDescr = bp.VerboseDescr()
-			abps = append(abps, abp)
 		}
 	} else {
 		for _, lbp := range d.target.LogicalBreakpoints {
@@ -1422,7 +1446,8 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
 	}
 
 	files := []string{}
-	for _, t := range d.target.Targets() {
+	t := proc.ValidTargets{Group: d.target}
+	for t.Next() {
 		for _, f := range t.BinInfo().Sources {
 			if regex.Match([]byte(f)) {
 				files = append(files, f)
@@ -1460,7 +1485,8 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
 	}
 
 	funcs := []string{}
-	for _, t := range d.target.Targets() {
+	t := proc.ValidTargets{Group: d.target}
+	for t.Next() {
 		for _, f := range t.BinInfo().Functions {
 			if regex.MatchString(f.Name) {
 				funcs = append(funcs, f.Name)
@@ -1484,7 +1510,8 @@ func (d *Debugger) Types(filter string) ([]string, error) {
 
 	r := []string{}
 
-	for _, t := range d.target.Targets() {
+	t := proc.ValidTargets{Group: d.target}
+	for t.Next() {
 		types, err := t.BinInfo().Types()
 		if err != nil {
 			return nil, err
@@ -1932,13 +1959,6 @@ func (d *Debugger) FindLocation(goid int64, frame, deferredCall int, locStr stri
 	d.targetMutex.Lock()
 	defer d.targetMutex.Unlock()
 
-	if len(d.target.Targets()) != 1 {
-		//TODO(aarzilli): if there is more than one target process all must be
-		//searched and the addresses returned need to specify which target process
-		//they belong to.
-		panic("multiple targets not implemented")
-	}
-
 	if _, err := d.target.Valid(); err != nil {
 		return nil, err
 	}
@@ -1948,7 +1968,7 @@ func (d *Debugger) FindLocation(goid int64, frame, deferredCall int, locStr stri
 		return nil, err
 	}
 
-	return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
+	return d.findLocation(goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
 }
 
 // FindLocationSpec will find the location specified by 'locStr' and 'locSpec'.
@@ -1959,34 +1979,39 @@ func (d *Debugger) FindLocationSpec(goid int64, frame, deferredCall int, locStr
 	d.targetMutex.Lock()
 	defer d.targetMutex.Unlock()
 
-	if len(d.target.Targets()) != 1 {
-		//TODO(aarzilli): if there is more than one target process all must be
-		//searched and the addresses returned need to specify which target process
-		//they belong to.
-		panic("multiple targets not implemented")
-	}
-
 	if _, err := d.target.Valid(); err != nil {
 		return nil, err
 	}
 
-	return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
+	return d.findLocation(goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
 }
 
-func (d *Debugger) findLocation(p *proc.Target, goid int64, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
-	s, _ := proc.ConvertEvalScope(p, goid, frame, deferredCall)
-
-	locs, err := locSpec.Find(p, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
-	for i := range locs {
-		if locs[i].PC == 0 {
-			continue
+func (d *Debugger) findLocation(goid int64, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
+	locations := []api.Location{}
+	t := proc.ValidTargets{Group: d.target}
+	for t.Next() {
+		pid := t.Pid()
+		s, _ := proc.ConvertEvalScope(t.Target, goid, frame, deferredCall)
+		locs, err := locSpec.Find(t.Target, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
+		if err != nil {
+			return nil, err
 		}
-		file, line, fn := p.BinInfo().PCToLine(locs[i].PC)
-		locs[i].File = file
-		locs[i].Line = line
-		locs[i].Function = api.ConvertFunction(fn)
+		for i := range locs {
+			if locs[i].PC == 0 {
+				continue
+			}
+			file, line, fn := t.BinInfo().PCToLine(locs[i].PC)
+			locs[i].File = file
+			locs[i].Line = line
+			locs[i].Function = api.ConvertFunction(fn)
+			locs[i].PCPids = make([]int, len(locs[i].PCs))
+			for j := range locs[i].PCs {
+				locs[i].PCPids[j] = pid
+			}
+		}
+		locations = append(locations, locs...)
 	}
-	return locs, err
+	return locations, nil
 }
 
 // Disassemble code between startPC and endPC.
@@ -2304,3 +2329,55 @@ func noDebugErrorWarning(err error) error {
 	}
 	return err
 }
+
+func verifyBinaryFormat(exePath string) error {
+	f, err := os.Open(exePath)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	switch runtime.GOOS {
+	case "windows":
+		// Make sure the binary exists and is an executable file
+		if filepath.Base(exePath) == exePath {
+			if _, err := exec.LookPath(exePath); err != nil {
+				return err
+			}
+		}
+	default:
+		fi, err := f.Stat()
+		if err != nil {
+			return err
+		}
+		if (fi.Mode() & 0111) == 0 {
+			return api.ErrNotExecutable
+		}
+	}
+
+	// check that the binary format is what we expect for the host system
+	var exe interface{ Close() error }
+
+	switch runtime.GOOS {
+	case "darwin":
+		exe, err = macho.NewFile(f)
+	case "linux", "freebsd":
+		exe, err = elf.NewFile(f)
+	case "windows":
+		exe, err = pe.NewFile(f)
+	default:
+		panic("attempting to open file Delve cannot parse")
+	}
+
+	if err != nil {
+		return api.ErrNotExecutable
+	}
+	exe.Close()
+	return nil
+}
+
+var attachErrorMessage = attachErrorMessageDefault
+
+func attachErrorMessageDefault(pid int, err error) error {
+	return fmt.Errorf("could not attach to pid %d: %s", pid, err)
+}
diff --git a/service/debugger/debugger_darwin.go b/service/debugger/debugger_darwin.go
deleted file mode 100644
index 9edeca65..00000000
--- a/service/debugger/debugger_darwin.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package debugger
-
-import (
-	"fmt"
-)
-
-func attachErrorMessage(pid int, err error) error {
-	//TODO: mention certificates?
-	return fmt.Errorf("could not attach to pid %d: %s", pid, err)
-}
diff --git a/service/debugger/debugger_freebsd.go b/service/debugger/debugger_freebsd.go
deleted file mode 100644
index 06c7f14d..00000000
--- a/service/debugger/debugger_freebsd.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package debugger
-
-import (
-	"fmt"
-)
-
-func attachErrorMessage(pid int, err error) error {
-	return fmt.Errorf("could not attach to pid %d: %s", pid, err)
-}
diff --git a/service/debugger/debugger_linux.go b/service/debugger/debugger_linux.go
index ab19c848..d4058cda 100644
--- a/service/debugger/debugger_linux.go
+++ b/service/debugger/debugger_linux.go
@@ -7,9 +7,13 @@ import (
 	"syscall"
 )
 
+func init() {
+	attachErrorMessage = attachErrorMessageLinux
+}
+
 //lint:file-ignore ST1005 errors here can be capitalized
 
-func attachErrorMessage(pid int, err error) error {
+func attachErrorMessageLinux(pid int, err error) error {
 	fallbackerr := fmt.Errorf("could not attach to pid %d: %s", pid, err)
 	if serr, ok := err.(syscall.Errno); ok {
 		switch serr {
diff --git a/service/debugger/debugger_unix.go b/service/debugger/debugger_unix.go
deleted file mode 100644
index 2978ce16..00000000
--- a/service/debugger/debugger_unix.go
+++ /dev/null
@@ -1,45 +0,0 @@
-//go:build !windows
-// +build !windows
-
-package debugger
-
-import (
-	"debug/elf"
-	"debug/macho"
-	"os"
-	"runtime"
-
-	"github.com/go-delve/delve/service/api"
-)
-
-func verifyBinaryFormat(exePath string) error {
-	f, err := os.Open(exePath)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	fi, err := f.Stat()
-	if err != nil {
-		return err
-	}
-	if (fi.Mode() & 0111) == 0 {
-		return api.ErrNotExecutable
-	}
-
-	// check that the binary format is what we expect for the host system
-	var exe interface{ Close() error }
-	switch runtime.GOOS {
-	case "darwin":
-		exe, err = macho.NewFile(f)
-	case "linux", "freebsd":
-		exe, err = elf.NewFile(f)
-	default:
-		panic("attempting to open file Delve cannot parse")
-	}
-	if err != nil {
-		return api.ErrNotExecutable
-	}
-	exe.Close()
-	return nil
-}
diff --git a/service/debugger/debugger_windows.go b/service/debugger/debugger_windows.go
deleted file mode 100644
index 0fa24930..00000000
--- a/service/debugger/debugger_windows.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package debugger
-
-import (
-	"debug/pe"
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-
-	"github.com/go-delve/delve/service/api"
-)
-
-func attachErrorMessage(pid int, err error) error {
-	return fmt.Errorf("could not attach to pid %d: %s", pid, err)
-}
-
-func verifyBinaryFormat(exePath string) error {
-	f, err := os.Open(exePath)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	// Make sure the binary exists and is an executable file
-	if filepath.Base(exePath) == exePath {
-		if _, err := exec.LookPath(exePath); err != nil {
-			return err
-		}
-	}
-
-	exe, err := pe.NewFile(f)
-	if err != nil {
-		return api.ErrNotExecutable
-	}
-	exe.Close()
-	return nil
-}


Test patch:
diff --git a/_fixtures/testvariablescgo/test.c b/_fixtures/testvariablescgo/test.c
index 2299e142..fdcae84f 100644
--- a/_fixtures/testvariablescgo/test.c
+++ b/_fixtures/testvariablescgo/test.c
@@ -7,8 +7,12 @@
 #elif __i386__
 #define BREAKPOINT asm("int3;")
 #elif __aarch64__
+#ifdef WIN32
+#define BREAKPOINT asm("brk 0xF000;")
+#else
 #define BREAKPOINT asm("brk 0;")
 #endif
+#endif
 
 #define N 100
 
diff --git a/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1
index a7a92f4f..78254217 100644
--- a/_scripts/test_windows.ps1
+++ b/_scripts/test_windows.ps1
@@ -77,3 +77,12 @@ $x = $LastExitCode
 if ($version -ne "gotip") {
 	Exit $x
 }
+
+# TODO: Remove once we have a windows/arm64 builder.
+# Test windows/arm64 compiles.
+$env:GOARCH = "arm64"
+go run _scripts/make.go build --tags exp.winarm64
+$x = $LastExitCode
+if ($version -ne "gotip") {
+	Exit $x
+}
diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go
index ffe123c6..9c4efd4d 100644
--- a/cmd/dlv/dlv_test.go
+++ b/cmd/dlv/dlv_test.go
@@ -460,9 +460,10 @@ func TestGeneratedDoc(t *testing.T) {
 	checkAutogenDoc(t, "pkg/terminal/starbind/starlark_mapping.go", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "go", "-"))
 	checkAutogenDoc(t, "Documentation/cli/starlark.md", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "doc/dummy", "Documentation/cli/starlark.md"))
 	checkAutogenDoc(t, "Documentation/backend_test_health.md", "go run _scripts/gen-backend_test_health.go", runScript("_scripts/gen-backend_test_health.go", "-"))
-	checkAutogenDoc(t, "_scripts/rtype-out.txt", "go run _scripts/rtype.go report _scripts/rtype-out.txt", runScript("_scripts/rtype.go", "report"))
-
-	runScript("_scripts/rtype.go", "check")
+	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
+		checkAutogenDoc(t, "_scripts/rtype-out.txt", "go run _scripts/rtype.go report _scripts/rtype-out.txt", runScript("_scripts/rtype.go", "report"))
+		runScript("_scripts/rtype.go", "check")
+	}
 }
 
 func TestExitInInit(t *testing.T) {
diff --git a/pkg/proc/proc_general_test.go b/pkg/proc/proc_general_test.go
index 870efc8c..872b79b6 100644
--- a/pkg/proc/proc_general_test.go
+++ b/pkg/proc/proc_general_test.go
@@ -112,7 +112,9 @@ func TestDwarfVersion(t *testing.T) {
 	// Tests that we correctly read the version of compilation units
 	fixture := protest.BuildFixture("math", 0)
 	bi := NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
-	assertNoError(bi.LoadBinaryInfo(fixture.Path, 0, nil), t, "LoadBinaryInfo")
+	// Use a fake entry point so LoadBinaryInfo does not error in case the binary is PIE.
+	const fakeEntryPoint = 1
+	assertNoError(bi.LoadBinaryInfo(fixture.Path, fakeEntryPoint, nil), t, "LoadBinaryInfo")
 	for _, cu := range bi.Images[0].compileUnits {
 		if cu.Version != 4 {
 			t.Errorf("compile unit %q at %#x has bad version %d", cu.name, cu.entry.Offset, cu.Version)
@@ -127,7 +129,9 @@ func TestRegabiFlagSentinel(t *testing.T) {
 	}
 	fixture := protest.BuildFixture("math", 0)
 	bi := NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
-	assertNoError(bi.LoadBinaryInfo(fixture.Path, 0, nil), t, "LoadBinaryInfo")
+	// Use a fake entry point so LoadBinaryInfo does not error in case the binary is PIE.
+	const fakeEntryPoint = 1
+	assertNoError(bi.LoadBinaryInfo(fixture.Path, fakeEntryPoint, nil), t, "LoadBinaryInfo")
 	if !bi.regabi {
 		t.Errorf("regabi flag not set %s GOEXPERIMENT=%s", runtime.Version(), os.Getenv("GOEXPERIMENT"))
 	}
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 57a3015c..25f5c5da 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -5234,7 +5234,7 @@ func TestIssue2319(t *testing.T) {
 }
 
 func TestDump(t *testing.T) {
-	if runtime.GOOS == "freebsd" || (runtime.GOOS == "darwin" && testBackend == "native") {
+	if runtime.GOOS == "freebsd" || (runtime.GOOS == "darwin" && testBackend == "native") || (runtime.GOOS == "windows" && runtime.GOARCH != "amd64") {
 		t.Skip("not supported")
 	}
 
diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go
index 537bb86f..ac76f625 100644
--- a/pkg/proc/variables_test.go
+++ b/pkg/proc/variables_test.go
@@ -7,6 +7,7 @@ import (
 	"io/ioutil"
 	"runtime"
 	"sort"
+	"strconv"
 	"strings"
 	"testing"
 
@@ -800,6 +801,10 @@ func TestEvalExpression(t *testing.T) {
 
 		// issue #1423 - special typecasts everywhere
 		{`string(byteslice) == "tèst"`, false, `true`, `false`, "", nil},
+
+		// issue #3138 - typecast to *interface{} breaking
+		{`*(*interface {})(uintptr(&iface1))`, false, `interface {}(*main.astruct) *{A: 1, B: 2}`, `interface {}(*main.astruct)…`, "interface {}", nil},
+		{`*(*struct {})(uintptr(&zsvar))`, false, `struct {} {}`, `struct {} {}`, "struct {}", nil},
 	}
 
 	ver, _ := goversion.Parse(runtime.Version())
@@ -815,26 +820,28 @@ func TestEvalExpression(t *testing.T) {
 	protest.AllowRecording(t)
 	withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
 		assertNoError(p.Continue(), t, "Continue() returned an error")
-		for _, tc := range testcases {
-			variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
-			if err != nil && err.Error() == "evaluating methods not supported on this version of Go" {
-				// this type of eval is unsupported with the current version of Go.
-				continue
-			}
-			if tc.err == nil {
-				assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name))
-				assertVariable(t, variable, tc)
-				variable, err := evalVariableWithCfg(p, tc.name, pshortLoadConfig)
-				assertNoError(err, t, fmt.Sprintf("EvalExpression(%s, pshortLoadConfig) returned an error", tc.name))
-				assertVariable(t, variable, tc.alternateVarTest())
-			} else {
-				if err == nil {
-					t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name)
+		for i, tc := range testcases {
+			t.Run(strconv.Itoa(i), func(t *testing.T) {
+				variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
+				if err != nil && err.Error() == "evaluating methods not supported on this version of Go" {
+					// this type of eval is unsupported with the current version of Go.
+					return
 				}
-				if tc.err.Error() != err.Error() {
-					t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
+				if tc.err == nil {
+					assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name))
+					assertVariable(t, variable, tc)
+					variable, err := evalVariableWithCfg(p, tc.name, pshortLoadConfig)
+					assertNoError(err, t, fmt.Sprintf("EvalExpression(%s, pshortLoadConfig) returned an error", tc.name))
+					assertVariable(t, variable, tc.alternateVarTest())
+				} else {
+					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())
+					}
 				}
-			}
+			})
 		}
 	})
 }
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 3c7ee125..8d67f8d0 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -3306,6 +3306,39 @@ func TestLogPoints(t *testing.T) {
 	})
 }
 
+// TestLogPointsShowFullValue tests that log points will not truncate the string value.
+func TestLogPointsShowFullValue(t *testing.T) {
+	runTest(t, "longstrings", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{16},
+			[]onBreakpoint{{
+				execute: func() {
+					checkStop(t, client, 1, "main.main", 16)
+					bps := []int{19}
+					logMessages := map[int]string{19: "{&s4097}"}
+					client.SetBreakpointsRequestWithArgs(fixture.Source, bps, nil, nil, logMessages)
+					client.ExpectSetBreakpointsResponse(t)
+
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					checkLogMessage(t, client.ExpectOutputEvent(t), 1, "*\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...+3585 more\"", fixture.Source, 19)
+
+					se := client.ExpectStoppedEvent(t)
+					if se.Body.Reason != "breakpoint" || se.Body.ThreadId != 1 {
+						t.Errorf("got stopped event = %#v, \nwant Reason=\"breakpoint\" ThreadId=1", se)
+					}
+					checkStop(t, client, 1, "main.main", 22)
+				},
+				disconnect: true,
+			}})
+	})
+}
+
 func checkLogMessage(t *testing.T, oe *dap.OutputEvent, goid int, text, path string, line int) {
 	t.Helper()
 	prefix := "> [Go "