Repository: go-delve/delve
Pull request #3141 (2022-09-20 21:07:24)

service/dap: show full value when evaluating log messages
When evaluating variables in a log message, do not truncate the string representation.

Updates golang/vscode-go#2447


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/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/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/service/dap/server.go b/service/dap/server.go
index a282f964..908073df 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -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..f9b83fb3 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"
@@ -2304,3 +2308,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 "