Repository: go-delve/delve
Pull request #2362 (2021-02-25 07:58:25)

service/dap: support auto-loading of unloaded interfaces
Updates #1515

While we use fully qualified names for `evaluateName` (added in #2292) for readability, the auto-loading expressions are based on [ClientHowto.md#loading-more-of-a-variable](https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#loading-more-of-a-variable). These expressions work in a context of any frame.

polinasok: > I see that the `onEvaluateRequest` can be called on stack frames that aren't the topmost frame of the current goroutine. AFAICT using qualifiedNameOrExpr with those, instead of the pointer expression, will not work.

Evaluate requests are not triggered by variable loading (which is what happens when you expand layers in VARIABLES pane), but by WATCH expressions and by manual expressions in DEBUG CONSOLE. These can happen relative to a non-topmost frame if a user highlights a different stackframe in the UI. Here is an illustration of this behavior with some creative snapshotting.

![image](https://user-images.githubusercontent.com/51177946/109275730-fa83b700-77c9-11eb-833c-78031e807157.png)

This is actually making me realize that variable loading should not be done with fully qualified names after all because it needs to work no matter what frame the users chooses to view. I will update this PR to use the "magic" expression.

However, I think evaluateName used to add variables to WATCH pane should stay as names between a list of readable and easily identifiable names and a list of cryptic and look-alike expressions with addresses in the WATCH pane, an average user would prefer the names. It does mean that those watch variables will only be useful when looking at their frame and not while debugging the rest of the code. But if the truly frame-less watching is needed, the user can enter the expression manually, can't they?
aarzilli: > However, I think evaluateName used to add variables to WATCH pane should stay as names between a list of readable and easily identifiable names and a list of cryptic and look-alike expressions with addresses in the WATCH pane, an average user would prefer the names. It does mean that those watch variables will only be useful when looking at their frame and not while debugging the rest of the code. But if the truly frame-less watching is needed, the user can enter the expression manually, can't they?

I agree with this.
polinasok: > > However, I think evaluateName used to add variables to WATCH pane should stay as names between a list of readable and easily identifiable names and a list of cryptic and look-alike expressions with addresses in the WATCH pane, an average user would prefer the names. It does mean that those watch variables will only be useful when looking at their frame and not while debugging the rest of the code. But if the truly frame-less watching is needed, the user can enter the expression manually, can't they?
> 
> I agree with this.

Great. I was actually thinking that it would be nice if users had a shortcut to get these expressions for that case (with good documentation to advertise). And then I realized that the shortcut is kind of there with dlv cli if you do the following:
```
(dlv) p aas // same as p *(&aas)
[]main.a len: 1, cap: 1, [
	{
		aas: []main.a len: 1, cap: 1, [
			(*main.a)(0xc00000c0e0),
		],},
]
(dlv) p &aas ### This gives you the address expression
(*[]main.a)(0xc000107390) ### Value not loaded
(dlv) // Dereference that expression to get to load the variable without the name
(dlv) p *(*[]main.a)(0xc000107390)
[]main.a len: 1, cap: 1, [
	{
		aas: []main.a len: 1, cap: 1, [
			(*main.a)(0xc00000c0e0),
		],},
]
```
But in DAP, based on legacy adapter, which is likely based on other adapters, we use a different syntax with <>. I used to think it was just cosmetic, but now realize that I should change it because it can actually be used in expressions.


Patch:
diff --git a/.deepsource.toml b/.deepsource.toml
index 6889372d..5f2b68ef 100644
--- a/.deepsource.toml
+++ b/.deepsource.toml
@@ -2,7 +2,7 @@ version = 1
 
 test_patterns = ["**/*_test.go"]
 
-exclude_patterns = ["vendor", "_fixtures", "_scripts"]
+exclude_patterns = ["vendor/**", "_fixtures/**", "_scripts/**"]
 
 [[analyzers]]
 name = "go"
diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts
index 8e0e0b63..6672672a 100644
--- a/.teamcity/settings.kts
+++ b/.teamcity/settings.kts
@@ -49,6 +49,10 @@ val targets = arrayOf(
         "windows/amd64/tip",
 
         "mac/amd64/1.16",
+        "mac/amd64/tip",
+
+        "mac/arm64/1.16",
+        "mac/arm64/tip"
 )
 
 project {
@@ -91,7 +95,7 @@ class AggregatorBuild(tests: Collection<BuildType>) : BuildType({
             vcsRootExtId = "${DslContext.settingsRoot.id}"
             provider = github {
                 authType = token {
-                    token = "credentialsJSON:5dc93081-e0b2-41e2-b8f0-dea3c96e6426"
+                    token = "credentialsJSON:a7e7526c-7195-4790-bbb6-9fb4692f92d0"
                 }
                 filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY
             }
@@ -101,7 +105,7 @@ class AggregatorBuild(tests: Collection<BuildType>) : BuildType({
             publisher = github {
                 githubUrl = "https://api.github.com"
                 authType = personalToken {
-                    token = "credentialsJSON:48af6e38-536d-4acb-ae2d-2fba57b6f3db"
+                    token = "credentialsJSON:a7e7526c-7195-4790-bbb6-9fb4692f92d0"
                 }
             }
             param("github_oauth_user", "")
@@ -230,7 +234,7 @@ class TestBuild(val os: String, val arch: String, version: String, buildId: Abso
             vcsRootExtId = "${DslContext.settingsRoot.id}"
             provider = github {
                 authType = token {
-                    token = "credentialsJSON:5dc93081-e0b2-41e2-b8f0-dea3c96e6426"
+                    token = "credentialsJSON:a7e7526c-7195-4790-bbb6-9fb4692f92d0"
                 }
                 filterAuthorRole = PullRequests.GitHubRoleFilter.EVERYBODY
             }
diff --git a/pkg/dwarf/frame/entries.go b/pkg/dwarf/frame/entries.go
index df31b231..cdfd204b 100644
--- a/pkg/dwarf/frame/entries.go
+++ b/pkg/dwarf/frame/entries.go
@@ -18,6 +18,9 @@ type CommonInformationEntry struct {
 	ReturnAddressRegister uint64
 	InitialInstructions   []byte
 	staticBase            uint64
+
+	// eh_frame pointer encoding
+	ptrEncAddr ptrEnc
 }
 
 // FrameDescriptionEntry represents a Frame Descriptor Entry in the
@@ -80,8 +83,64 @@ func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry,
 // Append appends otherFDEs to fdes and returns the result.
 func (fdes FrameDescriptionEntries) Append(otherFDEs FrameDescriptionEntries) FrameDescriptionEntries {
 	r := append(fdes, otherFDEs...)
-	sort.Slice(r, func(i, j int) bool {
+	sort.SliceStable(r, func(i, j int) bool {
 		return r[i].Begin() < r[j].Begin()
 	})
+	// remove duplicates
+	uniqFDEs := fdes[:0]
+	for _, fde := range fdes {
+		if len(uniqFDEs) > 0 {
+			last := uniqFDEs[len(uniqFDEs)-1]
+			if last.Begin() == fde.Begin() && last.End() == fde.End() {
+				continue
+			}
+		}
+		uniqFDEs = append(uniqFDEs, fde)
+	}
 	return r
 }
+
+// ptrEnc represents a pointer encoding value, used during eh_frame decoding
+// to determine how pointers were encoded.
+// Least significant 4 (0xf) bytes encode the size  as well as its
+// signed-ness,  most significant 4 bytes (0xf0) are flags describing how
+// the value should be interpreted (absolute, relative...)
+// See https://www.airs.com/blog/archives/460.
+type ptrEnc uint8
+
+const (
+	ptrEncAbs    ptrEnc = 0x00 // pointer-sized unsigned integer
+	ptrEncOmit   ptrEnc = 0xff // omitted
+	ptrEncUleb   ptrEnc = 0x01 // ULEB128
+	ptrEncUdata2 ptrEnc = 0x02 // 2 bytes
+	ptrEncUdata4 ptrEnc = 0x03 // 4 bytes
+	ptrEncUdata8 ptrEnc = 0x04 // 8 bytes
+	ptrEncSigned ptrEnc = 0x08 // pointer-sized signed integer
+	ptrEncSleb   ptrEnc = 0x09 // SLEB128
+	ptrEncSdata2 ptrEnc = 0x0a // 2 bytes, signed
+	ptrEncSdata4 ptrEnc = 0x0b // 4 bytes, signed
+	ptrEncSdata8 ptrEnc = 0x0c // 8 bytes, signed
+
+	ptrEncPCRel    ptrEnc = 0x10 // value is relative to the memory address where it appears
+	ptrEncTextRel  ptrEnc = 0x20 // value is relative to the address of the text section
+	ptrEncDataRel  ptrEnc = 0x30 // value is relative to the address of the data section
+	ptrEncFuncRel  ptrEnc = 0x40 // value is relative to the start of the function
+	ptrEncAligned  ptrEnc = 0x50 // value should be aligned
+	ptrEncIndirect ptrEnc = 0x80 // value is an address where the real value of the pointer is stored
+)
+
+// Supported returns true if this pointer encoding is supported.
+func (ptrEnc ptrEnc) Supported() bool {
+	if ptrEnc != ptrEncOmit {
+		szenc := ptrEnc & 0x0f
+		if ((szenc > ptrEncUdata8) && (szenc < ptrEncSigned)) || (szenc > ptrEncSdata8) {
+			// These values aren't defined at the moment
+			return false
+		}
+		if ptrEnc&0xf0 != ptrEncPCRel {
+			// Currently only the PC relative flag is supported
+			return false
+		}
+	}
+	return true
+}
diff --git a/pkg/dwarf/frame/parser.go b/pkg/dwarf/frame/parser.go
index 8b0c165b..ba5ddd59 100644
--- a/pkg/dwarf/frame/parser.go
+++ b/pkg/dwarf/frame/parser.go
@@ -6,6 +6,8 @@ package frame
 import (
 	"bytes"
 	"encoding/binary"
+	"fmt"
+	"io"
 
 	"github.com/go-delve/delve/pkg/dwarf/util"
 )
@@ -15,82 +17,135 @@ type parsefunc func(*parseContext) parsefunc
 type parseContext struct {
 	staticBase uint64
 
-	buf     *bytes.Buffer
-	entries FrameDescriptionEntries
-	common  *CommonInformationEntry
-	frame   *FrameDescriptionEntry
-	length  uint32
-	ptrSize int
+	buf         *bytes.Buffer
+	totalLen    int
+	entries     FrameDescriptionEntries
+	ciemap      map[int]*CommonInformationEntry
+	common      *CommonInformationEntry
+	frame       *FrameDescriptionEntry
+	length      uint32
+	ptrSize     int
+	ehFrameAddr uint64
+	err         error
 }
 
 // Parse takes in data (a byte slice) and returns FrameDescriptionEntries,
 // which is a slice of FrameDescriptionEntry. Each FrameDescriptionEntry
 // has a pointer to CommonInformationEntry.
-func Parse(data []byte, order binary.ByteOrder, staticBase uint64, ptrSize int) FrameDescriptionEntries {
+// If ehFrameAddr is not zero the .eh_frame format will be used, a minor variant of DWARF described at https://www.airs.com/blog/archives/460.
+// The value of ehFrameAddr will be used as the address at which eh_frame will be mapped into memory
+func Parse(data []byte, order binary.ByteOrder, staticBase uint64, ptrSize int, ehFrameAddr uint64) (FrameDescriptionEntries, error) {
 	var (
 		buf  = bytes.NewBuffer(data)
-		pctx = &parseContext{buf: buf, entries: newFrameIndex(), staticBase: staticBase, ptrSize: ptrSize}
+		pctx = &parseContext{buf: buf, totalLen: len(data), entries: newFrameIndex(), staticBase: staticBase, ptrSize: ptrSize, ehFrameAddr: ehFrameAddr, ciemap: map[int]*CommonInformationEntry{}}
 	)
 
 	for fn := parselength; buf.Len() != 0; {
 		fn = fn(pctx)
+		if pctx.err != nil {
+			return nil, pctx.err
+		}
 	}
 
 	for i := range pctx.entries {
 		pctx.entries[i].order = order
 	}
 
-	return pctx.entries
+	return pctx.entries, nil
 }
 
-func cieEntry(data []byte) bool {
-	return bytes.Equal(data, []byte{0xff, 0xff, 0xff, 0xff})
+func (ctx *parseContext) parsingEHFrame() bool {
+	return ctx.ehFrameAddr > 0
+}
+
+func (ctx *parseContext) cieEntry(cieid uint32) bool {
+	if ctx.parsingEHFrame() {
+		return cieid == 0x00
+	}
+	return cieid == 0xffffffff
+}
+
+func (ctx *parseContext) offset() int {
+	return ctx.totalLen - ctx.buf.Len()
 }
 
 func parselength(ctx *parseContext) parsefunc {
-	binary.Read(ctx.buf, binary.LittleEndian, &ctx.length)
+	start := ctx.offset()
+	binary.Read(ctx.buf, binary.LittleEndian, &ctx.length) //TODO(aarzilli): this does not support 64bit DWARF
 
 	if ctx.length == 0 {
 		// ZERO terminator
 		return parselength
 	}
 
-	var data = ctx.buf.Next(4)
+	var cieid uint32
+	binary.Read(ctx.buf, binary.LittleEndian, &cieid)
 
 	ctx.length -= 4 // take off the length of the CIE id / CIE pointer.
 
-	if cieEntry(data) {
+	if ctx.cieEntry(cieid) {
 		ctx.common = &CommonInformationEntry{Length: ctx.length, staticBase: ctx.staticBase}
+		ctx.ciemap[start] = ctx.common
 		return parseCIE
 	}
 
-	ctx.frame = &FrameDescriptionEntry{Length: ctx.length, CIE: ctx.common}
+	if ctx.ehFrameAddr > 0 {
+		cieid = uint32(start - int(cieid) + 4)
+	}
+
+	common := ctx.ciemap[int(cieid)]
+
+	if common == nil {
+		ctx.err = fmt.Errorf("unknown CIE_id %#x at %#x", cieid, start)
+	}
+
+	ctx.frame = &FrameDescriptionEntry{Length: ctx.length, CIE: common}
 	return parseFDE
 }
 
 func parseFDE(ctx *parseContext) parsefunc {
-	var num uint64
+	startOff := ctx.offset()
 	r := ctx.buf.Next(int(ctx.length))
 
 	reader := bytes.NewReader(r)
-	num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
+	num := ctx.readEncodedPtr(addrSum(ctx.ehFrameAddr+uint64(startOff), reader), reader, ctx.frame.CIE.ptrEncAddr)
 	ctx.frame.begin = num + ctx.staticBase
-	num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
-	ctx.frame.size = num
+
+	// For the size field in .eh_frame only the size encoding portion of the
+	// address pointer encoding is considered.
+	// See decode_frame_entry_1 in gdb/dwarf2-frame.c.
+	// For .debug_frame ptrEncAddr is always ptrEncAbs and never has flags.
+	sizePtrEnc := ctx.frame.CIE.ptrEncAddr & 0x0f
+	ctx.frame.size = ctx.readEncodedPtr(0, reader, sizePtrEnc)
 
 	// Insert into the tree after setting address range begin
 	// otherwise compares won't work.
 	ctx.entries = append(ctx.entries, ctx.frame)
 
+	if ctx.parsingEHFrame() && len(ctx.frame.CIE.Augmentation) > 0 {
+		// If we are parsing a .eh_frame and we saw an agumentation string then we
+		// need to read the augmentation data, which are encoded as a ULEB128
+		// size followed by 'size' bytes.
+		n, _ := util.DecodeULEB128(reader)
+		reader.Seek(int64(n), io.SeekCurrent)
+	}
+
 	// The rest of this entry consists of the instructions
 	// so we can just grab all of the data from the buffer
 	// cursor to length.
-	ctx.frame.Instructions = r[2*ctx.ptrSize:]
+
+	off, _ := reader.Seek(0, io.SeekCurrent)
+	ctx.frame.Instructions = r[off:]
 	ctx.length = 0
 
 	return parselength
 }
 
+func addrSum(base uint64, buf *bytes.Reader) uint64 {
+	n, _ := buf.Seek(0, io.SeekCurrent)
+	return base + uint64(n)
+}
+
 func parseCIE(ctx *parseContext) parsefunc {
 	data := ctx.buf.Next(int(ctx.length))
 	buf := bytes.NewBuffer(data)
@@ -100,6 +155,15 @@ func parseCIE(ctx *parseContext) parsefunc {
 	// parse augmentation
 	ctx.common.Augmentation, _ = util.ParseString(buf)
 
+	if ctx.parsingEHFrame() {
+		if ctx.common.Augmentation == "eh" {
+			ctx.err = fmt.Errorf("unsupported 'eh' augmentation at %#x", ctx.offset())
+		}
+		if len(ctx.common.Augmentation) > 0 && ctx.common.Augmentation[0] != 'z' {
+			ctx.err = fmt.Errorf("unsupported augmentation at %#x (does not start with 'z')", ctx.offset())
+		}
+	}
+
 	// parse code alignment factor
 	ctx.common.CodeAlignmentFactor, _ = util.DecodeULEB128(buf)
 
@@ -107,7 +171,48 @@ func parseCIE(ctx *parseContext) parsefunc {
 	ctx.common.DataAlignmentFactor, _ = util.DecodeSLEB128(buf)
 
 	// parse return address register
-	ctx.common.ReturnAddressRegister, _ = util.DecodeULEB128(buf)
+	if ctx.parsingEHFrame() && ctx.common.Version == 1 {
+		b, _ := buf.ReadByte()
+		ctx.common.ReturnAddressRegister = uint64(b)
+	} else {
+		ctx.common.ReturnAddressRegister, _ = util.DecodeULEB128(buf)
+	}
+
+	ctx.common.ptrEncAddr = ptrEncAbs
+
+	if ctx.parsingEHFrame() && len(ctx.common.Augmentation) > 0 {
+		_, _ = util.DecodeULEB128(buf) // augmentation data length
+		for i := 1; i < len(ctx.common.Augmentation); i++ {
+			switch ctx.common.Augmentation[i] {
+			case 'L':
+				_, _ = buf.ReadByte() // LSDA pointer encoding, we don't support this.
+			case 'R':
+				// Pointer encoding, describes how begin and size fields of FDEs are encoded.
+				b, _ := buf.ReadByte()
+				ctx.common.ptrEncAddr = ptrEnc(b)
+				if !ctx.common.ptrEncAddr.Supported() {
+					ctx.err = fmt.Errorf("pointer encoding not supported %#x at %#x", ctx.common.ptrEncAddr, ctx.offset())
+					return nil
+				}
+			case 'S':
+				// Signal handler invocation frame, we don't support this but there is no associated data to read.
+			case 'P':
+				// Personality function encoded as a pointer encoding byte followed by
+				// the pointer to the personality function encoded as specified by the
+				// pointer encoding.
+				// We don't support this but have to read it anyway.
+				e, _ := buf.ReadByte()
+				if !ptrEnc(e).Supported() {
+					ctx.err = fmt.Errorf("pointer encoding not supported %#x at %#x", e, ctx.offset())
+					return nil
+				}
+				ctx.readEncodedPtr(0, buf, ptrEnc(e))
+			default:
+				ctx.err = fmt.Errorf("unsupported augmentation character %c at %#x", ctx.common.Augmentation[i], ctx.offset())
+				return nil
+			}
+		}
+	}
 
 	// parse initial instructions
 	// The rest of this entry consists of the instructions
@@ -119,6 +224,48 @@ func parseCIE(ctx *parseContext) parsefunc {
 	return parselength
 }
 
+// readEncodedPtr reads a pointer from buf encoded as specified by ptrEnc.
+// This function is used to read pointers from a .eh_frame section, when
+// used to parse a .debug_frame section ptrEnc will always be ptrEncAbs.
+// The parameter addr is the address that the current byte of 'buf' will be
+// mapped to when the executable file containing the eh_frame section being
+// parse is loaded in memory.
+func (ctx *parseContext) readEncodedPtr(addr uint64, buf util.ByteReaderWithLen, ptrEnc ptrEnc) uint64 {
+	if ptrEnc == ptrEncOmit {
+		return 0
+	}
+
+	var ptr uint64
+
+	switch ptrEnc & 0xf {
+	case ptrEncAbs, ptrEncSigned:
+		ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, ctx.ptrSize)
+	case ptrEncUleb:
+		ptr, _ = util.DecodeULEB128(buf)
+	case ptrEncUdata2:
+		ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 2)
+	case ptrEncSdata2:
+		ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 2)
+		ptr = uint64(int16(ptr))
+	case ptrEncUdata4:
+		ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 4)
+	case ptrEncSdata4:
+		ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 4)
+		ptr = uint64(int32(ptr))
+	case ptrEncUdata8, ptrEncSdata8:
+		ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 8)
+	case ptrEncSleb:
+		n, _ := util.DecodeSLEB128(buf)
+		ptr = uint64(n)
+	}
+
+	if ptrEnc&0xf0 == ptrEncPCRel {
+		ptr += addr
+	}
+
+	return ptr
+}
+
 // DwarfEndian determines the endianness of the DWARF by using the version number field in the debug_info section
 // Trick borrowed from "debug/dwarf".New()
 func DwarfEndian(infoSec []byte) binary.ByteOrder {
diff --git a/pkg/dwarf/op/regs.go b/pkg/dwarf/op/regs.go
index 3e4b6b6b..88dcf23f 100644
--- a/pkg/dwarf/op/regs.go
+++ b/pkg/dwarf/op/regs.go
@@ -14,11 +14,12 @@ type DwarfRegisters struct {
 	ObjBase   int64
 	regs      []*DwarfRegister
 
-	ByteOrder binary.ByteOrder
-	PCRegNum  uint64
-	SPRegNum  uint64
-	BPRegNum  uint64
-	LRRegNum  uint64
+	ByteOrder  binary.ByteOrder
+	PCRegNum   uint64
+	SPRegNum   uint64
+	BPRegNum   uint64
+	LRRegNum   uint64
+	ChangeFunc RegisterChangeFunc
 
 	FloatLoadError   error // error produced when loading floating point registers
 	loadMoreCallback func()
@@ -29,6 +30,8 @@ type DwarfRegister struct {
 	Bytes     []byte
 }
 
+type RegisterChangeFunc func(regNum uint64, reg *DwarfRegister) error
+
 // NewDwarfRegisters returns a new DwarfRegisters object.
 func NewDwarfRegisters(staticBase uint64, regs []*DwarfRegister, byteOrder binary.ByteOrder, pcRegNum, spRegNum, bpRegNum, lrRegNum uint64) *DwarfRegisters {
 	return &DwarfRegisters{
@@ -152,3 +155,12 @@ func DwarfRegisterFromBytes(bytes []byte) *DwarfRegister {
 	}
 	return &DwarfRegister{Uint64Val: v, Bytes: bytes}
 }
+
+// FillBytes fills the Bytes slice of reg using Uint64Val.
+func (reg *DwarfRegister) FillBytes() {
+	if reg.Bytes != nil {
+		return
+	}
+	reg.Bytes = make([]byte, 8)
+	binary.LittleEndian.PutUint64(reg.Bytes, reg.Uint64Val)
+}
diff --git a/pkg/dwarf/regnum/amd64.go b/pkg/dwarf/regnum/amd64.go
new file mode 100644
index 00000000..2adfb5c6
--- /dev/null
+++ b/pkg/dwarf/regnum/amd64.go
@@ -0,0 +1,136 @@
+package regnum
+
+import (
+	"fmt"
+	"strings"
+)
+
+// The mapping between hardware registers and DWARF registers is specified
+// in the System V ABI AMD64 Architecture Processor Supplement page 57,
+// figure 3.36
+// https://www.uclibc.org/docs/psABI-x86_64.pdf
+
+const (
+	AMD64_Rax     = 0
+	AMD64_Rdx     = 1
+	AMD64_Rcx     = 2
+	AMD64_Rbx     = 3
+	AMD64_Rsi     = 4
+	AMD64_Rdi     = 5
+	AMD64_Rbp     = 6
+	AMD64_Rsp     = 7
+	AMD64_R8      = 8
+	AMD64_R9      = 9
+	AMD64_R10     = 10
+	AMD64_R11     = 11
+	AMD64_R12     = 12
+	AMD64_R13     = 13
+	AMD64_R14     = 14
+	AMD64_R15     = 15
+	AMD64_Rip     = 16
+	AMD64_XMM0    = 17 // XMM1 through XMM15 follow
+	AMD64_ST0     = 33 // ST(1) through ST(7) follow
+	AMD64_Rflags  = 49
+	AMD64_Es      = 50
+	AMD64_Cs      = 51
+	AMD64_Ss      = 52
+	AMD64_Ds      = 53
+	AMD64_Fs      = 54
+	AMD64_Gs      = 55
+	AMD64_Fs_base = 58
+	AMD64_Gs_base = 59
+	AMD64_MXCSR   = 64
+	AMD64_CW      = 65
+	AMD64_SW      = 66
+)
+
+var amd64DwarfToName = map[uint64]string{
+	AMD64_Rax:       "Rax",
+	AMD64_Rdx:       "Rdx",
+	AMD64_Rcx:       "Rcx",
+	AMD64_Rbx:       "Rbx",
+	AMD64_Rsi:       "Rsi",
+	AMD64_Rdi:       "Rdi",
+	AMD64_Rbp:       "Rbp",
+	AMD64_Rsp:       "Rsp",
+	AMD64_R8:        "R8",
+	AMD64_R9:        "R9",
+	AMD64_R10:       "R10",
+	AMD64_R11:       "R11",
+	AMD64_R12:       "R12",
+	AMD64_R13:       "R13",
+	AMD64_R14:       "R14",
+	AMD64_R15:       "R15",
+	AMD64_Rip:       "Rip",
+	AMD64_XMM0:      "XMM0",
+	AMD64_XMM0 + 1:  "XMM1",
+	AMD64_XMM0 + 2:  "XMM2",
+	AMD64_XMM0 + 3:  "XMM3",
+	AMD64_XMM0 + 4:  "XMM4",
+	AMD64_XMM0 + 5:  "XMM5",
+	AMD64_XMM0 + 6:  "XMM6",
+	AMD64_XMM0 + 7:  "XMM7",
+	AMD64_XMM0 + 8:  "XMM8",
+	AMD64_XMM0 + 9:  "XMM9",
+	AMD64_XMM0 + 10: "XMM10",
+	AMD64_XMM0 + 11: "XMM11",
+	AMD64_XMM0 + 12: "XMM12",
+	AMD64_XMM0 + 13: "XMM13",
+	AMD64_XMM0 + 14: "XMM14",
+	AMD64_XMM0 + 15: "XMM15",
+	AMD64_ST0:       "ST(0)",
+	AMD64_ST0 + 1:   "ST(1)",
+	AMD64_ST0 + 2:   "ST(2)",
+	AMD64_ST0 + 3:   "ST(3)",
+	AMD64_ST0 + 4:   "ST(4)",
+	AMD64_ST0 + 5:   "ST(5)",
+	AMD64_ST0 + 6:   "ST(6)",
+	AMD64_ST0 + 7:   "ST(7)",
+	AMD64_Rflags:    "Rflags",
+	AMD64_Es:        "Es",
+	AMD64_Cs:        "Cs",
+	AMD64_Ss:        "Ss",
+	AMD64_Ds:        "Ds",
+	AMD64_Fs:        "Fs",
+	AMD64_Gs:        "Gs",
+	AMD64_Fs_base:   "Fs_base",
+	AMD64_Gs_base:   "Gs_base",
+	AMD64_MXCSR:     "MXCSR",
+	AMD64_CW:        "CW",
+	AMD64_SW:        "SW",
+}
+
+var AMD64NameToDwarf = func() map[string]int {
+	r := make(map[string]int)
+	for regNum, regName := range amd64DwarfToName {
+		r[strings.ToLower(regName)] = int(regNum)
+	}
+	r["eflags"] = 49
+	r["st0"] = 33
+	r["st1"] = 34
+	r["st2"] = 35
+	r["st3"] = 36
+	r["st4"] = 37
+	r["st5"] = 38
+	r["st6"] = 39
+	r["st7"] = 40
+	return r
+}()
+
+func AMD64MaxRegNum() uint64 {
+	max := uint64(AMD64_Rip)
+	for i := range amd64DwarfToName {
+		if i > max {
+			max = i
+		}
+	}
+	return max
+}
+
+func AMD64ToName(num uint64) string {
+	name, ok := amd64DwarfToName[num]
+	if ok {
+		return name
+	}
+	return fmt.Sprintf("unknown%d", num)
+}
diff --git a/pkg/dwarf/regnum/arm64.go b/pkg/dwarf/regnum/arm64.go
new file mode 100644
index 00000000..13b15c1e
--- /dev/null
+++ b/pkg/dwarf/regnum/arm64.go
@@ -0,0 +1,35 @@
+package regnum
+
+import (
+	"fmt"
+)
+
+// The mapping between hardware registers and DWARF registers is specified
+// in the DWARF for the ARM® Architecture page 7,
+// Table 1
+// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0040b/IHI0040B_aadwarf.pdf
+
+const (
+	ARM64_X0 = 0  // X1 through X30 follow
+	ARM64_BP = 29 // also X29
+	ARM64_LR = 30 // also X30
+	ARM64_SP = 31
+	ARM64_PC = 32
+	ARM64_V0 = 64 // V1 through V31 follow
+
+)
+
+func ARM64ToName(num uint64) string {
+	switch {
+	case num <= 30:
+		return fmt.Sprintf("X%d", num)
+	case num == ARM64_SP:
+		return "SP"
+	case num == ARM64_PC:
+		return "PC"
+	case num >= ARM64_V0 && num <= 95:
+		return fmt.Sprintf("V%d", num-64)
+	default:
+		return fmt.Sprintf("unknown%d", num)
+	}
+}
diff --git a/pkg/dwarf/regnum/i386.go b/pkg/dwarf/regnum/i386.go
new file mode 100644
index 00000000..ab722700
--- /dev/null
+++ b/pkg/dwarf/regnum/i386.go
@@ -0,0 +1,102 @@
+package regnum
+
+import (
+	"fmt"
+	"strings"
+)
+
+// The mapping between hardware registers and DWARF registers is specified
+// in the System V ABI Intel386 Architecture Processor Supplement page 25,
+// table 2.14
+// https://www.uclibc.org/docs/psABI-i386.pdf
+
+const (
+	I386_Eax    = 0
+	I386_Ecx    = 1
+	I386_Edx    = 2
+	I386_Ebx    = 3
+	I386_Esp    = 4
+	I386_Ebp    = 5
+	I386_Esi    = 6
+	I386_Edi    = 7
+	I386_Eip    = 8
+	I386_Eflags = 9
+	I386_ST0    = 11 // ST(1) through ST(7) follow
+	I386_XMM0   = 21 // XMM1 through XMM7 follow
+	I386_Es     = 40
+	I386_Cs     = 41
+	I386_Ss     = 42
+	I386_Ds     = 43
+	I386_Fs     = 44
+	I386_Gs     = 45
+)
+
+var i386DwarfToName = map[int]string{
+	I386_Eax:      "Eax",
+	I386_Ecx:      "Ecx",
+	I386_Edx:      "Edx",
+	I386_Ebx:      "Ebx",
+	I386_Esp:      "Esp",
+	I386_Ebp:      "Ebp",
+	I386_Esi:      "Esi",
+	I386_Edi:      "Edi",
+	I386_Eip:      "Eip",
+	I386_Eflags:   "Eflags",
+	I386_ST0:      "ST(0)",
+	I386_ST0 + 1:  "ST(1)",
+	I386_ST0 + 2:  "ST(2)",
+	I386_ST0 + 3:  "ST(3)",
+	I386_ST0 + 4:  "ST(4)",
+	I386_ST0 + 5:  "ST(5)",
+	I386_ST0 + 6:  "ST(6)",
+	I386_ST0 + 7:  "ST(7)",
+	I386_XMM0:     "XMM0",
+	I386_XMM0 + 1: "XMM1",
+	I386_XMM0 + 2: "XMM2",
+	I386_XMM0 + 3: "XMM3",
+	I386_XMM0 + 4: "XMM4",
+	I386_XMM0 + 5: "XMM5",
+	I386_XMM0 + 6: "XMM6",
+	I386_XMM0 + 7: "XMM7",
+	I386_Es:       "Es",
+	I386_Cs:       "Cs",
+	I386_Ss:       "Ss",
+	I386_Ds:       "Ds",
+	I386_Fs:       "Fs",
+	I386_Gs:       "Gs",
+}
+
+var I386NameToDwarf = func() map[string]int {
+	r := make(map[string]int)
+	for regNum, regName := range i386DwarfToName {
+		r[strings.ToLower(regName)] = regNum
+	}
+	r["eflags"] = 9
+	r["st0"] = 11
+	r["st1"] = 12
+	r["st2"] = 13
+	r["st3"] = 14
+	r["st4"] = 15
+	r["st5"] = 16
+	r["st6"] = 17
+	r["st7"] = 18
+	return r
+}()
+
+func I386MaxRegNum() int {
+	max := int(I386_Eip)
+	for i := range i386DwarfToName {
+		if i > max {
+			max = i
+		}
+	}
+	return max
+}
+
+func I386ToName(num int) string {
+	name, ok := i386DwarfToName[num]
+	if ok {
+		return name
+	}
+	return fmt.Sprintf("unknown%d", num)
+}
diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/util/util.go
index 8cb06e40..0fcf64f7 100644
--- a/pkg/dwarf/util/util.go
+++ b/pkg/dwarf/util/util.go
@@ -8,12 +8,20 @@ import (
 	"io"
 )
 
+// ByteReaderWithLen is a io.ByteReader with a Len method. This interface is
+// satisified by both bytes.Buffer and bytes.Reader.
+type ByteReaderWithLen interface {
+	io.ByteReader
+	io.Reader
+	Len() int
+}
+
 // The Little Endian Base 128 format is defined in the DWARF v4 standard,
 // section 7.6, page 161 and following.
 
 // DecodeULEB128 decodes an unsigned Little Endian Base 128
 // represented number.
-func DecodeULEB128(buf *bytes.Buffer) (uint64, uint32) {
+func DecodeULEB128(buf ByteReaderWithLen) (uint64, uint32) {
 	var (
 		result uint64
 		shift  uint64
@@ -46,7 +54,7 @@ func DecodeULEB128(buf *bytes.Buffer) (uint64, uint32) {
 
 // DecodeSLEB128 decodes a signed Little Endian Base 128
 // represented number.
-func DecodeSLEB128(buf *bytes.Buffer) (int64, uint32) {
+func DecodeSLEB128(buf ByteReaderWithLen) (int64, uint32) {
 	var (
 		b      byte
 		err    error
diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go
index 21805c5a..6a20bbfe 100644
--- a/pkg/proc/amd64_arch.go
+++ b/pkg/proc/amd64_arch.go
@@ -10,12 +10,7 @@ import (
 
 	"github.com/go-delve/delve/pkg/dwarf/frame"
 	"github.com/go-delve/delve/pkg/dwarf/op"
-)
-
-const (
-	amd64DwarfIPRegNum uint64 = 16
-	amd64DwarfSPRegNum uint64 = 7
-	amd64DwarfBPRegNum uint64 = 6
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 )
 
 var amd64BreakInstruction = []byte{0xCC}
@@ -39,6 +34,10 @@ func AMD64Arch(goos string) *Arch {
 		DwarfRegisterToString:            amd64DwarfRegisterToString,
 		inhibitStepInto:                  func(*BinaryInfo, uint64) bool { return false },
 		asmDecode:                        amd64AsmDecode,
+		PCRegNum:                         regnum.AMD64_Rip,
+		SPRegNum:                         regnum.AMD64_Rsp,
+		BPRegNum:                         regnum.AMD64_Rbp,
+		ContextRegNum:                    regnum.AMD64_Rdx,
 	}
 }
 
@@ -68,24 +67,24 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 		// here).
 
 		return &frame.FrameContext{
-			RetAddrReg: amd64DwarfIPRegNum,
+			RetAddrReg: regnum.AMD64_Rip,
 			Regs: map[uint64]frame.DWRule{
-				amd64DwarfIPRegNum: {
+				regnum.AMD64_Rip: {
 					Rule:   frame.RuleOffset,
 					Offset: int64(-a.PtrSize()),
 				},
-				amd64DwarfBPRegNum: {
+				regnum.AMD64_Rbp: {
 					Rule:   frame.RuleOffset,
 					Offset: int64(-2 * a.PtrSize()),
 				},
-				amd64DwarfSPRegNum: {
+				regnum.AMD64_Rsp: {
 					Rule:   frame.RuleValOffset,
 					Offset: 0,
 				},
 			},
 			CFA: frame.DWRule{
 				Rule:   frame.RuleCFA,
-				Reg:    amd64DwarfBPRegNum,
+				Reg:    regnum.AMD64_Rbp,
 				Offset: int64(2 * a.PtrSize()),
 			},
 		}
@@ -112,10 +111,10 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 	// so that we can use it to unwind the stack even when we encounter frames
 	// without descriptor entries.
 	// If there isn't a rule already we emit one.
-	if fctxt.Regs[amd64DwarfBPRegNum].Rule == frame.RuleUndefined {
-		fctxt.Regs[amd64DwarfBPRegNum] = frame.DWRule{
+	if fctxt.Regs[regnum.AMD64_Rbp].Rule == frame.RuleUndefined {
+		fctxt.Regs[regnum.AMD64_Rbp] = frame.DWRule{
 			Rule:   frame.RuleFramePointer,
-			Reg:    amd64DwarfBPRegNum,
+			Reg:    regnum.AMD64_Rbp,
 			Offset: 0,
 		}
 	}
@@ -244,110 +243,22 @@ func amd64SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool {
 // in the System V ABI AMD64 Architecture Processor Supplement page 57,
 // figure 3.36
 // https://www.uclibc.org/docs/psABI-x86_64.pdf
-func amd64RegSize(regnum uint64) int {
+func amd64RegSize(rn uint64) int {
 	// XMM registers
-	if regnum > amd64DwarfIPRegNum && regnum <= 32 {
+	if rn > regnum.AMD64_Rip && rn <= 32 {
 		return 16
 	}
 	// x87 registers
-	if regnum >= 33 && regnum <= 40 {
+	if rn >= 33 && rn <= 40 {
 		return 10
 	}
 	return 8
 }
 
-// The mapping between hardware registers and DWARF registers is specified
-// in the System V ABI AMD64 Architecture Processor Supplement page 57,
-// figure 3.36
-// https://www.uclibc.org/docs/psABI-x86_64.pdf
-
-var amd64DwarfToName = map[int]string{
-	0:  "Rax",
-	1:  "Rdx",
-	2:  "Rcx",
-	3:  "Rbx",
-	4:  "Rsi",
-	5:  "Rdi",
-	6:  "Rbp",
-	7:  "Rsp",
-	8:  "R8",
-	9:  "R9",
-	10: "R10",
-	11: "R11",
-	12: "R12",
-	13: "R13",
-	14: "R14",
-	15: "R15",
-	16: "Rip",
-	17: "XMM0",
-	18: "XMM1",
-	19: "XMM2",
-	20: "XMM3",
-	21: "XMM4",
-	22: "XMM5",
-	23: "XMM6",
-	24: "XMM7",
-	25: "XMM8",
-	26: "XMM9",
-	27: "XMM10",
-	28: "XMM11",
-	29: "XMM12",
-	30: "XMM13",
-	31: "XMM14",
-	32: "XMM15",
-	33: "ST(0)",
-	34: "ST(1)",
-	35: "ST(2)",
-	36: "ST(3)",
-	37: "ST(4)",
-	38: "ST(5)",
-	39: "ST(6)",
-	40: "ST(7)",
-	49: "Rflags",
-	50: "Es",
-	51: "Cs",
-	52: "Ss",
-	53: "Ds",
-	54: "Fs",
-	55: "Gs",
-	58: "Fs_base",
-	59: "Gs_base",
-	64: "MXCSR",
-	65: "CW",
-	66: "SW",
-}
-
-var amd64NameToDwarf = func() map[string]int {
-	r := make(map[string]int)
-	for regNum, regName := range amd64DwarfToName {
-		r[strings.ToLower(regName)] = regNum
-	}
-	r["eflags"] = 49
-	r["st0"] = 33
-	r["st1"] = 34
-	r["st2"] = 35
-	r["st3"] = 36
-	r["st4"] = 37
-	r["st5"] = 38
-	r["st6"] = 39
-	r["st7"] = 40
-	return r
-}()
-
-func maxAmd64DwarfRegister() int {
-	max := int(amd64DwarfIPRegNum)
-	for i := range amd64DwarfToName {
-		if i > max {
-			max = i
-		}
-	}
-	return max
-}
-
 func amd64RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.DwarfRegisters {
-	dregs := initDwarfRegistersFromSlice(maxAmd64DwarfRegister(), regs, amd64NameToDwarf)
-	dr := op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, amd64DwarfIPRegNum, amd64DwarfSPRegNum, amd64DwarfBPRegNum, 0)
-	dr.SetLoadMoreCallback(loadMoreDwarfRegistersFromSliceFunc(dr, regs, amd64NameToDwarf))
+	dregs := initDwarfRegistersFromSlice(int(regnum.AMD64MaxRegNum()), regs, regnum.AMD64NameToDwarf)
+	dr := op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, regnum.AMD64_Rip, regnum.AMD64_Rsp, regnum.AMD64_Rbp, 0)
+	dr.SetLoadMoreCallback(loadMoreDwarfRegistersFromSliceFunc(dr, regs, regnum.AMD64NameToDwarf))
 	return *dr
 }
 
@@ -389,18 +300,19 @@ func loadMoreDwarfRegistersFromSliceFunc(dr *op.DwarfRegisters, regs Registers,
 }
 
 func amd64AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters {
-	dregs := make([]*op.DwarfRegister, amd64DwarfIPRegNum+1)
-	dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc)
-	dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp)
-	dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp)
+	dregs := make([]*op.DwarfRegister, regnum.AMD64_Rip+1)
+	dregs[regnum.AMD64_Rip] = op.DwarfRegisterFromUint64(pc)
+	dregs[regnum.AMD64_Rsp] = op.DwarfRegisterFromUint64(sp)
+	dregs[regnum.AMD64_Rbp] = op.DwarfRegisterFromUint64(bp)
 
-	return *op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, amd64DwarfIPRegNum, amd64DwarfSPRegNum, amd64DwarfBPRegNum, 0)
+	return *op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, regnum.AMD64_Rip, regnum.AMD64_Rsp, regnum.AMD64_Rbp, 0)
 }
 
 func amd64DwarfRegisterToString(i int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
-	name, ok := amd64DwarfToName[i]
-	if !ok {
-		name = fmt.Sprintf("unknown%d", i)
+	name = regnum.AMD64ToName(uint64(i))
+
+	if reg == nil {
+		return name, false, ""
 	}
 
 	switch n := strings.ToLower(name); n {
diff --git a/pkg/proc/amd64util/xsave.go b/pkg/proc/amd64util/xsave.go
index b90bb3e4..16e621fd 100644
--- a/pkg/proc/amd64util/xsave.go
+++ b/pkg/proc/amd64util/xsave.go
@@ -74,6 +74,7 @@ func (xsave *AMD64Xstate) Decode() []proc.Register {
 const (
 	_XSTATE_MAX_KNOWN_SIZE = 2969
 
+	_XSAVE_XMM_REGION_START        = 160
 	_XSAVE_HEADER_START            = 512
 	_XSAVE_HEADER_LEN              = 64
 	_XSAVE_EXTENDED_REGION_START   = 576
@@ -129,3 +130,61 @@ func AMD64XstateRead(xstateargs []byte, readLegacy bool, regset *AMD64Xstate) er
 
 	return nil
 }
+
+func (xstate *AMD64Xstate) SetXmmRegister(n int, value []byte) error {
+	if n >= 16 {
+		return fmt.Errorf("setting register XMM%d not supported", n)
+	}
+	if len(value) > 64 {
+		return fmt.Errorf("value of register XMM%d too large (%d bytes)", n, len(value))
+	}
+
+	// Copy least significant 16 bytes to Xsave area
+
+	xmmval := value
+	if len(xmmval) > 16 {
+		xmmval = xmmval[:16]
+	}
+	rest := value[len(xmmval):]
+
+	xmmpos := _XSAVE_XMM_REGION_START + (n * 16)
+	if xmmpos >= len(xstate.Xsave) {
+		return fmt.Errorf("could not set XMM%d: not in XSAVE area", n)
+	}
+
+	copy(xstate.Xsave[xmmpos:], xmmval)
+
+	if len(rest) == 0 {
+		return nil
+	}
+
+	// Copy bytes [16, 32) to Xsave area
+
+	ymmval := rest
+	if len(ymmval) > 16 {
+		ymmval = ymmval[:16]
+	}
+	rest = rest[len(ymmval):]
+
+	ymmpos := _XSAVE_EXTENDED_REGION_START + (n * 16)
+	if ymmpos >= len(xstate.Xsave) {
+		return fmt.Errorf("could not set XMM%d: bytes 16..%d not in XSAVE area", n, 16+len(ymmval))
+	}
+
+	copy(xstate.Xsave[ymmpos:], ymmval)
+
+	if len(rest) == 0 {
+		return nil
+	}
+
+	// Copy bytes [32, 64) to Xsave area
+
+	zmmval := rest
+	zmmpos := _XSAVE_AVX512_ZMM_REGION_START + (n * 32)
+	if zmmpos >= len(xstate.Xsave) {
+		return fmt.Errorf("could not set XMM%d: bytes 32..%d not in XSAVE area", n, 32+len(zmmval))
+	}
+
+	copy(xstate.Xsave[zmmpos:], zmmval)
+	return nil
+}
diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go
index 732b8704..39d2ec23 100644
--- a/pkg/proc/arch.go
+++ b/pkg/proc/arch.go
@@ -17,6 +17,10 @@ type Arch struct {
 	breakInstrMovesPC        bool
 	derefTLS                 bool
 	usesLR                   bool // architecture uses a link register, also called RA on some architectures
+	PCRegNum                 uint64
+	SPRegNum                 uint64
+	BPRegNum                 uint64
+	ContextRegNum            uint64 // register used to pass a closure context when calling a function pointer
 
 	// asmDecode decodes the assembly instruction starting at mem[0:] into asmInst.
 	// It assumes that the Loc and AtPC fields of asmInst have already been filled.
@@ -34,7 +38,9 @@ type Arch struct {
 	// addrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in
 	// PC, SP, and BP registers in the format used by the DWARF expression interpreter.
 	addrAndStackRegsToDwarfRegisters func(uint64, uint64, uint64, uint64, uint64) op.DwarfRegisters
-	// DwarfRegisterToString returns the name and value representation of the given register.
+	// DwarfRegisterToString returns the name and value representation of the
+	// given register, the register value can be nil in which case only the
+	// register name will be returned.
 	DwarfRegisterToString func(int, *op.DwarfRegister) (string, bool, string)
 	// inhibitStepInto returns whether StepBreakpoint can be set at pc.
 	inhibitStepInto func(bi *BinaryInfo, pc uint64) bool
diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go
index 49b9395a..7952ab04 100644
--- a/pkg/proc/arm64_arch.go
+++ b/pkg/proc/arm64_arch.go
@@ -8,16 +8,10 @@ import (
 
 	"github.com/go-delve/delve/pkg/dwarf/frame"
 	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 	"golang.org/x/arch/arm64/arm64asm"
 )
 
-const (
-	arm64DwarfIPRegNum uint64 = 32
-	arm64DwarfSPRegNum uint64 = 31
-	arm64DwarfLRRegNum uint64 = 30
-	arm64DwarfBPRegNum uint64 = 29
-)
-
 var arm64BreakInstruction = []byte{0x0, 0x0, 0x20, 0xd4}
 
 // ARM64Arch returns an initialized ARM64
@@ -40,6 +34,8 @@ func ARM64Arch(goos string) *Arch {
 		inhibitStepInto:                  func(*BinaryInfo, uint64) bool { return false },
 		asmDecode:                        arm64AsmDecode,
 		usesLR:                           true,
+		PCRegNum:                         regnum.ARM64_PC,
+		SPRegNum:                         regnum.ARM64_SP,
 	}
 }
 
@@ -69,24 +65,24 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 		// here).
 
 		return &frame.FrameContext{
-			RetAddrReg: arm64DwarfIPRegNum,
+			RetAddrReg: regnum.ARM64_PC,
 			Regs: map[uint64]frame.DWRule{
-				arm64DwarfIPRegNum: frame.DWRule{
+				regnum.ARM64_PC: frame.DWRule{
 					Rule:   frame.RuleOffset,
 					Offset: int64(-a.PtrSize()),
 				},
-				arm64DwarfBPRegNum: frame.DWRule{
+				regnum.ARM64_BP: frame.DWRule{
 					Rule:   frame.RuleOffset,
 					Offset: int64(-2 * a.PtrSize()),
 				},
-				arm64DwarfSPRegNum: frame.DWRule{
+				regnum.ARM64_SP: frame.DWRule{
 					Rule:   frame.RuleValOffset,
 					Offset: 0,
 				},
 			},
 			CFA: frame.DWRule{
 				Rule:   frame.RuleCFA,
-				Reg:    arm64DwarfBPRegNum,
+				Reg:    regnum.ARM64_BP,
 				Offset: int64(2 * a.PtrSize()),
 			},
 		}
@@ -113,17 +109,17 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 	// so that we can use it to unwind the stack even when we encounter frames
 	// without descriptor entries.
 	// If there isn't a rule already we emit one.
-	if fctxt.Regs[arm64DwarfBPRegNum].Rule == frame.RuleUndefined {
-		fctxt.Regs[arm64DwarfBPRegNum] = frame.DWRule{
+	if fctxt.Regs[regnum.ARM64_BP].Rule == frame.RuleUndefined {
+		fctxt.Regs[regnum.ARM64_BP] = frame.DWRule{
 			Rule:   frame.RuleFramePointer,
-			Reg:    arm64DwarfBPRegNum,
+			Reg:    regnum.ARM64_BP,
 			Offset: 0,
 		}
 	}
-	if fctxt.Regs[arm64DwarfLRRegNum].Rule == frame.RuleUndefined {
-		fctxt.Regs[arm64DwarfLRRegNum] = frame.DWRule{
+	if fctxt.Regs[regnum.ARM64_LR].Rule == frame.RuleUndefined {
+		fctxt.Regs[regnum.ARM64_LR] = frame.DWRule{
 			Rule:   frame.RuleFramePointer,
-			Reg:    arm64DwarfLRRegNum,
+			Reg:    regnum.ARM64_LR,
 			Offset: 0,
 		}
 	}
@@ -317,8 +313,8 @@ var arm64NameToDwarf = func() map[string]int {
 	for i := 0; i <= 30; i++ {
 		r[fmt.Sprintf("x%d", i)] = i
 	}
-	r["pc"] = int(arm64DwarfIPRegNum)
-	r["lr"] = int(arm64DwarfLRRegNum)
+	r["pc"] = int(regnum.ARM64_PC)
+	r["lr"] = int(regnum.ARM64_LR)
 	r["sp"] = 31
 	for i := 0; i <= 31; i++ {
 		r[fmt.Sprintf("v%d", i)] = i + 64
@@ -327,7 +323,7 @@ var arm64NameToDwarf = func() map[string]int {
 }()
 
 func maxArm64DwarfRegister() int {
-	max := int(arm64DwarfIPRegNum)
+	max := int(regnum.ARM64_PC)
 	for i := range arm64DwarfToHardware {
 		if i > max {
 			max = i
@@ -339,11 +335,11 @@ func maxArm64DwarfRegister() int {
 func arm64RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.DwarfRegisters {
 	dregs := make([]*op.DwarfRegister, maxArm64DwarfRegister()+1)
 
-	dregs[arm64DwarfIPRegNum] = op.DwarfRegisterFromUint64(regs.PC())
-	dregs[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(regs.SP())
-	dregs[arm64DwarfBPRegNum] = op.DwarfRegisterFromUint64(regs.BP())
+	dregs[regnum.ARM64_PC] = op.DwarfRegisterFromUint64(regs.PC())
+	dregs[regnum.ARM64_SP] = op.DwarfRegisterFromUint64(regs.SP())
+	dregs[regnum.ARM64_BP] = op.DwarfRegisterFromUint64(regs.BP())
 	if lr, err := regs.Get(int(arm64asm.X30)); err != nil {
-		dregs[arm64DwarfLRRegNum] = op.DwarfRegisterFromUint64(lr)
+		dregs[regnum.ARM64_LR] = op.DwarfRegisterFromUint64(lr)
 	}
 
 	for dwarfReg, asmReg := range arm64DwarfToHardware {
@@ -353,34 +349,26 @@ func arm64RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.DwarfR
 		}
 	}
 
-	dr := op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, arm64DwarfIPRegNum, arm64DwarfSPRegNum, arm64DwarfBPRegNum, arm64DwarfLRRegNum)
+	dr := op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, regnum.ARM64_PC, regnum.ARM64_SP, regnum.ARM64_BP, regnum.ARM64_LR)
 	dr.SetLoadMoreCallback(loadMoreDwarfRegistersFromSliceFunc(dr, regs, arm64NameToDwarf))
 	return *dr
 }
 
 func arm64AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters {
-	dregs := make([]*op.DwarfRegister, arm64DwarfIPRegNum+1)
-	dregs[arm64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc)
-	dregs[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp)
-	dregs[arm64DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp)
-	dregs[arm64DwarfLRRegNum] = op.DwarfRegisterFromUint64(lr)
+	dregs := make([]*op.DwarfRegister, regnum.ARM64_PC+1)
+	dregs[regnum.ARM64_PC] = op.DwarfRegisterFromUint64(pc)
+	dregs[regnum.ARM64_SP] = op.DwarfRegisterFromUint64(sp)
+	dregs[regnum.ARM64_BP] = op.DwarfRegisterFromUint64(bp)
+	dregs[regnum.ARM64_LR] = op.DwarfRegisterFromUint64(lr)
 
-	return *op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, arm64DwarfIPRegNum, arm64DwarfSPRegNum, arm64DwarfBPRegNum, arm64DwarfLRRegNum)
+	return *op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, regnum.ARM64_PC, regnum.ARM64_SP, regnum.ARM64_BP, regnum.ARM64_LR)
 }
 
 func arm64DwarfRegisterToString(i int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
-	// see arm64DwarfToHardware table for explanation
-	switch {
-	case i <= 30:
-		name = fmt.Sprintf("X%d", i)
-	case i == 31:
-		name = "SP"
-	case i == 32:
-		name = "PC"
-	case i >= 64 && i <= 95:
-		name = fmt.Sprintf("V%d", i-64)
-	default:
-		name = fmt.Sprintf("unknown%d", i)
+	name = regnum.ARM64ToName(uint64(i))
+
+	if reg == nil {
+		return name, false, ""
 	}
 
 	if reg.Bytes != nil && name[0] == 'V' {
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index d31cc4eb..22e5237f 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -809,7 +809,7 @@ func (bi *BinaryInfo) LoadImageFromData(dwdata *dwarf.Data, debugFrameBytes, deb
 	image.dwarfTreeCache, _ = simplelru.NewLRU(dwarfTreeCacheSize, nil)
 
 	if debugFrameBytes != nil {
-		bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0, bi.Arch.PtrSize())
+		bi.frameEntries, _ = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0, bi.Arch.PtrSize(), 0)
 	}
 
 	image.loclist2 = loclist.NewDwarf2Reader(debugLocBytes, bi.Arch.PtrSize())
@@ -1009,6 +1009,38 @@ func (bi *BinaryInfo) funcToImage(fn *Function) *Image {
 	return fn.cu.image
 }
 
+// parseDebugFrameGeneral parses a debug_frame and a eh_frame section.
+// At least one of the two must be present and parsed correctly, if
+// debug_frame is present it must be parsable correctly.
+func (bi *BinaryInfo) parseDebugFrameGeneral(image *Image, debugFrameBytes []byte, debugFrameName string, debugFrameErr error, ehFrameBytes []byte, ehFrameAddr uint64, ehFrameName string, byteOrder binary.ByteOrder) {
+	if debugFrameBytes == nil && ehFrameBytes == nil {
+		image.setLoadError("could not get %s section: %v", debugFrameName, debugFrameErr)
+		return
+	}
+
+	if debugFrameBytes != nil {
+		fe, err := frame.Parse(debugFrameBytes, byteOrder, image.StaticBase, bi.Arch.PtrSize(), 0)
+		if err != nil {
+			image.setLoadError("could not parse %s section: %v", debugFrameName, err)
+			return
+		}
+		bi.frameEntries = bi.frameEntries.Append(fe)
+	}
+
+	if ehFrameBytes != nil && ehFrameAddr > 0 {
+		fe, err := frame.Parse(ehFrameBytes, byteOrder, image.StaticBase, bi.Arch.PtrSize(), ehFrameAddr)
+		if err != nil {
+			if debugFrameBytes == nil {
+				image.setLoadError("could not parse %s section: %v", ehFrameName, err)
+				return
+			}
+			bi.logger.Warnf("could not parse %s section: %v", ehFrameName, err)
+			return
+		}
+		bi.frameEntries = bi.frameEntries.Append(fe)
+	}
+}
+
 // ELF ///////////////////////////////////////////////////////////////
 
 // ErrNoBuildIDNote is used in openSeparateDebugInfo to signal there's no
@@ -1207,13 +1239,16 @@ func (bi *BinaryInfo) loadSymbolName(image *Image, file *elf.File, wg *sync.Wait
 func (bi *BinaryInfo) parseDebugFrameElf(image *Image, exe *elf.File, debugInfoBytes []byte, wg *sync.WaitGroup) {
 	defer wg.Done()
 
-	debugFrameData, err := godwarf.GetDebugSectionElf(exe, "frame")
-	if err != nil {
-		image.setLoadError("could not get .debug_frame section: %v", err)
-		return
+	debugFrameData, debugFrameErr := godwarf.GetDebugSectionElf(exe, "frame")
+	ehFrameSection := exe.Section(".eh_frame")
+	var ehFrameData []byte
+	var ehFrameAddr uint64
+	if ehFrameSection != nil {
+		ehFrameAddr = ehFrameSection.Addr
+		ehFrameData, _ = ehFrameSection.Data()
 	}
 
-	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoBytes), image.StaticBase, bi.Arch.PtrSize()))
+	bi.parseDebugFrameGeneral(image, debugFrameData, ".debug_frame", debugFrameErr, ehFrameData, ehFrameAddr, ".eh_frame", frame.DwarfEndian(debugInfoBytes))
 }
 
 func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.WaitGroup) {
@@ -1363,12 +1398,7 @@ func (bi *BinaryInfo) parseDebugFramePE(image *Image, exe *pe.File, debugInfoByt
 	defer wg.Done()
 
 	debugFrameBytes, err := godwarf.GetDebugSectionPE(exe, "frame")
-	if err != nil {
-		image.setLoadError("could not get .debug_frame section: %v", err)
-		return
-	}
-
-	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase, bi.Arch.PtrSize()))
+	bi.parseDebugFrameGeneral(image, debugFrameBytes, ".debug_frame", err, nil, 0, "", frame.DwarfEndian(debugInfoBytes))
 }
 
 // Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
@@ -1453,13 +1483,16 @@ func (bi *BinaryInfo) setGStructOffsetMacho() {
 func (bi *BinaryInfo) parseDebugFrameMacho(image *Image, exe *macho.File, debugInfoBytes []byte, wg *sync.WaitGroup) {
 	defer wg.Done()
 
-	debugFrameBytes, err := godwarf.GetDebugSectionMacho(exe, "frame")
-	if err != nil {
-		image.setLoadError("could not get __debug_frame section: %v", err)
-		return
+	debugFrameBytes, debugFrameErr := godwarf.GetDebugSectionMacho(exe, "frame")
+	ehFrameSection := exe.Section("__eh_frame")
+	var ehFrameBytes []byte
+	var ehFrameAddr uint64
+	if ehFrameSection != nil {
+		ehFrameAddr = ehFrameSection.Addr
+		ehFrameBytes, _ = ehFrameSection.Data()
 	}
 
-	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase, bi.Arch.PtrSize()))
+	bi.parseDebugFrameGeneral(image, debugFrameBytes, "__debug_frame", debugFrameErr, ehFrameBytes, ehFrameAddr, "__eh_frame", frame.DwarfEndian(debugInfoBytes))
 }
 
 // Do not call this function directly it isn't able to deal correctly with package paths
diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go
index 4855dbb7..c586daae 100644
--- a/pkg/proc/core/core.go
+++ b/pkg/proc/core/core.go
@@ -6,6 +6,7 @@ import (
 	"io"
 
 	"github.com/go-delve/delve/pkg/elfwriter"
+	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/proc"
 )
 
@@ -372,6 +373,12 @@ func (t *thread) SetDX(uint64) error {
 	return ErrChangeRegisterCore
 }
 
+// ChangeRegs will always return an error, you cannot
+// change register values when debugging core files.
+func (t *thread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
+	return ErrChangeRegisterCore
+}
+
 // Breakpoints will return all breakpoints for the process.
 func (p *process) Breakpoints() *proc.BreakpointMap {
 	return &p.breakpoints
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index e5422a46..026ea39d 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -421,13 +421,13 @@ func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) erro
 	sp := regs.SP()
 	// push PC on the stack
 	sp -= uint64(bi.Arch.PtrSize())
-	if err := thread.SetSP(sp); err != nil {
+	if err := setSP(thread, sp); err != nil {
 		return err
 	}
 	if err := writePointer(bi, thread.ProcessMemory(), sp, regs.PC()); err != nil {
 		return err
 	}
-	return thread.SetPC(callAddr)
+	return setPC(thread, callAddr)
 }
 
 // funcCallEvalFuncExpr evaluates expr.Fun and returns the function that we're trying to call.
@@ -761,7 +761,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
 		if fncall.closureAddr != 0 {
 			// When calling a function pointer we must set the DX register to the
 			// address of the function pointer itself.
-			thread.SetDX(fncall.closureAddr)
+			setClosureReg(thread, fncall.closureAddr)
 		}
 		cfa := regs.SP()
 		oldpc := regs.PC()
@@ -770,8 +770,8 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
 		err := funcCallEvalArgs(callScope, fncall, cfa)
 		if err != nil {
 			// rolling back the call, note: this works because we called regs.Copy() above
-			thread.SetSP(cfa)
-			thread.SetPC(oldpc)
+			setSP(thread, cfa)
+			setPC(thread, oldpc)
 			fncall.err = err
 			fncall.lateCallFailure = true
 			break
@@ -784,10 +784,10 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread
 		if err := thread.RestoreRegisters(fncall.savedRegs); err != nil {
 			fncall.err = fmt.Errorf("could not restore registers: %v", err)
 		}
-		if err := thread.SetPC(pc); err != nil {
+		if err := setPC(thread, pc); err != nil {
 			fncall.err = fmt.Errorf("could not restore PC: %v", err)
 		}
-		if err := thread.SetSP(sp); err != nil {
+		if err := setSP(thread, sp); err != nil {
 			fncall.err = fmt.Errorf("could not restore SP: %v", err)
 		}
 		if err := stepInstructionOut(p, thread, debugCallFunctionName, debugCallFunctionName); err != nil {
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index b997e478..d7c800bf 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -80,6 +80,7 @@ import (
 	"golang.org/x/arch/arm64/arm64asm"
 	"golang.org/x/arch/x86/x86asm"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/elfwriter"
 	"github.com/go-delve/delve/pkg/logflags"
 	"github.com/go-delve/delve/pkg/proc"
@@ -1744,7 +1745,7 @@ func (t *gdbThread) SetCurrentBreakpoint(adjustPC bool) error {
 	pc := regs.PC()
 	if bp, ok := t.p.FindBreakpoint(pc); ok {
 		if t.regs.PC() != bp.Addr {
-			if err := t.SetPC(bp.Addr); err != nil {
+			if err := t.setPC(bp.Addr); err != nil {
 				return err
 			}
 		}
@@ -1770,13 +1771,6 @@ func (regs *gdbRegisters) setPC(value uint64) {
 func (regs *gdbRegisters) SP() uint64 {
 	return binary.LittleEndian.Uint64(regs.regs[regnameSP].value)
 }
-func (regs *gdbRegisters) setSP(value uint64) {
-	binary.LittleEndian.PutUint64(regs.regs[regnameSP].value, value)
-}
-
-func (regs *gdbRegisters) setDX(value uint64) {
-	binary.LittleEndian.PutUint64(regs.regs[regnameDX].value, value)
-}
 
 func (regs *gdbRegisters) BP() uint64 {
 	return binary.LittleEndian.Uint64(regs.regs[regnameBP].value)
@@ -2180,7 +2174,7 @@ func (r *gdbRegisters) FloatLoadError() error {
 }
 
 // SetPC will set the value of the PC register to the given value.
-func (t *gdbThread) SetPC(pc uint64) error {
+func (t *gdbThread) setPC(pc uint64) error {
 	_, _ = t.Registers() // Registes must be loaded first
 	t.regs.setPC(pc)
 	if t.p.gcmdok {
@@ -2190,30 +2184,26 @@ func (t *gdbThread) SetPC(pc uint64) error {
 	return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
 }
 
-// SetSP will set the value of the SP register to the given value.
-func (t *gdbThread) SetSP(sp uint64) error {
-	_, _ = t.Registers() // Registes must be loaded first
-	t.regs.setSP(sp)
-	if t.p.gcmdok {
-		return t.p.conn.writeRegisters(t.strID, t.regs.buf)
+// SetReg will change the value of a list of registers
+func (t *gdbThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
+	regName, _, _ := t.p.bi.Arch.DwarfRegisterToString(int(regNum), nil)
+	regName = strings.ToLower(regName)
+	_, _ = t.Registers() // Registers must be loaded first
+	gdbreg, ok := t.regs.regs[regName]
+	if !ok && strings.HasPrefix(regName, "xmm") {
+		// XMMn and YMMn are the same amd64 register (in different sizes), if we
+		// don't find XMMn try YMMn instead.
+		gdbreg, ok = t.regs.regs["y"+regName[1:]]
 	}
-	reg := t.regs.regs[regnameSP]
-	return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
-}
-
-// SetDX will set the value of the DX register to the given value.
-func (t *gdbThread) SetDX(dx uint64) error {
-	if t.p.bi.Arch.Name == "arm64" {
-		return fmt.Errorf("not supported")
+	if !ok {
+		return fmt.Errorf("could not set register %s: not found", regName)
 	}
-
-	_, _ = t.Registers() // Registes must be loaded first
-	t.regs.setDX(dx)
-	if t.p.gcmdok {
-		return t.p.conn.writeRegisters(t.strID, t.regs.buf)
+	reg.FillBytes()
+	if len(reg.Bytes) != len(gdbreg.value) {
+		return fmt.Errorf("could not set register %s: wrong size, expected %d got %d", regName, len(gdbreg.value), len(reg.Bytes))
 	}
-	reg := t.regs.regs[regnameDX]
-	return t.p.conn.writeRegister(t.strID, reg.regnum, reg.value)
+	copy(gdbreg.value, reg.Bytes)
+	return t.p.conn.writeRegister(t.strID, gdbreg.regnum, gdbreg.value)
 }
 
 func (regs *gdbRegisters) Slice(floatingPoint bool) ([]proc.Register, error) {
diff --git a/pkg/proc/i386_arch.go b/pkg/proc/i386_arch.go
index f22787d1..bf22d359 100644
--- a/pkg/proc/i386_arch.go
+++ b/pkg/proc/i386_arch.go
@@ -3,15 +3,11 @@ package proc
 import (
 	"encoding/binary"
 	"fmt"
-	"github.com/go-delve/delve/pkg/dwarf/frame"
-	"github.com/go-delve/delve/pkg/dwarf/op"
 	"strings"
-)
 
-const (
-	i386DwarfIPRegNum uint64 = 8
-	i386DwarfSPRegNum uint64 = 4
-	i386DwarfBPRegNum uint64 = 5
+	"github.com/go-delve/delve/pkg/dwarf/frame"
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 )
 
 var i386BreakInstruction = []byte{0xCC}
@@ -36,6 +32,8 @@ func I386Arch(goos string) *Arch {
 		DwarfRegisterToString:            i386DwarfRegisterToString,
 		inhibitStepInto:                  i386InhibitStepInto,
 		asmDecode:                        i386AsmDecode,
+		PCRegNum:                         regnum.I386_Eip,
+		SPRegNum:                         regnum.I386_Esp,
 	}
 }
 
@@ -65,24 +63,24 @@ func i386FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryI
 		// here).
 
 		return &frame.FrameContext{
-			RetAddrReg: i386DwarfIPRegNum,
+			RetAddrReg: regnum.I386_Eip,
 			Regs: map[uint64]frame.DWRule{
-				i386DwarfIPRegNum: frame.DWRule{
+				regnum.I386_Eip: frame.DWRule{
 					Rule:   frame.RuleOffset,
 					Offset: int64(-i.PtrSize()),
 				},
-				i386DwarfBPRegNum: frame.DWRule{
+				regnum.I386_Ebp: frame.DWRule{
 					Rule:   frame.RuleOffset,
 					Offset: int64(-2 * i.PtrSize()),
 				},
-				i386DwarfSPRegNum: frame.DWRule{
+				regnum.I386_Esp: frame.DWRule{
 					Rule:   frame.RuleValOffset,
 					Offset: 0,
 				},
 			},
 			CFA: frame.DWRule{
 				Rule:   frame.RuleCFA,
-				Reg:    i386DwarfBPRegNum,
+				Reg:    regnum.I386_Ebp,
 				Offset: int64(2 * i.PtrSize()),
 			},
 		}
@@ -103,10 +101,10 @@ func i386FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryI
 	// so that we can use it to unwind the stack even when we encounter frames
 	// without descriptor entries.
 	// If there isn't i rule already we emit one.
-	if fctxt.Regs[i386DwarfBPRegNum].Rule == frame.RuleUndefined {
-		fctxt.Regs[i386DwarfBPRegNum] = frame.DWRule{
+	if fctxt.Regs[regnum.I386_Ebp].Rule == frame.RuleUndefined {
+		fctxt.Regs[regnum.I386_Ebp] = frame.DWRule{
 			Rule:   frame.RuleFramePointer,
-			Reg:    i386DwarfBPRegNum,
+			Reg:    regnum.I386_Ebp,
 			Offset: 0,
 		}
 	}
@@ -186,94 +184,28 @@ func i386RegSize(regnum uint64) int {
 	return 4
 }
 
-// The mapping between hardware registers and DWARF registers is specified
-// in the System V ABI Intel386 Architecture Processor Supplement page 25,
-// table 2.14
-// https://www.uclibc.org/docs/psABI-i386.pdf
-
-var i386DwarfToName = map[int]string{
-	0:  "Eax",
-	1:  "Ecx",
-	2:  "Edx",
-	3:  "Ebx",
-	4:  "Esp",
-	5:  "Ebp",
-	6:  "Esi",
-	7:  "Edi",
-	8:  "Eip",
-	9:  "Eflags",
-	11: "ST(0)",
-	12: "ST(1)",
-	13: "ST(2)",
-	14: "ST(3)",
-	15: "ST(4)",
-	16: "ST(5)",
-	17: "ST(6)",
-	18: "ST(7)",
-	21: "XMM0",
-	22: "XMM1",
-	23: "XMM2",
-	24: "XMM3",
-	25: "XMM4",
-	26: "XMM5",
-	27: "XMM6",
-	28: "XMM7",
-	40: "Es",
-	41: "Cs",
-	42: "Ss",
-	43: "Ds",
-	44: "Fs",
-	45: "Gs",
-}
-
-var i386NameToDwarf = func() map[string]int {
-	r := make(map[string]int)
-	for regNum, regName := range i386DwarfToName {
-		r[strings.ToLower(regName)] = regNum
-	}
-	r["eflags"] = 9
-	r["st0"] = 11
-	r["st1"] = 12
-	r["st2"] = 13
-	r["st3"] = 14
-	r["st4"] = 15
-	r["st5"] = 16
-	r["st6"] = 17
-	r["st7"] = 18
-	return r
-}()
-
-func maxI386DwarfRegister() int {
-	max := int(i386DwarfIPRegNum)
-	for i := range i386DwarfToName {
-		if i > max {
-			max = i
-		}
-	}
-	return max
-}
-
 func i386RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.DwarfRegisters {
-	dregs := initDwarfRegistersFromSlice(maxI386DwarfRegister(), regs, i386NameToDwarf)
-	dr := op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, i386DwarfIPRegNum, i386DwarfSPRegNum, i386DwarfBPRegNum, 0)
-	dr.SetLoadMoreCallback(loadMoreDwarfRegistersFromSliceFunc(dr, regs, i386NameToDwarf))
+	dregs := initDwarfRegistersFromSlice(regnum.I386MaxRegNum(), regs, regnum.I386NameToDwarf)
+	dr := op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, regnum.I386_Eip, regnum.I386_Esp, regnum.I386_Ebp, 0)
+	dr.SetLoadMoreCallback(loadMoreDwarfRegistersFromSliceFunc(dr, regs, regnum.I386NameToDwarf))
 
 	return *dr
 }
 
 func i386AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters {
-	dregs := make([]*op.DwarfRegister, i386DwarfIPRegNum+1)
-	dregs[i386DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc)
-	dregs[i386DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp)
-	dregs[i386DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp)
+	dregs := make([]*op.DwarfRegister, regnum.I386_Eip+1)
+	dregs[regnum.I386_Eip] = op.DwarfRegisterFromUint64(pc)
+	dregs[regnum.I386_Esp] = op.DwarfRegisterFromUint64(sp)
+	dregs[regnum.I386_Ebp] = op.DwarfRegisterFromUint64(bp)
 
-	return *op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, i386DwarfIPRegNum, i386DwarfSPRegNum, i386DwarfBPRegNum, 0)
+	return *op.NewDwarfRegisters(staticBase, dregs, binary.LittleEndian, regnum.I386_Eip, regnum.I386_Esp, regnum.I386_Ebp, 0)
 }
 
 func i386DwarfRegisterToString(j int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
-	name, ok := i386DwarfToName[j]
-	if !ok {
-		name = fmt.Sprintf("unknown%d", j)
+	name = regnum.I386ToName(j)
+
+	if reg == nil {
+		return name, false, ""
 	}
 
 	switch n := strings.ToLower(name); n {
diff --git a/pkg/proc/linutil/regs_amd64_arch.go b/pkg/proc/linutil/regs_amd64_arch.go
index 6ea4bbc9..6d6f0cf4 100644
--- a/pkg/proc/linutil/regs_amd64_arch.go
+++ b/pkg/proc/linutil/regs_amd64_arch.go
@@ -1,8 +1,12 @@
 package linutil
 
 import (
+	"fmt"
+
 	"golang.org/x/arch/x86/x86asm"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/pkg/proc/amd64util"
 )
@@ -307,3 +311,71 @@ func (r *AMD64Registers) Copy() (proc.Registers, error) {
 	}
 	return &rr, nil
 }
+
+func (r *AMD64Registers) SetReg(regNum uint64, reg *op.DwarfRegister) (bool, error) {
+	var p *uint64
+	switch regNum {
+	case regnum.AMD64_Rax:
+		p = &r.Regs.Rax
+	case regnum.AMD64_Rbx:
+		p = &r.Regs.Rbx
+	case regnum.AMD64_Rcx:
+		p = &r.Regs.Rcx
+	case regnum.AMD64_Rdx:
+		p = &r.Regs.Rdx
+	case regnum.AMD64_Rsi:
+		p = &r.Regs.Rsi
+	case regnum.AMD64_Rdi:
+		p = &r.Regs.Rdi
+	case regnum.AMD64_Rbp:
+		p = &r.Regs.Rbp
+	case regnum.AMD64_Rsp:
+		p = &r.Regs.Rsp
+	case regnum.AMD64_R8:
+		p = &r.Regs.R8
+	case regnum.AMD64_R9:
+		p = &r.Regs.R9
+	case regnum.AMD64_R10:
+		p = &r.Regs.R10
+	case regnum.AMD64_R11:
+		p = &r.Regs.R11
+	case regnum.AMD64_R12:
+		p = &r.Regs.R12
+	case regnum.AMD64_R13:
+		p = &r.Regs.R13
+	case regnum.AMD64_R14:
+		p = &r.Regs.R14
+	case regnum.AMD64_R15:
+		p = &r.Regs.R15
+	case regnum.AMD64_Rip:
+		p = &r.Regs.Rip
+	}
+
+	if p != nil {
+		if reg.Bytes != nil && len(reg.Bytes) != 8 {
+			return false, fmt.Errorf("wrong number of bytes for register %s (%d)", regnum.AMD64ToName(regNum), len(reg.Bytes))
+		}
+		*p = reg.Uint64Val
+		return false, nil
+	}
+
+	if r.loadFpRegs != nil {
+		err := r.loadFpRegs(r)
+		if err != nil {
+			return false, err
+		}
+		r.loadFpRegs = nil
+	}
+
+	if regNum < regnum.AMD64_XMM0 || regNum > regnum.AMD64_XMM0+15 {
+		return false, fmt.Errorf("can not set %s", regnum.AMD64ToName(regNum))
+	}
+
+	reg.FillBytes()
+
+	err := r.Fpregset.SetXmmRegister(int(regNum-regnum.AMD64_XMM0), reg.Bytes)
+	if err != nil {
+		return false, err
+	}
+	return true, nil
+}
diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go
index 4a795cbf..70ff9434 100644
--- a/pkg/proc/mem.go
+++ b/pkg/proc/mem.go
@@ -89,27 +89,28 @@ const fakeAddress = 0xbeef0000
 // memory.
 type compositeMemory struct {
 	realmem MemoryReadWriter
+	arch    *Arch
 	regs    op.DwarfRegisters
 	pieces  []op.Piece
 	data    []byte
 }
 
-func newCompositeMemory(mem MemoryReadWriter, regs op.DwarfRegisters, pieces []op.Piece) (*compositeMemory, error) {
-	cmem := &compositeMemory{realmem: mem, regs: regs, pieces: pieces, data: []byte{}}
-	for _, piece := range pieces {
+func newCompositeMemory(mem MemoryReadWriter, arch *Arch, regs op.DwarfRegisters, pieces []op.Piece) (*compositeMemory, error) {
+	cmem := &compositeMemory{realmem: mem, arch: arch, regs: regs, pieces: pieces, data: []byte{}}
+	for i := range pieces {
+		piece := &pieces[i]
 		if piece.IsRegister {
 			reg := regs.Bytes(piece.RegNum)
-			sz := piece.Size
-			if sz == 0 && len(pieces) == 1 {
-				sz = len(reg)
+			if piece.Size == 0 && len(pieces) == 1 {
+				piece.Size = len(reg)
 			}
-			if sz > len(reg) {
+			if piece.Size > len(reg) {
 				if regs.FloatLoadError != nil {
-					return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d), also error loading floating point registers: %v", sz, piece.RegNum, len(reg), regs.FloatLoadError)
+					return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d), also error loading floating point registers: %v", piece.Size, piece.RegNum, len(reg), regs.FloatLoadError)
 				}
-				return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d)", sz, piece.RegNum, len(reg))
+				return nil, fmt.Errorf("could not read %d bytes from register %d (size: %d)", piece.Size, piece.RegNum, len(reg))
 			}
-			cmem.data = append(cmem.data, reg[:sz]...)
+			cmem.data = append(cmem.data, reg[:piece.Size]...)
 		} else {
 			buf := make([]byte, piece.Size)
 			mem.ReadMemory(buf, uint64(piece.Addr))
@@ -129,8 +130,41 @@ func (mem *compositeMemory) ReadMemory(data []byte, addr uint64) (int, error) {
 }
 
 func (mem *compositeMemory) WriteMemory(addr uint64, data []byte) (int, error) {
-	//TODO(aarzilli): implement
-	return 0, errors.New("can't write composite memory")
+	addr -= fakeAddress
+	if addr >= uint64(len(mem.data)) || addr+uint64(len(data)) > uint64(len(mem.data)) {
+		return 0, errors.New("write out of bounds")
+	}
+	if mem.regs.ChangeFunc == nil {
+		return 0, errors.New("can not write registers")
+	}
+
+	copy(mem.data[addr:], data)
+
+	curAddr := uint64(0)
+	donesz := 0
+	for _, piece := range mem.pieces {
+		if curAddr < (addr+uint64(len(data))) && addr < (curAddr+uint64(piece.Size)) {
+			// changed memory interval overlaps current piece
+			pieceMem := mem.data[curAddr : curAddr+uint64(piece.Size)]
+
+			if piece.IsRegister {
+				err := mem.regs.ChangeFunc(piece.RegNum, op.DwarfRegisterFromBytes(pieceMem))
+				if err != nil {
+					return donesz, err
+				}
+			} else {
+				n, err := mem.realmem.WriteMemory(uint64(piece.Addr), pieceMem)
+				if err != nil {
+					return donesz + n, err
+				}
+
+			}
+			donesz += piece.Size
+		}
+		curAddr += uint64(piece.Size)
+	}
+
+	return len(data), nil
 }
 
 // DereferenceMemory returns a MemoryReadWriter that can read and write the
diff --git a/pkg/proc/native/nonative_darwin.go b/pkg/proc/native/nonative_darwin.go
index 9531239c..95614cc3 100644
--- a/pkg/proc/native/nonative_darwin.go
+++ b/pkg/proc/native/nonative_darwin.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"sync"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/proc"
 )
 
@@ -82,17 +83,12 @@ func (dbp *nativeProcess) EntryPoint() (uint64, error) {
 }
 
 // SetPC sets the value of the PC register.
-func (t *nativeThread) SetPC(pc uint64) error {
+func (t *nativeThread) setPC(pc uint64) error {
 	panic(ErrNativeBackendDisabled)
 }
 
-// SetSP sets the value of the SP register.
-func (t *nativeThread) SetSP(sp uint64) error {
-	panic(ErrNativeBackendDisabled)
-}
-
-// SetDX sets the value of the DX register.
-func (t *nativeThread) SetDX(dx uint64) error {
+// SetReg changes the value of the specified register.
+func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
 	panic(ErrNativeBackendDisabled)
 }
 
diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go
index 78e92344..3bed3a9a 100644
--- a/pkg/proc/native/proc_linux.go
+++ b/pkg/proc/native/proc_linux.go
@@ -594,7 +594,7 @@ func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error)
 				}
 				if !isHardcodedBreakpoint {
 					// phantom breakpoint hit
-					_ = th.SetPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction())))
+					_ = th.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction())))
 					th.os.setbp = false
 					if trapthread.ThreadID() == th.ThreadID() {
 						// Will switch to a different thread for trapthread because we don't
diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go
index baba658b..dbad2370 100644
--- a/pkg/proc/native/proc_windows.go
+++ b/pkg/proc/native/proc_windows.go
@@ -325,7 +325,7 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
 						}
 					}
 					if !atbp {
-						thread.SetPC(uint64(exception.ExceptionRecord.ExceptionAddress))
+						thread.setPC(uint64(exception.ExceptionRecord.ExceptionAddress))
 					}
 				}
 
diff --git a/pkg/proc/native/register_linux_386.go b/pkg/proc/native/register_linux_386.go
index 72bfd90e..906b34fc 100644
--- a/pkg/proc/native/register_linux_386.go
+++ b/pkg/proc/native/register_linux_386.go
@@ -5,13 +5,15 @@ import (
 
 	sys "golang.org/x/sys/unix"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/pkg/proc/amd64util"
 	"github.com/go-delve/delve/pkg/proc/linutil"
 )
 
-// SetPC sets EIP to the value specified by 'pc'.
-func (thread *nativeThread) SetPC(pc uint64) error {
+// setPC sets EIP to the value specified by 'pc'.
+func (thread *nativeThread) setPC(pc uint64) error {
 	ir, err := registers(thread)
 	if err != nil {
 		return err
@@ -22,29 +24,26 @@ func (thread *nativeThread) SetPC(pc uint64) error {
 	return err
 }
 
-// SetSP sets ESP to the value specified by 'sp'
-func (thread *nativeThread) SetSP(sp uint64) (err error) {
-	var ir proc.Registers
-	ir, err = registers(thread)
+func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
+	ir, err := registers(thread)
 	if err != nil {
 		return err
 	}
 	r := ir.(*linutil.I386Registers)
-	r.Regs.Esp = int32(sp)
-	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
-	return
-}
-
-func (thread *nativeThread) SetDX(dx uint64) (err error) {
-	var ir proc.Registers
-	ir, err = registers(thread)
-	if err != nil {
-		return err
+	switch regNum {
+	case regnum.I386_Eip:
+		r.Regs.Eip = int32(reg.Uint64Val)
+	case regnum.I386_Esp:
+		r.Regs.Esp = int32(reg.Uint64Val)
+	case regnum.I386_Edx:
+		r.Regs.Edx = int32(reg.Uint64Val)
+	default:
+		//TODO(aarzilli): when the register calling convention is adopted by Go on
+		// i386 this should be implemented.
+		return fmt.Errorf("changing register %d not implemented", regNum)
 	}
-	r := ir.(*linutil.I386Registers)
-	r.Regs.Edx = int32(dx)
 	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
-	return
+	return err
 }
 
 func registers(thread *nativeThread) (proc.Registers, error) {
diff --git a/pkg/proc/native/registers_darwin_amd64.go b/pkg/proc/native/registers_darwin_amd64.go
index 8923441a..e036494c 100644
--- a/pkg/proc/native/registers_darwin_amd64.go
+++ b/pkg/proc/native/registers_darwin_amd64.go
@@ -5,12 +5,13 @@ package native
 // #include "threads_darwin.h"
 import "C"
 import (
-	"errors"
 	"fmt"
 	"unsafe"
 
 	"golang.org/x/arch/x86/x86asm"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 	"github.com/go-delve/delve/pkg/proc"
 )
 
@@ -111,7 +112,7 @@ func (r *Regs) GAddr() (uint64, bool) {
 }
 
 // SetPC sets the RIP register to the value specified by `pc`.
-func (thread *nativeThread) SetPC(pc uint64) error {
+func (thread *nativeThread) setPC(pc uint64) error {
 	kret := C.set_pc(thread.os.threadAct, C.uint64_t(pc))
 	if kret != C.KERN_SUCCESS {
 		return fmt.Errorf("could not set pc")
@@ -119,13 +120,12 @@ func (thread *nativeThread) SetPC(pc uint64) error {
 	return nil
 }
 
-// SetSP sets the RSP register to the value specified by `pc`.
-func (thread *nativeThread) SetSP(sp uint64) error {
-	return errors.New("not implemented")
-}
-
-func (thread *nativeThread) SetDX(dx uint64) error {
-	return errors.New("not implemented")
+// SetReg changes the value of the specified register.
+func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
+	if regNum != regnum.AMD64_Rip {
+		return fmt.Errorf("changing register %d not implemented", regNum)
+	}
+	return thread.setPC(reg.Uint64Val)
 }
 
 func (r *Regs) Get(n int) (uint64, error) {
diff --git a/pkg/proc/native/registers_freebsd_amd64.go b/pkg/proc/native/registers_freebsd_amd64.go
index 98e09912..7b3ffd9a 100644
--- a/pkg/proc/native/registers_freebsd_amd64.go
+++ b/pkg/proc/native/registers_freebsd_amd64.go
@@ -5,13 +5,15 @@ import (
 
 	sys "golang.org/x/sys/unix"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/pkg/proc/amd64util"
 	"github.com/go-delve/delve/pkg/proc/fbsdutil"
 )
 
 // SetPC sets RIP to the value specified by 'pc'.
-func (thread *nativeThread) SetPC(pc uint64) error {
+func (thread *nativeThread) setPC(pc uint64) error {
 	ir, err := registers(thread)
 	if err != nil {
 		return err
@@ -22,27 +24,23 @@ func (thread *nativeThread) SetPC(pc uint64) error {
 	return err
 }
 
-// SetSP sets RSP to the value specified by 'sp'
-func (thread *nativeThread) SetSP(sp uint64) (err error) {
-	var ir proc.Registers
-	ir, err = registers(thread)
+// SetReg changes the value of the specified register.
+func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) (err error) {
+	ir, err := registers(thread)
 	if err != nil {
 		return err
 	}
 	r := ir.(*fbsdutil.AMD64Registers)
-	r.Regs.Rsp = int64(sp)
-	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) })
-	return
-}
-
-func (thread *nativeThread) SetDX(dx uint64) (err error) {
-	var ir proc.Registers
-	ir, err = registers(thread)
-	if err != nil {
-		return err
+	switch regNum {
+	case regnum.AMD64_Rip:
+		r.Regs.Rip = int64(reg.Uint64Val)
+	case regnum.AMD64_Rsp:
+		r.Regs.Rsp = int64(reg.Uint64Val)
+	case regnum.AMD64_Rdx:
+		r.Regs.Rdx = int64(reg.Uint64Val)
+	default:
+		return fmt.Errorf("changing register %d not implemented", regNum)
 	}
-	r := ir.(*fbsdutil.AMD64Registers)
-	r.Regs.Rdx = int64(dx)
 	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) })
 	return
 }
diff --git a/pkg/proc/native/registers_linux_amd64.go b/pkg/proc/native/registers_linux_amd64.go
index c189de2b..676d8964 100644
--- a/pkg/proc/native/registers_linux_amd64.go
+++ b/pkg/proc/native/registers_linux_amd64.go
@@ -2,16 +2,19 @@ package native
 
 import (
 	"fmt"
+	"syscall"
+	"unsafe"
 
 	sys "golang.org/x/sys/unix"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/pkg/proc/amd64util"
 	"github.com/go-delve/delve/pkg/proc/linutil"
 )
 
 // SetPC sets RIP to the value specified by 'pc'.
-func (thread *nativeThread) SetPC(pc uint64) error {
+func (thread *nativeThread) setPC(pc uint64) error {
 	ir, err := registers(thread)
 	if err != nil {
 		return err
@@ -22,29 +25,31 @@ func (thread *nativeThread) SetPC(pc uint64) error {
 	return err
 }
 
-// SetSP sets RSP to the value specified by 'sp'
-func (thread *nativeThread) SetSP(sp uint64) (err error) {
-	var ir proc.Registers
-	ir, err = registers(thread)
+// SetReg changes the value of the specified register.
+func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
+	ir, err := registers(thread)
 	if err != nil {
 		return err
 	}
 	r := ir.(*linutil.AMD64Registers)
-	r.Regs.Rsp = sp
-	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
-	return
-}
-
-func (thread *nativeThread) SetDX(dx uint64) (err error) {
-	var ir proc.Registers
-	ir, err = registers(thread)
+	fpchanged, err := r.SetReg(regNum, reg)
 	if err != nil {
 		return err
 	}
-	r := ir.(*linutil.AMD64Registers)
-	r.Regs.Rdx = dx
-	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
-	return
+	thread.dbp.execPtraceFunc(func() {
+		err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs))
+		if err != nil {
+			return
+		}
+		if fpchanged && r.Fpregset != nil && r.Fpregset.Xsave != nil {
+			iov := sys.Iovec{Base: &r.Fpregset.Xsave[0], Len: uint64(len(r.Fpregset.Xsave))}
+			_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(thread.ID), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
+			if err == syscall.Errno(0) {
+				err = nil
+			}
+		}
+	})
+	return err
 }
 
 func registers(thread *nativeThread) (proc.Registers, error) {
diff --git a/pkg/proc/native/registers_linux_arm64.go b/pkg/proc/native/registers_linux_arm64.go
index 19971298..cebbd175 100644
--- a/pkg/proc/native/registers_linux_arm64.go
+++ b/pkg/proc/native/registers_linux_arm64.go
@@ -8,6 +8,8 @@ import (
 
 	sys "golang.org/x/sys/unix"
 
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"github.com/go-delve/delve/pkg/dwarf/regnum"
 	"github.com/go-delve/delve/pkg/proc"
 	"github.com/go-delve/delve/pkg/proc/linutil"
 )
@@ -64,8 +66,8 @@ func ptraceGetFpRegset(tid int) (fpregset []byte, err error) {
 	return fpregset, err
 }
 
-// SetPC sets PC to the value specified by 'pc'.
-func (thread *nativeThread) SetPC(pc uint64) error {
+// setPC sets PC to the value specified by 'pc'.
+func (thread *nativeThread) setPC(pc uint64) error {
 	ir, err := registers(thread)
 	if err != nil {
 		return err
@@ -76,21 +78,26 @@ func (thread *nativeThread) SetPC(pc uint64) error {
 	return err
 }
 
-// SetSP sets RSP to the value specified by 'sp'
-func (thread *nativeThread) SetSP(sp uint64) (err error) {
-	var ir proc.Registers
-	ir, err = registers(thread)
+func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
+	ir, err := registers(thread)
 	if err != nil {
 		return err
 	}
 	r := ir.(*linutil.ARM64Registers)
-	r.Regs.Sp = sp
-	thread.dbp.execPtraceFunc(func() { err = ptraceSetGRegs(thread.ID, r.Regs) })
-	return
-}
 
-func (thread *nativeThread) SetDX(dx uint64) (err error) {
-	return fmt.Errorf("not supported")
+	switch regNum {
+	case regnum.ARM64_PC:
+		r.Regs.Pc = reg.Uint64Val
+	case regnum.ARM64_SP:
+		r.Regs.Sp = reg.Uint64Val
+	default:
+		//TODO(aarzilli): when the register calling convention is adopted by Go on
+		// arm64 this should be implemented.
+		return fmt.Errorf("changing register %d not implemented", regNum)
+	}
+
+	thread.dbp.execPtraceFunc(func() { err = ptraceSetGRegs(thread.ID, r.Regs) })
+	return err
 }
 
 func registers(thread *nativeThread) (proc.Registers, error) {
diff --git a/pkg/proc/native/registers_windows_amd64.go b/pkg/proc/native/registers_windows_amd64.go
index f3669e40..2f44bc33 100644
--- a/pkg/proc/native/registers_windows_amd64.go
+++ b/pkg/proc/native/registers_windows_amd64.go
@@ -4,12 +4,14 @@ import (
 	"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"
 	"github.com/go-delve/delve/pkg/proc/winutil"
 )
 
 // SetPC sets the RIP register to the value specified by `pc`.
-func (thread *nativeThread) SetPC(pc uint64) error {
+func (thread *nativeThread) setPC(pc uint64) error {
 	context := winutil.NewCONTEXT()
 	context.ContextFlags = _CONTEXT_ALL
 
@@ -23,8 +25,8 @@ func (thread *nativeThread) SetPC(pc uint64) error {
 	return _SetThreadContext(thread.os.hThread, context)
 }
 
-// SetSP sets the RSP register to the value specified by `sp`.
-func (thread *nativeThread) SetSP(sp uint64) error {
+// SetReg changes the value of the specified register.
+func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
 	context := winutil.NewCONTEXT()
 	context.ContextFlags = _CONTEXT_ALL
 
@@ -33,21 +35,60 @@ func (thread *nativeThread) SetSP(sp uint64) error {
 		return err
 	}
 
-	context.Rsp = sp
-
-	return _SetThreadContext(thread.os.hThread, context)
-}
-
-func (thread *nativeThread) SetDX(dx uint64) error {
-	context := winutil.NewCONTEXT()
-	context.ContextFlags = _CONTEXT_ALL
-
-	err := _GetThreadContext(thread.os.hThread, context)
-	if err != nil {
-		return err
+	var p *uint64
+
+	switch regNum {
+	case regnum.AMD64_Rax:
+		p = &context.Rax
+	case regnum.AMD64_Rbx:
+		p = &context.Rbx
+	case regnum.AMD64_Rcx:
+		p = &context.Rcx
+	case regnum.AMD64_Rdx:
+		p = &context.Rdx
+	case regnum.AMD64_Rsi:
+		p = &context.Rsi
+	case regnum.AMD64_Rdi:
+		p = &context.Rdi
+	case regnum.AMD64_Rbp:
+		p = &context.Rbp
+	case regnum.AMD64_Rsp:
+		p = &context.Rsp
+	case regnum.AMD64_R8:
+		p = &context.R8
+	case regnum.AMD64_R9:
+		p = &context.R9
+	case regnum.AMD64_R10:
+		p = &context.R10
+	case regnum.AMD64_R11:
+		p = &context.R11
+	case regnum.AMD64_R12:
+		p = &context.R12
+	case regnum.AMD64_R13:
+		p = &context.R13
+	case regnum.AMD64_R14:
+		p = &context.R14
+	case regnum.AMD64_R15:
+		p = &context.R15
+	case regnum.AMD64_Rip:
+		p = &context.Rip
 	}
 
-	context.Rdx = dx
+	if p != nil {
+		if reg.Bytes != nil && len(reg.Bytes) != 8 {
+			return fmt.Errorf("wrong number of bytes for register %s (%d)", regnum.AMD64ToName(regNum), len(reg.Bytes))
+		}
+		*p = reg.Uint64Val
+	} else {
+		if regNum < regnum.AMD64_XMM0 || regNum > regnum.AMD64_XMM0+15 {
+			return fmt.Errorf("can not set register %s", regnum.AMD64ToName(regNum))
+		}
+		reg.FillBytes()
+		if len(reg.Bytes) > 16 {
+			return fmt.Errorf("too many bytes when setting register %s", regnum.AMD64ToName(regNum))
+		}
+		copy(context.FltSave.XmmRegisters[(regNum-regnum.AMD64_XMM0)*16:], reg.Bytes)
+	}
 
 	return _SetThreadContext(thread.os.hThread, context)
 }
diff --git a/pkg/proc/native/threads.go b/pkg/proc/native/threads.go
index c15d63bd..286318cd 100644
--- a/pkg/proc/native/threads.go
+++ b/pkg/proc/native/threads.go
@@ -122,7 +122,7 @@ func (t *nativeThread) SetCurrentBreakpoint(adjustPC bool) error {
 
 	if bp, ok := t.dbp.FindBreakpoint(pc, adjustPC); ok {
 		if adjustPC {
-			if err = t.SetPC(bp.Addr); err != nil {
+			if err = t.setPC(bp.Addr); err != nil {
 				return err
 			}
 		}
diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go
index aa1b3861..0e05d474 100644
--- a/pkg/proc/stack.go
+++ b/pkg/proc/stack.go
@@ -100,7 +100,9 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
 			return nil, err
 		}
 		so := thread.BinInfo().PCToImage(regs.PC())
-		it := newStackIterator(thread.BinInfo(), thread.ProcessMemory(), thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs), 0, nil, -1, nil, 0)
+		dwarfRegs := thread.BinInfo().Arch.RegistersToDwarfRegisters(so.StaticBase, regs)
+		dwarfRegs.ChangeFunc = thread.SetReg
+		it := newStackIterator(thread.BinInfo(), thread.ProcessMemory(), dwarfRegs, 0, nil, -1, nil, 0)
 		return it.stacktrace(depth)
 	}
 	return g.Stacktrace(depth, 0)
@@ -119,9 +121,11 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
 			return nil, err
 		}
 		so := bi.PCToImage(regs.PC())
+		dwarfRegs := bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs)
+		dwarfRegs.ChangeFunc = g.Thread.SetReg
 		return newStackIterator(
 			bi, g.variable.mem,
-			bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs),
+			dwarfRegs,
 			g.stack.hi, stkbar, g.stkbarPos, g, opts), nil
 	}
 	so := g.variable.bi.PCToImage(g.PC)
diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go
index ba4aa835..385ce9e2 100644
--- a/pkg/proc/target_exec.go
+++ b/pkg/proc/target_exec.go
@@ -127,7 +127,7 @@ func (dbp *Target) Continue() error {
 				// In linux-arm64, PtraceSingleStep seems cannot step over BRK instruction
 				// (linux-arm64 feature or kernel bug maybe).
 				if !arch.BreakInstrMovesPC() {
-					curthread.SetPC(loc.PC + uint64(arch.BreakpointSize()))
+					setPC(curthread, loc.PC+uint64(arch.BreakpointSize()))
 				}
 				// Single-step current thread until we exit runtime.breakpoint and
 				// runtime.Breakpoint.
@@ -145,7 +145,7 @@ func (dbp *Target) Continue() error {
 					bp := make([]byte, bpsize)
 					_, err = dbp.Memory().ReadMemory(bp, loc.PC)
 					if bytes.Equal(bp, arch.BreakpointInstruction()) {
-						curthread.SetPC(loc.PC + uint64(bpsize))
+						setPC(curthread, loc.PC+uint64(bpsize))
 					}
 				}
 				return conditionErrors(threads)
diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go
index edc36e78..35b33670 100644
--- a/pkg/proc/threads.go
+++ b/pkg/proc/threads.go
@@ -2,6 +2,8 @@ package proc
 
 import (
 	"errors"
+
+	"github.com/go-delve/delve/pkg/dwarf/op"
 )
 
 // Thread represents a thread.
@@ -31,9 +33,10 @@ type Thread interface {
 	// Common returns the CommonThread structure for this thread
 	Common() *CommonThread
 
-	SetPC(uint64) error
-	SetSP(uint64) error
-	SetDX(uint64) error
+	// SetReg changes the value of the specified register. A minimal
+	// implementation of this interface can support just setting the PC
+	// register.
+	SetReg(uint64, *op.DwarfRegister) error
 }
 
 // Location represents the location of a thread.
@@ -83,3 +86,15 @@ func topframe(g *G, thread Thread) (Stackframe, Stackframe, error) {
 		return frames[0], frames[1], nil
 	}
 }
+
+func setPC(thread Thread, newPC uint64) error {
+	return thread.SetReg(thread.BinInfo().Arch.PCRegNum, op.DwarfRegisterFromUint64(newPC))
+}
+
+func setSP(thread Thread, newSP uint64) error {
+	return thread.SetReg(thread.BinInfo().Arch.SPRegNum, op.DwarfRegisterFromUint64(newSP))
+}
+
+func setClosureReg(thread Thread, newClosureReg uint64) error {
+	return thread.SetReg(thread.BinInfo().Arch.ContextRegNum, op.DwarfRegisterFromUint64(newClosureReg))
+}
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 82dfd81b..3ff8e5f4 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -1147,7 +1147,7 @@ func extractVarInfoFromEntry(bi *BinaryInfo, image *Image, regs op.DwarfRegister
 	if pieces != nil {
 		addr = fakeAddress
 		var cmem *compositeMemory
-		cmem, err = newCompositeMemory(mem, regs, pieces)
+		cmem, err = newCompositeMemory(mem, bi.Arch, regs, pieces)
 		if cmem != nil {
 			mem = cmem
 		}
diff --git a/service/dap/server.go b/service/dap/server.go
index cfb67abc..29fa9068 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -432,8 +432,19 @@ func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
 const debugBinary string = "./__debug_bin"
 
 func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
-	// TODO(polina): Respond with an error if debug session is in progress?
+	// Validate launch request mode
+	mode, ok := request.Arguments["mode"]
+	if !ok || mode == "" {
+		mode = "debug"
+	}
+	if !isValidLaunchMode(mode) {
+		s.sendErrorResponse(request.Request,
+			FailedToLaunch, "Failed to launch",
+			fmt.Sprintf("Unsupported 'mode' value %q in debug configuration.", mode))
+		return
+	}
 
+	// TODO(polina): Respond with an error if debug session is in progress?
 	program, ok := request.Arguments["program"].(string)
 	if !ok || program == "" {
 		s.sendErrorResponse(request.Request,
@@ -442,11 +453,6 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		return
 	}
 
-	mode, ok := request.Arguments["mode"]
-	if !ok || mode == "" {
-		mode = "debug"
-	}
-
 	if mode == "debug" || mode == "test" {
 		output, ok := request.Arguments["output"].(string)
 		if !ok || output == "" {
@@ -486,14 +492,6 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		s.binaryToRemove = debugname
 	}
 
-	// TODO(polina): support "remote" mode
-	if mode != "exec" && mode != "debug" && mode != "test" {
-		s.sendErrorResponse(request.Request,
-			FailedToLaunch, "Failed to launch",
-			fmt.Sprintf("Unsupported 'mode' value %q in debug configuration.", mode))
-		return
-	}
-
 	s.setLaunchAttachArgs(request)
 
 	var targetArgs []string
@@ -535,6 +533,16 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 	s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)})
 }
 
+// TODO(polina): support "remote" mode
+func isValidLaunchMode(launchMode interface{}) bool {
+	switch launchMode {
+	case "exec", "debug", "test":
+		return true
+	}
+
+	return false
+}
+
 // onDisconnectRequest handles the DisconnectRequest. Per the DAP spec,
 // it disconnects the debuggee and signals that the debug adaptor
 // (in our case this TCP server) can be terminated.
@@ -1142,11 +1150,21 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 		} else if len(v.Children) == 0 || v.Children[0].Kind == reflect.Invalid && v.Children[0].Addr == 0 {
 			value = "nil <" + typeName + ">"
 		} else {
+			value = "<" + typeName + "(" + v.Children[0].TypeString() + ")" + ">"
 			if v.Children[0].OnlyAddr { // Not fully loaded
-				value = "<" + typeName + "(" + v.Children[0].TypeString() + ")" + ">" + " (data not loaded)"
-				// Since child is not fully loaded, don't provide a reference to view it.
-			} else {
-				value = "<" + typeName + "(" + v.Children[0].TypeString() + ")" + ">"
+				loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr)
+				// We might be loading variables from the frame that's not topmost, so use
+				// frame-independent address-based expression.
+				s.log.Debugf("loading %s (type %s) with %s", qualifiedNameOrExpr, typeName, loadExpr)
+				vLoaded, err := s.debugger.EvalVariableInScope(-1, 0, 0, loadExpr, DefaultLoadConfig)
+				if err != nil {
+					value += fmt.Sprintf(" - FAILED TO LOAD: %s", err)
+				} else {
+					v.Children = vLoaded.Children
+				}
+			}
+			// Provide a reference to the child only if it fully loaded
+			if !v.Children[0].OnlyAddr {
 				// TODO(polina): should we remove one level of indirection and skip "data"?
 				// Then we will have:
 				//   Before:


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 1e94da3b..00000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Delve CI
-
-on: [push, pull_request]
-
-jobs:
-  build:
-    runs-on: ${{matrix.os}}
-    strategy:
-      matrix:
-        include:
-          - go: 1.15
-            os: macos-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-go@v1
-        with:
-          go-version: ${{matrix.go}}
-      - run: go run _scripts/make.go test
diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md
index 5807bfd6..279ac095 100644
--- a/Documentation/backend_test_health.md
+++ b/Documentation/backend_test_health.md
@@ -1,20 +1,23 @@
 Tests skipped by each supported backend:
 
-* 386 skipped = 2.7% (4/147)
+* 386 skipped = 2.7% (4/148)
 	* 1 broken
 	* 3 broken - cgo stacktraces
-* arm64 skipped = 2.7% (4/147)
+* arm64 skipped = 2.7% (4/148)
 	* 2 broken
 	* 1 broken - cgo stacktraces
 	* 1 broken - global variable symbolication
-* darwin/lldb skipped = 0.68% (1/147)
+* darwin/arm64 skipped = 0.68% (1/148)
+	* 1 broken - cgo stacktraces
+* darwin/lldb skipped = 0.68% (1/148)
 	* 1 upstream issue
-* freebsd skipped = 7.5% (11/147)
+* freebsd skipped = 8.1% (12/148)
 	* 11 broken
-* linux/386/pie skipped = 0.68% (1/147)
+	* 1 not implemented
+* linux/386/pie skipped = 0.68% (1/148)
 	* 1 broken
-* pie skipped = 0.68% (1/147)
+* pie skipped = 0.68% (1/148)
 	* 1 upstream issue - https://github.com/golang/go/issues/29322
-* windows skipped = 1.4% (2/147)
+* windows skipped = 1.4% (2/148)
 	* 1 broken
 	* 1 upstream issue
diff --git a/_fixtures/testvariables.go b/_fixtures/testvariables.go
index d957d29e..41bbf429 100644
--- a/_fixtures/testvariables.go
+++ b/_fixtures/testvariables.go
@@ -56,14 +56,15 @@ func foobar(baz string, bar FooBar) {
 		c128 = complex(float64(2), float64(3))
 		i32  = [2]int32{1, 2}
 		f    = barfoo
-		ms   = Nest{0, &Nest{1, &Nest{2, &Nest{3, &Nest{4, nil}}}}} // Test recursion capping
+		ms   = Nest{0, &Nest{1, &Nest{2, &Nest{3, &Nest{4, nil}}}}} // Test recursion capping on structs
+		ni   = []interface{}([]interface{}{[]interface{}{123}})     // Test recursion capping on interfaces
 		ba   = make([]int, 200, 200)                                // Test array size capping
 		mp   = map[int]interface{}{1: 42, 2: 43}
 	)
 
 	runtime.Breakpoint()
 	barfoo()
-	fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ba, p1, mp)
+	fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ni, ba, p1, mp)
 }
 
 var p1 = 10
diff --git a/_scripts/latestver.py b/_scripts/latestver.py
new file mode 100644
index 00000000..d4138136
--- /dev/null
+++ b/_scripts/latestver.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+import json
+import urllib
+import sys
+
+ver = sys.argv[1]
+d = json.loads(urllib.urlopen('https://golang.org/dl/?mode=json&include=all').read())
+for x in d:
+	if x['version'][:len(ver)] == ver:
+		print x['version']
+		exit(0)
diff --git a/_scripts/test_mac.sh b/_scripts/test_mac.sh
index 88419938..9ca17d89 100644
--- a/_scripts/test_mac.sh
+++ b/_scripts/test_mac.sh
@@ -8,22 +8,30 @@ ARCH=$2
 TMPDIR=$3
 
 if [ "$GOVERSION" = "gotip" ]; then
-    git clone https://go.googlesource.com/go $TMPDIR/go
-    export GOROOT_BOOTSTRAP=$GOROOT
-    cd $TMPDIR/go/src
+    exit 0
+    bootstrapver=$(curl https://golang.org/VERSION?m=text)
+    curl -sSL "https://storage.googleapis.com/golang/$bootstrapver.darwin-$ARCH.tar.gz" | tar -xz
+    git clone https://go.googlesource.com/go $TMPDIR/go-tip
+    export GOROOT_BOOTSTRAP=$TMPDIR/go
+    export GOROOT=$TMPDIR/go-tip
+    cd $TMPDIR/go-tip/src
     ./make.bash
     cd -
 else
+    echo Finding latest patch version for $GOVERSION
+    GOVERSION=$(python _scripts/latestver.py $GOVERSION)
+    echo Go $GOVERSION on $ARCH
     cd $TMPDIR
-    curl -sSL "https://storage.googleapis.com/golang/$GOVERSION.darwin-$ARCH.tar.gz" | tar -vxz
+    curl -sSL "https://storage.googleapis.com/golang/$GOVERSION.darwin-$ARCH.tar.gz" | tar -xz
     cd -
+    export GOROOT="$TMPDIR/go"
 fi
 
 mkdir -p $TMPDIR/gopath
 
-export GOROOT="$TMPDIR/go"
 export GOPATH="$TMPDIR/gopath"
 export GOARCH="$ARCH"
 export PATH="$GOROOT/bin:$PATH"
+go version
 
 make test
diff --git a/pkg/dwarf/frame/entries_test.go b/pkg/dwarf/frame/entries_test.go
index 30506380..593d4516 100644
--- a/pkg/dwarf/frame/entries_test.go
+++ b/pkg/dwarf/frame/entries_test.go
@@ -67,7 +67,7 @@ func BenchmarkFDEForPC(b *testing.B) {
 	if err != nil {
 		b.Fatal(err)
 	}
-	fdes := Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
+	fdes, _ := Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch(), 0)
 
 	for i := 0; i < b.N; i++ {
 		// bench worst case, exhaustive search
diff --git a/pkg/dwarf/frame/parser_test.go b/pkg/dwarf/frame/parser_test.go
index ce6c9e50..752ff3b5 100644
--- a/pkg/dwarf/frame/parser_test.go
+++ b/pkg/dwarf/frame/parser_test.go
@@ -14,6 +14,7 @@ func TestParseCIE(t *testing.T) {
 		common: &CommonInformationEntry{Length: 12},
 		length: 12,
 	}
+	ctx.totalLen = ctx.buf.Len()
 	_ = parseCIE(ctx)
 
 	common := ctx.common
@@ -53,6 +54,6 @@ func BenchmarkParse(b *testing.B) {
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
+		Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch(), 0)
 	}
 }
diff --git a/pkg/proc/dwarf_export_test.go b/pkg/proc/dwarf_export_test.go
index de8f505a..57f723fc 100644
--- a/pkg/proc/dwarf_export_test.go
+++ b/pkg/proc/dwarf_export_test.go
@@ -1,6 +1,21 @@
 package proc
 
+import "github.com/go-delve/delve/pkg/dwarf/op"
+
 // PackageVars returns bi.packageVars (for tests)
 func (bi *BinaryInfo) PackageVars() []packageVar {
 	return bi.packageVars
 }
+
+func NewCompositeMemory(p *Target, pieces []op.Piece) (*compositeMemory, error) {
+	regs, err := p.CurrentThread().Registers()
+	if err != nil {
+		return nil, err
+	}
+
+	arch := p.BinInfo().Arch
+	dwarfregs := arch.RegistersToDwarfRegisters(0, regs)
+	dwarfregs.ChangeFunc = p.CurrentThread().SetReg
+
+	return newCompositeMemory(p.Memory(), arch, dwarfregs, pieces)
+}
diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go
index 0ce6c83e..c7935dac 100644
--- a/pkg/proc/dwarf_expr_test.go
+++ b/pkg/proc/dwarf_expr_test.go
@@ -77,8 +77,17 @@ func (mem *fakeMemory) ReadMemory(data []byte, addr uint64) (int, error) {
 	return len(data), nil
 }
 
-func (mem *fakeMemory) WriteMemory(uint64, []byte) (int, error) {
-	return 0, fmt.Errorf("not implemented")
+func (mem *fakeMemory) WriteMemory(addr uint64, data []byte) (int, error) {
+	if uint64(addr) < mem.base {
+		return 0, fmt.Errorf("write out of bounds %d %#x", len(data), addr)
+	}
+	start := uint64(addr) - mem.base
+	end := uint64(len(data)) + start
+	if end > uint64(len(mem.data)) {
+		panic(fmt.Errorf("write out of bounds %d %#x", len(data), addr))
+	}
+	copy(mem.data[start:end], data)
+	return len(data), nil
 }
 
 func uintExprCheck(t *testing.T, scope *proc.EvalScope, expr string, tgt uint64) {
@@ -93,13 +102,14 @@ func uintExprCheck(t *testing.T, scope *proc.EvalScope, expr string, tgt uint64)
 	}
 }
 
-func dwarfExprCheck(t *testing.T, mem proc.MemoryReadWriter, regs op.DwarfRegisters, bi *proc.BinaryInfo, testCases map[string]uint16, fn *proc.Function) *proc.EvalScope {
-	scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: fn}, Regs: regs, Mem: mem, BinInfo: bi}
+func fakeScope(mem proc.MemoryReadWriter, regs op.DwarfRegisters, bi *proc.BinaryInfo, fn *proc.Function) *proc.EvalScope {
+	return &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: fn}, Regs: regs, Mem: mem, BinInfo: bi}
+}
+
+func dwarfExprCheck(t *testing.T, scope *proc.EvalScope, testCases map[string]uint16) {
 	for name, value := range testCases {
 		uintExprCheck(t, scope, name, uint64(value))
 	}
-
-	return scope
 }
 
 func dwarfRegisters(bi *proc.BinaryInfo, regs *linutil.AMD64Registers) op.DwarfRegisters {
@@ -137,7 +147,7 @@ func TestDwarfExprRegisters(t *testing.T) {
 	regs.Regs.Rax = uint64(testCases["a"])
 	regs.Regs.Rdx = uint64(testCases["c"])
 
-	dwarfExprCheck(t, mem, dwarfRegisters(bi, &regs), bi, testCases, mainfn)
+	dwarfExprCheck(t, fakeScope(mem, dwarfRegisters(bi, &regs), bi, mainfn), testCases)
 }
 
 func TestDwarfExprComposite(t *testing.T) {
@@ -192,7 +202,17 @@ func TestDwarfExprComposite(t *testing.T) {
 	regs.Regs.Rcx = uint64(testCases["pair.k"])
 	regs.Regs.Rbx = uint64(testCases["n"])
 
-	scope := dwarfExprCheck(t, mem, dwarfRegisters(bi, &regs), bi, testCases, mainfn)
+	dwarfRegs := dwarfRegisters(bi, &regs)
+	var changeCalls []string
+	dwarfRegs.ChangeFunc = func(regNum uint64, reg *op.DwarfRegister) error {
+		t.Logf("SetReg(%d, %x)", regNum, reg.Bytes)
+		changeCalls = append(changeCalls, fmt.Sprintf("%d - %x", regNum, reg.Bytes))
+		return nil
+	}
+
+	scope := fakeScope(mem, dwarfRegs, bi, mainfn)
+
+	dwarfExprCheck(t, scope, testCases)
 
 	thevar, err := scope.EvalExpression("s", normalLoadConfig)
 	assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", "s"))
@@ -203,6 +223,29 @@ func TestDwarfExprComposite(t *testing.T) {
 			t.Errorf("expected value %q got %q", stringVal, v)
 		}
 	}
+
+	// Test writes to composite memory
+
+	assertNoError(scope.SetVariable("n", "47"), t, "SetVariable(n, 47)")
+	assertNoError(scope.SetVariable("pair.k", "12"), t, "SetVariable(pair.k, 12)")
+	assertNoError(scope.SetVariable("pair.v", "13"), t, "SetVariable(pair.v, 13)")
+
+	for i := range changeCalls {
+		t.Logf("%q\n", changeCalls[i])
+	}
+
+	if len(changeCalls) != 2 {
+		t.Errorf("wrong number of calls to SetReg")
+	}
+	if changeCalls[0] != "3 - 2f00000000000000" {
+		t.Errorf("wrong call to SetReg (Rbx)")
+	}
+	if changeCalls[1] != "2 - 0c00" {
+		t.Errorf("wrong call to SetReg (Rcx)")
+	}
+	if mem.data[0x10] != 13 || mem.data[0x11] != 0x00 {
+		t.Errorf("memory was not written %v", mem.data[:2])
+	}
 }
 
 func TestDwarfExprLoclist(t *testing.T) {
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 9149ccab..02fada29 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -2,6 +2,7 @@ package proc_test
 
 import (
 	"bytes"
+	"encoding/binary"
 	"flag"
 	"fmt"
 	"go/ast"
@@ -836,6 +837,8 @@ func TestCGONext(t *testing.T) {
 	}
 	protest.MustHaveCgo(t)
 
+	skipOn(t, "broken - cgo stacktraces", "darwin", "arm64")
+
 	protest.AllowRecording(t)
 	withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) {
 		setFunctionBreakpoint(p, t, "main.main")
@@ -942,6 +945,8 @@ func stackMatch(stack []loc, locations []proc.Stackframe, skipRuntime bool) bool
 }
 
 func TestStacktraceGoroutine(t *testing.T) {
+	skipOn(t, "broken - cgo stacktraces", "darwin", "arm64")
+
 	mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}}
 	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
 		mainStack[0].line = 15
@@ -5169,3 +5174,72 @@ func TestDump(t *testing.T) {
 		}
 	})
 }
+
+func TestCompositeMemoryWrite(t *testing.T) {
+	if runtime.GOARCH != "amd64" {
+		t.Skip("only valid on amd64")
+	}
+	skipOn(t, "not implemented", "freebsd")
+	withTestProcess("fputest/", t, func(p *proc.Target, fixture protest.Fixture) {
+		getregs := func() (pc, rax, xmm1 uint64) {
+			regs, err := p.CurrentThread().Registers()
+			assertNoError(err, t, "Registers")
+			fmtregs, err := regs.Slice(true)
+			assertNoError(err, t, "register slice")
+
+			var xmm1buf []byte
+
+			for _, reg := range fmtregs {
+				switch strings.ToLower(reg.Name) {
+				case "rax":
+					rax = reg.Reg.Uint64Val
+				case "xmm1":
+					xmm1buf = reg.Reg.Bytes
+				}
+			}
+
+			xmm1 = binary.LittleEndian.Uint64(xmm1buf[:8])
+
+			return regs.PC(), rax, xmm1
+		}
+
+		getmem := func(mem proc.MemoryReader) uint64 {
+			buf := make([]byte, 8)
+			_, err := mem.ReadMemory(buf, 0xbeef0000)
+			assertNoError(err, t, "ReadMemory")
+			return binary.LittleEndian.Uint64(buf)
+		}
+
+		assertNoError(p.Continue(), t, "Continue()")
+		oldPc, oldRax, oldXmm1 := getregs()
+		t.Logf("PC %#x AX %#x XMM1 %#x", oldPc, oldRax, oldXmm1)
+
+		memRax, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, RegNum: 0, IsRegister: true}})
+		assertNoError(err, t, "NewCompositeMemory (rax)")
+		memXmm1, err := proc.NewCompositeMemory(p, []op.Piece{{Size: 0, RegNum: 18, IsRegister: true}})
+		assertNoError(err, t, "NewCompositeMemory (xmm1)")
+
+		if memRax := getmem(memRax); memRax != oldRax {
+			t.Errorf("reading rax memory, expected %#x got %#x", oldRax, memRax)
+		}
+		if memXmm1 := getmem(memXmm1); memXmm1 != oldXmm1 {
+			t.Errorf("reading xmm1 memory, expected %#x got %#x", oldXmm1, memXmm1)
+		}
+
+		_, err = memRax.WriteMemory(0xbeef0000, []byte{0xef, 0xbe, 0x0d, 0xf0, 0xef, 0xbe, 0x0d, 0xf0})
+		assertNoError(err, t, "WriteMemory (rax)")
+		_, err = memXmm1.WriteMemory(0xbeef0000, []byte{0xef, 0xbe, 0x0d, 0xf0, 0xef, 0xbe, 0x0d, 0xf0})
+		assertNoError(err, t, "WriteMemory (xmm1)")
+
+		newPc, newRax, newXmm1 := getregs()
+		t.Logf("PC %#x AX %#x XMM1 %#x", newPc, newRax, newXmm1)
+
+		const tgt = 0xf00dbeeff00dbeef
+		if newRax != tgt {
+			t.Errorf("reading rax register, expected %#x, got %#x", uint64(tgt), newRax)
+		}
+		if newXmm1 != tgt {
+			t.Errorf("reading xmm1 register, expected %#x, got %#x", uint64(tgt), newXmm1)
+		}
+	})
+}
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index 5669c98d..f4c2a514 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -690,9 +690,9 @@ func TestListCmd(t *testing.T) {
 		term.MustExec("continue")
 		term.MustExec("continue")
 		listIsAt(t, term, "list", 27, 22, 32)
-		listIsAt(t, term, "list 69", 69, 64, 73)
-		listIsAt(t, term, "frame 1 list", 65, 60, 70)
-		listIsAt(t, term, "frame 1 list 69", 69, 64, 73)
+		listIsAt(t, term, "list 69", 69, 64, 74)
+		listIsAt(t, term, "frame 1 list", 66, 61, 71)
+		listIsAt(t, term, "frame 1 list 69", 69, 64, 74)
 		_, err := term.Exec("frame 50 list")
 		if err == nil {
 			t.Fatalf("Expected error requesting 50th frame")
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 96d6e769..45a071a4 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -751,7 +751,7 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 					client.StackTraceRequest(1, 0, 20)
 					stack := client.ExpectStackTraceResponse(t)
 
-					startLineno := 65
+					startLineno := 66
 					if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
 						// Go1.15 on windows inserts a NOP after the call to
 						// runtime.Breakpoint and marks it same line as the
@@ -794,7 +794,7 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 
 					client.VariablesRequest(1001)
 					locals := client.ExpectVariablesResponse(t)
-					expectChildren(t, locals, "Locals", 30)
+					expectChildren(t, locals, "Locals", 31)
 
 					// reflect.Kind == Bool
 					expectVarExact(t, locals, -1, "b1", "b1", "true", noChildren)
@@ -1397,42 +1397,17 @@ func TestVariablesLoading(t *testing.T) {
 						ref = expectVarExact(t, tm, 0, "v", "tm.v", "<[]map[string]main.astruct> (length: 1, cap: 1)", hasChildren)
 						if ref > 0 {
 							client.VariablesRequest(ref)
-							tm_v := client.ExpectVariablesResponse(t)
-							expectChildren(t, tm_v, "tm.v", 1)
+							tmV := client.ExpectVariablesResponse(t)
+							expectChildren(t, tmV, "tm.v", 1)
 							// TODO(polina): there should be children here once we support auto loading - test for "tm.v[0]["gutters"]"
-							expectVarExact(t, tm_v, 0, "[0]", "tm.v[0]", "<map[string]main.astruct> (length: 66, loaded: 0)", noChildren)
+							expectVarExact(t, tmV, 0, "[0]", "tm.v[0]", "<map[string]main.astruct> (length: 66, loaded: 0)", noChildren)
 						}
 					}
-
-					// Interface fully missing due to hitting LoadConfig.MaxVariableRecurse
-					ref = expectVarRegex(t, locals, -1, "ptrinf", "ptrinf", `<\*interface {}>\(0x[0-9a-f]+\)`, hasChildren)
-					if ref > 0 {
-						client.VariablesRequest(ref)
-						ptrinf := client.ExpectVariablesResponse(t)
-						ref = expectVarExact(t, ptrinf, 0, "", "(*ptrinf)", "<interface {}(**interface {})>", hasChildren)
-						if ref > 0 {
-							client.VariablesRequest(ref)
-							ptrinfVal := client.ExpectVariablesResponse(t)
-							ref = expectVarRegex(t, ptrinfVal, 0, "", `\(\*ptrinf\)\.\(data\)`, `<\*\*interface {}>\(0x[0-9a-f]+\)`, hasChildren)
-							if ref > 0 {
-								client.VariablesRequest(ref)
-								ptrinfValData := client.ExpectVariablesResponse(t)
-								ref = expectVarRegex(t, ptrinfValData, 0, "", `\(\*\(\*ptrinf\)\.\(data\)\)`, `<\*interface {}>\(0x[0-9a-f]+\)`, hasChildren)
-								if ref > 0 {
-									client.VariablesRequest(ref)
-									iface6dataValVal := client.ExpectVariablesResponse(t)
-									expectVarExact(t, iface6dataValVal, 0, "", "(*(*(*ptrinf).(data)))", "<interface {}(**interface {})> (data not loaded)", noChildren)
-								}
-							}
-						}
-					}
-
-					// Pointer fully missing - see below
 				},
 				disconnect: true,
 			}})
 	})
-	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
+	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSessionWithBPs(t, client, "launch",
 			// Launch
 			func() {
@@ -1448,14 +1423,54 @@ func TestVariablesLoading(t *testing.T) {
 					client.StackTraceRequest(1, 0, 0)
 					client.ExpectStackTraceResponse(t)
 
-					client.ScopesRequest(1000)
-					client.ExpectScopesResponse(t)
+					var loadvars = func(frame int) {
+						client.ScopesRequest(frame)
+						scopes := client.ExpectScopesResponse(t)
+						localsRef := 0
+						for _, s := range scopes.Body.Scopes {
+							if s.Name == "Locals" {
+								localsRef = s.VariablesReference
+							}
+						}
 
-					client.VariablesRequest(1001) // Locals
-					locals := client.ExpectVariablesResponse(t)
+						client.VariablesRequest(localsRef)
+						locals := client.ExpectVariablesResponse(t)
+
+						// Interface auto-loaded when hitting LoadConfig.MaxVariableRecurse=1
+
+						ref := expectVarExact(t, locals, -1, "ni", "ni", "<[]interface {}> (length: 1, cap: 1)", hasChildren)
+						if ref > 0 {
+							client.VariablesRequest(ref)
+							ni := client.ExpectVariablesResponse(t)
+							ref = expectVarExact(t, ni, 0, "[0]", "ni[0]", "<interface {}([]interface {})>", hasChildren)
+							if ref > 0 {
+								client.VariablesRequest(ref)
+								niI1 := client.ExpectVariablesResponse(t)
+								ref = expectVarExact(t, niI1, 0, "data", "ni[0].(data)", "<[]interface {}> (length: 1, cap: 1)", hasChildren)
+								if ref > 0 {
+									// Auto-loading happens here
+									client.VariablesRequest(ref)
+									niI1Data := client.ExpectVariablesResponse(t)
+									ref = expectVarExact(t, niI1Data, 0, "[0]", "ni[0].(data)[0]", "<interface {}(int)>", hasChildren)
+									if ref > 0 {
+										client.VariablesRequest(ref)
+										niI1DataI2 := client.ExpectVariablesResponse(t)
+										expectVarExact(t, niI1DataI2, 0, "data", "ni[0].(data)[0].(data)", "123", noChildren)
+									}
+								}
+							}
+						}
 
-					// Pointer value not loaded
-					expectVarRegex(t, locals, -1, "ptrinf", "ptrinf", `<\*interface {}>\(0x[0-9a-f]+\) \(value not loaded\)`, noChildren)
+						// Pointer value not loaded with LoadConfig.FollowPointers=false
+						expectVarRegex(t, locals, -1, "a7", "a7", `<\*main.FooBar>\(0x[0-9a-f]+\) \(value not loaded\)`, noChildren)
+					}
+					loadvars(1000 /*first topmost frame*/)
+					// step into another function
+					client.StepInRequest(1)
+					client.ExpectStepInResponse(t)
+					client.ExpectStoppedEvent(t)
+					handleStop(t, client, 1, "main.barfoo", 24)
+					loadvars(1001 /*second frame here is same as topmost above*/)
 				},
 				disconnect: true,
 			}})
@@ -1676,7 +1691,7 @@ func TestEvaluateRequest(t *testing.T) {
 			fixture.Source, []int{}, // Breakpoint set in the program
 			[]onBreakpoint{{ // Stop at first breakpoint
 				execute: func() {
-					handleStop(t, client, 1, "main.foobar", 65)
+					handleStop(t, client, 1, "main.foobar", 66)
 
 					// Variable lookup
 					client.EvaluateRequest("a2", 1000, "this context will be ignored")
@@ -2559,6 +2574,10 @@ func TestBadLaunchRequests(t *testing.T) {
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "123"})
 		expectFailedToLaunch(client.ExpectErrorResponse(t)) // Build error
 
+		client.LaunchRequest("", fixture.Path, stopOnEntry)
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
+			"Failed to launch: Build error: exit status 1")
+
 		// We failed to launch the program. Make sure shutdown still works.
 		client.DisconnectRequest()
 		dresp := client.ExpectDisconnectResponse(t)
diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go
index 3cb3a995..bde8f9fd 100644
--- a/service/test/integration1_test.go
+++ b/service/test/integration1_test.go
@@ -716,6 +716,9 @@ func Test1ClientServer_SetVariable(t *testing.T) {
 }
 
 func Test1ClientServer_FullStacktrace(t *testing.T) {
+	if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+		t.Skip("cgo doesn't work on darwin/arm64")
+	}
 	withTestClient1("goroutinestackprog", t, func(c *rpc1.RPCClient) {
 		_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
 		assertNoError(err, t, "CreateBreakpoint()")
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index 5cab11d8..270b1bd7 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -911,6 +911,9 @@ func TestClientServer_SetVariable(t *testing.T) {
 
 func TestClientServer_FullStacktrace(t *testing.T) {
 	protest.AllowRecording(t)
+	if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+		t.Skip("cgo doesn't work on darwin/arm64")
+	}
 	withTestClient2("goroutinestackprog", t, func(c service.Client) {
 		_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
 		assertNoError(err, t, "CreateBreakpoint()")
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index 77bdcd94..c2087930 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -446,6 +446,7 @@ func TestLocalVariables(t *testing.T) {
 				{"mp", true, "map[int]interface {} [1: 42, 2: 43, ]", "", "map[int]interface {}", nil},
 				{"ms", true, "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)…", "", "main.Nest", nil},
 				{"neg", true, "-1", "", "int", nil},
+				{"ni", true, "[]interface {} len: 1, cap: 1, [[]interface {} len: 1, cap: 1, [*(*interface {})…", "", "[]interface {}", nil},
 				{"u16", true, "65535", "", "uint16", nil},
 				{"u32", true, "4294967295", "", "uint32", nil},
 				{"u64", true, "18446744073709551615", "", "uint64", nil},
@@ -1550,10 +1551,6 @@ func TestPluginVariables(t *testing.T) {
 func TestCgoEval(t *testing.T) {
 	protest.MustHaveCgo(t)
 
-	if runtime.GOARCH == "arm64" {
-		t.Skip("cgo evaluation broken on arm64")
-	}
-
 	testcases := []varTest{
 		{"s", true, `"a string"`, `"a string"`, "*char", nil},
 		{"longstring", true, `"averylongstring0123456789a0123456789b0123456789c0123456789d01234...+1 more"`, `"averylongstring0123456789a0123456789b0123456789c0123456789d01234...+1 more"`, "*const char", nil},
@@ -1568,6 +1565,10 @@ func TestCgoEval(t *testing.T) {
 		{"v_align_check[90]", false, "align_check {a: 90, b: 90}", "align_check {a: 90, b: 90}", "align_check", nil},
 	}
 
+	if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+		t.Skip("cgo doesn't work on darwin/arm64")
+	}
+
 	protest.AllowRecording(t)
 	withTestProcess("testvariablescgo/", t, func(p *proc.Target, fixture protest.Fixture) {
 		assertNoError(p.Continue(), t, "Continue() returned an error")