Repository: go-delve/delve
Pull request #1921 (2020-03-10 15:03:45)

proc: cache result of GetG
```
proc: cache result of GetG

Benchmark before:

BenchmarkConditionalBreakpoints-4   	       1	7031242832 ns/op

Benchmark after:

BenchmarkConditionalBreakpoints-4   	       1	5282482841 ns/op

Conditional breakpoint evaluation latency: 0.70ms -> 0.52ms

Updates #1549

```



Patch:
diff --git a/.cirrus.yml b/.cirrus.yml
index a5727d0f..d7d0cfb3 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -1,9 +1,28 @@
-env:
-  GOFLAGS: -mod=vendor
+freebsd_task:
+  env:
+    GOFLAGS: -mod=vendor
+  
+  freebsd_instance:
+    image: freebsd-11-2-release-amd64
 
-freebsd_instance:
-  image: freebsd-11-2-release-amd64
-
-test_task:
   install_script: pkg install -y go gcc git
   test_script: make test
+
+linux386_task:
+  container:
+    image: i386/ubuntu:18.04
+  env:
+    matrix:
+      - GOVERSION: 1.12
+      - GOVERSION: 1.13
+      - GOVERSION: 1.14
+  test_script:
+    -  apt-get -y update
+    -  apt-get -y install software-properties-common
+    -  apt-get -y install git
+    -  add-apt-repository ppa:longsleep/golang-backports
+    -  apt-get -y install golang-${GOVERSION}-go
+    -  export PATH=$PATH:/usr/lib/go-${GOVERSION}/bin
+    -  go version
+    -  uname -a
+    -  make test
diff --git a/pkg/dwarf/frame/parser.go b/pkg/dwarf/frame/parser.go
index f0b1f920..6d12dad2 100644
--- a/pkg/dwarf/frame/parser.go
+++ b/pkg/dwarf/frame/parser.go
@@ -20,15 +20,16 @@ type parseContext struct {
 	common  *CommonInformationEntry
 	frame   *FrameDescriptionEntry
 	length  uint32
+	ptrSize int
 }
 
 // Parse takes in data (a byte slice) and returns a slice of
 // commonInformationEntry structures. Each commonInformationEntry
 // has a slice of frameDescriptionEntry structures.
-func Parse(data []byte, order binary.ByteOrder, staticBase uint64) FrameDescriptionEntries {
+func Parse(data []byte, order binary.ByteOrder, staticBase uint64, ptrSize int) FrameDescriptionEntries {
 	var (
 		buf  = bytes.NewBuffer(data)
-		pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase}
+		pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase, ptrSize: ptrSize}
 	)
 
 	for fn := parselength; buf.Len() != 0; {
@@ -68,10 +69,14 @@ func parselength(ctx *parseContext) parsefunc {
 }
 
 func parseFDE(ctx *parseContext) parsefunc {
+	var num uint64
 	r := ctx.buf.Next(int(ctx.length))
 
-	ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) + ctx.staticBase
-	ctx.frame.size = binary.LittleEndian.Uint64(r[8:16])
+	reader := bytes.NewReader(r)
+	num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
+	ctx.frame.begin = num + ctx.staticBase
+	num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
+	ctx.frame.size = num
 
 	// Insert into the tree after setting address range begin
 	// otherwise compares won't work.
@@ -80,7 +85,7 @@ func parseFDE(ctx *parseContext) parsefunc {
 	// 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[16:]
+	ctx.frame.Instructions = r[2*ctx.ptrSize:]
 	ctx.length = 0
 
 	return parselength
diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go
index d0b9e901..d0761993 100644
--- a/pkg/dwarf/line/line_parser.go
+++ b/pkg/dwarf/line/line_parser.go
@@ -41,6 +41,7 @@ type DebugLineInfo struct {
 
 	// if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/)
 	normalizeBackslash bool
+	ptrSize int
 }
 
 type FileEntry struct {
@@ -53,7 +54,7 @@ type FileEntry struct {
 type DebugLines []*DebugLineInfo
 
 // ParseAll parses all debug_line segments found in data
-func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) DebugLines {
+func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines {
 	var (
 		lines = make(DebugLines, 0)
 		buf   = bytes.NewBuffer(data)
@@ -61,7 +62,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
 
 	// We have to parse multiple file name tables here.
 	for buf.Len() > 0 {
-		lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash))
+		lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash, ptrSize))
 	}
 
 	return lines
@@ -69,10 +70,11 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
 
 // Parse parses a single debug_line segment from buf. Compdir is the
 // DW_AT_comp_dir attribute of the associated compile unit.
-func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) *DebugLineInfo {
+func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo {
 	dbl := new(DebugLineInfo)
 	dbl.Logf = logfn
 	dbl.staticBase = staticBase
+	dbl.ptrSize = ptrSize
 	dbl.Lookup = make(map[string]*FileEntry)
 	dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)
 
diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go
index cb041544..b92f8849 100644
--- a/pkg/dwarf/line/state_machine.go
+++ b/pkg/dwarf/line/state_machine.go
@@ -46,6 +46,7 @@ type StateMachine struct {
 	lastAddress uint64
 	lastFile    string
 	lastLine    int
+	ptrSize     int
 }
 
 type opcodeKind uint8
@@ -102,13 +103,23 @@ var extendedopcodes = map[byte]opcodefn{
 	DW_LINE_define_file:  definefile,
 }
 
-func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine {
+func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *StateMachine {
 	opcodes := make([]opcodefn, len(standardopcodes)+1)
 	opcodes[0] = execExtendedOpcode
 	for op := range standardopcodes {
 		opcodes[op] = standardopcodes[op]
 	}
-	sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1), address: dbl.staticBase, lastAddress: ^uint64(0)}
+	sm := &StateMachine{
+		dbl:         dbl,
+		file:        dbl.FileNames[0].Path,
+		line:        1,
+		buf:         bytes.NewBuffer(instructions),
+		opcodes:     opcodes,
+		isStmt:      dbl.Prologue.InitialIsStmt == uint8(1),
+		address:     dbl.staticBase,
+		lastAddress: ^uint64(0),
+		ptrSize:     ptrSize,
+	}
 	return sm
 }
 
@@ -121,7 +132,7 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64)
 
 	var (
 		lastAddr uint64
-		sm       = newStateMachine(lineInfo, lineInfo.Instructions)
+		sm       = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
 	)
 
 	for {
@@ -153,7 +164,7 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile stri
 	var (
 		pcs      []uint64
 		lastaddr uint64
-		sm       = newStateMachine(lineInfo, lineInfo.Instructions)
+		sm       = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
 	)
 
 	for {
@@ -189,7 +200,7 @@ func (sm *StateMachine) copy() *StateMachine {
 func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) {
 	sm = lineInfo.stateMachineCache[basePC]
 	if sm == nil {
-		sm = newStateMachine(lineInfo, lineInfo.Instructions)
+		sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
 		sm.PCToLine(basePC)
 		lineInfo.stateMachineCache[basePC] = sm
 	}
@@ -219,7 +230,7 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
 func (lineInfo *DebugLineInfo) stateMachineFor(basePC, pc uint64) *StateMachine {
 	var sm *StateMachine
 	if basePC == 0 {
-		sm = newStateMachine(lineInfo, lineInfo.Instructions)
+		sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
 	} else {
 		// Try to use the last state machine that we used for this function, if
 		// there isn't one or it's already past pc try to clone the cached state
@@ -274,7 +285,7 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
 		return 0
 	}
 
-	sm := newStateMachine(lineInfo, lineInfo.Instructions)
+	sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
 
 	// if no instruction marked is_stmt is found fallback to the first
 	// instruction assigned to the filename:line.
@@ -393,7 +404,7 @@ func (lineInfo *DebugLineInfo) FirstStmtForLine(start, end uint64) (pc uint64, f
 }
 
 func (lineInfo *DebugLineInfo) FirstFile() string {
-	sm := newStateMachine(lineInfo, lineInfo.Instructions)
+	sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
 	for {
 		if sm.valid {
 			return sm.file
@@ -530,11 +541,10 @@ func endsequence(sm *StateMachine, buf *bytes.Buffer) {
 }
 
 func setaddress(sm *StateMachine, buf *bytes.Buffer) {
-	//TODO: this needs to be changed to support 32bit architectures (addr must be target arch pointer sized) -- also target endianness
-	var addr uint64
-
-	binary.Read(buf, binary.LittleEndian, &addr)
-
+	addr, err := util.ReadUintRaw(buf, binary.LittleEndian, sm.ptrSize)
+	if err != nil {
+		panic(err)
+	}
 	sm.address = addr + sm.dbl.staticBase
 }
 
diff --git a/pkg/dwarf/op/op.go b/pkg/dwarf/op/op.go
index 89af5a68..ac6bd52e 100644
--- a/pkg/dwarf/op/op.go
+++ b/pkg/dwarf/op/op.go
@@ -17,10 +17,11 @@ type Opcode byte
 type stackfn func(Opcode, *context) error
 
 type context struct {
-	buf    *bytes.Buffer
-	stack  []int64
-	pieces []Piece
-	reg    bool
+	buf     *bytes.Buffer
+	stack   []int64
+	pieces  []Piece
+	reg     bool
+	ptrSize int
 
 	DwarfRegisters
 }
@@ -36,11 +37,12 @@ type Piece struct {
 // ExecuteStackProgram executes a DWARF location expression and returns
 // either an address (int64), or a slice of Pieces for location expressions
 // that don't evaluate to an address (such as register and composite expressions).
-func ExecuteStackProgram(regs DwarfRegisters, instructions []byte) (int64, []Piece, error) {
+func ExecuteStackProgram(regs DwarfRegisters, instructions []byte, ptrSize int) (int64, []Piece, error) {
 	ctxt := &context{
 		buf:            bytes.NewBuffer(instructions),
 		stack:          make([]int64, 0, 3),
 		DwarfRegisters: regs,
+		ptrSize:        ptrSize,
 	}
 
 	for {
@@ -133,7 +135,12 @@ func callframecfa(opcode Opcode, ctxt *context) error {
 }
 
 func addr(opcode Opcode, ctxt *context) error {
-	ctxt.stack = append(ctxt.stack, int64(binary.LittleEndian.Uint64(ctxt.buf.Next(8))+ctxt.StaticBase))
+	buf := ctxt.buf.Next(ctxt.ptrSize)
+	stack, err := util.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, ctxt.ptrSize)
+	if err != nil {
+		return err
+	}
+	ctxt.stack = append(ctxt.stack, int64(stack+ctxt.StaticBase))
 	return nil
 }
 
diff --git a/pkg/dwarf/reader/reader.go b/pkg/dwarf/reader/reader.go
index d59de829..f3118e9f 100644
--- a/pkg/dwarf/reader/reader.go
+++ b/pkg/dwarf/reader/reader.go
@@ -64,7 +64,7 @@ func (reader *Reader) SeekToFunction(pc RelAddr) (*dwarf.Entry, error) {
 }
 
 // Returns the address for the named entry.
-func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
+func (reader *Reader) AddrFor(name string, staticBase uint64, ptrSize int) (uint64, error) {
 	entry, err := reader.FindEntryNamed(name, false)
 	if err != nil {
 		return 0, err
@@ -73,7 +73,7 @@ func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
 	if !ok {
 		return 0, fmt.Errorf("type assertion failed")
 	}
-	addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions)
+	addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions, ptrSize)
 	if err != nil {
 		return 0, err
 	}
@@ -82,7 +82,7 @@ func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
 
 // Returns the address for the named struct member. Expects the reader to be at the parent entry
 // or one of the parents children, thus does not seek to parent by itself.
-func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (uint64, error) {
+func (reader *Reader) AddrForMember(member string, initialInstructions []byte, ptrSize int) (uint64, error) {
 	for {
 		entry, err := reader.NextMemberVariable()
 		if err != nil {
@@ -99,7 +99,7 @@ func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (
 		if !ok {
 			continue
 		}
-		addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...))
+		addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...), ptrSize)
 		return uint64(addr), err
 	}
 }
diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/util/util.go
index 012f5308..20e4e2b9 100644
--- a/pkg/dwarf/util/util.go
+++ b/pkg/dwarf/util/util.go
@@ -2,6 +2,8 @@ package util
 
 import (
 	"bytes"
+	"encoding/binary"
+	"fmt"
 	"io"
 )
 
@@ -124,3 +126,33 @@ func ParseString(data *bytes.Buffer) (string, uint32) {
 
 	return str[:len(str)-1], uint32(len(str))
 }
+
+// ReadUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader.
+func ReadUintRaw(reader io.Reader, order binary.ByteOrder, ptrSize int) (uint64, error) {
+	switch ptrSize {
+	case 4:
+		var n uint32
+		if err := binary.Read(reader, order, &n); err != nil {
+			return 0, err
+		}
+		return uint64(n), nil
+	case 8:
+		var n uint64
+		if err := binary.Read(reader, order, &n); err != nil {
+			return 0, err
+		}
+		return n, nil
+	}
+	return 0, fmt.Errorf("not supprted ptr size %d", ptrSize)
+}
+
+// WriteUint writes an integer of ptrSize bytes to writer, in the specified byte order.
+func WriteUint(writer io.Writer, order binary.ByteOrder, ptrSize int, data uint64) error {
+	switch ptrSize {
+	case 4:
+		return binary.Write(writer, order, uint32(data))
+	case 8:
+		return binary.Write(writer, order, data)
+	}
+	return fmt.Errorf("not support prt size %d", ptrSize)
+}
diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go
index 202dc53b..92731cb4 100644
--- a/pkg/proc/amd64_arch.go
+++ b/pkg/proc/amd64_arch.go
@@ -51,7 +51,7 @@ func (a *AMD64) PtrSize() int {
 	return 8
 }
 
-// MaxInstructionLength returns the maximum lenght of an instruction.
+// MaxInstructionLength returns the maximum length of an instruction.
 func (a *AMD64) MaxInstructionLength() int {
 	return 15
 }
@@ -558,3 +558,9 @@ func formatX87Reg(b []byte) string {
 
 	return fmt.Sprintf("%#04x%016x\t%g", exponent, mantissa, f)
 }
+
+// InhibitStepInto returns whether StepBreakpoint can be set at pc.
+// Always return false on amd64.
+func (a *AMD64) InhibitStepInto(bi *BinaryInfo, pc uint64) bool {
+	return false
+}
diff --git a/pkg/proc/amd64_disasm.go b/pkg/proc/amd64_disasm.go
index 84ac9446..67ba7083 100644
--- a/pkg/proc/amd64_disasm.go
+++ b/pkg/proc/amd64_disasm.go
@@ -5,134 +5,19 @@
 package proc
 
 import (
-	"encoding/binary"
-
 	"golang.org/x/arch/x86/x86asm"
 )
 
 // 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.
 func (a *AMD64) AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo) error {
-	inst, err := x86asm.Decode(mem, 64)
-	if err != nil {
-		asmInst.Inst = (*amd64ArchInst)(nil)
-		asmInst.Size = 1
-		asmInst.Bytes = mem[:asmInst.Size]
-		return err
-	}
-
-	asmInst.Size = inst.Len
-	asmInst.Bytes = mem[:asmInst.Size]
-	patchPCRelAMD64(asmInst.Loc.PC, &inst)
-	asmInst.Inst = (*amd64ArchInst)(&inst)
-	asmInst.Kind = OtherInstruction
-
-	switch inst.Op {
-	case x86asm.CALL, x86asm.LCALL:
-		asmInst.Kind = CallInstruction
-	case x86asm.RET, x86asm.LRET:
-		asmInst.Kind = RetInstruction
-	}
-
-	asmInst.DestLoc = resolveCallArgAMD64(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
-	return nil
+	return x86AsmDecode(asmInst, mem, regs, memrw, bi, 64)
 }
 
 func (a *AMD64) Prologues() []opcodeSeq {
 	return prologuesAMD64
 }
 
-// converts PC relative arguments to absolute addresses
-func patchPCRelAMD64(pc uint64, inst *x86asm.Inst) {
-	for i := range inst.Args {
-		rel, isrel := inst.Args[i].(x86asm.Rel)
-		if isrel {
-			inst.Args[i] = x86asm.Imm(int64(pc) + int64(rel) + int64(inst.Len))
-		}
-	}
-}
-
-type amd64ArchInst x86asm.Inst
-
-func (inst *amd64ArchInst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string {
-	if inst == nil {
-		return "?"
-	}
-
-	var text string
-
-	switch flavour {
-	case GNUFlavour:
-		text = x86asm.GNUSyntax(x86asm.Inst(*inst), pc, symLookup)
-	case GoFlavour:
-		text = x86asm.GoSyntax(x86asm.Inst(*inst), pc, symLookup)
-	case IntelFlavour:
-		fallthrough
-	default:
-		text = x86asm.IntelSyntax(x86asm.Inst(*inst), pc, symLookup)
-	}
-
-	return text
-
-}
-
-func (inst *amd64ArchInst) OpcodeEquals(op uint64) bool {
-	if inst == nil {
-		return false
-	}
-	return uint64(inst.Op) == op
-}
-
-func resolveCallArgAMD64(inst *x86asm.Inst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
-	if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
-		return nil
-	}
-
-	var pc uint64
-	var err error
-
-	switch arg := inst.Args[0].(type) {
-	case x86asm.Imm:
-		pc = uint64(arg)
-	case x86asm.Reg:
-		if !currentGoroutine || regs == nil {
-			return nil
-		}
-		pc, err = regs.Get(int(arg))
-		if err != nil {
-			return nil
-		}
-	case x86asm.Mem:
-		if !currentGoroutine || regs == nil {
-			return nil
-		}
-		if arg.Segment != 0 {
-			return nil
-		}
-		base, err1 := regs.Get(int(arg.Base))
-		index, err2 := regs.Get(int(arg.Index))
-		if err1 != nil || err2 != nil {
-			return nil
-		}
-		addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp)
-		//TODO: should this always be 64 bits instead of inst.MemBytes?
-		pcbytes := make([]byte, inst.MemBytes)
-		_, err := mem.ReadMemory(pcbytes, addr)
-		if err != nil {
-			return nil
-		}
-		pc = binary.LittleEndian.Uint64(pcbytes)
-	default:
-		return nil
-	}
-
-	file, line, fn := bininfo.PCToLine(pc)
-	if fn == nil {
-		return &Location{PC: pc}
-	}
-	return &Location{PC: pc, File: file, Line: line, Fn: fn}
-}
-
 // Possible stacksplit prologues are inserted by stacksplit in
 // $GOROOT/src/cmd/internal/obj/x86/obj6.go.
 // The stacksplit prologue will always begin with loading curg in CX, this
diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go
index 28d16635..c915575d 100644
--- a/pkg/proc/arch.go
+++ b/pkg/proc/arch.go
@@ -22,8 +22,10 @@ type Arch interface {
 	RegistersToDwarfRegisters(uint64, Registers) op.DwarfRegisters
 	AddrAndStackRegsToDwarfRegisters(uint64, uint64, uint64, uint64, uint64) op.DwarfRegisters
 	DwarfRegisterToString(int, *op.DwarfRegister) (string, bool, string)
+	InhibitStepInto(bi *BinaryInfo, pc uint64) bool
 }
 
+// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_amd64.s.
 const (
 	crosscall2SPOffsetBad        = 0x8
 	crosscall2SPOffsetWindows    = 0x118
diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go
index cdfd4b66..8fe7096c 100644
--- a/pkg/proc/arm64_arch.go
+++ b/pkg/proc/arm64_arch.go
@@ -52,7 +52,7 @@ func (a *ARM64) PtrSize() int {
 	return 8
 }
 
-// MaxInstructionLength returns the maximum lenght of an instruction.
+// MaxInstructionLength returns the maximum length of an instruction.
 func (a *ARM64) MaxInstructionLength() int {
 	return 4
 }
@@ -465,3 +465,9 @@ func (a *ARM64) DwarfRegisterToString(i int, reg *op.DwarfRegister) (name string
 	}
 	return name, false, fmt.Sprintf("%#x", reg.Bytes)
 }
+
+// InhibitStepInto returns whether StepBreakpoint can be set at pc.
+// Always return false on arm64.
+func (a *ARM64) InhibitStepInto(bi *BinaryInfo, pc uint64) bool {
+	return false
+}
diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go
index 47451324..ae3da942 100644
--- a/pkg/proc/bininfo.go
+++ b/pkg/proc/bininfo.go
@@ -27,6 +27,7 @@ import (
 	"github.com/go-delve/delve/pkg/dwarf/loclist"
 	"github.com/go-delve/delve/pkg/dwarf/op"
 	"github.com/go-delve/delve/pkg/dwarf/reader"
+	"github.com/go-delve/delve/pkg/dwarf/util"
 	"github.com/go-delve/delve/pkg/goversion"
 	"github.com/go-delve/delve/pkg/logflags"
 	"github.com/sirupsen/logrus"
@@ -50,6 +51,9 @@ type BinaryInfo struct {
 	// LookupFunc maps function names to a description of the function.
 	LookupFunc map[string]*Function
 
+	// SymNames maps addr to a description *elf.Symbol of this addr.
+	SymNames map[uint64]*elf.Symbol
+
 	// Images is a list of loaded shared libraries (also known as
 	// shared objects on linux or DLLs on windows).
 	Images []*Image
@@ -97,15 +101,6 @@ type BinaryInfo struct {
 	logger *logrus.Entry
 }
 
-// ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture.
-var ErrUnsupportedLinuxArch = errors.New("unsupported architecture - only linux/amd64 and linux/arm64 are supported")
-
-// ErrUnsupportedWindowsArch is returned when attempting to debug a binary compiled for an unsupported architecture.
-var ErrUnsupportedWindowsArch = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported")
-
-// ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture.
-var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported")
-
 // ErrCouldNotDetermineRelocation is an error returned when Delve could not determine the base address of a
 // position independant executable.
 var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
@@ -114,6 +109,61 @@ var ErrCouldNotDetermineRelocation = errors.New("could not determine the base ad
 // section or find an external debug info file.
 var ErrNoDebugInfoFound = errors.New("could not open debug info")
 
+// ErrUnsupportedArch is returned when attempting to debug a binary compiled for an unsupported architecture.
+type ErrUnsupportedArch struct {
+	os      string
+	cpuArch CpuArch
+}
+
+type CpuArch interface {
+	String() string
+}
+
+func (e *ErrUnsupportedArch) Error() string {
+	var supportArchs []CpuArch
+	switch e.os {
+	case "linux":
+		for linuxArch, _ := range supportedLinuxArch {
+			supportArchs = append(supportArchs, linuxArch)
+		}
+	case "windows":
+		for windowArch, _ := range supportedWindowsArch {
+			supportArchs = append(supportArchs, windowArch)
+		}
+	case "darwin":
+		for darwinArch, _ := range supportedDarwinArch {
+			supportArchs = append(supportArchs, darwinArch)
+		}
+	}
+
+	errStr := "unsupported architecture of " + e.os + "/" + e.cpuArch.String()
+	errStr += " - only"
+	for _, arch := range supportArchs {
+		errStr += " " + e.os + "/" + arch.String() + " "
+	}
+	if len(supportArchs) == 1 {
+		errStr += "is supported"
+	} else {
+		errStr += "are supported"
+	}
+
+	return errStr
+}
+
+var supportedLinuxArch = map[elf.Machine]bool{
+	elf.EM_X86_64:  true,
+	elf.EM_AARCH64: true,
+	elf.EM_386:     true,
+}
+
+var supportedWindowsArch = map[PEMachine]bool{
+	IMAGE_FILE_MACHINE_AMD64: true,
+}
+
+var supportedDarwinArch = map[macho.Cpu]bool{
+	macho.CpuAmd64: true,
+}
+
 const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
 
 type compileUnit struct {
@@ -260,12 +310,13 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo {
 
 	// TODO: find better way to determine proc arch (perhaps use executable file info).
 	switch goarch {
+	case "386":
+		r.Arch = I386Arch(goos)
 	case "amd64":
 		r.Arch = AMD64Arch(goos)
 	case "arm64":
 		r.Arch = ARM64Arch(goos)
 	}
-
 	return r
 }
 
@@ -614,7 +665,7 @@ func (bi *BinaryInfo) LoadImageFromData(dwdata *dwarf.Data, debugFrameBytes, deb
 	image.typeCache = make(map[dwarf.Offset]godwarf.Type)
 
 	if debugFrameBytes != nil {
-		bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0)
+		bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0, bi.Arch.PtrSize())
 	}
 
 	image.loclist = loclist.New(debugLocBytes, bi.Arch.PtrSize())
@@ -697,7 +748,7 @@ func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, r
 	if err != nil {
 		return 0, nil, "", err
 	}
-	addr, pieces, err := op.ExecuteStackProgram(regs, instr)
+	addr, pieces, err := op.ExecuteStackProgram(regs, instr, bi.Arch.PtrSize())
 	return addr, pieces, descr, err
 }
 
@@ -827,9 +878,9 @@ func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugIn
 		return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, err.Error())
 	}
 
-	if elfFile.Machine != elf.EM_X86_64 && elfFile.Machine != elf.EM_AARCH64 {
+	if !supportedLinuxArch[elfFile.Machine] {
 		sepFile.Close()
-		return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, ErrUnsupportedLinuxArch.Error())
+		return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, &ErrUnsupportedArch{os: "linux", cpuArch: elfFile.Machine})
 	}
 
 	return sepFile, elfFile, nil
@@ -875,8 +926,8 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
 	if err != nil {
 		return err
 	}
-	if elfFile.Machine != elf.EM_X86_64 && elfFile.Machine != elf.EM_AARCH64 {
-		return ErrUnsupportedLinuxArch
+	if !supportedLinuxArch[elfFile.Machine] {
+		return &ErrUnsupportedArch{os: "linux", cpuArch: elfFile.Machine}
 	}
 
 	if image.index == 0 {
@@ -924,9 +975,10 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
 	debugLocBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "loc")
 	image.loclist = loclist.New(debugLocBytes, bi.Arch.PtrSize())
 
-	wg.Add(2)
+	wg.Add(3)
 	go bi.parseDebugFrameElf(image, dwarfFile, wg)
 	go bi.loadDebugInfoMaps(image, debugLineBytes, wg, nil)
+	go bi.loadSymbolName(image, elfFile, wg)
 	if image.index == 0 {
 		// determine g struct offset only when loading the executable file
 		wg.Add(1)
@@ -935,6 +987,25 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
 	return nil
 }
 
+//  STT_FUNC is a code object, see /usr/include/elf.h for a full definition.
+const STT_FUNC = 2
+
+func (bi *BinaryInfo) loadSymbolName(image *Image, file *elf.File, wg *sync.WaitGroup) {
+	defer wg.Done()
+	if bi.SymNames == nil {
+		bi.SymNames = make(map[uint64]*elf.Symbol)
+	}
+	symSecs, _ := file.Symbols()
+	if symSecs != nil {
+		for _, symSec := range symSecs {
+			if symSec.Info == STT_FUNC { // TODO(chainhelen), need to parse others types.
+				s := symSec
+				bi.SymNames[symSec.Value+image.StaticBase] = &s
+			}
+		}
+	}
+}
+
 func (bi *BinaryInfo) parseDebugFrameElf(image *Image, exe *elf.File, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -949,7 +1020,7 @@ func (bi *BinaryInfo) parseDebugFrameElf(image *Image, exe *elf.File, wg *sync.W
 		return
 	}
 
-	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), image.StaticBase))
+	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), image.StaticBase, bi.Arch.PtrSize()))
 }
 
 func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.WaitGroup) {
@@ -957,7 +1028,9 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.
 
 	// This is a bit arcane. Essentially:
 	// - If the program is pure Go, it can do whatever it wants, and puts the G
-	//   pointer at %fs-8.
+	//   pointer at %fs-8 on 64 bit.
+	// - %Gs is the index of private storage in GDT on 32 bit, and puts the G
+	//   pointer at -4(tls).
 	// - Otherwise, Go asks the external linker to place the G pointer by
 	//   emitting runtime.tlsg, a TLS symbol, which is relocated to the chosen
 	//   offset in libc's TLS block.
@@ -974,10 +1047,6 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.
 			break
 		}
 	}
-	if tlsg == nil {
-		bi.gStructOffset = ^uint64(8) + 1 // -8
-		return
-	}
 	var tls *elf.Prog
 	for _, prog := range exe.Progs {
 		if prog.Type == elf.PT_TLS {
@@ -985,8 +1054,8 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.
 			break
 		}
 	}
-	if tls == nil {
-		bi.gStructOffset = ^uint64(8) + 1 // -8
+	if tlsg == nil || tls == nil {
+		bi.gStructOffset = ^uint64(bi.Arch.PtrSize()) + 1 //-ptrSize
 		return
 	}
 
@@ -1012,8 +1081,9 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
 		return err
 	}
 	image.closer = closer
-	if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
-		return ErrUnsupportedWindowsArch
+	cpuArch := PEMachine(peFile.Machine)
+	if !supportedWindowsArch[cpuArch] {
+		return &ErrUnsupportedArch{os: "windows", cpuArch: cpuArch}
 	}
 	image.dwarf, err = peFile.DWARF()
 	if err != nil {
@@ -1078,7 +1148,7 @@ func (bi *BinaryInfo) parseDebugFramePE(image *Image, exe *pe.File, wg *sync.Wai
 		return
 	}
 
-	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase))
+	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase, bi.Arch.PtrSize()))
 }
 
 // Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
@@ -1107,8 +1177,8 @@ func loadBinaryInfoMacho(bi *BinaryInfo, image *Image, path string, entryPoint u
 		return err
 	}
 	image.closer = exe
-	if exe.Cpu != macho.CpuAmd64 {
-		return ErrUnsupportedDarwinArch
+	if !supportedDarwinArch[exe.Cpu] {
+		return &ErrUnsupportedArch{os: "darwin", cpuArch: exe.Cpu}
 	}
 	image.dwarf, err = exe.DWARF()
 	if err != nil {
@@ -1156,7 +1226,7 @@ func (bi *BinaryInfo) parseDebugFrameMacho(image *Image, exe *macho.File, wg *sy
 		return
 	}
 
-	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase))
+	bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase, bi.Arch.PtrSize()))
 }
 
 // Do not call this function directly it isn't able to deal correctly with package paths
@@ -1327,7 +1397,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg
 						logger.Printf(fmt, args)
 					}
 				}
-				cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows")
+				cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize())
 			}
 			cu.producer, _ = entry.Val(dwarf.AttrProducer).(string)
 			if cu.isgo && cu.producer != "" {
@@ -1418,7 +1488,7 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex
 				var addr uint64
 				if loc, ok := entry.Val(dwarf.AttrLocation).([]byte); ok {
 					if len(loc) == bi.Arch.PtrSize()+1 && op.Opcode(loc[0]) == op.DW_OP_addr {
-						addr = binary.LittleEndian.Uint64(loc[1:])
+						addr, _ = util.ReadUintRaw(bytes.NewReader(loc[1:]), binary.LittleEndian, bi.Arch.PtrSize())
 					}
 				}
 				if !cu.isgo {
@@ -1741,6 +1811,9 @@ func (bi *BinaryInfo) symLookup(addr uint64) (string, uint64) {
 		}
 		return "", 0
 	}
+	if sym, ok := bi.SymNames[addr]; ok {
+		return sym.Name, addr
+	}
 	i := sort.Search(len(bi.packageVars), func(i int) bool {
 		return bi.packageVars[i].addr >= addr
 	})
diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go
index cee1a651..81545c0d 100644
--- a/pkg/proc/core/core.go
+++ b/pkg/proc/core/core.go
@@ -155,12 +155,13 @@ type Process struct {
 
 	entryPoint uint64
 
-	bi                *proc.BinaryInfo
-	breakpoints       proc.BreakpointMap
-	currentThread     *Thread
-	selectedGoroutine *proc.G
+	bi            *proc.BinaryInfo
+	breakpoints   proc.BreakpointMap
+	currentThread *Thread
 }
 
+var _ proc.ProcessInternal = &Process{}
+
 // Thread represents a thread in the core file being debugged.
 type Thread struct {
 	th     osThread
@@ -212,17 +213,12 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
 		return nil, err
 	}
 
-	if err := p.initialize(exePath, debugInfoDirs); err != nil {
-		return nil, err
-	}
-
-	return proc.NewTarget(p, false), nil
-}
-
-// initialize for core files doesn't do much
-// aside from call the post initialization setup.
-func (p *Process) initialize(path string, debugInfoDirs []string) error {
-	return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint)
+	return proc.NewTarget(p, proc.NewTargetConfig{
+		Path:                exePath,
+		DebugInfoDirs:       debugInfoDirs,
+		WriteBreakpoint:     p.writeBreakpoint,
+		DisableAsyncPreempt: false,
+		StopReason:          proc.StopAttached})
 }
 
 // BinInfo will return the binary info.
@@ -230,13 +226,6 @@ func (p *Process) BinInfo() *proc.BinaryInfo {
 	return p.bi
 }
 
-// SetSelectedGoroutine will set internally the goroutine that should be
-// the default for any command executed, the goroutine being actively
-// followed.
-func (p *Process) SetSelectedGoroutine(g *proc.G) {
-	p.selectedGoroutine = g
-}
-
 // EntryPoint will return the entry point address for this core file.
 func (p *Process) EntryPoint() (uint64, error) {
 	return p.entryPoint, nil
@@ -392,8 +381,8 @@ func (p *Process) ClearInternalBreakpoints() error {
 
 // ContinueOnce will always return an error because you
 // cannot control execution of a core file.
-func (p *Process) ContinueOnce() (proc.Thread, error) {
-	return nil, ErrContinueCore
+func (p *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
+	return nil, proc.StopUnknown, ErrContinueCore
 }
 
 // StepInstruction will always return an error
@@ -443,40 +432,11 @@ func (p *Process) Pid() int {
 func (p *Process) ResumeNotify(chan<- struct{}) {
 }
 
-// SelectedGoroutine returns the current active and selected
-// goroutine.
-func (p *Process) SelectedGoroutine() *proc.G {
-	return p.selectedGoroutine
-}
-
 // SetBreakpoint will always return an error for core files as you cannot write memory or control execution.
 func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
 	return nil, ErrWriteCore
 }
 
-// SwitchGoroutine will change the selected and active goroutine.
-func (p *Process) SwitchGoroutine(g *proc.G) error {
-	if g == nil {
-		// user specified -1 and selectedGoroutine is nil
-		return nil
-	}
-	if g.Thread != nil {
-		return p.SwitchThread(g.Thread.ThreadID())
-	}
-	p.selectedGoroutine = g
-	return nil
-}
-
-// SwitchThread will change the selected and active thread.
-func (p *Process) SwitchThread(tid int) error {
-	if th, ok := p.Threads[tid]; ok {
-		p.currentThread = th
-		p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
-		return nil
-	}
-	return fmt.Errorf("thread %d does not exist", tid)
-}
-
 // ThreadList will return a list of all threads currently in the process.
 func (p *Process) ThreadList() []proc.Thread {
 	r := make([]proc.Thread, 0, len(p.Threads))
@@ -491,3 +451,8 @@ func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
 	t, ok := p.Threads[threadID]
 	return t, ok
 }
+
+// SetCurrentThread is used internally by proc.Target to change the current thread.
+func (p *Process) SetCurrentThread(th proc.Thread) {
+	p.currentThread = th.(*Thread)
+}
diff --git a/pkg/proc/core/linux_core.go b/pkg/proc/core/linux_core.go
index e52ab0a2..03727246 100644
--- a/pkg/proc/core/linux_core.go
+++ b/pkg/proc/core/linux_core.go
@@ -118,24 +118,29 @@ func readLinuxCore(corePath, exePath string) (*Process, error) {
 		return nil, err
 	}
 	memory := buildMemory(coreFile, exeELF, exe, notes)
-	entryPoint := findEntryPoint(notes)
-	p := &Process{
-		mem:         memory,
-		Threads:     map[int]*Thread{},
-		entryPoint:  entryPoint,
-		breakpoints: proc.NewBreakpointMap(),
-	}
 
+	// TODO support 386
+	var bi *proc.BinaryInfo
 	switch machineType {
 	case EM_X86_64:
-		p.bi = proc.NewBinaryInfo("linux", "amd64")
-		linuxThreadsFromNotes(p, notes, machineType)
+		bi = proc.NewBinaryInfo("linux", "amd64")
 	case EM_AARCH64:
-		p.bi = proc.NewBinaryInfo("linux", "arm64")
-		linuxThreadsFromNotes(p, notes, machineType)
+		bi = proc.NewBinaryInfo("linux", "arm64")
 	default:
 		return nil, fmt.Errorf("unsupported machine type")
 	}
+
+	entryPoint := findEntryPoint(notes, bi.Arch.PtrSize())
+
+	p := &Process{
+		mem:         memory,
+		Threads:     map[int]*Thread{},
+		entryPoint:  entryPoint,
+		bi:          bi,
+		breakpoints: proc.NewBreakpointMap(),
+	}
+
+	linuxThreadsFromNotes(p, notes, machineType)
 	return p, nil
 }
 
@@ -352,10 +357,10 @@ func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*Note) proc.Me
 	return memory
 }
 
-func findEntryPoint(notes []*Note) uint64 {
+func findEntryPoint(notes []*Note, ptrSize int) uint64 {
 	for _, note := range notes {
 		if note.Type == NT_AUXV {
-			return linutil.EntryPointFromAuxvAMD64(note.Desc.([]byte))
+			return linutil.EntryPointFromAuxv(note.Desc.([]byte), ptrSize)
 		}
 	}
 	return 0
diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go
index a64f32e8..9ec3a8f4 100644
--- a/pkg/proc/fncall.go
+++ b/pkg/proc/fncall.go
@@ -542,7 +542,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
 			err = fmt.Errorf("could not get argument location of %s: %v", argname, err)
 		} else {
 			var pieces []op.Piece
-			off, pieces, err = op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog)
+			off, pieces, err = op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog, bi.Arch.PtrSize())
 			if err != nil {
 				err = fmt.Errorf("unsupported location expression for argument %s: %v", argname, err)
 			}
diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go
index b0add2d8..4e7078c6 100644
--- a/pkg/proc/gdbserial/gdbserver.go
+++ b/pkg/proc/gdbserial/gdbserver.go
@@ -104,9 +104,8 @@ type Process struct {
 	bi   *proc.BinaryInfo
 	conn gdbConn
 
-	threads           map[int]*Thread
-	currentThread     *Thread
-	selectedGoroutine *proc.G
+	threads       map[int]*Thread
+	currentThread *Thread
 
 	exited, detached bool
 	ctrlC            bool // ctrl-c was sent to stop inferior
@@ -125,9 +124,10 @@ type Process struct {
 	waitChan chan *os.ProcessState
 
 	onDetach func() // called after a successful detach
-
 }
 
+var _ proc.ProcessInternal = &Process{}
+
 // Thread represents an operating system thread.
 type Thread struct {
 	ID                int
@@ -198,7 +198,7 @@ func New(process *os.Process) *Process {
 }
 
 // Listen waits for a connection from the stub.
-func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string) error {
+func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
 	acceptChan := make(chan net.Conn)
 
 	go func() {
@@ -210,25 +210,25 @@ func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoD
 	case conn := <-acceptChan:
 		listener.Close()
 		if conn == nil {
-			return errors.New("could not connect")
+			return nil, errors.New("could not connect")
 		}
-		return p.Connect(conn, path, pid, debugInfoDirs)
+		return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
 	case status := <-p.waitChan:
 		listener.Close()
-		return fmt.Errorf("stub exited while waiting for connection: %v", status)
+		return nil, fmt.Errorf("stub exited while waiting for connection: %v", status)
 	}
 }
 
 // Dial attempts to connect to the stub.
-func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string) error {
+func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
 	for {
 		conn, err := net.Dial("tcp", addr)
 		if err == nil {
-			return p.Connect(conn, path, pid, debugInfoDirs)
+			return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
 		}
 		select {
 		case status := <-p.waitChan:
-			return fmt.Errorf("stub exited while attempting to connect: %v", status)
+			return nil, fmt.Errorf("stub exited while attempting to connect: %v", status)
 		default:
 		}
 		time.Sleep(time.Second)
@@ -241,13 +241,13 @@ func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string
 // program and the PID of the target process, both are optional, however
 // some stubs do not provide ways to determine path and pid automatically
 // and Connect will be unable to function without knowing them.
-func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error {
+func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
 	p.conn.conn = conn
 	p.conn.pid = pid
 	err := p.conn.handshake()
 	if err != nil {
 		conn.Close()
-		return err
+		return nil, err
 	}
 
 	if verbuf, err := p.conn.exec([]byte("$qGDBServerVersion"), "init"); err == nil {
@@ -262,8 +262,9 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
 		}
 	}
 
-	if err := p.initialize(path, debugInfoDirs); err != nil {
-		return err
+	tgt, err := p.initialize(path, debugInfoDirs, stopReason)
+	if err != nil {
+		return nil, err
 	}
 
 	// None of the stubs we support returns the value of fs_base or gs_base
@@ -279,7 +280,8 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
 			p.loadGInstrAddr = addr
 		}
 	}
-	return nil
+
+	return tgt, nil
 }
 
 // unusedPort returns an unused tcp port
@@ -402,15 +404,13 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
 	p := New(process.Process)
 	p.conn.isDebugserver = isDebugserver
 
+	var tgt *proc.Target
 	if listener != nil {
-		err = p.Listen(listener, cmd[0], 0, debugInfoDirs)
+		tgt, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
 	} else {
-		err = p.Dial(port, cmd[0], 0, debugInfoDirs)
-	}
-	if err != nil {
-		return nil, err
+		tgt, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
 	}
-	return proc.NewTarget(p, runtime.GOOS == "darwin"), nil
+	return tgt, err
 }
 
 // LLDBAttach starts an instance of lldb-server and connects to it, asking
@@ -454,15 +454,13 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err
 	p := New(process.Process)
 	p.conn.isDebugserver = isDebugserver
 
+	var tgt *proc.Target
 	if listener != nil {
-		err = p.Listen(listener, path, pid, debugInfoDirs)
+		tgt, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached)
 	} else {
-		err = p.Dial(port, path, pid, debugInfoDirs)
+		tgt, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached)
 	}
-	if err != nil {
-		return nil, err
-	}
-	return proc.NewTarget(p, false), nil
+	return tgt, err
 }
 
 // EntryPoint will return the process entry point address, useful for
@@ -473,7 +471,7 @@ func (p *Process) EntryPoint() (uint64, error) {
 		// If we can't read the auxiliary vector it just means it's not supported
 		// by the OS or by the stub. If we are debugging a PIE and the entry point
 		// is needed proc.LoadBinaryInfo will complain about it.
-		entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
+		entryPoint = linutil.EntryPointFromAuxv(auxv, p.BinInfo().Arch.PtrSize())
 	}
 	return entryPoint, nil
 }
@@ -481,7 +479,7 @@ func (p *Process) EntryPoint() (uint64, error) {
 // initialize uses qProcessInfo to load the inferior's PID and
 // executable path. This command is not supported by all stubs and not all
 // stubs will report both the PID and executable path.
-func (p *Process) initialize(path string, debugInfoDirs []string) error {
+func (p *Process) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
 	var err error
 	if path == "" {
 		// If we are attaching to a running process and the user didn't specify
@@ -495,11 +493,11 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
 				_, path, err = queryProcessInfo(p, p.Pid())
 				if err != nil {
 					p.conn.conn.Close()
-					return err
+					return nil, err
 				}
 			} else {
 				p.conn.conn.Close()
-				return fmt.Errorf("could not determine executable path: %v", err)
+				return nil, fmt.Errorf("could not determine executable path: %v", err)
 			}
 		}
 	}
@@ -520,7 +518,7 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
 	if err != nil {
 		p.conn.conn.Close()
 		p.bi.Close()
-		return err
+		return nil, err
 	}
 	p.clearThreadSignals()
 
@@ -529,14 +527,20 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
 		if err != nil && !isProtocolErrorUnsupported(err) {
 			p.conn.conn.Close()
 			p.bi.Close()
-			return err
+			return nil, err
 		}
 	}
-	if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint); err != nil {
+	tgt, err := proc.NewTarget(p, proc.NewTargetConfig{
+		Path:                path,
+		DebugInfoDirs:       debugInfoDirs,
+		WriteBreakpoint:     p.writeBreakpoint,
+		DisableAsyncPreempt: runtime.GOOS == "darwin",
+		StopReason:          stopReason})
+	if err != nil {
 		p.conn.conn.Close()
-		return err
+		return nil, err
 	}
-	return nil
+	return tgt, nil
 }
 
 func queryProcessInfo(p *Process, pid int) (int, string, error) {
@@ -606,9 +610,9 @@ func (p *Process) CurrentThread() proc.Thread {
 	return p.currentThread
 }
 
-// SelectedGoroutine returns the current actuve selected goroutine.
-func (p *Process) SelectedGoroutine() *proc.G {
-	return p.selectedGoroutine
+// SetCurrentThread is used internally by proc.Target to change the current thread.
+func (p *Process) SetCurrentThread(th proc.Thread) {
+	p.currentThread = th.(*Thread)
 }
 
 const (
@@ -628,9 +632,9 @@ const (
 
 // ContinueOnce will continue execution of the process until
 // a breakpoint is hit or signal is received.
-func (p *Process) ContinueOnce() (proc.Thread, error) {
+func (p *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
 	if p.exited {
-		return nil, &proc.ErrProcessExited{Pid: p.conn.pid}
+		return nil, proc.StopExited, &proc.ErrProcessExited{Pid: p.conn.pid}
 	}
 
 	if p.conn.direction == proc.Forward {
@@ -638,7 +642,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
 		for _, thread := range p.threads {
 			if thread.CurrentBreakpoint.Breakpoint != nil {
 				if err := thread.stepInstruction(&threadUpdater{p: p}); err != nil {
-					return nil, err
+					return nil, proc.StopUnknown, err
 				}
 			}
 		}
@@ -664,7 +668,7 @@ continueLoop:
 			if _, exited := err.(proc.ErrProcessExited); exited {
 				p.exited = true
 			}
-			return nil, err
+			return nil, proc.StopExited, err
 		}
 
 		// For stubs that support qThreadStopInfo updateThreadList will
@@ -689,16 +693,16 @@ continueLoop:
 
 	if p.BinInfo().GOOS == "linux" {
 		if err := linutil.ElfUpdateSharedObjects(p); err != nil {
-			return nil, err
+			return nil, proc.StopUnknown, err
 		}
 	}
 
 	if err := p.setCurrentBreakpoints(); err != nil {
-		return nil, err
+		return nil, proc.StopUnknown, err
 	}
 
 	if trapthread == nil {
-		return nil, fmt.Errorf("could not find thread %s", threadID)
+		return nil, proc.StopUnknown, fmt.Errorf("could not find thread %s", threadID)
 	}
 
 	var err error
@@ -720,7 +724,7 @@ continueLoop:
 		// the signals that are reported here can not be propagated back to the target process.
 		trapthread.sig = 0
 	}
-	return trapthread, err
+	return trapthread, proc.StopUnknown, err
 }
 
 func (p *Process) findThreadByStrID(threadID string) *Thread {
@@ -808,38 +812,6 @@ func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread
 	return trapthread, shouldStop
 }
 
-// SetSelectedGoroutine will set internally the goroutine that should be
-// the default for any command executed, the goroutine being actively
-// followed.
-func (p *Process) SetSelectedGoroutine(g *proc.G) {
-	p.selectedGoroutine = g
-}
-
-// SwitchThread will change the internal selected thread.
-func (p *Process) SwitchThread(tid int) error {
-	if p.exited {
-		return proc.ErrProcessExited{Pid: p.conn.pid}
-	}
-	if th, ok := p.threads[tid]; ok {
-		p.currentThread = th
-		p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
-		return nil
-	}
-	return fmt.Errorf("thread %d does not exist", tid)
-}
-
-// SwitchGoroutine will change the internal selected goroutine.
-func (p *Process) SwitchGoroutine(g *proc.G) error {
-	if g == nil {
-		return nil
-	}
-	if g.Thread != nil {
-		return p.SwitchThread(g.Thread.ThreadID())
-	}
-	p.selectedGoroutine = g
-	return nil
-}
-
 // RequestManualStop will attempt to stop the process
 // without a breakpoint or signal having been recieved.
 func (p *Process) RequestManualStop() error {
@@ -944,7 +916,6 @@ func (p *Process) Restart(pos string) error {
 	}
 	p.clearThreadSignals()
 	p.clearThreadRegisters()
-	p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
 
 	for addr := range p.breakpoints.M {
 		p.conn.setBreakpoint(addr)
diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go
index ff5afec9..70db36f5 100644
--- a/pkg/proc/gdbserial/rr.go
+++ b/pkg/proc/gdbserial/rr.go
@@ -90,13 +90,13 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string)
 			safeRemoveAll(p.tracedir)
 		}
 	}
-	err = p.Dial(init.port, init.exe, 0, debugInfoDirs)
+	tgt, err := p.Dial(init.port, init.exe, 0, debugInfoDirs, proc.StopLaunched)
 	if err != nil {
 		rrcmd.Process.Kill()
 		return nil, err
 	}
 
-	return proc.NewTarget(p, false), nil
+	return tgt, nil
 }
 
 // ErrPerfEventParanoid is the error returned by Reply and Record if
diff --git a/pkg/proc/goroutine_cache.go b/pkg/proc/goroutine_cache.go
index c4613a10..e81f4d2b 100644
--- a/pkg/proc/goroutine_cache.go
+++ b/pkg/proc/goroutine_cache.go
@@ -13,13 +13,13 @@ func (gcache *goroutineCache) init(bi *BinaryInfo) {
 	exeimage := bi.Images[0]
 	rdr := exeimage.DwarfReader()
 
-	gcache.allglenAddr, _ = rdr.AddrFor("runtime.allglen", exeimage.StaticBase)
+	gcache.allglenAddr, _ = rdr.AddrFor("runtime.allglen", exeimage.StaticBase, bi.Arch.PtrSize())
 
 	rdr.Seek(0)
-	gcache.allgentryAddr, err = rdr.AddrFor("runtime.allgs", exeimage.StaticBase)
+	gcache.allgentryAddr, err = rdr.AddrFor("runtime.allgs", exeimage.StaticBase, bi.Arch.PtrSize())
 	if err != nil {
 		// try old name (pre Go 1.6)
-		gcache.allgentryAddr, _ = rdr.AddrFor("runtime.allg", exeimage.StaticBase)
+		gcache.allgentryAddr, _ = rdr.AddrFor("runtime.allg", exeimage.StaticBase, bi.Arch.PtrSize())
 	}
 }
 
@@ -31,6 +31,7 @@ func (gcache *goroutineCache) getRuntimeAllg(bi *BinaryInfo, mem MemoryReadWrite
 	if err != nil {
 		return 0, 0, err
 	}
+	
 	allgptr, err := readUintRaw(mem, uintptr(gcache.allgentryAddr), int64(bi.Arch.PtrSize()))
 	if err != nil {
 		return 0, 0, err
diff --git a/pkg/proc/i386_arch.go b/pkg/proc/i386_arch.go
new file mode 100644
index 00000000..c48342d7
--- /dev/null
+++ b/pkg/proc/i386_arch.go
@@ -0,0 +1,369 @@
+package proc
+
+import (
+	"encoding/binary"
+	"fmt"
+	"github.com/go-delve/delve/pkg/dwarf/frame"
+	"github.com/go-delve/delve/pkg/dwarf/op"
+	"strings"
+)
+
+// I386 represents the Intel386 CPU architecture.
+type I386 struct {
+	gStructOffset uint64
+	goos          string
+
+	// crosscall2fn is the DIE of crosscall2, a function used by the go runtime
+	// to call C functions. This function in go 1.9 (and previous versions) had
+	// a bad frame descriptor which needs to be fixed to generate good stack
+	// traces.
+	crosscall2fn *Function
+
+	// sigreturnfn is the DIE of runtime.sigreturn, the return trampoline for
+	// the signal handler. See comment in FixFrameUnwindContext for a
+	// description of why this is needed.
+	sigreturnfn *Function
+}
+
+const (
+	i386DwarfIPRegNum uint64 = 8
+	i386DwarfSPRegNum uint64 = 4
+	i386DwarfBPRegNum uint64 = 5
+)
+
+var i386BreakInstruction = []byte{0xCC}
+
+// I386Arch returns an initialized I386Arch
+// struct.
+func I386Arch(goos string) *I386 {
+	return &I386{
+		goos: goos,
+	}
+}
+
+// PtrSize returns the size of a pointer
+// on this architecture.
+func (i *I386) PtrSize() int {
+	return 4
+}
+
+// MaxInstructionLength returns the maximum length of an instruction.
+func (i *I386) MaxInstructionLength() int {
+	return 15
+}
+
+// BreakpointInstruction returns the Breakpoint
+// instruction for this architecture.
+func (i *I386) BreakpointInstruction() []byte {
+	return i386BreakInstruction
+}
+
+// BreakInstrMovesPC returns whether the
+// breakpoint instruction will change the value
+// of PC after being executed
+func (i *I386) BreakInstrMovesPC() bool {
+	return true
+}
+
+// BreakpointSize returns the size of the
+// breakpoint instruction on this architecture.
+func (i *I386) BreakpointSize() int {
+	return len(i386BreakInstruction)
+}
+
+// TODO, Not sure, always return false for now. Need to test on windows.
+func (i *I386) DerefTLS() bool {
+	return false
+}
+
+// FixFrameUnwindContext adds default architecture rules to fctxt or returns
+// the default frame unwind context if fctxt is nil.
+func (i *I386) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext {
+	if i.sigreturnfn == nil {
+		i.sigreturnfn = bi.LookupFunc["runtime.sigreturn"]
+	}
+
+	if fctxt == nil || (i.sigreturnfn != nil && pc >= i.sigreturnfn.Entry && pc < i.sigreturnfn.End) {
+		// When there's no frame descriptor entry use BP (the frame pointer) instead
+		// - return register is [bp + i.PtrSize()] (i.e. [cfa-i.PtrSize()])
+		// - cfa is bp + i.PtrSize()*2
+		// - bp is [bp] (i.e. [cfa-i.PtrSize()*2])
+		// - sp is cfa
+
+		// When the signal handler runs it will move the execution to the signal
+		// handling stack (installed using the sigaltstack system call).
+		// This isn't i proper stack switch: the pointer to g in TLS will still
+		// refer to whatever g was executing on that thread before the signal was
+		// received.
+		// Since go did not execute i stack switch the previous value of sp, pc
+		// and bp is not saved inside g.sched, as it normally would.
+		// The only way to recover is to either read sp/pc from the signal context
+		// parameter (the ucontext_t* parameter) or to unconditionally follow the
+		// frame pointer when we get to runtime.sigreturn (which is what we do
+		// here).
+
+		return &frame.FrameContext{
+			RetAddrReg: i386DwarfIPRegNum,
+			Regs: map[uint64]frame.DWRule{
+				i386DwarfIPRegNum: frame.DWRule{
+					Rule:   frame.RuleOffset,
+					Offset: int64(-i.PtrSize()),
+				},
+				i386DwarfBPRegNum: frame.DWRule{
+					Rule:   frame.RuleOffset,
+					Offset: int64(-2 * i.PtrSize()),
+				},
+				i386DwarfSPRegNum: frame.DWRule{
+					Rule:   frame.RuleValOffset,
+					Offset: 0,
+				},
+			},
+			CFA: frame.DWRule{
+				Rule:   frame.RuleCFA,
+				Reg:    i386DwarfBPRegNum,
+				Offset: int64(2 * i.PtrSize()),
+			},
+		}
+	}
+
+	if i.crosscall2fn == nil {
+		i.crosscall2fn = bi.LookupFunc["crosscall2"]
+	}
+
+	// TODO(chainhelen), need to check whether there is a bad frame descriptor like amd64.
+	// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_386.s.
+	if i.crosscall2fn != nil && pc >= i.crosscall2fn.Entry && pc < i.crosscall2fn.End {
+		rule := fctxt.CFA
+		fctxt.CFA = rule
+	}
+
+	// We assume that EBP is the frame pointer and we want to keep it updated,
+	// 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{
+			Rule:   frame.RuleFramePointer,
+			Reg:    i386DwarfBPRegNum,
+			Offset: 0,
+		}
+	}
+
+	return fctxt
+}
+
+// SwitchStack will use the current frame to determine if it's time to
+// switch between the system stack and the goroutine stack or vice versa.
+// Sets it.atend when the top of the stack is reached.
+func (i *I386) SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool {
+	if it.frame.Current.Fn == nil {
+		return false
+	}
+	switch it.frame.Current.Fn.Name {
+	case "runtime.asmcgocall", "runtime.cgocallback_gofunc": // TODO(chainhelen), need to support cgo stacktraces.
+		return false
+	case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
+		// Look for "top of stack" functions.
+		it.atend = true
+		return true
+
+	case "runtime.mstart":
+		// Calls to runtime.systemstack will switch to the systemstack then:
+		// 1. alter the goroutine stack so that it looks like systemstack_switch
+		//    was called
+		// 2. alter the system stack so that it looks like the bottom-most frame
+		//    belongs to runtime.mstart
+		// If we find a runtime.mstart frame on the system stack of a goroutine
+		// parked on runtime.systemstack_switch we assume runtime.systemstack was
+		// called and continue tracing from the parked position.
+
+		if it.top || !it.systemstack || it.g == nil {
+			return false
+		}
+		if fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" {
+			return false
+		}
+
+		it.switchToGoroutineStack()
+		return true
+
+	default:
+		if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") && it.frame.Current.Fn.Name != "runtime.fatalthrow" {
+			// The runtime switches to the system stack in multiple places.
+			// This usually happens through a call to runtime.systemstack but there
+			// are functions that switch to the system stack manually (for example
+			// runtime.morestack).
+			// Since we are only interested in printing the system stack for cgo
+			// calls we switch directly to the goroutine stack if we detect that the
+			// function at the top of the stack is a runtime function.
+			//
+			// The function "runtime.fatalthrow" is deliberately excluded from this
+			// because it can end up in the stack during a cgo call and switching to
+			// the goroutine stack will exclude all the C functions from the stack
+			// trace.
+			it.switchToGoroutineStack()
+			return true
+		}
+
+		return false
+	}
+}
+
+// RegSize returns the size (in bytes) of register regnum.
+// 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
+func (i *I386) RegSize(regnum uint64) int {
+	// XMM registers
+	if regnum >= 21 && regnum <= 36 {
+		return 16
+	}
+	// x87 registers
+	if regnum >= 11 && regnum <= 18 {
+		return 10
+	}
+	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 (i *I386) RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.DwarfRegisters {
+	dregs := make([]*op.DwarfRegister, maxI386DwarfRegister()+1)
+
+	for _, reg := range regs.Slice(true) {
+		if dwarfReg, ok := i386NameToDwarf[strings.ToLower(reg.Name)]; ok {
+			dregs[dwarfReg] = reg.Reg
+		}
+	}
+
+	return op.DwarfRegisters{
+		StaticBase: staticBase,
+		Regs:       dregs,
+		ByteOrder:  binary.LittleEndian,
+		PCRegNum:   i386DwarfIPRegNum,
+		SPRegNum:   i386DwarfSPRegNum,
+		BPRegNum:   i386DwarfBPRegNum,
+	}
+}
+
+// AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in
+// PC, SP, and BP registers in the format used by the DWARF expression interpreter.
+func (i *I386) AddrAndStackRegsToDwarfRegisters(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)
+
+	return op.DwarfRegisters{
+		StaticBase: staticBase,
+		Regs:       dregs,
+		ByteOrder:  binary.LittleEndian,
+		PCRegNum:   i386DwarfIPRegNum,
+		SPRegNum:   i386DwarfSPRegNum,
+		BPRegNum:   i386DwarfBPRegNum,
+	}
+}
+
+func (i *I386) DwarfRegisterToString(j int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
+	name, ok := i386DwarfToName[j]
+	if !ok {
+		name = fmt.Sprintf("unknown%d", j)
+	}
+
+	switch n := strings.ToLower(name); n {
+	case "eflags":
+		return name, false, eflagsDescription.Describe(reg.Uint64Val, 32)
+
+	case "tw", "fop":
+		return name, true, fmt.Sprintf("%#04x", reg.Uint64Val)
+
+	default:
+		if reg.Bytes != nil && strings.HasPrefix(name, "xmm") {
+			return name, true, formatSSEReg(reg.Bytes)
+		} else if reg.Bytes != nil && strings.HasPrefix(name, "st(") {
+			return name, true, formatX87Reg(reg.Bytes)
+		} else if reg.Bytes == nil || (reg.Bytes != nil && len(reg.Bytes) <= 8) {
+			return name, false, fmt.Sprintf("%#016x", reg.Uint64Val)
+		} else {
+			return name, false, fmt.Sprintf("%#x", reg.Bytes)
+		}
+	}
+}
+
+// InhibitStepInto returns whether StepBreakpoint can be set at pc.
+// When cgo or pie on 386 linux, compiler will insert more instructions (ex: call __x86.get_pc_thunk.).
+// StepBreakpoint shouldn't be set on __x86.get_pc_thunk and skip it.
+// See comments on stacksplit in $GOROOT/src/cmd/internal/obj/x86/obj6.go for generated instructions details.
+func (i *I386) InhibitStepInto(bi *BinaryInfo, pc uint64) bool {
+	if bi.SymNames != nil && bi.SymNames[pc] != nil &&
+		strings.HasPrefix(bi.SymNames[pc].Name, "__x86.get_pc_thunk.") {
+		return true
+	}
+	return false
+}
diff --git a/pkg/proc/i386_disasm.go b/pkg/proc/i386_disasm.go
new file mode 100644
index 00000000..3f560a2c
--- /dev/null
+++ b/pkg/proc/i386_disasm.go
@@ -0,0 +1,45 @@
+// TODO: disassembler support should be compiled in unconditionally,
+// instead of being decided by the build-target architecture, and be
+// part of the Arch object instead.
+
+package proc
+
+import (
+	"golang.org/x/arch/x86/x86asm"
+)
+
+// 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.
+func (i *I386) AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo) error {
+	return x86AsmDecode(asmInst, mem, regs, memrw, bi, 32)
+}
+
+func (i *I386) Prologues() []opcodeSeq {
+	return prologuesI386
+}
+
+// Possible stacksplit prologues are inserted by stacksplit in
+// $GOROOT/src/cmd/internal/obj/x86/obj6.go.
+// If 386 on linux when pie, the stacksplit prologue beigin with `call __x86.get_pc_thunk.` sometime.
+var prologuesI386 []opcodeSeq
+
+func init() {
+	var i386GetPcIns = opcodeSeq{uint64(x86asm.CALL)}
+	var tinyStacksplit = opcodeSeq{uint64(x86asm.CMP), uint64(x86asm.JBE)}
+	var smallStacksplit = opcodeSeq{uint64(x86asm.LEA), uint64(x86asm.CMP), uint64(x86asm.JBE)}
+	var bigStacksplit = opcodeSeq{uint64(x86asm.MOV), uint64(x86asm.CMP), uint64(x86asm.JE), uint64(x86asm.LEA), uint64(x86asm.SUB), uint64(x86asm.CMP), uint64(x86asm.JBE)}
+	var unixGetG = opcodeSeq{uint64(x86asm.MOV), uint64(x86asm.MOV)}
+
+	prologuesI386 = make([]opcodeSeq, 0, 2*3)
+	for _, getPcIns := range []opcodeSeq{{}, i386GetPcIns} {
+		for _, getG := range []opcodeSeq{unixGetG} { // TODO(chainhelen), need to support other OSs.
+			for _, stacksplit := range []opcodeSeq{tinyStacksplit, smallStacksplit, bigStacksplit} {
+				prologue := make(opcodeSeq, 0, len(getPcIns)+len(getG)+len(stacksplit))
+				prologue = append(prologue, getPcIns...)
+				prologue = append(prologue, getG...)
+				prologue = append(prologue, stacksplit...)
+				prologuesI386 = append(prologuesI386, prologue)
+			}
+		}
+	}
+}
diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go
index 45424b3a..a177973e 100644
--- a/pkg/proc/interface.go
+++ b/pkg/proc/interface.go
@@ -18,16 +18,27 @@ type Process interface {
 	RecordingManipulation
 }
 
-// RecordingManipulation is an interface for manipulating process recordings.
-type RecordingManipulation interface {
-	// Recorded returns true if the current process is a recording and the path
-	// to the trace directory.
-	Recorded() (recorded bool, tracedir string)
+// ProcessInternal holds a set of methods that are not meant to be called by
+// anyone except for an instance of `proc.Target`. These methods are not
+// safe to use by themselves and should never be called directly outside of
+// the `proc` package.
+// This is temporary and in support of an ongoing refactor.
+type ProcessInternal interface {
+	SetCurrentThread(Thread)
 	// Restart restarts the recording from the specified position, or from the
 	// last checkpoint if pos == "".
 	// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
 	// number.
 	Restart(pos string) error
+	Detach(bool) error
+	ContinueOnce() (trapthread Thread, stopReason StopReason, err error)
+}
+
+// RecordingManipulation is an interface for manipulating process recordings.
+type RecordingManipulation interface {
+	// Recorded returns true if the current process is a recording and the path
+	// to the trace directory.
+	Recorded() (recorded bool, tracedir string)
 	// Direction changes execution direction.
 	Direction(Direction) error
 	// When returns current recording position.
@@ -71,7 +82,6 @@ type Info interface {
 	EntryPoint() (uint64, error)
 
 	ThreadInfo
-	GoroutineInfo
 }
 
 // ThreadInfo is an interface for getting information on active threads
@@ -82,22 +92,12 @@ type ThreadInfo interface {
 	CurrentThread() Thread
 }
 
-// GoroutineInfo is an interface for getting information on running goroutines.
-type GoroutineInfo interface {
-	SelectedGoroutine() *G
-	SetSelectedGoroutine(*G)
-}
-
 // ProcessManipulation is an interface for changing the execution state of a process.
 type ProcessManipulation interface {
-	ContinueOnce() (trapthread Thread, err error)
-	SwitchThread(int) error
-	SwitchGoroutine(*G) error
 	RequestManualStop() error
 	// CheckAndClearManualStopRequest returns true the first time it's called
 	// after a call to RequestManualStop.
 	CheckAndClearManualStopRequest() bool
-	Detach(bool) error
 }
 
 // BreakpointManipulation is an interface for managing breakpoints.
diff --git a/pkg/proc/linutil/auxv.go b/pkg/proc/linutil/auxv.go
index 786c3232..a19ea23b 100644
--- a/pkg/proc/linutil/auxv.go
+++ b/pkg/proc/linutil/auxv.go
@@ -6,33 +6,34 @@ import (
 )
 
 const (
-	_AT_NULL_AMD64  = 0
-	_AT_ENTRY_AMD64 = 9
+	_AT_NULL  = 0
+	_AT_ENTRY = 9
 )
 
 // EntryPointFromAuxv searches the elf auxiliary vector for the entry point
 // address.
 // For a description of the auxiliary vector (auxv) format see:
 // System V Application Binary Interface, AMD64 Architecture Processor
-// Supplement, section 3.4.3
-func EntryPointFromAuxvAMD64(auxv []byte) uint64 {
+// Supplement, section 3.4.3.
+// System V Application Binary Interface, Intel386 Architecture Processor
+// Supplement (fourth edition), section 3-28.
+func EntryPointFromAuxv(auxv []byte, ptrSize int) uint64 {
 	rd := bytes.NewBuffer(auxv)
 
 	for {
-		var tag, val uint64
-		err := binary.Read(rd, binary.LittleEndian, &tag)
+		tag, err := readUintRaw(rd, binary.LittleEndian, ptrSize)
 		if err != nil {
 			return 0
 		}
-		err = binary.Read(rd, binary.LittleEndian, &val)
+		val, err := readUintRaw(rd, binary.LittleEndian, ptrSize)
 		if err != nil {
 			return 0
 		}
 
 		switch tag {
-		case _AT_NULL_AMD64:
+		case _AT_NULL:
 			return 0
-		case _AT_ENTRY_AMD64:
+		case _AT_ENTRY:
 			return val
 		}
 	}
diff --git a/pkg/proc/linutil/dynamic.go b/pkg/proc/linutil/dynamic.go
index 1f70ff05..73fd34dc 100644
--- a/pkg/proc/linutil/dynamic.go
+++ b/pkg/proc/linutil/dynamic.go
@@ -5,6 +5,7 @@ import (
 	"encoding/binary"
 	"errors"
 	"fmt"
+	"io"
 
 	"github.com/go-delve/delve/pkg/proc"
 )
@@ -21,6 +22,25 @@ const (
 	_DT_DEBUG = 21 // DT_DEBUG as defined by SysV ABI specification
 )
 
+// readUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader.
+func readUintRaw(reader io.Reader, order binary.ByteOrder, ptrSize int) (uint64, error) {
+	switch ptrSize {
+	case 4:
+		var n uint32
+		if err := binary.Read(reader, order, &n); err != nil {
+			return 0, err
+		}
+		return uint64(n), nil
+	case 8:
+		var n uint64
+		if err := binary.Read(reader, order, &n); err != nil {
+			return 0, err
+		}
+		return n, nil
+	}
+	return 0, fmt.Errorf("not supprted ptr size %d", ptrSize)
+}
+
 // dynamicSearchDebug searches for the DT_DEBUG entry in the .dynamic section
 func dynamicSearchDebug(p proc.Process) (uint64, error) {
 	bi := p.BinInfo()
@@ -36,10 +56,10 @@ func dynamicSearchDebug(p proc.Process) (uint64, error) {
 
 	for {
 		var tag, val uint64
-		if err := binary.Read(rd, binary.LittleEndian, &tag); err != nil {
+		if tag, err = readUintRaw(rd, binary.LittleEndian, p.BinInfo().Arch.PtrSize()); err != nil {
 			return 0, err
 		}
-		if err := binary.Read(rd, binary.LittleEndian, &val); err != nil {
+		if val, err = readUintRaw(rd, binary.LittleEndian, p.BinInfo().Arch.PtrSize()); err != nil {
 			return 0, err
 		}
 		switch tag {
@@ -51,24 +71,13 @@ func dynamicSearchDebug(p proc.Process) (uint64, error) {
 	}
 }
 
-// hard-coded offsets of the fields of the r_debug and link_map structs, see
-// /usr/include/elf/link.h for a full description of those structs.
-const (
-	_R_DEBUG_MAP_OFFSET   = 8
-	_LINK_MAP_ADDR_OFFSET = 0  // offset of link_map.l_addr field (base address shared object is loaded at)
-	_LINK_MAP_NAME_OFFSET = 8  // offset of link_map.l_name field (absolute file name object was found in)
-	_LINK_MAP_LD          = 16 // offset of link_map.l_ld field (dynamic section of the shared object)
-	_LINK_MAP_NEXT        = 24 // offset of link_map.l_next field
-	_LINK_MAP_PREV        = 32 // offset of link_map.l_prev field
-)
-
 func readPtr(p proc.Process, addr uint64) (uint64, error) {
 	ptrbuf := make([]byte, p.BinInfo().Arch.PtrSize())
 	_, err := p.CurrentThread().ReadMemory(ptrbuf, uintptr(addr))
 	if err != nil {
 		return 0, err
 	}
-	return binary.LittleEndian.Uint64(ptrbuf), nil
+	return readUintRaw(bytes.NewReader(ptrbuf), binary.LittleEndian, p.BinInfo().Arch.PtrSize())
 }
 
 type linkMap struct {
@@ -145,7 +154,11 @@ func ElfUpdateSharedObjects(p proc.Process) error {
 		return nil
 	}
 
-	r_map, err := readPtr(p, debugAddr+_R_DEBUG_MAP_OFFSET)
+	// Offsets of the fields of the r_debug and link_map structs,
+	// see /usr/include/elf/link.h for a full description of those structs.
+	debugMapOffset := uint64(p.BinInfo().Arch.PtrSize())
+
+	r_map, err := readPtr(p, debugAddr+debugMapOffset)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/proc/linutil/regs_i386_arch.go b/pkg/proc/linutil/regs_i386_arch.go
new file mode 100644
index 00000000..6ec04d23
--- /dev/null
+++ b/pkg/proc/linutil/regs_i386_arch.go
@@ -0,0 +1,286 @@
+package linutil
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"github.com/go-delve/delve/pkg/proc"
+	"golang.org/x/arch/x86/x86asm"
+)
+
+// I386Registers implements the proc.Registers interface for the native/linux
+// backend and core/linux backends, on I386.
+type I386Registers struct {
+	Regs     *I386PtraceRegs
+	Fpregs   []proc.Register
+	Fpregset *I386Xstate
+	Tls      uint64
+}
+
+// I386PtraceRegs is the struct used by the linux kernel to return the
+// general purpose registers for I386 CPUs.
+type I386PtraceRegs struct {
+	Ebx      int32
+	Ecx      int32
+	Edx      int32
+	Esi      int32
+	Edi      int32
+	Ebp      int32
+	Eax      int32
+	Xds      int32
+	Xes      int32
+	Xfs      int32
+	Xgs      int32
+	Orig_eax int32
+	Eip      int32
+	Xcs      int32
+	Eflags   int32
+	Esp      int32
+	Xss      int32
+}
+
+// Slice returns the registers as a list of (name, value) pairs.
+func (r *I386Registers) Slice(floatingPoint bool) []proc.Register {
+	var regs = []struct {
+		k string
+		v int32
+	}{
+		{"Ebx", r.Regs.Ebx},
+		{"Ecx", r.Regs.Ecx},
+		{"Edx", r.Regs.Edx},
+		{"Esi", r.Regs.Esi},
+		{"Edi", r.Regs.Edi},
+		{"Ebp", r.Regs.Ebp},
+		{"Eax", r.Regs.Eax},
+		{"Xds", r.Regs.Xds},
+		{"Xes", r.Regs.Xes},
+		{"Xfs", r.Regs.Xfs},
+		{"Xgs", r.Regs.Xgs},
+		{"Orig_eax", r.Regs.Orig_eax},
+		{"Eip", r.Regs.Eip},
+		{"Xcs", r.Regs.Xcs},
+		{"Eflags", r.Regs.Eflags},
+		{"Esp", r.Regs.Esp},
+		{"Xss", r.Regs.Xss},
+	}
+	out := make([]proc.Register, 0, len(regs)+len(r.Fpregs))
+	for _, reg := range regs {
+		out = proc.AppendUint64Register(out, reg.k, uint64(uint32(reg.v)))
+	}
+	if floatingPoint {
+		out = append(out, r.Fpregs...)
+	}
+	return out
+}
+
+// PC returns the value of EIP register.
+func (r *I386Registers) PC() uint64 {
+	return uint64(uint32(r.Regs.Eip))
+}
+
+// SP returns the value of ESP register.
+func (r *I386Registers) SP() uint64 {
+	return uint64(uint32(r.Regs.Esp))
+}
+
+func (r *I386Registers) BP() uint64 {
+	return uint64(uint32(r.Regs.Ebp))
+}
+
+// CX returns the value of ECX register.
+func (r *I386Registers) CX() uint64 {
+	return uint64(uint32(r.Regs.Ecx))
+}
+
+// TLS returns the address of the thread local storage memory segment.
+func (r I386Registers) TLS() uint64 {
+	return r.Tls
+}
+
+// GAddr returns the address of the G variable if it is known, 0 and false
+// otherwise.
+func (r *I386Registers) GAddr() (uint64, bool) {
+	return 0, false
+}
+
+// Get returns the value of the n-th register (in x86asm order).
+func (r *I386Registers) Get(n int) (uint64, error) {
+	reg := x86asm.Reg(n)
+	const (
+		mask8  = 0x000000ff
+		mask16 = 0x0000ffff
+	)
+
+	switch reg {
+	// 8-bit
+	case x86asm.AL:
+		return uint64(r.Regs.Eax) & mask8, nil
+	case x86asm.CL:
+		return uint64(r.Regs.Ecx) & mask8, nil
+	case x86asm.DL:
+		return uint64(r.Regs.Edx) & mask8, nil
+	case x86asm.BL:
+		return uint64(r.Regs.Ebx) & mask8, nil
+	case x86asm.AH:
+		return (uint64(r.Regs.Eax) >> 8) & mask8, nil
+	case x86asm.CH:
+		return (uint64(r.Regs.Ecx) >> 8) & mask8, nil
+	case x86asm.DH:
+		return (uint64(r.Regs.Edx) >> 8) & mask8, nil
+	case x86asm.BH:
+		return (uint64(r.Regs.Ebx) >> 8) & mask8, nil
+	case x86asm.SPB:
+		return uint64(r.Regs.Esp) & mask8, nil
+	case x86asm.BPB:
+		return uint64(r.Regs.Ebp) & mask8, nil
+	case x86asm.SIB:
+		return uint64(r.Regs.Esi) & mask8, nil
+	case x86asm.DIB:
+		return uint64(r.Regs.Edi) & mask8, nil
+
+	// 16-bit
+	case x86asm.AX:
+		return uint64(r.Regs.Eax) & mask16, nil
+	case x86asm.CX:
+		return uint64(r.Regs.Ecx) & mask16, nil
+	case x86asm.DX:
+		return uint64(r.Regs.Edx) & mask16, nil
+	case x86asm.BX:
+		return uint64(r.Regs.Ebx) & mask16, nil
+	case x86asm.SP:
+		return uint64(r.Regs.Esp) & mask16, nil
+	case x86asm.BP:
+		return uint64(r.Regs.Ebp) & mask16, nil
+	case x86asm.SI:
+		return uint64(r.Regs.Esi) & mask16, nil
+	case x86asm.DI:
+		return uint64(r.Regs.Edi) & mask16, nil
+
+	// 32-bit
+	case x86asm.EAX:
+		return uint64(uint32(r.Regs.Eax)), nil
+	case x86asm.ECX:
+		return uint64(uint32(r.Regs.Ecx)), nil
+	case x86asm.EDX:
+		return uint64(uint32(r.Regs.Edx)), nil
+	case x86asm.EBX:
+		return uint64(uint32(r.Regs.Ebx)), nil
+	case x86asm.ESP:
+		return uint64(uint32(r.Regs.Esp)), nil
+	case x86asm.EBP:
+		return uint64(uint32(r.Regs.Ebp)), nil
+	case x86asm.ESI:
+		return uint64(uint32(r.Regs.Esi)), nil
+	case x86asm.EDI:
+		return uint64(uint32(r.Regs.Edi)), nil
+	}
+	return 0, proc.ErrUnknownRegister
+}
+
+// Copy returns a copy of these registers that is guarenteed not to change.
+func (r *I386Registers) Copy() proc.Registers {
+	var rr I386Registers
+	rr.Regs = &I386PtraceRegs{}
+	rr.Fpregset = &I386Xstate{}
+	*(rr.Regs) = *(r.Regs)
+	if r.Fpregset != nil {
+		*(rr.Fpregset) = *(r.Fpregset)
+	}
+	if r.Fpregs != nil {
+		rr.Fpregs = make([]proc.Register, len(r.Fpregs))
+		copy(rr.Fpregs, r.Fpregs)
+	}
+	return &rr
+}
+
+// I386PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
+type I386PtraceFpRegs struct {
+	Cwd      uint16
+	Swd      uint16
+	Ftw      uint16
+	Fop      uint16
+	Rip      uint64
+	Rdp      uint64
+	Mxcsr    uint32
+	MxcrMask uint32
+	StSpace  [32]uint32
+	XmmSpace [256]byte
+	Padding  [24]uint32
+}
+
+// I386Xstate represents amd64 XSAVE area. See Section 13.1 (and
+// following) of Intel® 64 and IA-32 Architectures Software Developer’s
+// Manual, Volume 1: Basic Architecture.
+type I386Xstate struct {
+	I386PtraceFpRegs
+	Xsave    []byte // raw xsave area
+	AvxState bool   // contains AVX state
+	YmmSpace [256]byte
+}
+
+// Decode decodes an XSAVE area to a list of name/value pairs of registers.
+func (xsave *I386Xstate) Decode() (regs []proc.Register) {
+	// x87 registers
+	regs = proc.AppendUint64Register(regs, "CW", uint64(xsave.Cwd))
+	regs = proc.AppendUint64Register(regs, "SW", uint64(xsave.Swd))
+	regs = proc.AppendUint64Register(regs, "TW", uint64(xsave.Ftw))
+	regs = proc.AppendUint64Register(regs, "FOP", uint64(xsave.Fop))
+	regs = proc.AppendUint64Register(regs, "FIP", uint64(xsave.Rip))
+	regs = proc.AppendUint64Register(regs, "FDP", uint64(xsave.Rdp))
+
+	for i := 0; i < len(xsave.StSpace); i += 4 {
+		var buf bytes.Buffer
+		binary.Write(&buf, binary.LittleEndian, uint64(xsave.StSpace[i+1])<<32|uint64(xsave.StSpace[i]))
+		binary.Write(&buf, binary.LittleEndian, uint16(xsave.StSpace[i+2]))
+		regs = proc.AppendBytesRegister(regs, fmt.Sprintf("ST(%d)", i/4), buf.Bytes())
+	}
+
+	// SSE registers
+	regs = proc.AppendUint64Register(regs, "MXCSR", uint64(xsave.Mxcsr))
+	regs = proc.AppendUint64Register(regs, "MXCSR_MASK", uint64(xsave.MxcrMask))
+
+	for i := 0; i < len(xsave.XmmSpace); i += 16 {
+		regs = proc.AppendBytesRegister(regs, fmt.Sprintf("XMM%d", i/16), xsave.XmmSpace[i:i+16])
+		if xsave.AvxState {
+			regs = proc.AppendBytesRegister(regs, fmt.Sprintf("YMM%d", i/16), xsave.YmmSpace[i:i+16])
+		}
+	}
+
+	return
+}
+
+// LinuxX86XstateRead reads a byte array containing an XSAVE area into regset.
+// If readLegacy is true regset.PtraceFpRegs will be filled with the
+// contents of the legacy region of the XSAVE area.
+// See Section 13.1 (and following) of Intel® 64 and IA-32 Architectures
+// Software Developer’s Manual, Volume 1: Basic Architecture.
+func I386XstateRead(xstateargs []byte, readLegacy bool, regset *I386Xstate) error {
+	if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= len(xstateargs) {
+		return nil
+	}
+	if readLegacy {
+		rdr := bytes.NewReader(xstateargs[:_XSAVE_HEADER_START])
+		if err := binary.Read(rdr, binary.LittleEndian, &regset.I386PtraceFpRegs); err != nil {
+			return err
+		}
+	}
+	xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
+	xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
+	xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
+
+	if xcomp_bv&(1<<63) != 0 {
+		// compact format not supported
+		return nil
+	}
+
+	if xstate_bv&(1<<2) == 0 {
+		// AVX state not present
+		return nil
+	}
+
+	avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:]
+	regset.AvxState = true
+	copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
+
+	return nil
+}
diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go
index 5bff11b6..8089734f 100644
--- a/pkg/proc/native/proc.go
+++ b/pkg/proc/native/proc.go
@@ -1,7 +1,6 @@
 package native
 
 import (
-	"fmt"
 	"go/ast"
 	"runtime"
 	"sync"
@@ -26,10 +25,6 @@ type Process struct {
 	// Active thread
 	currentThread *Thread
 
-	// Goroutine that will be used by default to set breakpoint, eval variables, etc...
-	// Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
-	selectedGoroutine *proc.G
-
 	os                  *OSProcessDetails
 	firstStart          bool
 	stopMu              sync.Mutex
@@ -42,6 +37,8 @@ type Process struct {
 	exited, detached bool
 }
 
+var _ proc.ProcessInternal = &Process{}
+
 // New returns an initialized Process struct. Before returning,
 // it will also launch a goroutine in order to handle ptrace(2)
 // functions. For more information, see the documentation on
@@ -151,12 +148,6 @@ func (dbp *Process) Pid() int {
 	return dbp.pid
 }
 
-// SelectedGoroutine returns the current selected,
-// active goroutine.
-func (dbp *Process) SelectedGoroutine() *proc.G {
-	return dbp.selectedGoroutine
-}
-
 // ThreadList returns a list of threads in the process.
 func (dbp *Process) ThreadList() []proc.Thread {
 	r := make([]proc.Thread, 0, len(dbp.threads))
@@ -177,6 +168,11 @@ func (dbp *Process) CurrentThread() proc.Thread {
 	return dbp.currentThread
 }
 
+// SetCurrentThread is used internally by proc.Target to change the current thread.
+func (p *Process) SetCurrentThread(th proc.Thread) {
+	p.currentThread = th.(*Thread)
+}
+
 // Breakpoints returns a list of breakpoints currently set.
 func (dbp *Process) Breakpoints() *proc.BreakpointMap {
 	return &dbp.breakpoints
@@ -237,13 +233,13 @@ func (dbp *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
 
 // ContinueOnce will continue the target until it stops.
 // This could be the result of a breakpoint or signal.
-func (dbp *Process) ContinueOnce() (proc.Thread, error) {
+func (dbp *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
 	if dbp.exited {
-		return nil, &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()}
 	}
 
 	if err := dbp.resume(); err != nil {
-		return nil, err
+		return nil, proc.StopUnknown, err
 	}
 
 	for _, th := range dbp.threads {
@@ -257,42 +253,12 @@ func (dbp *Process) ContinueOnce() (proc.Thread, error) {
 
 	trapthread, err := dbp.trapWait(-1)
 	if err != nil {
-		return nil, err
+		return nil, proc.StopUnknown, err
 	}
 	if err := dbp.stop(trapthread); err != nil {
-		return nil, err
-	}
-	return trapthread, err
-}
-
-// SwitchThread changes from current thread to the thread specified by `tid`.
-func (dbp *Process) SwitchThread(tid int) error {
-	if dbp.exited {
-		return &proc.ErrProcessExited{Pid: dbp.Pid()}
+		return nil, proc.StopUnknown, err
 	}
-	if th, ok := dbp.threads[tid]; ok {
-		dbp.currentThread = th
-		dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
-		return nil
-	}
-	return fmt.Errorf("thread %d does not exist", tid)
-}
-
-// SwitchGoroutine changes from current thread to the thread
-// running the specified goroutine.
-func (dbp *Process) SwitchGoroutine(g *proc.G) error {
-	if dbp.exited {
-		return &proc.ErrProcessExited{Pid: dbp.Pid()}
-	}
-	if g == nil {
-		// user specified -1 and selectedGoroutine is nil
-		return nil
-	}
-	if g.Thread != nil {
-		return dbp.SwitchThread(g.Thread.ThreadID())
-	}
-	dbp.selectedGoroutine = g
-	return nil
+	return trapthread, proc.StopUnknown, err
 }
 
 // FindBreakpoint finds the breakpoint for the given pc.
@@ -312,21 +278,23 @@ func (dbp *Process) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakpoint,
 
 // initialize will ensure that all relevant information is loaded
 // so the process is ready to be debugged.
-func (dbp *Process) initialize(path string, debugInfoDirs []string) error {
+func (dbp *Process) initialize(path string, debugInfoDirs []string) (*proc.Target, error) {
 	if err := initialize(dbp); err != nil {
-		return err
+		return nil, err
 	}
 	if err := dbp.updateThreadList(); err != nil {
-		return err
+		return nil, err
 	}
-	return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint)
-}
-
-// SetSelectedGoroutine will set internally the goroutine that should be
-// the default for any command executed, the goroutine being actively
-// followed.
-func (dbp *Process) SetSelectedGoroutine(g *proc.G) {
-	dbp.selectedGoroutine = g
+	stopReason := proc.StopLaunched
+	if !dbp.childProcess {
+		stopReason = proc.StopAttached
+	}
+	return proc.NewTarget(dbp, proc.NewTargetConfig{
+		Path:                path,
+		DebugInfoDirs:       debugInfoDirs,
+		WriteBreakpoint:     dbp.writeBreakpoint,
+		DisableAsyncPreempt: runtime.GOOS == "windows",
+		StopReason:          stopReason})
 }
 
 // ClearInternalBreakpoints will clear all non-user set breakpoints. These
diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go
index 41a1eaca..bbc8e1af 100644
--- a/pkg/proc/native/proc_darwin.go
+++ b/pkg/proc/native/proc_darwin.go
@@ -117,16 +117,14 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
 	}
 
 	dbp.os.initialized = true
-	err = dbp.initialize(argv0Go, []string{})
-	if err != nil {
-		return nil, err
-	}
+	dbp.currentThread = trapthread
 
-	if err := dbp.SwitchThread(trapthread.ID); err != nil {
+	tgt, err := dbp.initialize(argv0Go, []string{})
+	if err != nil {
 		return nil, err
 	}
 
-	return proc.NewTarget(dbp, false), err
+	return tgt, err
 }
 
 // Attach to an existing process with the given PID.
@@ -153,12 +151,12 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
 		return nil, err
 	}
 
-	err = dbp.initialize("", []string{})
+	tgt, err := dbp.initialize("", []string{})
 	if err != nil {
 		dbp.Detach(false)
 		return nil, err
 	}
-	return proc.NewTarget(dbp, false), nil
+	return tgt, nil
 }
 
 // Kill kills the process.
@@ -269,7 +267,7 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
 	dbp.threads[port] = thread
 	thread.os.threadAct = C.thread_act_t(port)
 	if dbp.currentThread == nil {
-		dbp.SwitchThread(thread.ID)
+		dbp.currentThread = thread
 	}
 	return thread, nil
 }
diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go
index f24d6004..b3ae6e85 100644
--- a/pkg/proc/native/proc_freebsd.go
+++ b/pkg/proc/native/proc_freebsd.go
@@ -84,10 +84,11 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
 	if err != nil {
 		return nil, fmt.Errorf("waiting for target execve failed: %s", err)
 	}
-	if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
+	tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
+	if err != nil {
 		return nil, err
 	}
-	return proc.NewTarget(dbp, false), nil
+	return tgt, nil
 }
 
 // Attach to an existing process with the given PID. Once attached, if
@@ -106,12 +107,12 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
 		return nil, err
 	}
 
-	err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
+	tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
 	if err != nil {
 		dbp.Detach(false)
 		return nil, err
 	}
-	return proc.NewTarget(dbp, false), nil
+	return tgt, nil
 }
 
 func initialize(dbp *Process) error {
@@ -165,7 +166,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
 	}
 
 	if dbp.currentThread == nil {
-		dbp.SwitchThread(tid)
+		dbp.currentThread = dbp.threads[tid]
 	}
 
 	return dbp.threads[tid], nil
diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go
index 75dece1e..61368b96 100644
--- a/pkg/proc/native/proc_linux.go
+++ b/pkg/proc/native/proc_linux.go
@@ -90,10 +90,11 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
 	if err != nil {
 		return nil, fmt.Errorf("waiting for target execve failed: %s", err)
 	}
-	if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
+	tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
+	if err != nil {
 		return nil, err
 	}
-	return proc.NewTarget(dbp, false), nil
+	return tgt, nil
 }
 
 // Attach to an existing process with the given PID. Once attached, if
@@ -112,7 +113,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
 		return nil, err
 	}
 
-	err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
+	tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
 	if err != nil {
 		dbp.Detach(false)
 		return nil, err
@@ -124,7 +125,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
 	if err != nil {
 		return nil, err
 	}
-	return proc.NewTarget(dbp, false), nil
+	return tgt, nil
 }
 
 func initialize(dbp *Process) error {
@@ -222,7 +223,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
 		os:  new(OSSpecificDetails),
 	}
 	if dbp.currentThread == nil {
-		dbp.SwitchThread(tid)
+		dbp.currentThread = dbp.threads[tid]
 	}
 	return dbp.threads[tid], nil
 }
@@ -545,7 +546,7 @@ func (dbp *Process) EntryPoint() (uint64, error) {
 		return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
 	}
 
-	return linutil.EntryPointFromAuxvAMD64(auxvbuf), nil
+	return linutil.EntryPointFromAuxv(auxvbuf, dbp.bi.Arch.PtrSize()), nil
 }
 
 func killProcess(pid int) error {
diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go
index 63a2b969..4e3c56ca 100644
--- a/pkg/proc/native/proc_windows.go
+++ b/pkg/proc/native/proc_windows.go
@@ -78,11 +78,12 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
 	dbp.pid = p.Pid
 	dbp.childProcess = true
 
-	if err = dbp.initialize(argv0Go, []string{}); err != nil {
+	tgt, err := dbp.initialize(argv0Go, []string{})
+	if err != nil {
 		dbp.Detach(true)
 		return nil, err
 	}
-	return proc.NewTarget(dbp, true), nil
+	return tgt, nil
 }
 
 func initialize(dbp *Process) error {
@@ -167,11 +168,12 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
 	if err != nil {
 		return nil, err
 	}
-	if err = dbp.initialize(exepath, []string{}); err != nil {
+	tgt, err := dbp.initialize(exepath, []string{})
+	if err != nil {
 		dbp.Detach(true)
 		return nil, err
 	}
-	return proc.NewTarget(dbp, true), nil
+	return tgt, nil
 }
 
 // kill kills the process.
@@ -223,7 +225,7 @@ func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, susp
 	thread.os.hThread = hThread
 	dbp.threads[threadID] = thread
 	if dbp.currentThread == nil {
-		dbp.SwitchThread(thread.ID)
+		dbp.currentThread = dbp.threads[threadID]
 	}
 	if suspendNewThreads {
 		_, err := _SuspendThread(thread.os.hThread)
diff --git a/pkg/proc/native/ptrace_linux.go b/pkg/proc/native/ptrace_linux.go
index 849516d8..ab4f5372 100644
--- a/pkg/proc/native/ptrace_linux.go
+++ b/pkg/proc/native/ptrace_linux.go
@@ -49,31 +49,3 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
 	}
 	return val, nil
 }
-
-// ProcessVmRead calls process_vm_readv
-func ProcessVmRead(tid int, addr uintptr, data []byte) (int, error) {
-	len_iov := uint64(len(data))
-	local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
-	remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
-	p_local := uintptr(unsafe.Pointer(&local_iov))
-	p_remote := uintptr(unsafe.Pointer(&remote_iov))
-	n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0)
-	if err != syscall.Errno(0) {
-		return 0, err
-	}
-	return int(n), nil
-}
-
-// ProcessVmWrite calls process_vm_writev
-func ProcessVmWrite(tid int, addr uintptr, data []byte) (int, error) {
-	len_iov := uint64(len(data))
-	local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
-	remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
-	p_local := uintptr(unsafe.Pointer(&local_iov))
-	p_remote := uintptr(unsafe.Pointer(&remote_iov))
-	n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0)
-	if err != syscall.Errno(0) {
-		return 0, err
-	}
-	return int(n), nil
-}
diff --git a/pkg/proc/native/ptrace_linux_386.go b/pkg/proc/native/ptrace_linux_386.go
new file mode 100644
index 00000000..269f95e2
--- /dev/null
+++ b/pkg/proc/native/ptrace_linux_386.go
@@ -0,0 +1,93 @@
+package native
+
+import "C"
+
+import (
+	"fmt"
+	"syscall"
+	"unsafe"
+
+	sys "golang.org/x/sys/unix"
+
+	"github.com/go-delve/delve/pkg/proc/linutil"
+)
+
+// PtraceGetRegset returns floating point registers of the specified thread
+// using PTRACE.
+// See i386_linux_fetch_inferior_registers in gdb/i386-linux-nat.c.html
+// and i386_supply_xsave in gdb/i386-tdep.c.html
+// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1: Basic Architecture
+func PtraceGetRegset(tid int) (regset linutil.I386Xstate, err error) {
+	_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.I386PtraceFpRegs)), 0, 0)
+	if err == syscall.Errno(0) || err == syscall.ENODEV {
+		// ignore ENODEV, it just means this CPU doesn't have X87 registers (??)
+		err = nil
+	}
+
+	var xstateargs [_X86_XSTATE_MAX_SIZE]byte
+	iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
+	_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
+	if err != syscall.Errno(0) {
+		if err == syscall.ENODEV || err == syscall.EIO {
+			// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/go-delve/delve/issues/1022
+			// also ignore EIO, it means that we are running on an old kernel (pre 2.6.34) and PTRACE_GETREGSET is not implemented
+			err = nil
+		}
+		return
+	} else {
+		err = nil
+	}
+
+	regset.Xsave = xstateargs[:iov.Len]
+	err = linutil.I386XstateRead(regset.Xsave, false, &regset)
+	return
+}
+
+// PtraceGetTls return the addr of tls by PTRACE_GET_THREAD_AREA for specify thread.
+// See http://man7.org/linux/man-pages/man2/ptrace.2.html for detail about PTRACE_GET_THREAD_AREA.
+// struct user_desc at https://golang.org/src/runtime/sys_linux_386.s
+// type UserDesc struct {
+// 	 EntryNumber uint32
+//	 BaseAddr    uint32
+//	 Limit       uint32
+//	 Flag        uint32
+// }
+func PtraceGetTls(gs int32, tid int) (uint32, error) {
+	ud := [4]uint32{}
+
+	// Gs usually is 0x33
+	_, _, err := syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GET_THREAD_AREA, uintptr(tid), uintptr(gs>>3), uintptr(unsafe.Pointer(&ud)), 0, 0)
+	if err == syscall.ENODEV || err == syscall.EIO {
+		return 0, fmt.Errorf("%s", err)
+	}
+
+	return uint32(ud[1]), nil
+}
+
+// ProcessVmRead calls process_vm_readv
+func ProcessVmRead(tid int, addr uintptr, data []byte) (int, error) {
+	len_iov := uint32(len(data))
+	local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
+	remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
+	p_local := uintptr(unsafe.Pointer(&local_iov))
+	p_remote := uintptr(unsafe.Pointer(&remote_iov))
+	n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0)
+	if err != syscall.Errno(0) {
+		return 0, err
+	}
+	return int(n), nil
+}
+
+// ProcessVmWrite calls process_vm_writev
+func ProcessVmWrite(tid int, addr uintptr, data []byte) (int, error) {
+	len_iov := uint32(len(data))
+	local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
+	remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
+	p_local := uintptr(unsafe.Pointer(&local_iov))
+	p_remote := uintptr(unsafe.Pointer(&remote_iov))
+	n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0)
+	if err != syscall.Errno(0) {
+		return 0, err
+	}
+	return int(n), nil
+}
diff --git a/pkg/proc/native/ptrace_linux_64bit.go b/pkg/proc/native/ptrace_linux_64bit.go
new file mode 100644
index 00000000..9206f046
--- /dev/null
+++ b/pkg/proc/native/ptrace_linux_64bit.go
@@ -0,0 +1,38 @@
+// +build linux,amd64 linux,arm64
+
+package native
+
+import (
+	"syscall"
+	"unsafe"
+
+	sys "golang.org/x/sys/unix"
+)
+
+// ProcessVmRead calls process_vm_readv
+func ProcessVmRead(tid int, addr uintptr, data []byte) (int, error) {
+	len_iov := uint64(len(data))
+	local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
+	remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
+	p_local := uintptr(unsafe.Pointer(&local_iov))
+	p_remote := uintptr(unsafe.Pointer(&remote_iov))
+	n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0)
+	if err != syscall.Errno(0) {
+		return 0, err
+	}
+	return int(n), nil
+}
+
+// ProcessVmWrite calls process_vm_writev
+func ProcessVmWrite(tid int, addr uintptr, data []byte) (int, error) {
+	len_iov := uint64(len(data))
+	local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
+	remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
+	p_local := uintptr(unsafe.Pointer(&local_iov))
+	p_remote := uintptr(unsafe.Pointer(&remote_iov))
+	n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0)
+	if err != syscall.Errno(0) {
+		return 0, err
+	}
+	return int(n), nil
+}
diff --git a/pkg/proc/native/ptrace_linux_amd64.go b/pkg/proc/native/ptrace_linux_amd64.go
index 089410fa..5532c76f 100644
--- a/pkg/proc/native/ptrace_linux_amd64.go
+++ b/pkg/proc/native/ptrace_linux_amd64.go
@@ -38,4 +38,4 @@ func PtraceGetRegset(tid int) (regset linutil.AMD64Xstate, err error) {
 	regset.Xsave = xstateargs[:iov.Len]
 	err = linutil.AMD64XstateRead(regset.Xsave, false, &regset)
 	return
-}
+}
\ No newline at end of file
diff --git a/pkg/proc/native/register_linux_386.go b/pkg/proc/native/register_linux_386.go
new file mode 100644
index 00000000..9eb7d162
--- /dev/null
+++ b/pkg/proc/native/register_linux_386.go
@@ -0,0 +1,91 @@
+package native
+
+import (
+	"fmt"
+
+	sys "golang.org/x/sys/unix"
+
+	"github.com/go-delve/delve/pkg/proc"
+	"github.com/go-delve/delve/pkg/proc/linutil"
+)
+
+// SetPC sets EIP to the value specified by 'pc'.
+func (thread *Thread) SetPC(pc uint64) error {
+	ir, err := registers(thread, false)
+	if err != nil {
+		return err
+	}
+	r := ir.(*linutil.I386Registers)
+	r.Regs.Eip = int32(pc)
+	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
+	return err
+}
+
+// SetSP sets ESP to the value specified by 'sp'
+func (thread *Thread) SetSP(sp uint64) (err error) {
+	var ir proc.Registers
+	ir, err = registers(thread, false)
+	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 *Thread) SetDX(dx uint64) (err error) {
+	var ir proc.Registers
+	ir, err = registers(thread, false)
+	if err != nil {
+		return err
+	}
+	r := ir.(*linutil.I386Registers)
+	r.Regs.Edx = int32(dx)
+	thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
+	return
+}
+
+func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
+	var (
+		regs linutil.I386PtraceRegs
+		err  error
+	)
+	thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, (*sys.PtraceRegs)(&regs)) })
+	if err != nil {
+		return nil, err
+	}
+	r := &linutil.I386Registers{&regs, nil, nil, 0}
+	if floatingPoint {
+		var fpregset linutil.I386Xstate
+		r.Fpregs, fpregset, err = thread.fpRegisters()
+		r.Fpregset = &fpregset
+		if err != nil {
+			return nil, err
+		}
+	}
+	thread.dbp.execPtraceFunc(func() {
+		tls, _ := PtraceGetTls(regs.Xgs, thread.ThreadID())
+		r.Tls = uint64(tls)
+	})
+	return r, nil
+}
+
+const (
+	_X86_XSTATE_MAX_SIZE = 2688
+	_NT_X86_XSTATE       = 0x202
+
+	_XSAVE_HEADER_START          = 512
+	_XSAVE_HEADER_LEN            = 64
+	_XSAVE_EXTENDED_REGION_START = 576
+	_XSAVE_SSE_REGION_LEN        = 416
+)
+
+func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs linutil.I386Xstate, err error) {
+	thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
+	regs = fpregs.Decode()
+	if err != nil {
+		err = fmt.Errorf("could not get floating point registers: %v", err.Error())
+	}
+	return
+}
diff --git a/pkg/proc/native/support_sentinel.go b/pkg/proc/native/support_sentinel.go
index 826a021f..8dcdb5a8 100644
--- a/pkg/proc/native/support_sentinel.go
+++ b/pkg/proc/native/support_sentinel.go
@@ -1,5 +1,5 @@
 // This file is used to detect build on unsupported GOOS/GOARCH combinations.
 
-//+build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64 darwin,!amd64 windows,!amd64 freebsd,!amd64
+//+build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64,!386 darwin,!amd64 windows,!amd64 freebsd,!amd64
 
 package your_operating_system_and_architecture_combination_is_not_supported_by_delve
diff --git a/pkg/proc/native/threads_linux_386.go b/pkg/proc/native/threads_linux_386.go
new file mode 100644
index 00000000..02e2972f
--- /dev/null
+++ b/pkg/proc/native/threads_linux_386.go
@@ -0,0 +1,10 @@
+package native
+
+import (
+	"fmt"
+	"github.com/go-delve/delve/pkg/proc"
+)
+
+func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
+	return fmt.Errorf("restore regs not supported on i386")
+}
diff --git a/pkg/proc/pe.go b/pkg/proc/pe.go
new file mode 100644
index 00000000..21a9a27a
--- /dev/null
+++ b/pkg/proc/pe.go
@@ -0,0 +1,66 @@
+package proc
+
+import (
+	"fmt"
+)
+
+const (
+	IMAGE_FILE_MACHINE_UNKNOWN   = 0x0
+	IMAGE_FILE_MACHINE_AM33      = 0x1d3
+	IMAGE_FILE_MACHINE_AMD64     = 0x8664
+	IMAGE_FILE_MACHINE_ARM       = 0x1c0
+	IMAGE_FILE_MACHINE_ARMNT     = 0x1c4
+	IMAGE_FILE_MACHINE_ARM64     = 0xaa64
+	IMAGE_FILE_MACHINE_EBC       = 0xebc
+	IMAGE_FILE_MACHINE_I386      = 0x14c
+	IMAGE_FILE_MACHINE_IA64      = 0x200
+	IMAGE_FILE_MACHINE_M32R      = 0x9041
+	IMAGE_FILE_MACHINE_MIPS16    = 0x266
+	IMAGE_FILE_MACHINE_MIPSFPU   = 0x366
+	IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466
+	IMAGE_FILE_MACHINE_POWERPC   = 0x1f0
+	IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1
+	IMAGE_FILE_MACHINE_R4000     = 0x166
+	IMAGE_FILE_MACHINE_SH3       = 0x1a2
+	IMAGE_FILE_MACHINE_SH3DSP    = 0x1a3
+	IMAGE_FILE_MACHINE_SH4       = 0x1a6
+	IMAGE_FILE_MACHINE_SH5       = 0x1a8
+	IMAGE_FILE_MACHINE_THUMB     = 0x1c2
+	IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169
+)
+
+type PEMachine uint16
+
+// PEMachineString map pe machine to name, See $GOROOT/src/debug/pe/pe.go for detail
+var PEMachineString = map[uint16]string{
+	IMAGE_FILE_MACHINE_UNKNOWN:   "unknown",
+	IMAGE_FILE_MACHINE_AM33:      "am33",
+	IMAGE_FILE_MACHINE_AMD64:     "amd64",
+	IMAGE_FILE_MACHINE_ARM:       "arm",
+	IMAGE_FILE_MACHINE_ARMNT:     "armnt",
+	IMAGE_FILE_MACHINE_ARM64:     "arm64",
+	IMAGE_FILE_MACHINE_EBC:       "ebc",
+	IMAGE_FILE_MACHINE_I386:      "i386",
+	IMAGE_FILE_MACHINE_IA64:      "ia64",
+	IMAGE_FILE_MACHINE_M32R:      "m32r",
+	IMAGE_FILE_MACHINE_MIPS16:    "mips16",
+	IMAGE_FILE_MACHINE_MIPSFPU:   "mipsfpu",
+	IMAGE_FILE_MACHINE_MIPSFPU16: "mipsfpu16",
+	IMAGE_FILE_MACHINE_POWERPC:   "powerpc",
+	IMAGE_FILE_MACHINE_POWERPCFP: "powerpcfp",
+	IMAGE_FILE_MACHINE_R4000:     "r4000",
+	IMAGE_FILE_MACHINE_SH3:       "sh3",
+	IMAGE_FILE_MACHINE_SH3DSP:    "sh3dsp",
+	IMAGE_FILE_MACHINE_SH4:       "sh4",
+	IMAGE_FILE_MACHINE_SH5:       "sh5",
+	IMAGE_FILE_MACHINE_THUMB:     "thumb",
+	IMAGE_FILE_MACHINE_WCEMIPSV2: "wcemipsv2",
+}
+
+func (m PEMachine) String() string {
+	str, ok := PEMachineString[uint16(m)]
+	if ok {
+		return str
+	}
+	return fmt.Sprintf("unkown image file machine code %d\n", uint16(m))
+}
diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go
index 5121872c..ed91af6e 100644
--- a/pkg/proc/proc.go
+++ b/pkg/proc/proc.go
@@ -55,33 +55,6 @@ func (pe ProcessDetachedError) Error() string {
 	return "detached from the process"
 }
 
-// PostInitializationSetup handles all of the initialization procedures
-// that must happen after Delve creates or attaches to a process.
-func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error {
-	entryPoint, err := p.EntryPoint()
-	if err != nil {
-		return err
-	}
-
-	err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
-	if err != nil {
-		return err
-	}
-	for _, image := range p.BinInfo().Images {
-		if image.loadErr != nil {
-			return image.loadErr
-		}
-	}
-
-	g, _ := GetG(p.CurrentThread())
-	p.SetSelectedGoroutine(g)
-
-	createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
-	createFatalThrowBreakpoint(p, writeBreakpoint)
-
-	return nil
-}
-
 // FindFileLocation returns the PC for a given file:line.
 // Assumes that `file` is normalized to lower case and '/' on Windows.
 func FindFileLocation(p Process, fileName string, lineno int) ([]uint64, error) {
@@ -175,16 +148,19 @@ func Continue(dbp *Target) error {
 		// Make sure we clear internal breakpoints if we simultaneously receive a
 		// manual stop request and hit a breakpoint.
 		if dbp.CheckAndClearManualStopRequest() {
+			dbp.StopReason = StopManual
 			dbp.ClearInternalBreakpoints()
 		}
 	}()
 	for {
 		if dbp.CheckAndClearManualStopRequest() {
+			dbp.StopReason = StopManual
 			dbp.ClearInternalBreakpoints()
 			return nil
 		}
 		dbp.ClearAllGCache()
-		trapthread, err := dbp.ContinueOnce()
+		trapthread, stopReason, err := dbp.proc.ContinueOnce()
+		dbp.StopReason = stopReason
 		if err != nil {
 			return err
 		}
@@ -232,6 +208,7 @@ func Continue(dbp *Target) error {
 				if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
 					return err
 				}
+				dbp.StopReason = StopHardcodedBreakpoint
 				return conditionErrors(threads)
 			case g == nil || dbp.fncallForG[g.ID] == nil:
 				// a hardcoded breakpoint somewhere else in the code (probably cgo), or manual stop in cgo
@@ -272,6 +249,7 @@ func Continue(dbp *Target) error {
 				if err := dbp.ClearInternalBreakpoints(); err != nil {
 					return err
 				}
+				dbp.StopReason = StopNextFinished
 				return conditionErrors(threads)
 			}
 		case curbp.Active:
@@ -288,6 +266,7 @@ func Continue(dbp *Target) error {
 			if curbp.Name == UnrecoveredPanic {
 				dbp.ClearInternalBreakpoints()
 			}
+			dbp.StopReason = StopBreakpoint
 			return conditionErrors(threads)
 		default:
 			// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
@@ -295,6 +274,7 @@ func Continue(dbp *Target) error {
 		if callInjectionDone {
 			// a call injection was finished, don't let a breakpoint with a failed
 			// condition or a step breakpoint shadow this.
+			dbp.StopReason = StopCallReturned
 			return conditionErrors(threads)
 		}
 	}
@@ -318,7 +298,7 @@ func conditionErrors(threads []Thread) error {
 // 	- a thread with onTriggeredInternalBreakpoint() == true
 // 	- a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
 // 	- trapthread
-func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
+func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error {
 	for _, th := range threads {
 		if bp := th.Breakpoint(); bp.Active && bp.Internal {
 			return dbp.SwitchThread(th.ThreadID())
@@ -339,7 +319,7 @@ func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
 // function is neither fnname1 or fnname2.
 // This function is used to step out of runtime.Breakpoint as well as
 // runtime.debugCallV1.
-func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string) error {
+func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string) error {
 	for {
 		if err := curthread.StepInstruction(); err != nil {
 			return err
@@ -538,7 +518,7 @@ func StepInstruction(dbp *Target) (err error) {
 		return err
 	}
 	if tg, _ := GetG(thread); tg != nil {
-		dbp.SetSelectedGoroutine(tg)
+		dbp.selectedGoroutine = tg
 	}
 	return nil
 }
diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go
index f99d0e2d..0efb92f7 100644
--- a/pkg/proc/stack.go
+++ b/pkg/proc/stack.go
@@ -112,21 +112,22 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
 		return nil, err
 	}
 
+	bi := g.variable.bi
 	if g.Thread != nil {
 		regs, err := g.Thread.Registers(true)
 		if err != nil {
 			return nil, err
 		}
-		so := g.variable.bi.PCToImage(regs.PC())
+		so := bi.PCToImage(regs.PC())
 		return newStackIterator(
-			g.variable.bi, g.Thread,
-			g.variable.bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs),
+			bi, g.Thread,
+			bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs),
 			g.stackhi, stkbar, g.stkbarPos, g, opts), nil
 	}
 	so := g.variable.bi.PCToImage(g.PC)
 	return newStackIterator(
-		g.variable.bi, g.variable.mem,
-		g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
+		bi, g.variable.mem,
+		bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
 		g.stackhi, stkbar, g.stkbarPos, g, opts), nil
 }
 
@@ -438,11 +439,7 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
 	// implicit.
 	// See also the comment in dwarf2_frame_default_init in
 	// $GDB_SOURCE/dwarf2-frame.c
-	if _, ok := it.bi.Arch.(*ARM64); ok {
-		callFrameRegs.AddReg(uint64(arm64DwarfSPRegNum), cfareg)
-	} else {
-		callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg)
-	}
+	callFrameRegs.AddReg(callFrameRegs.SPRegNum, cfareg)
 
 	for i, regRule := range framectx.Regs {
 		reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
@@ -488,13 +485,13 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c
 	case frame.RuleRegister:
 		return it.regs.Reg(rule.Reg), nil
 	case frame.RuleExpression:
-		v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
+		v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression, it.bi.Arch.PtrSize())
 		if err != nil {
 			return nil, err
 		}
 		return it.readRegisterAt(regnum, uint64(v))
 	case frame.RuleValExpression:
-		v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
+		v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression, it.bi.Arch.PtrSize())
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/proc/target.go b/pkg/proc/target.go
index 6a4bc43b..f56ee242 100644
--- a/pkg/proc/target.go
+++ b/pkg/proc/target.go
@@ -1,9 +1,24 @@
 package proc
 
+import (
+	"fmt"
+)
+
 // Target represents the process being debugged.
 type Target struct {
 	Process
 
+	proc ProcessInternal
+
+	// StopReason describes the reason why the target process is stopped.
+	// A process could be stopped for multiple simultaneous reasons, in which
+	// case only one will be reported.
+	StopReason StopReason
+
+	// Goroutine that will be used by default to set breakpoint, eval variables, etc...
+	// Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
+	selectedGoroutine *G
+
 	// fncallForG stores a mapping of current active function calls.
 	fncallForG map[int]*callInjection
 
@@ -16,19 +31,69 @@ type Target struct {
 	gcache goroutineCache
 }
 
+// StopReason describes the reason why the target process is stopped.
+// A process could be stopped for multiple simultaneous reasons, in which
+// case only one will be reported.
+type StopReason uint8
+
+const (
+	StopUnknown             StopReason = iota
+	StopLaunched                       // The process was just launched
+	StopAttached                       // The debugger stopped the process after attaching
+	StopExited                         // The target proces terminated
+	StopBreakpoint                     // The target process hit one or more software breakpoints
+	StopHardcodedBreakpoint            // The target process hit a hardcoded breakpoint (for example runtime.Breakpoint())
+	StopManual                         // A manual stop was requested
+	StopNextFinished                   // The next/step/stepout command terminated
+	StopCallReturned                   // An injected call commpleted
+)
+
+// NewTargetConfig contains the configuration for a new Target object,
+type NewTargetConfig struct {
+	Path                string            // path of the main executable
+	DebugInfoDirs       []string          // Directories to search for split debug info
+	WriteBreakpoint     WriteBreakpointFn // Function to write a breakpoint to the target process
+	DisableAsyncPreempt bool              // Go 1.14 asynchronous preemption should be disabled
+	StopReason          StopReason        // Initial stop reason
+}
+
 // NewTarget returns an initialized Target object.
-func NewTarget(p Process, disableAsyncPreempt bool) *Target {
+func NewTarget(p Process, cfg NewTargetConfig) (*Target, error) {
+	entryPoint, err := p.EntryPoint()
+	if err != nil {
+		return nil, err
+	}
+
+	err = p.BinInfo().LoadBinaryInfo(cfg.Path, entryPoint, cfg.DebugInfoDirs)
+	if err != nil {
+		return nil, err
+	}
+	for _, image := range p.BinInfo().Images {
+		if image.loadErr != nil {
+			return nil, image.loadErr
+		}
+	}
+
 	t := &Target{
 		Process:    p,
+		proc:       p.(ProcessInternal),
 		fncallForG: make(map[int]*callInjection),
+		StopReason: cfg.StopReason,
 	}
+
+	g, _ := GetG(p.CurrentThread())
+	t.selectedGoroutine = g
+
+	createUnrecoveredPanicBreakpoint(p, cfg.WriteBreakpoint)
+	createFatalThrowBreakpoint(p, cfg.WriteBreakpoint)
+
 	t.gcache.init(p.BinInfo())
 
-	if disableAsyncPreempt {
+	if cfg.DisableAsyncPreempt {
 		setAsyncPreemptOff(t, 1)
 	}
 
-	return t
+	return t, nil
 }
 
 // SupportsFunctionCalls returns whether or not the backend supports
@@ -47,6 +112,9 @@ func (t *Target) SupportsFunctionCalls() bool {
 // This should be called anytime the target process executes instructions.
 func (t *Target) ClearAllGCache() {
 	t.gcache.Clear()
+	for _, thread := range t.ThreadList() {
+		thread.Common().g = nil
+	}
 }
 
 // Restart will start the process over from the location specified by the "from" locspec.
@@ -54,7 +122,50 @@ func (t *Target) ClearAllGCache() {
 // Restarting of a normal process happens at a higher level (debugger.Restart).
 func (t *Target) Restart(from string) error {
 	t.ClearAllGCache()
-	return t.Process.Restart(from)
+	err := t.proc.Restart(from)
+	if err != nil {
+		return err
+	}
+	t.selectedGoroutine, _ = GetG(t.CurrentThread())
+	if from != "" {
+		t.StopReason = StopManual
+	} else {
+		t.StopReason = StopLaunched
+	}
+	return nil
+}
+
+// SelectedGoroutine returns the currently selected goroutine.
+func (t *Target) SelectedGoroutine() *G {
+	return t.selectedGoroutine
+}
+
+// SwitchGoroutine will change the selected and active goroutine.
+func (p *Target) SwitchGoroutine(g *G) error {
+	if ok, err := p.Valid(); !ok {
+		return err
+	}
+	if g == nil {
+		return nil
+	}
+	if g.Thread != nil {
+		return p.SwitchThread(g.Thread.ThreadID())
+	}
+	p.selectedGoroutine = g
+	return nil
+}
+
+// SwitchThread will change the selected and active thread.
+func (p *Target) SwitchThread(tid int) error {
+	if ok, err := p.Valid(); !ok {
+		return err
+	}
+	if th, ok := p.FindThread(tid); ok {
+		p.proc.SetCurrentThread(th)
+		p.selectedGoroutine, _ = GetG(p.CurrentThread())
+		return nil
+	}
+	return fmt.Errorf("thread %d does not exist", tid)
 }
 
 // Detach will detach the target from the underylying process.
@@ -65,5 +176,6 @@ func (t *Target) Detach(kill bool) error {
 	if !kill && t.asyncPreemptChanged {
 		setAsyncPreemptOff(t, t.asyncPreemptOff)
 	}
-	return t.Process.Detach(kill)
+	t.StopReason = StopUnknown
+	return t.proc.Detach(kill)
 }
diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go
index 514a1fa2..7ca2edc9 100644
--- a/pkg/proc/threads.go
+++ b/pkg/proc/threads.go
@@ -1,7 +1,6 @@
 package proc
 
 import (
-	"encoding/binary"
 	"errors"
 	"fmt"
 	"go/ast"
@@ -70,6 +69,7 @@ func (tbe ErrThreadBlocked) Error() string {
 // implementations of the Thread interface.
 type CommonThread struct {
 	returnValues []*Variable
+	g            *G // cached g for this thread
 }
 
 // ReturnValues reads the return values from the function executing on
@@ -142,7 +142,7 @@ func (err *ErrNoSourceForPC) Error() string {
 // for an inlined function call. Everything works the same as normal except
 // when removing instructions belonging to inlined calls we also remove all
 // instructions belonging to the current inlined call.
-func next(dbp Process, stepInto, inlinedStepOut bool) error {
+func next(dbp *Target, stepInto, inlinedStepOut bool) error {
 	selg := dbp.SelectedGoroutine()
 	curthread := dbp.CurrentThread()
 	topframe, retframe, err := topframe(selg, curthread)
@@ -209,7 +209,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
 				continue
 			}
 
-			if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
+			if instr.DestLoc != nil {
 				if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
 					return err
 				}
@@ -399,6 +399,11 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
 
 	pc := instr.DestLoc.PC
 
+	// Skip InhibitStepInto functions for different arch.
+	if dbp.BinInfo().Arch.InhibitStepInto(dbp.BinInfo(), pc) {
+		return nil
+	}
+
 	// We want to skip the function prologue but we should only do it if the
 	// destination address of the CALL instruction is the entry point of the
 	// function.
@@ -427,12 +432,11 @@ func getGVariable(thread Thread) (*Variable, error) {
 
 	gaddr, hasgaddr := regs.GAddr()
 	if !hasgaddr {
-		gaddrbs := make([]byte, thread.Arch().PtrSize())
-		_, err := thread.ReadMemory(gaddrbs, uintptr(regs.TLS()+thread.BinInfo().GStructOffset()))
+		var err error
+		gaddr, err = readUintRaw(thread, uintptr(regs.TLS()+thread.BinInfo().GStructOffset()), int64(thread.BinInfo().Arch.PtrSize()))
 		if err != nil {
 			return nil, err
 		}
-		gaddr = binary.LittleEndian.Uint64(gaddrbs)
 	}
 
 	return newGVariable(thread, uintptr(gaddr), thread.Arch().DerefTLS())
@@ -478,6 +482,9 @@ func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) {
 // In order to get around all this craziness, we read the address of the G structure for
 // the current thread from the thread local storage area.
 func GetG(thread Thread) (*G, error) {
+	if thread.Common().g != nil {
+		return thread.Common().g, nil
+	}
 	if loc, _ := thread.Location(); loc != nil && loc.Fn != nil && loc.Fn.Name == "runtime.clone" {
 		// When threads are executing runtime.clone the value of TLS is unreliable.
 		return nil, nil
@@ -520,6 +527,7 @@ func GetG(thread Thread) (*G, error) {
 	if loc, err := thread.Location(); err == nil {
 		g.CurrentLoc = *loc
 	}
+	thread.Common().g = g
 	return g, nil
 }
 
diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go
index 96a559fd..2a06aa3a 100644
--- a/pkg/proc/variables.go
+++ b/pkg/proc/variables.go
@@ -1488,13 +1488,12 @@ func (v *Variable) readFunctionPtr() {
 
 // funcvalAddr reads the address of the funcval contained in a function variable.
 func (v *Variable) funcvalAddr() uint64 {
-	val := make([]byte, v.bi.Arch.PtrSize())
-	_, err := v.mem.ReadMemory(val, v.Addr)
+	val, err := readUintRaw(v.mem, v.Addr, int64(v.bi.Arch.PtrSize()))
 	if err != nil {
 		v.Unreadable = err
 		return 0
 	}
-	return binary.LittleEndian.Uint64(val)
+	return val
 }
 
 func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
diff --git a/pkg/proc/x86_disasm.go b/pkg/proc/x86_disasm.go
new file mode 100644
index 00000000..a4c1ef38
--- /dev/null
+++ b/pkg/proc/x86_disasm.go
@@ -0,0 +1,120 @@
+package proc
+
+import (
+	"golang.org/x/arch/x86/x86asm"
+)
+
+type x86Inst x86asm.Inst
+
+// 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.
+func x86AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo, bit int) error {
+	inst, err := x86asm.Decode(mem, bit)
+	if err != nil {
+		asmInst.Inst = (*x86Inst)(nil)
+		asmInst.Size = 1
+		asmInst.Bytes = mem[:asmInst.Size]
+		return err
+	}
+
+	asmInst.Size = inst.Len
+	asmInst.Bytes = mem[:asmInst.Size]
+	patchPCRelX86(asmInst.Loc.PC, &inst)
+	asmInst.Inst = (*x86Inst)(&inst)
+	asmInst.Kind = OtherInstruction
+
+	switch inst.Op {
+	case x86asm.CALL, x86asm.LCALL:
+		asmInst.Kind = CallInstruction
+	case x86asm.RET, x86asm.LRET:
+		asmInst.Kind = RetInstruction
+	}
+
+	asmInst.DestLoc = resolveCallArgX86(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
+	return nil
+}
+
+// converts PC relative arguments to absolute addresses
+func patchPCRelX86(pc uint64, inst *x86asm.Inst) {
+	for i := range inst.Args {
+		rel, isrel := inst.Args[i].(x86asm.Rel)
+		if isrel {
+			inst.Args[i] = x86asm.Imm(int64(pc) + int64(rel) + int64(inst.Len))
+		}
+	}
+}
+
+func (inst *x86Inst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string {
+	if inst == nil {
+		return "?"
+	}
+
+	var text string
+
+	switch flavour {
+	case GNUFlavour:
+		text = x86asm.GNUSyntax(x86asm.Inst(*inst), pc, symLookup)
+	case GoFlavour:
+		text = x86asm.GoSyntax(x86asm.Inst(*inst), pc, symLookup)
+	case IntelFlavour:
+		fallthrough
+	default:
+		text = x86asm.IntelSyntax(x86asm.Inst(*inst), pc, symLookup)
+	}
+
+	return text
+}
+
+func (inst *x86Inst) OpcodeEquals(op uint64) bool {
+	if inst == nil {
+		return false
+	}
+	return uint64(inst.Op) == op
+}
+
+func resolveCallArgX86(inst *x86asm.Inst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
+	if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
+		return nil
+	}
+
+	var pc uint64
+	var err error
+
+	switch arg := inst.Args[0].(type) {
+	case x86asm.Imm:
+		pc = uint64(arg)
+	case x86asm.Reg:
+		if !currentGoroutine || regs == nil {
+			return nil
+		}
+		pc, err = regs.Get(int(arg))
+		if err != nil {
+			return nil
+		}
+	case x86asm.Mem:
+		if !currentGoroutine || regs == nil {
+			return nil
+		}
+		if arg.Segment != 0 {
+			return nil
+		}
+		base, err1 := regs.Get(int(arg.Base))
+		index, err2 := regs.Get(int(arg.Index))
+		if err1 != nil || err2 != nil {
+			return nil
+		}
+		addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp)
+		pc, err = readUintRaw(mem, addr, int64(inst.MemBytes))
+		if err != nil {
+			return nil
+		}
+	default:
+		return nil
+	}
+
+	file, line, fn := bininfo.PCToLine(pc)
+	if fn == nil {
+		return &Location{PC: pc}
+	}
+	return &Location{PC: pc, File: file, Line: line, Fn: fn}
+}
diff --git a/service/dap/error_ids.go b/service/dap/error_ids.go
index 6f448476..9f1b9f05 100644
--- a/service/dap/error_ids.go
+++ b/service/dap/error_ids.go
@@ -11,6 +11,7 @@ const (
 	// TODO(polina): confirm if the extension expects specific ids
 	// for specific cases, and we must match the existing adaptor
 	// or if these codes can evolve.
-	FailedToContinue = 3000
+	FailedToContinue       = 3000
+	UnableToDisplayThreads = 2003
 	// Add more codes as we support more requests
 )
diff --git a/service/dap/server.go b/service/dap/server.go
index c9931ae9..78646d26 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -244,7 +244,7 @@ func (s *Server) handleRequest(request dap.Message) {
 	case *dap.SourceRequest:
 		s.sendUnsupportedErrorResponse(request.Request)
 	case *dap.ThreadsRequest:
-		s.sendUnsupportedErrorResponse(request.Request)
+		s.onThreadsRequest(request)
 	case *dap.TerminateThreadsRequest:
 		s.sendUnsupportedErrorResponse(request.Request)
 	case *dap.EvaluateRequest:
@@ -453,6 +453,49 @@ func (s *Server) onContinueRequest(request *dap.ContinueRequest) {
 	s.doContinue()
 }
 
+func (s *Server) onThreadsRequest(request *dap.ThreadsRequest) {
+	if s.debugger == nil {
+		s.sendErrorResponse(request.Request, UnableToDisplayThreads, "Unable to display threads", "debugger is nil")
+		return
+	}
+	gs, _, err := s.debugger.Goroutines(0, 0)
+	if err != nil {
+		switch err.(type) {
+		case *proc.ErrProcessExited:
+			// If the program exits very quickly, the initial threads request will complete after it has exited.
+			// A TerminatedEvent has already been sent. Ignore the err returned in this case.
+			s.send(&dap.ThreadsResponse{Response: *newResponse(request.Request)})
+		default:
+			s.sendErrorResponse(request.Request, UnableToDisplayThreads, "Unable to display threads", err.Error())
+		}
+		return
+	}
+
+	threads := make([]dap.Thread, len(gs))
+	if len(threads) == 0 {
+		// Depending on the debug session stage, goroutines information
+		// might not be available. However, the DAP spec states that
+		// "even if a debug adapter does not support multiple threads,
+		// it must implement the threads request and return a single
+		// (dummy) thread".
+		threads = []dap.Thread{{Id: 1, Name: "Dummy"}}
+	} else {
+		for i, g := range gs {
+			threads[i].Id = g.ID
+			if loc := g.UserCurrentLoc; loc.Function != nil {
+				threads[i].Name = loc.Function.Name()
+			} else {
+				threads[i].Name = fmt.Sprintf("%s@%d", loc.File, loc.Line)
+			}
+		}
+	}
+	response := &dap.ThreadsResponse{
+		Response: *newResponse(request.Request),
+		Body:     dap.ThreadsResponseBody{Threads: threads},
+	}
+	s.send(response)
+}
+
 func (s *Server) sendErrorResponse(request dap.Request, id int, summary string, details string) {
 	er := &dap.ErrorResponse{}
 	er.Type = "response"
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 7b54a1ee..5bf9b18a 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -127,7 +127,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
 		d.log.Infof("launching process with args: %v", d.processArgs)
 		p, err := d.Launch(d.processArgs, d.config.WorkingDir)
 		if err != nil {
-			if err != proc.ErrNotExecutable && err != proc.ErrUnsupportedLinuxArch && err != proc.ErrUnsupportedWindowsArch && err != proc.ErrUnsupportedDarwinArch {
+			if _, ok := err.(*proc.ErrUnsupportedArch); !ok {
 				err = go11DecodeErrorCheck(err)
 				err = fmt.Errorf("could not launch process: %s", err)
 			}


Test patch:
diff --git a/_fixtures/cgostacktest/hello.c b/_fixtures/cgostacktest/hello.c
index 7e179be1..7226ffed 100644
--- a/_fixtures/cgostacktest/hello.c
+++ b/_fixtures/cgostacktest/hello.c
@@ -4,6 +4,8 @@
 
 #ifdef __amd64__
 #define BREAKPOINT asm("int3;")
+#elif __i386__
+#define BREAKPOINT asm("int3;")
 #elif __aarch64__
 #define BREAKPOINT asm("brk 0;")
 #endif
diff --git a/pkg/dwarf/frame/entries_test.go b/pkg/dwarf/frame/entries_test.go
index a608120a..cd919af4 100644
--- a/pkg/dwarf/frame/entries_test.go
+++ b/pkg/dwarf/frame/entries_test.go
@@ -5,8 +5,13 @@ import (
 	"io/ioutil"
 	"os"
 	"testing"
+	"unsafe"
 )
 
+func ptrSizeByRuntimeArch() int {
+	return int(unsafe.Sizeof(uintptr(0)))
+}
+
 func TestFDEForPC(t *testing.T) {
 	frames := NewFrameIndex()
 	frames = append(frames,
@@ -62,7 +67,7 @@ func BenchmarkFDEForPC(b *testing.B) {
 	if err != nil {
 		b.Fatal(err)
 	}
-	fdes := Parse(data, binary.BigEndian, 0)
+	fdes := Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
 
 	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 d9d37673..d03ea01c 100644
--- a/pkg/dwarf/frame/parser_test.go
+++ b/pkg/dwarf/frame/parser_test.go
@@ -12,8 +12,8 @@ import (
 
 func TestParseCIE(t *testing.T) {
 	ctx := &parseContext{
-		buf : bytes.NewBuffer([]byte{3,0,1,124,16,12,7,8,5,16,2,0,36,0,0,0,0,0,0,0,0,16,64,0,0,0,0,0}),
-		common: &CommonInformationEntry{Length:12},
+		buf:    bytes.NewBuffer([]byte{3, 0, 1, 124, 16, 12, 7, 8, 5, 16, 2, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 16, 64, 0, 0, 0, 0, 0}),
+		common: &CommonInformationEntry{Length: 12},
 		length: 12,
 	}
 	_ = parseCIE(ctx)
@@ -35,8 +35,8 @@ func TestParseCIE(t *testing.T) {
 	if common.ReturnAddressRegister != 16 {
 		t.Fatalf("Expected ReturnAddressRegister 16, but get %d", common.ReturnAddressRegister)
 	}
-	initialInstructions := []byte{12,7,8,5,16,2,0}
-	if !bytes.Equal(common.InitialInstructions, initialInstructions){
+	initialInstructions := []byte{12, 7, 8, 5, 16, 2, 0}
+	if !bytes.Equal(common.InitialInstructions, initialInstructions) {
 		t.Fatalf("Expected InitialInstructions %v, but get %v", initialInstructions, common.InitialInstructions)
 	}
 }
@@ -56,6 +56,6 @@ func BenchmarkParse(b *testing.B) {
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		Parse(data, binary.BigEndian, 0)
+		Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
 	}
 }
diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go
index 08a8a6eb..ac3c2405 100644
--- a/pkg/dwarf/line/line_parser_test.go
+++ b/pkg/dwarf/line/line_parser_test.go
@@ -13,6 +13,7 @@ import (
 	"strings"
 	"testing"
 	"time"
+	"unsafe"
 
 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
 	"github.com/pkg/profile"
@@ -65,10 +66,13 @@ const (
 	opcodeBaseGo111 uint8  = 11
 )
 
+func ptrSizeByRuntimeArch() int {
+	return int(unsafe.Sizeof(uintptr(0)))
+}
+
 func testDebugLinePrologueParser(p string, t *testing.T) {
 	data := grabDebugLineSection(p, t)
-	debugLines := ParseAll(data, nil, 0, true)
-
+	debugLines := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
 	mainFileFound := false
 
 	for _, dbl := range debugLines {
@@ -173,7 +177,7 @@ func BenchmarkLineParser(b *testing.B) {
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		_ = ParseAll(data, nil, 0, true)
+		_ = ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
 	}
 }
 
@@ -188,7 +192,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines {
 		tb.Fatal("Could not read test data", err)
 	}
 
-	return ParseAll(data, nil, 0, true)
+	return ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
 }
 
 func BenchmarkStateMachine(b *testing.B) {
@@ -196,7 +200,7 @@ func BenchmarkStateMachine(b *testing.B) {
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
-		sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
+		sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions, ptrSizeByRuntimeArch())
 
 		for {
 			if err := sm.next(); err != nil {
@@ -223,7 +227,7 @@ func setupTestPCToLine(t testing.TB, lineInfos DebugLines) ([]pctolineEntry, []u
 	entries := []pctolineEntry{}
 	basePCs := []uint64{}
 
-	sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
+	sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions, ptrSizeByRuntimeArch())
 	for {
 		if err := sm.next(); err != nil {
 			break
@@ -310,7 +314,7 @@ func TestDebugLineC(t *testing.T) {
 		t.Fatal("Could not read test data", err)
 	}
 
-	parsed := ParseAll(data, nil, 0, true)
+	parsed := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
 
 	if len(parsed) == 0 {
 		t.Fatal("Parser result is empty")
diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go
index 3a36d183..5088c4f4 100644
--- a/pkg/dwarf/line/state_machine_test.go
+++ b/pkg/dwarf/line/state_machine_test.go
@@ -43,7 +43,6 @@ func TestGrafana(t *testing.T) {
 	if runtime.GOOS == "windows" {
 		t.Skip("filepath.Join ruins this test on windows")
 	}
-
 	debugBytes, err := slurpGzip("_testdata/debug.grafana.debug.gz")
 	if err != nil {
 		t.Fatal(err)
@@ -80,8 +79,8 @@ func TestGrafana(t *testing.T) {
 		}
 		cuname, _ := e.Val(dwarf.AttrName).(string)
 
-		lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false)
-		sm := newStateMachine(lineInfo, lineInfo.Instructions)
+		lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false, 8)
+		sm := newStateMachine(lineInfo, lineInfo.Instructions, 8)
 
 		lnrdr, err := data.LineReader(e)
 		if err != nil {
@@ -137,13 +136,13 @@ func TestMultipleSequences(t *testing.T) {
 	const thefile = "thefile.go"
 
 	instr := bytes.NewBuffer(nil)
+	ptrSize := ptrSizeByRuntimeArch()
 
 	write_DW_LNE_set_address := func(addr uint64) {
 		instr.WriteByte(0)
 		util.EncodeULEB128(instr, 9) // 1 + ptr_size
 		instr.WriteByte(DW_LINE_set_address)
-		binary.Write(instr, binary.LittleEndian, addr)
-
+		util.WriteUint(instr, binary.LittleEndian, ptrSize, addr)
 	}
 
 	write_DW_LNS_copy := func() {
@@ -215,6 +214,7 @@ func TestMultipleSequences(t *testing.T) {
 		IncludeDirs:  []string{},
 		FileNames:    []*FileEntry{&FileEntry{Path: thefile}},
 		Instructions: instr.Bytes(),
+		ptrSize:      ptrSize,
 	}
 
 	// Test that PCToLine is correct for all three sequences
@@ -235,7 +235,7 @@ func TestMultipleSequences(t *testing.T) {
 		{0x600002, 12},
 		{0x600004, 13},
 	} {
-		sm := newStateMachine(lines, lines.Instructions)
+		sm := newStateMachine(lines, lines.Instructions, lines.ptrSize)
 		file, curline, ok := sm.PCToLine(testCase.pc)
 		if !ok {
 			t.Fatalf("Could not find %#x", testCase.pc)
diff --git a/pkg/dwarf/op/op_test.go b/pkg/dwarf/op/op_test.go
index c46c8c7a..0a346261 100644
--- a/pkg/dwarf/op/op_test.go
+++ b/pkg/dwarf/op/op_test.go
@@ -1,13 +1,20 @@
 package op
 
-import "testing"
+import (
+	"testing"
+	"unsafe"
+)
+
+func ptrSizeByRuntimeArch() int {
+	return int(unsafe.Sizeof(uintptr(0)))
+}
 
 func TestExecuteStackProgram(t *testing.T) {
 	var (
 		instructions = []byte{byte(DW_OP_consts), 0x1c, byte(DW_OP_consts), 0x1c, byte(DW_OP_plus)}
 		expected     = int64(56)
 	)
-	actual, _, err := ExecuteStackProgram(DwarfRegisters{}, instructions)
+	actual, _, err := ExecuteStackProgram(DwarfRegisters{}, instructions, ptrSizeByRuntimeArch())
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go
index 3d8d429b..6a236e26 100644
--- a/pkg/proc/dwarf_expr_test.go
+++ b/pkg/proc/dwarf_expr_test.go
@@ -11,6 +11,7 @@ import (
 	"fmt"
 	"go/constant"
 	"testing"
+	"unsafe"
 
 	"github.com/go-delve/delve/pkg/dwarf/dwarfbuilder"
 	"github.com/go-delve/delve/pkg/dwarf/godwarf"
@@ -19,7 +20,20 @@ import (
 	"github.com/go-delve/delve/pkg/proc/linutil"
 )
 
-const defaultCFA = 0xc420051d00
+func ptrSizeByRuntimeArch() int {
+	return int(unsafe.Sizeof(uintptr(0)))
+}
+
+func fakeCFA() uint64 {
+	ptrSize := ptrSizeByRuntimeArch()
+	if ptrSize == 8 {
+		return 0xc420051d00
+	}
+	if ptrSize == 4 {
+		return 0xc4251d00
+	}
+	panic(fmt.Errorf("not support ptr size %d", ptrSize))
+}
 
 func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) (*proc.BinaryInfo, *dwarf.Data) {
 	abbrev, aranges, frame, info, line, pubnames, ranges, str, loc, err := dwb.Build()
@@ -92,8 +106,8 @@ func dwarfRegisters(bi *proc.BinaryInfo, regs *linutil.AMD64Registers) op.DwarfR
 	a := proc.AMD64Arch("linux")
 	so := bi.PCToImage(regs.PC())
 	dwarfRegs := a.RegistersToDwarfRegisters(so.StaticBase, regs)
-	dwarfRegs.CFA = defaultCFA
-	dwarfRegs.FrameBase = defaultCFA
+	dwarfRegs.CFA = int64(fakeCFA())
+	dwarfRegs.FrameBase = int64(fakeCFA())
 	return dwarfRegs
 }
 
@@ -118,8 +132,7 @@ func TestDwarfExprRegisters(t *testing.T) {
 	bi, _ := fakeBinaryInfo(t, dwb)
 
 	mainfn := bi.LookupFunc["main.main"]
-
-	mem := newFakeMemory(defaultCFA, uint64(0), uint64(testCases["b"]))
+	mem := newFakeMemory(fakeCFA(), uint64(0), uint64(testCases["b"]))
 	regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{}}
 	regs.Regs.Rax = uint64(testCases["a"])
 	regs.Regs.Rdx = uint64(testCases["c"])
@@ -171,11 +184,11 @@ func TestDwarfExprComposite(t *testing.T) {
 
 	mainfn := bi.LookupFunc["main.main"]
 
-	mem := newFakeMemory(defaultCFA, uint64(0), uint64(0), uint16(testCases["pair.v"]), []byte(stringVal))
+	mem := newFakeMemory(fakeCFA(), uint64(0), uint64(0), uint16(testCases["pair.v"]), []byte(stringVal))
 	var regs linutil.AMD64Registers
 	regs.Regs = &linutil.AMD64PtraceRegs{}
 	regs.Regs.Rax = uint64(len(stringVal))
-	regs.Regs.Rdx = defaultCFA + 18
+	regs.Regs.Rdx = fakeCFA() + 18
 	regs.Regs.Rcx = uint64(testCases["pair.k"])
 	regs.Regs.Rbx = uint64(testCases["n"])
 
@@ -211,7 +224,7 @@ func TestDwarfExprLoclist(t *testing.T) {
 
 	mainfn := bi.LookupFunc["main.main"]
 
-	mem := newFakeMemory(defaultCFA, uint16(before), uint16(after))
+	mem := newFakeMemory(fakeCFA(), uint16(before), uint16(after))
 	const PC = 0x40100
 	regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{Rip: PC}}
 
@@ -248,7 +261,7 @@ func TestIssue1419(t *testing.T) {
 
 	mainfn := bi.LookupFunc["main.main"]
 
-	mem := newFakeMemory(defaultCFA)
+	mem := newFakeMemory(fakeCFA())
 
 	scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: op.DwarfRegisters{}, Mem: mem, BinInfo: bi}
 
diff --git a/pkg/proc/fbsdutil/regs_test.go b/pkg/proc/fbsdutil/regs_test.go
index 30773ffa..19aa7641 100644
--- a/pkg/proc/fbsdutil/regs_test.go
+++ b/pkg/proc/fbsdutil/regs_test.go
@@ -46,7 +46,7 @@ func TestAMD64Get(t *testing.T) {
 		t.Fatal(err)
 	}
 	if eax != 0xDEADBEEF {
-		t.Fatalf("expected %#v, got %#v\n", 0xdeadbeef, eax)
+		t.Fatalf("expected %#v, got %#v\n", uint64(0xdeadbeef), eax)
 	}
 
 	// Test RAX, full 64 bits of register
diff --git a/pkg/proc/linutil/regs_test.go b/pkg/proc/linutil/regs_test.go
index 7d6fb879..a9ed0fbf 100644
--- a/pkg/proc/linutil/regs_test.go
+++ b/pkg/proc/linutil/regs_test.go
@@ -46,7 +46,7 @@ func TestAMD64Get(t *testing.T) {
 		t.Fatal(err)
 	}
 	if eax != 0xDEADBEEF {
-		t.Fatalf("expected %#v, got %#v\n", 0xdeadbeef, eax)
+		t.Fatalf("expected %#v, got %#v\n", uint64(0xdeadbeef), eax)
 	}
 
 	// Test RAX, full 64 bits of register
diff --git a/pkg/proc/proc_general_test.go b/pkg/proc/proc_general_test.go
index 8af69eac..2e35469d 100644
--- a/pkg/proc/proc_general_test.go
+++ b/pkg/proc/proc_general_test.go
@@ -2,13 +2,25 @@ package proc
 
 import (
 	"testing"
+	"unsafe"
 )
 
+func ptrSizeByRuntimeArch() int {
+	return int(unsafe.Sizeof(uintptr(0)))
+}
+
 func TestIssue554(t *testing.T) {
 	// unsigned integer overflow in proc.(*memCache).contains was
 	// causing it to always return true for address 0xffffffffffffffff
 	mem := memCache{true, 0x20, make([]byte, 100), nil}
-	if mem.contains(0xffffffffffffffff, 40) {
+	var addr uint64
+	switch ptrSizeByRuntimeArch() {
+	case 4:
+		addr = 0xffffffff
+	case 8:
+		addr = 0xffffffffffffffff
+	}
+	if mem.contains(uintptr(addr), 40) {
 		t.Fatalf("should be false")
 	}
 }
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index dd15e580..70d913c6 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -2073,15 +2073,17 @@ func TestUnsupportedArch(t *testing.T) {
 		t.Skip("test not valid for this backend")
 	}
 
-	switch err {
-	case proc.ErrUnsupportedLinuxArch, proc.ErrUnsupportedWindowsArch, proc.ErrUnsupportedDarwinArch:
-		// all good
-	case nil:
+	if err == nil {
 		p.Detach(true)
 		t.Fatal("Launch is expected to fail, but succeeded")
-	default:
-		t.Fatal(err)
 	}
+
+	if _, ok := err.(*proc.ErrUnsupportedArch); ok {
+		// all good
+		return
+	}
+
+	t.Fatal(err)
 }
 
 func TestIssue573(t *testing.T) {
@@ -3261,6 +3263,10 @@ func TestCgoStacktrace(t *testing.T) {
 		}
 	}
 
+	if runtime.GOARCH == "386" {
+		t.Skip("cgo stacktraces not supported on i386 for now")
+	}
+
 	// Tests that:
 	// a) we correctly identify the goroutine while we are executing cgo code
 	// b) that we can stitch together the system stack (where cgo code
@@ -3360,6 +3366,10 @@ func TestCgoSources(t *testing.T) {
 		}
 	}
 
+	if runtime.GOARCH == "386" {
+		t.Skip("cgo stacktraces not supported on i386 for now")
+	}
+
 	withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) {
 		sources := p.BinInfo().Sources
 		for _, needle := range []string{"main.go", "hello.c"} {
@@ -3429,6 +3439,10 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) {
 }
 
 func TestIssue1034(t *testing.T) {
+	if runtime.GOARCH == "386" {
+		t.Skip("cgo stacktraces not supported on i386 for now")
+	}
+
 	// The external linker on macOS produces an abbrev for DW_TAG_subprogram
 	// without the "has children" flag, we should support this.
 	withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) {
@@ -3446,6 +3460,10 @@ func TestIssue1034(t *testing.T) {
 }
 
 func TestIssue1008(t *testing.T) {
+	if runtime.GOARCH == "386" {
+		t.Skip("cgo stacktraces not supported on i386 for now")
+	}
+
 	// The external linker on macOS inserts "end of sequence" extended opcodes
 	// in debug_line. which we should support correctly.
 	withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) {
@@ -3565,6 +3583,14 @@ func TestDisassembleGlobalVars(t *testing.T) {
 	if runtime.GOARCH == "arm64" {
 		t.Skip("On ARM64 symLookup can't look up variables due to how they are loaded, see issue #1778")
 	}
+	// On 386 linux when pie, the genered code use __x86.get_pc_thunk to ensure position-independent.
+	// Locate global variable by
+	//    `CALL __x86.get_pc_thunk.ax(SB) 0xb0f7f
+	//     LEAL 0xc0a19(AX), AX`
+	// dynamically.
+	if runtime.GOARCH == "386" && runtime.GOOS == "linux" && buildMode == "pie" {
+		t.Skip("On 386 linux when pie, symLookup can't look up global variables")
+	}
 	withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
 		mainfn := p.BinInfo().LookupFunc["main.main"]
 		regs, _ := p.CurrentThread().Registers(false)
@@ -4115,8 +4141,8 @@ func TestReadDeferArgs(t *testing.T) {
 }
 
 func TestIssue1374(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FunctionCall for now")
+	if runtime.GOARCH == "arm64" || runtime.GOARCH == "386" {
+		t.Skip(fmt.Errorf("%s does not support FunctionCall for now", runtime.GOARCH))
 	}
 	// Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
 	protest.MustSupportFunctionCalls(t, testBackend)
@@ -4337,9 +4363,10 @@ func TestCallConcurrent(t *testing.T) {
 	if runtime.GOOS == "freebsd" {
 		t.Skip("test is not valid on FreeBSD")
 	}
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FunctionCall for now")
+	if runtime.GOARCH == "arm64" || runtime.GOARCH == "386" {
+		t.Skip(fmt.Sprintf("%s does not support FunctionCall for now", runtime.GOARCH))
 	}
+
 	protest.MustSupportFunctionCalls(t, testBackend)
 	withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
 		bp := setFileBreakpoint(p, t, fixture.Source, 24)
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index 384e2c13..d422ccb4 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -978,8 +978,8 @@ func findStarFile(name string) string {
 }
 
 func TestIssue1598(t *testing.T) {
-	if runtime.GOARCH == "arm64" {
-		t.Skip("arm64 does not support FunctionCall for now")
+	if runtime.GOARCH == "arm64" || runtime.GOARCH == "386" {
+		t.Skip(fmt.Errorf("%s does not support FunctionCall for now", runtime.GOARCH))
 	}
 	test.MustSupportFunctionCalls(t, testBackend)
 	withTestTerminal("issue1598", t, func(term *FakeTerminal) {
diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go
index 7d607bc7..1a97e428 100644
--- a/service/dap/daptest/client.go
+++ b/service/dap/daptest/client.go
@@ -4,7 +4,6 @@ package daptest
 
 import (
 	"bufio"
-	"encoding/json"
 	"fmt"
 	"log"
 	"net"
@@ -34,6 +33,7 @@ func NewClient(addr string) *Client {
 		log.Fatal("dialing:", err)
 	}
 	c := &Client{conn: conn, reader: bufio.NewReader(conn)}
+	c.seq = 1 // match VS Code numbering
 	return c
 }
 
@@ -43,8 +43,6 @@ func (c *Client) Close() {
 }
 
 func (c *Client) send(request dap.Message) {
-	jsonmsg, _ := json.Marshal(request)
-	fmt.Println("[client -> server]", string(jsonmsg))
 	dap.WriteProtocolMessage(c.conn, request)
 }
 
@@ -120,6 +118,16 @@ func (c *Client) ExpectConfigurationDoneResponse(t *testing.T) *dap.Configuratio
 	return c.expectReadProtocolMessage(t).(*dap.ConfigurationDoneResponse)
 }
 
+func (c *Client) ExpectThreadsResponse(t *testing.T) *dap.ThreadsResponse {
+	t.Helper()
+	return c.expectReadProtocolMessage(t).(*dap.ThreadsResponse)
+}
+
+func (c *Client) ExpectStackTraceResponse(t *testing.T) *dap.StackTraceResponse {
+	t.Helper()
+	return c.expectReadProtocolMessage(t).(*dap.StackTraceResponse)
+}
+
 // InitializeRequest sends an 'initialize' request.
 func (c *Client) InitializeRequest() {
 	request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
@@ -199,6 +207,18 @@ func (c *Client) ContinueRequest(thread int) {
 	c.send(request)
 }
 
+// ThreadsRequest sends a 'threads' request.
+func (c *Client) ThreadsRequest() {
+	request := &dap.ThreadsRequest{Request: *c.newRequest("threads")}
+	c.send(request)
+}
+
+// StackTraceRequest sends a 'stackTrace' request.
+func (c *Client) StackTraceRequest() {
+	request := &dap.StackTraceRequest{Request: *c.newRequest("stackTrace")}
+	c.send(request)
+}
+
 // UnknownRequest triggers dap.DecodeProtocolMessageFieldError.
 func (c *Client) UnknownRequest() {
 	request := c.newRequest("unknown")
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 5e1ceeef..a462ed95 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -6,6 +6,8 @@ import (
 	"net"
 	"os"
 	"path/filepath"
+	"reflect"
+	"strings"
 	"sync"
 	"testing"
 	"time"
@@ -66,66 +68,190 @@ func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.F
 	test(client, fixture)
 }
 
+// TestStopOnEntry emulates the message exchange that can be observed with
+// VS Code for the most basic debug session with "stopOnEntry" enabled:
+// - User selects "Start Debugging":  1 >> initialize
+//                                 :  1 << initialize
+//                                 :  2 >> launch
+//                                 :    << initialized event
+//                                 :  2 << launch
+//                                 :  3 >> setBreakpoints (empty)
+//                                 :  3 << setBreakpoints
+//                                 :  4 >> setExceptionBreakpoints (empty)
+//                                 :  4 << setExceptionBreakpoints
+//                                 :  5 >> configurationDone
+// - Program stops upon launching  :    << stopped event
+//                                 :  5 << configurationDone
+//                                 :  6 >> threads
+//                                 :  6 << threads (Dummy)
+//                                 :  7 >> threads
+//                                 :  7 << threads (Dummy)
+//                                 :  8 >> stackTrace
+//                                 :  8 << stackTrace (Unable to produce stack trace)
+//                                 :  9 >> stackTrace
+//                                 :  9 << stackTrace (Unable to produce stack trace)
+// - User selects "Continue"       : 10 >> continue
+//                                 : 10 << continue
+// - Program runs to completion    :    << terminated event
+//                                 : 11 >> disconnect
+//                                 : 11 << disconnect
+// This test exhaustively tests Seq and RequestSeq on all messages from the
+// server. Other tests do not necessarily need to repeat all these checks.
 func TestStopOnEntry(t *testing.T) {
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
-		// This test exhaustively tests Seq and RequestSeq on all messages from the
-		// server. Other tests shouldn't necessarily repeat these checks.
+		// 1 >> initialize, << initialize
 		client.InitializeRequest()
 		initResp := client.ExpectInitializeResponse(t)
-		if initResp.Seq != 0 || initResp.RequestSeq != 0 {
-			t.Errorf("got %#v, want Seq=0, RequestSeq=0", initResp)
+		if initResp.Seq != 0 || initResp.RequestSeq != 1 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=1", initResp)
 		}
 
+		// 2 >> launch, << initialized, << launch
 		client.LaunchRequest("exec", fixture.Path, stopOnEntry)
-		initEv := client.ExpectInitializedEvent(t)
-		if initEv.Seq != 0 {
-			t.Errorf("got %#v, want Seq=0", initEv)
+		initEvent := client.ExpectInitializedEvent(t)
+		if initEvent.Seq != 0 {
+			t.Errorf("\ngot %#v\nwant Seq=0", initEvent)
 		}
-
 		launchResp := client.ExpectLaunchResponse(t)
-		if launchResp.Seq != 0 || launchResp.RequestSeq != 1 {
-			t.Errorf("got %#v, want Seq=0, RequestSeq=1", launchResp)
+		if launchResp.Seq != 0 || launchResp.RequestSeq != 2 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=2", launchResp)
+		}
+
+		// 3 >> setBreakpoints, << setBreakpoints
+		client.SetBreakpointsRequest(fixture.Source, nil)
+		sbpResp := client.ExpectSetBreakpointsResponse(t)
+		if sbpResp.Seq != 0 || sbpResp.RequestSeq != 3 || len(sbpResp.Body.Breakpoints) != 0 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=3, len(Breakpoints)=0", sbpResp)
 		}
 
+		// 4 >> setExceptionBreakpoints, << setExceptionBreakpoints
 		client.SetExceptionBreakpointsRequest()
-		sResp := client.ExpectSetExceptionBreakpointsResponse(t)
-		if sResp.Seq != 0 || sResp.RequestSeq != 2 {
-			t.Errorf("got %#v, want Seq=0, RequestSeq=2", sResp)
+		sebpResp := client.ExpectSetExceptionBreakpointsResponse(t)
+		if sebpResp.Seq != 0 || sebpResp.RequestSeq != 4 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=4", sebpResp)
 		}
 
+		// 5 >> configurationDone, << stopped, << configurationDone
 		client.ConfigurationDoneRequest()
 		stopEvent := client.ExpectStoppedEvent(t)
 		if stopEvent.Seq != 0 ||
 			stopEvent.Body.Reason != "breakpoint" ||
 			stopEvent.Body.ThreadId != 1 ||
 			!stopEvent.Body.AllThreadsStopped {
-			t.Errorf("got %#v, want Seq=0, Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", stopEvent)
+			t.Errorf("\ngot %#v\nwant Seq=0, Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", stopEvent)
 		}
-
 		cdResp := client.ExpectConfigurationDoneResponse(t)
-		if cdResp.Seq != 0 || cdResp.RequestSeq != 3 {
-			t.Errorf("got %#v, want Seq=0, RequestSeq=3", cdResp)
+		if cdResp.Seq != 0 || cdResp.RequestSeq != 5 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=5", cdResp)
+		}
+
+		// 6 >> threads, << threads
+		client.ThreadsRequest()
+		tResp := client.ExpectThreadsResponse(t)
+		if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) != 1 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)=1", tResp)
+		}
+		if tResp.Body.Threads[0].Id != 1 || tResp.Body.Threads[0].Name != "Dummy" {
+			t.Errorf("\ngot %#v\nwant Id=1, Name=\"Dummy\"", tResp)
 		}
 
+		// 7 >> threads, << threads
+		client.ThreadsRequest()
+		tResp = client.ExpectThreadsResponse(t)
+		if tResp.Seq != 0 || tResp.RequestSeq != 7 || len(tResp.Body.Threads) != 1 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7 len(Threads)=1", tResp)
+		}
+
+		// 8 >> stackTrace, << stackTrace
+		client.StackTraceRequest()
+		stResp := client.ExpectErrorResponse(t)
+		if stResp.Seq != 0 || stResp.RequestSeq != 8 || stResp.Message != "Unsupported command" {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=8 Message=\"Unsupported command\"", stResp)
+		}
+
+		// 9 >> stackTrace, << stackTrace
+		client.StackTraceRequest()
+		stResp = client.ExpectErrorResponse(t)
+		if stResp.Seq != 0 || stResp.RequestSeq != 9 || stResp.Message != "Unsupported command" {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=9 Message=\"Unsupported command\"", stResp)
+		}
+
+		// 10 >> continue, << continue, << terminated
 		client.ContinueRequest(1)
 		contResp := client.ExpectContinueResponse(t)
-		if contResp.Seq != 0 || contResp.RequestSeq != 4 {
-			t.Errorf("got %#v, want Seq=0, RequestSeq=4", contResp)
+		if contResp.Seq != 0 || contResp.RequestSeq != 10 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=10", contResp)
+		}
+		termEvent := client.ExpectTerminatedEvent(t)
+		if termEvent.Seq != 0 {
+			t.Errorf("\ngot %#v\nwant Seq=0", termEvent)
 		}
 
-		termEv := client.ExpectTerminatedEvent(t)
-		if termEv.Seq != 0 {
-			t.Errorf("got %#v, want Seq=0", termEv)
+		// 11 >> disconnect, << disconnect
+		client.DisconnectRequest()
+		dResp := client.ExpectDisconnectResponse(t)
+		if dResp.Seq != 0 || dResp.RequestSeq != 11 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=11", dResp)
 		}
+	})
+}
 
+// Like the test above, except the program is configured to continue on entry.
+func TestContinueOnEntry(t *testing.T) {
+	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
+		// 1 >> initialize, << initialize
+		client.InitializeRequest()
+		client.ExpectInitializeResponse(t)
+
+		// 2 >> launch, << initialized, << launch
+		client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+		client.ExpectInitializedEvent(t)
+		client.ExpectLaunchResponse(t)
+
+		// 3 >> setBreakpoints, << setBreakpoints
+		client.SetBreakpointsRequest(fixture.Source, nil)
+		client.ExpectSetBreakpointsResponse(t)
+
+		// 4 >> setExceptionBreakpoints, << setExceptionBreakpoints
+		client.SetExceptionBreakpointsRequest()
+		client.ExpectSetExceptionBreakpointsResponse(t)
+
+		// 5 >> configurationDone, << configurationDone
+		client.ConfigurationDoneRequest()
+		client.ExpectConfigurationDoneResponse(t)
+		// "Continue" happens behind the scenes
+
+		// For now continue is blocking and runs until a stop or
+		// termination. But once we upgrade the server to be async,
+		// a simultaneous threads request can be made while continue
+		// is running. Note that vscode-go just keeps track of the
+		// continue state and would just return a dummy response
+		// without talking to debugger if continue was in progress.
+		// TODO(polina): test this once it is possible
+
+		client.ExpectTerminatedEvent(t)
+
+		// It is possible for the program to terminate before the initial
+		// threads request is processed.
+
+		// 6 >> threads, << threads
+		client.ThreadsRequest()
+		tResp := client.ExpectThreadsResponse(t)
+		if tResp.Seq != 0 || tResp.RequestSeq != 6 || len(tResp.Body.Threads) != 0 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=6 len(Threads)=0", tResp)
+		}
+
+		// 7 >> disconnect, << disconnect
 		client.DisconnectRequest()
 		dResp := client.ExpectDisconnectResponse(t)
-		if dResp.Seq != 0 || dResp.RequestSeq != 5 {
-			t.Errorf("got %#v, want Seq=0, RequestSeq=5", dResp)
+		if dResp.Seq != 0 || dResp.RequestSeq != 7 {
+			t.Errorf("\ngot %#v\nwant Seq=0, RequestSeq=7", dResp)
 		}
 	})
 }
 
+// TestSetBreakpoint corresponds to a debug session that is configured to
+// continue on entry with a pre-set breakpoint.
 func TestSetBreakpoint(t *testing.T) {
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 		client.InitializeRequest()
@@ -133,10 +259,7 @@ func TestSetBreakpoint(t *testing.T) {
 
 		client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
 		client.ExpectInitializedEvent(t)
-		launchResp := client.ExpectLaunchResponse(t)
-		if launchResp.RequestSeq != 1 {
-			t.Errorf("got %#v, want RequestSeq=1", launchResp)
-		}
+		client.ExpectLaunchResponse(t)
 
 		client.SetBreakpointsRequest(fixture.Source, []int{8, 100})
 		sResp := client.ExpectSetBreakpointsResponse(t)
@@ -152,31 +275,50 @@ func TestSetBreakpoint(t *testing.T) {
 		client.ExpectSetExceptionBreakpointsResponse(t)
 
 		client.ConfigurationDoneRequest()
-		cdResp := client.ExpectConfigurationDoneResponse(t)
-		if cdResp.RequestSeq != 4 {
-			t.Errorf("got %#v, want RequestSeq=4", cdResp)
-		}
+		client.ExpectConfigurationDoneResponse(t)
+		// This triggers "continue"
+
+		// TODO(polina): add a no-op threads request
+		// with dummy response here once server becomes async
+		// to match what happens in VS Code.
 
-		client.ContinueRequest(1)
 		stopEvent1 := client.ExpectStoppedEvent(t)
 		if stopEvent1.Body.Reason != "breakpoint" ||
 			stopEvent1.Body.ThreadId != 1 ||
 			!stopEvent1.Body.AllThreadsStopped {
 			t.Errorf("got %#v, want Body={Reason=\"breakpoint\", ThreadId=1, AllThreadsStopped=true}", stopEvent1)
 		}
-		client.ExpectContinueResponse(t)
+
+		client.ThreadsRequest()
+		tResp := client.ExpectThreadsResponse(t)
+		if len(tResp.Body.Threads) < 2 { // 1 main + runtime
+			t.Errorf("\ngot  %#v\nwant len(Threads)>1", tResp.Body.Threads)
+		}
+		// TODO(polina): can we reliably test for these values?
+		wantMain := dap.Thread{Id: 1, Name: "main.Increment"}
+		wantRuntime := dap.Thread{Id: 2, Name: "runtime.gopark"}
+		for _, got := range tResp.Body.Threads {
+			if !reflect.DeepEqual(got, wantMain) && !strings.HasPrefix(got.Name, "runtime") {
+				t.Errorf("\ngot  %#v\nwant []dap.Thread{%#v, %#v, ...}", tResp.Body.Threads, wantMain, wantRuntime)
+			}
+		}
+
+		// TODO(polina): add other status checking requests
+		// that are not yet supported (stackTrace, scopes, variables)
 
 		client.ContinueRequest(1)
-		client.ExpectTerminatedEvent(t)
 		client.ExpectContinueResponse(t)
+		// "Continue" is triggered after the response is sent
 
+		client.ExpectTerminatedEvent(t)
 		client.DisconnectRequest()
 		client.ExpectDisconnectResponse(t)
 	})
 }
 
 // runDebugSesion is a helper for executing the standard init and shutdown
-// sequences while specifying unique launch criteria via parameters.
+// sequences for a program that does not stop on entry
+// while specifying unique launch criteria via parameters.
 func runDebugSession(t *testing.T, client *daptest.Client, launchRequest func()) {
 	client.InitializeRequest()
 	client.ExpectInitializeResponse(t)
@@ -185,9 +327,14 @@ func runDebugSession(t *testing.T, client *daptest.Client, launchRequest func())
 	client.ExpectInitializedEvent(t)
 	client.ExpectLaunchResponse(t)
 
+	// Skip no-op setBreakpoints
+	// Skip no-op setExceptionBreakpoints
+
 	client.ConfigurationDoneRequest()
 	client.ExpectConfigurationDoneResponse(t)
 
+	// Program automatically continues to completion
+
 	client.ExpectTerminatedEvent(t)
 	client.DisconnectRequest()
 	client.ExpectDisconnectResponse(t)
@@ -218,7 +365,7 @@ func TestLaunchTestRequest(t *testing.T) {
 
 func TestBadLaunchRequests(t *testing.T) {
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
-		seqCnt := 0
+		seqCnt := 1
 		expectFailedToLaunch := func(response *dap.ErrorResponse) {
 			t.Helper()
 			if response.RequestSeq != seqCnt {
diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go
index 27e3d051..980f0cb6 100644
--- a/service/test/integration1_test.go
+++ b/service/test/integration1_test.go
@@ -919,8 +919,13 @@ func Test1Disasm(t *testing.T) {
 			t.Fatal("PC instruction not found")
 		}
 
-		startinstr := getCurinstr(d3)
+		if runtime.GOARCH == "386" && buildMode == "pie" {
+			// Skip the rest of the test because on intel 386 with PIE build mode
+			// the compiler will insert calls to __x86.get_pc_thunk which do not have DIEs and we can't resolve.
+			return
+		}
 
+		startinstr := getCurinstr(d3)
 		count := 0
 		for {
 			if count > 20 {
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index 6a635de4..9cf441c1 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -1026,8 +1026,13 @@ func TestDisasm(t *testing.T) {
 			t.Fatal("PC instruction not found")
 		}
 
-		startinstr := getCurinstr(d3)
+		if runtime.GOARCH == "386" && buildMode == "pie" {
+			// Skip the rest of the test because on intel 386 with PIE build mode
+			// the compiler will insert calls to __x86.get_pc_thunk which do not have DIEs and we can't resolve.
+			return
+		}
 
+		startinstr := getCurinstr(d3)
 		count := 0
 		for {
 			if count > 20 {
@@ -1154,13 +1159,14 @@ func TestSkipPrologue2(t *testing.T) {
 		callme3 := findLocationHelper(t, c, "main.callme3", false, 1, 0)[0]
 		callme3Z := uint64(clientEvalVariable(t, c, "main.callme3").Addr)
 		ver, _ := goversion.Parse(runtime.Version())
-		if ver.Major < 0 || ver.AfterOrEqual(goversion.GoVer18Beta) {
+
+		if (ver.Major < 0 || ver.AfterOrEqual(goversion.GoVer18Beta)) && runtime.GOARCH != "386" {
 			findLocationHelper(t, c, "callme.go:19", false, 1, callme3)
 		} else {
 			// callme3 does not have local variables therefore the first line of the
 			// function is immediately after the prologue
-			// This is only true before 1.8 where frame pointer chaining introduced a
-			// bit of prologue even for functions without local variables
+			// This is only true before go1.8 or on Intel386 where frame pointer chaining
+			// introduced a bit of prologue even for functions without local variables
 			findLocationHelper(t, c, "callme.go:19", false, 1, callme3Z)
 		}
 		if callme3 == callme3Z {
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index 00cf95ae..418fcf3b 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -1001,13 +1001,11 @@ func TestPackageRenames(t *testing.T) {
 		// Renamed imports
 		{"badexpr", true, `interface {}(*go/ast.BadExpr) *{From: 1, To: 2}`, "", "interface {}", nil},
 		{"req", true, `interface {}(*net/http.Request) *{Method: "amethod", …`, "", "interface {}", nil},
-		{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
 
 		// Package name that doesn't match import path
 		{"iface3", true, `interface {}(*github.com/go-delve/delve/_fixtures/internal/dir0/renamedpackage.SomeType) *{A: true}`, "", "interface {}", nil},
 
 		// Interfaces to anonymous types
-		{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
 		{"dir0someType", true, "interface {}(*github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType) *{X: 3}", "", "interface {}", nil},
 		{"dir1someType", true, "interface {}(github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType) {X: 1, Y: 2}", "", "interface {}", nil},
 		{"amap3", true, "interface {}(map[github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType]github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType) [{X: 4}: {X: 5, Y: 6}, ]", "", "interface {}", nil},
@@ -1022,6 +1020,16 @@ func TestPackageRenames(t *testing.T) {
 		{`"dir1/pkg".A`, false, "1", "", "int", nil},
 	}
 
+	testcases_i386 := []varTest{
+		{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
+		{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
+	}
+
+	testcases_64bit := []varTest{
+		{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
+		{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
+	}
+
 	testcases1_8 := []varTest{
 		// before 1.9 embedded struct fields have fieldname == type
 		{"astruct2", true, `interface {}(*struct { github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType; X int }) *{github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType: github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType {X: 1, Y: 2}, X: 10}`, "", "interface {}", nil},
@@ -1054,6 +1062,12 @@ func TestPackageRenames(t *testing.T) {
 		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
 			testPackageRenamesHelper(t, p, testcases1_13)
 		}
+
+		if runtime.GOARCH == "386" {
+			testPackageRenamesHelper(t, p, testcases_i386)
+		} else {
+			testPackageRenamesHelper(t, p, testcases_64bit)
+		}
 	})
 }