proc: implement stacktrace of arm64 (#1780)

* proc: separate amd64-arch code

separate amd64 code about stacktrace, so we can add arm64 stacktrace code.

* proc: implemente stacktrace of arm64

* delve now can use stack, frame commands on arm64-arch debug.

Co-authored-by: tykcd996 <tang.yuke@zte.com.cn>
Co-authored-by: hengwu0 <wu.heng@zte.com.cn>

* test: remove skip-code of stacktrace on arm64

* add LR DWARF register and remove skip-code for fixed tests

* proc: fix the Continue command after the hardcoded breakpoint on arm64

Arm64 use hardware breakpoint, and it will not set PC to the next instruction like amd64. We should move PC in both runtime.breakpoints and hardcoded breakpoints(probably cgo).

* proc: implement cgo stacktrace on arm64

* proc: combine amd64_stack.go and arm64_stack.go file

* proc: reorganize the stacktrace code

* move SwitchStack function arch-related
* fix Continue command after manual stop on arm64
* add timeout flag to make.go to enable infinite timeouts

Co-authored-by: aarzilli <alessandro.arzilli@gmail.com>
Co-authored-by: hengwu0 <wu.heng@zte.com.cn>

Co-authored-by: tykcd996 <56993522+tykcd996@users.noreply.github.com>
Co-authored-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
This commit is contained in:
hengwu0 2020-01-22 01:11:20 +08:00 committed by Derek Parker
parent 7fe81ca1d6
commit 3f7571ec30
16 changed files with 290 additions and 216 deletions

@ -1,7 +1,6 @@
package main package main
// #cgo CFLAGS: -g -Wall -O0 // #cgo CFLAGS: -g -Wall -O0
/* /*
void sigsegv(int x) { void sigsegv(int x) {
int *p = NULL; int *p = NULL;

@ -2,7 +2,11 @@
#include "_cgo_export.h" #include "_cgo_export.h"
#ifdef __amd64__
#define BREAKPOINT asm("int3;") #define BREAKPOINT asm("int3;")
#elif __aarch64__
#define BREAKPOINT asm("brk 0;")
#endif
void helloworld_pt2(int x) { void helloworld_pt2(int x) {
BREAKPOINT; BREAKPOINT;

@ -17,6 +17,7 @@ type DwarfRegisters struct {
PCRegNum uint64 PCRegNum uint64
SPRegNum uint64 SPRegNum uint64
BPRegNum uint64 BPRegNum uint64
LRRegNum uint64
} }
type DwarfRegister struct { type DwarfRegister struct {

@ -2,6 +2,7 @@ package proc
import ( import (
"encoding/binary" "encoding/binary"
"strings"
"github.com/go-delve/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
@ -159,6 +160,124 @@ func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *
return fctxt return fctxt
} }
// cgocallSPOffsetSaveSlot is the offset from systemstack.SP where
// (goroutine.SP - StackHi) is saved in runtime.asmcgocall after the stack
// switch happens.
const amd64cgocallSPOffsetSaveSlot = 0x28
// 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 (a *AMD64) SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool {
if it.frame.Current.Fn == nil {
return false
}
switch it.frame.Current.Fn.Name {
case "runtime.asmcgocall":
if it.top || !it.systemstack {
return false
}
// This function is called by a goroutine to execute a C function and
// switches from the goroutine stack to the system stack.
// Since we are unwinding the stack from callee to caller we have to switch
// from the system stack to the goroutine stack.
off, _ := readIntRaw(it.mem, uintptr(it.regs.SP()+amd64cgocallSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize())) // reads "offset of SP from StackHi" from where runtime.asmcgocall saved it
oldsp := it.regs.SP()
it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(int64(it.stackhi) - off)
// runtime.asmcgocall can also be called from inside the system stack,
// in that case no stack switch actually happens
if it.regs.SP() == oldsp {
return false
}
it.systemstack = false
// advances to the next frame in the call stack
it.frame.addrret = uint64(int64(it.regs.SP()) + int64(it.bi.Arch.PtrSize()))
it.frame.Ret, _ = readUintRaw(it.mem, uintptr(it.frame.addrret), int64(it.bi.Arch.PtrSize()))
it.pc = it.frame.Ret
it.top = false
return true
case "runtime.cgocallback_gofunc":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_amd64.s
//
// When a C functions calls back into go it will eventually call into
// runtime.cgocallback_gofunc which is the function that does the stack
// switch from the system stack back into the goroutine stack
// Since we are going backwards on the stack here we see the transition
// as goroutine stack -> system stack.
if it.top || it.systemstack {
return false
}
if it.g0_sched_sp <= 0 {
return false
}
// entering the system stack
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g0_sched_sp
// reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack
it.g0_sched_sp, _ = readUintRaw(it.mem, uintptr(it.regs.SP()), int64(it.bi.Arch.PtrSize()))
it.top = false
callFrameRegs, ret, retaddr := it.advanceRegs()
frameOnSystemStack := it.newStackframe(ret, retaddr)
it.pc = frameOnSystemStack.Ret
it.regs = callFrameRegs
it.systemstack = true
return true
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. // RegSize returns the size (in bytes) of register regnum.
// The mapping between hardware registers and DWARF registers is specified // The mapping between hardware registers and DWARF registers is specified
// in the System V ABI AMD64 Architecture Processor Supplement page 57, // in the System V ABI AMD64 Architecture Processor Supplement page 57,
@ -288,7 +407,7 @@ func (a *AMD64) RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.
// AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in // AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in
// PC, SP, and BP registers in the format used by the DWARF expression interpreter. // PC, SP, and BP registers in the format used by the DWARF expression interpreter.
func (a *AMD64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp uint64) op.DwarfRegisters { func (a *AMD64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters {
dregs := make([]*op.DwarfRegister, amd64DwarfIPRegNum+1) dregs := make([]*op.DwarfRegister, amd64DwarfIPRegNum+1)
dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc) dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc)
dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp) dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp)

@ -17,9 +17,10 @@ type Arch interface {
BreakpointSize() int BreakpointSize() int
DerefTLS() bool DerefTLS() bool
FixFrameUnwindContext(*frame.FrameContext, uint64, *BinaryInfo) *frame.FrameContext FixFrameUnwindContext(*frame.FrameContext, uint64, *BinaryInfo) *frame.FrameContext
SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool
RegSize(uint64) int RegSize(uint64) int
RegistersToDwarfRegisters(uint64, Registers) op.DwarfRegisters RegistersToDwarfRegisters(uint64, Registers) op.DwarfRegisters
AddrAndStackRegsToDwarfRegisters(uint64, uint64, uint64, uint64) op.DwarfRegisters AddrAndStackRegsToDwarfRegisters(uint64, uint64, uint64, uint64, uint64) op.DwarfRegisters
} }
const ( const (

@ -2,6 +2,7 @@ package proc
import ( import (
"encoding/binary" "encoding/binary"
"strings"
"github.com/go-delve/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
@ -28,6 +29,7 @@ type ARM64 struct {
const ( const (
arm64DwarfIPRegNum uint64 = 32 arm64DwarfIPRegNum uint64 = 32
arm64DwarfSPRegNum uint64 = 31 arm64DwarfSPRegNum uint64 = 31
arm64DwarfLRRegNum uint64 = 30
arm64DwarfBPRegNum uint64 = 29 arm64DwarfBPRegNum uint64 = 29
) )
@ -154,10 +156,116 @@ func (a *ARM64) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *
Offset: 0, Offset: 0,
} }
} }
if fctxt.Regs[arm64DwarfLRRegNum].Rule == frame.RuleUndefined {
fctxt.Regs[arm64DwarfLRRegNum] = frame.DWRule{
Rule: frame.RuleFramePointer,
Reg: arm64DwarfLRRegNum,
Offset: 0,
}
}
return fctxt return fctxt
} }
const arm64cgocallSPOffsetSaveSlot = 0x8
const prevG0schedSPOffsetSaveSlot = 0x10
const spAlign = 16
func (a *ARM64) SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool {
if it.frame.Current.Fn != nil {
switch it.frame.Current.Fn.Name {
case "runtime.asmcgocall", "runtime.cgocallback_gofunc", "runtime.sigpanic":
//do nothing
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
// Look for "top of stack" functions.
it.atend = true
return true
case "crosscall2":
//The offsets get from runtime/cgo/asm_arm64.s:10
newsp, _ := readUintRaw(it.mem, uintptr(it.regs.SP()+8*24), int64(it.bi.Arch.PtrSize()))
newbp, _ := readUintRaw(it.mem, uintptr(it.regs.SP()+8*14), int64(it.bi.Arch.PtrSize()))
newlr, _ := readUintRaw(it.mem, uintptr(it.regs.SP()+8*15), int64(it.bi.Arch.PtrSize()))
if it.regs.Reg(it.regs.BPRegNum) != nil {
it.regs.Reg(it.regs.BPRegNum).Uint64Val = uint64(newbp)
} else {
reg, _ := it.readRegisterAt(it.regs.BPRegNum, it.regs.SP()+8*14)
it.regs.AddReg(it.regs.BPRegNum, reg)
}
it.regs.Reg(it.regs.LRRegNum).Uint64Val = uint64(newlr)
it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(newsp)
it.pc = newlr
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.
it.switchToGoroutineStack()
return true
}
}
}
_, _, fn := it.bi.PCToLine(it.frame.Ret)
if fn == nil {
return false
}
switch fn.Name {
case "runtime.asmcgocall":
if !it.systemstack {
return false
}
// This function is called by a goroutine to execute a C function and
// switches from the goroutine stack to the system stack.
// Since we are unwinding the stack from callee to caller we have to switch
// from the system stack to the goroutine stack.
off, _ := readIntRaw(it.mem, uintptr(callFrameRegs.SP()+arm64cgocallSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize()))
oldsp := callFrameRegs.SP()
newsp := uint64(int64(it.stackhi) - off)
// runtime.asmcgocall can also be called from inside the system stack,
// in that case no stack switch actually happens
if newsp == oldsp {
return false
}
it.systemstack = false
callFrameRegs.Reg(callFrameRegs.SPRegNum).Uint64Val = uint64(int64(newsp))
return false
case "runtime.cgocallback_gofunc":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_arm64.s
//
// When a C functions calls back into go it will eventually call into
// runtime.cgocallback_gofunc which is the function that does the stack
// switch from the system stack back into the goroutine stack
// Since we are going backwards on the stack here we see the transition
// as goroutine stack -> system stack.
if it.systemstack {
return false
}
if it.g0_sched_sp <= 0 {
return false
}
// entering the system stack
callFrameRegs.Reg(callFrameRegs.SPRegNum).Uint64Val = it.g0_sched_sp
// reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack
it.g0_sched_sp, _ = readUintRaw(it.mem, uintptr(callFrameRegs.SP()+prevG0schedSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize()))
it.systemstack = true
return false
}
return false
}
func (a *ARM64) RegSize(regnum uint64) int { func (a *ARM64) RegSize(regnum uint64) int {
// fp registers // fp registers
if regnum >= 64 && regnum <= 95 { if regnum >= 64 && regnum <= 95 {
@ -257,6 +365,9 @@ func (a *ARM64) RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.
dregs[arm64DwarfIPRegNum] = op.DwarfRegisterFromUint64(regs.PC()) dregs[arm64DwarfIPRegNum] = op.DwarfRegisterFromUint64(regs.PC())
dregs[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(regs.SP()) dregs[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(regs.SP())
dregs[arm64DwarfBPRegNum] = op.DwarfRegisterFromUint64(regs.BP()) dregs[arm64DwarfBPRegNum] = op.DwarfRegisterFromUint64(regs.BP())
if lr, err := regs.Get(int(arm64asm.X30)); err != nil {
dregs[arm64DwarfLRRegNum] = op.DwarfRegisterFromUint64(lr)
}
for dwarfReg, asmReg := range arm64DwarfToHardware { for dwarfReg, asmReg := range arm64DwarfToHardware {
v, err := regs.Get(int(asmReg)) v, err := regs.Get(int(asmReg))
@ -272,16 +383,18 @@ func (a *ARM64) RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.
PCRegNum: arm64DwarfIPRegNum, PCRegNum: arm64DwarfIPRegNum,
SPRegNum: arm64DwarfSPRegNum, SPRegNum: arm64DwarfSPRegNum,
BPRegNum: arm64DwarfBPRegNum, BPRegNum: arm64DwarfBPRegNum,
LRRegNum: arm64DwarfLRRegNum,
} }
} }
// AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in // AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in
// PC, SP, and BP registers in the format used by the DWARF expression interpreter. // PC, SP, and BP registers in the format used by the DWARF expression interpreter.
func (a *ARM64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp uint64) op.DwarfRegisters { func (a *ARM64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters {
dregs := make([]*op.DwarfRegister, arm64DwarfIPRegNum+1) dregs := make([]*op.DwarfRegister, arm64DwarfIPRegNum+1)
dregs[arm64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc) dregs[arm64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc)
dregs[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp) dregs[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp)
dregs[arm64DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp) dregs[arm64DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp)
dregs[arm64DwarfLRRegNum] = op.DwarfRegisterFromUint64(lr)
return op.DwarfRegisters{ return op.DwarfRegisters{
StaticBase: staticBase, StaticBase: staticBase,
@ -290,5 +403,6 @@ func (a *ARM64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp uint64)
PCRegNum: arm64DwarfIPRegNum, PCRegNum: arm64DwarfIPRegNum,
SPRegNum: arm64DwarfSPRegNum, SPRegNum: arm64DwarfSPRegNum,
BPRegNum: arm64DwarfBPRegNum, BPRegNum: arm64DwarfBPRegNum,
LRRegNum: arm64DwarfLRRegNum,
} }
} }

@ -103,7 +103,7 @@ func (r *ARM64Registers) GAddr() (uint64, bool) {
return r.Regs.Regs[28], true return r.Regs.Regs[28], true
} }
// Get returns the value of the n-th register (in x86asm order). // Get returns the value of the n-th register (in arm64asm order).
func (r *ARM64Registers) Get(n int) (uint64, error) { func (r *ARM64Registers) Get(n int) (uint64, error) {
reg := arm64asm.Reg(n) reg := arm64asm.Reg(n)

@ -1,6 +1,7 @@
package proc package proc
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@ -212,13 +213,14 @@ func Continue(dbp Process) error {
return conditionErrors(threads) return conditionErrors(threads)
} }
g, _ := GetG(curthread) g, _ := GetG(curthread)
arch := dbp.BinInfo().Arch
switch { switch {
case loc.Fn.Name == "runtime.breakpoint": case loc.Fn.Name == "runtime.breakpoint":
// In linux-arm64, PtraceSingleStep seems cannot step over BRK instruction // In linux-arm64, PtraceSingleStep seems cannot step over BRK instruction
// (linux-arm64 feature or kernel bug maybe). // (linux-arm64 feature or kernel bug maybe).
if !curthread.Arch().BreakInstrMovesPC() { if !arch.BreakInstrMovesPC() {
curthread.SetPC(loc.PC + uint64(curthread.Arch().BreakpointSize())) curthread.SetPC(loc.PC + uint64(arch.BreakpointSize()))
} }
// Single-step current thread until we exit runtime.breakpoint and // Single-step current thread until we exit runtime.breakpoint and
// runtime.Breakpoint. // runtime.Breakpoint.
@ -229,7 +231,15 @@ func Continue(dbp Process) error {
} }
return conditionErrors(threads) return conditionErrors(threads)
case g == nil || dbp.Common().fncallForG[g.ID] == nil: case g == nil || dbp.Common().fncallForG[g.ID] == nil:
// a hardcoded breakpoint somewhere else in the code (probably cgo) // a hardcoded breakpoint somewhere else in the code (probably cgo), or manual stop in cgo
if !arch.BreakInstrMovesPC() {
bpsize := arch.BreakpointSize()
bp := make([]byte, bpsize)
_, err = dbp.CurrentThread().ReadMemory(bp, uintptr(loc.PC))
if bytes.Equal(bp, arch.BreakpointInstruction()) {
curthread.SetPC(loc.PC + uint64(bpsize))
}
}
return conditionErrors(threads) return conditionErrors(threads)
} }
case curbp.Active && curbp.Internal: case curbp.Active && curbp.Internal:

@ -820,9 +820,6 @@ func (l1 *loc) match(l2 proc.Stackframe) bool {
} }
func TestStacktrace(t *testing.T) { func TestStacktrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
stacks := [][]loc{ stacks := [][]loc{
{{4, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}}, {{4, "main.stacktraceme"}, {8, "main.func1"}, {16, "main.main"}},
{{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}}, {{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}},
@ -858,9 +855,6 @@ func TestStacktrace(t *testing.T) {
} }
func TestStacktrace2(t *testing.T) { func TestStacktrace2(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
withTestProcess("retstack", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("retstack", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
@ -909,9 +903,6 @@ func stackMatch(stack []loc, locations []proc.Stackframe, skipRuntime bool) bool
} }
func TestStacktraceGoroutine(t *testing.T) { func TestStacktraceGoroutine(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}} mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
mainStack[0].line = 15 mainStack[0].line = 15
@ -1235,9 +1226,6 @@ func TestVariableEvaluation(t *testing.T) {
} }
func TestFrameEvaluation(t *testing.T) { func TestFrameEvaluation(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "main.stacktraceme") setFunctionBreakpoint(p, t, "main.stacktraceme")
@ -1718,9 +1706,6 @@ func TestIssue384(t *testing.T) {
} }
func TestIssue332_Part1(t *testing.T) { func TestIssue332_Part1(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// Next shouldn't step inside a function call // Next shouldn't step inside a function call
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("issue332", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("issue332", t, func(p proc.Process, fixture protest.Fixture) {
@ -1742,9 +1727,6 @@ func TestIssue332_Part1(t *testing.T) {
} }
func TestIssue332_Part2(t *testing.T) { func TestIssue332_Part2(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// Step should skip a function's prologue // Step should skip a function's prologue
// In some parts of the prologue, for some functions, the FDE data is incorrect // In some parts of the prologue, for some functions, the FDE data is incorrect
// which leads to 'next' and 'stack' failing with error "could not find FDE for PC: <garbage>" // which leads to 'next' and 'stack' failing with error "could not find FDE for PC: <garbage>"
@ -1902,9 +1884,6 @@ func TestCmdLineArgs(t *testing.T) {
} }
func TestIssue462(t *testing.T) { func TestIssue462(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// Stacktrace of Goroutine 0 fails with an error // Stacktrace of Goroutine 0 fails with an error
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return return
@ -1934,9 +1913,6 @@ func TestNextParked(t *testing.T) {
if runtime.GOOS == "freebsd" { if runtime.GOOS == "freebsd" {
t.Skip("test is not valid on FreeBSD") t.Skip("test is not valid on FreeBSD")
} }
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) {
bp := setFunctionBreakpoint(p, t, "main.sayhi") bp := setFunctionBreakpoint(p, t, "main.sayhi")
@ -1987,9 +1963,6 @@ func TestNextParked(t *testing.T) {
} }
func TestStepParked(t *testing.T) { func TestStepParked(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
if runtime.GOOS == "freebsd" { if runtime.GOOS == "freebsd" {
t.Skip("test is not valid on FreeBSD") t.Skip("test is not valid on FreeBSD")
} }
@ -2335,9 +2308,6 @@ func TestStepConcurrentDirect(t *testing.T) {
if runtime.GOOS == "freebsd" { if runtime.GOOS == "freebsd" {
t.Skip("test is not valid on FreeBSD") t.Skip("test is not valid on FreeBSD")
} }
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 37) bp := setFileBreakpoint(p, t, fixture.Source, 37)
@ -2713,9 +2683,6 @@ func getg(goid int, gs []*proc.G) *proc.G {
} }
func TestStacktraceWithBarriers(t *testing.T) { func TestStacktraceWithBarriers(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// Go's Garbage Collector will insert stack barriers into stacks. // Go's Garbage Collector will insert stack barriers into stacks.
// This stack barrier is inserted by overwriting the return address for the // This stack barrier is inserted by overwriting the return address for the
// stack frame with the address of runtime.stackBarrier. // stack frame with the address of runtime.stackBarrier.
@ -3281,9 +3248,6 @@ func frameInFile(frame proc.Stackframe, file string) bool {
} }
func TestCgoStacktrace(t *testing.T) { func TestCgoStacktrace(t *testing.T) {
if runtime.GOARCH != "amd64" {
t.Skip("amd64 only")
}
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
ver, _ := goversion.Parse(runtime.Version()) ver, _ := goversion.Parse(runtime.Version())
if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) { if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
@ -3389,9 +3353,6 @@ func TestCgoStacktrace(t *testing.T) {
} }
func TestCgoSources(t *testing.T) { func TestCgoSources(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Cgo-debug for now")
}
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
ver, _ := goversion.Parse(runtime.Version()) ver, _ := goversion.Parse(runtime.Version())
if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) { if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
@ -3417,9 +3378,6 @@ func TestCgoSources(t *testing.T) {
} }
func TestSystemstackStacktrace(t *testing.T) { func TestSystemstackStacktrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// check that we can follow a stack switch initiated by runtime.systemstack() // check that we can follow a stack switch initiated by runtime.systemstack()
withTestProcess("panic", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("panic", t, func(p proc.Process, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "runtime.startpanic_m") setFunctionBreakpoint(p, t, "runtime.startpanic_m")
@ -3438,9 +3396,6 @@ func TestSystemstackStacktrace(t *testing.T) {
} }
func TestSystemstackOnRuntimeNewstack(t *testing.T) { func TestSystemstackOnRuntimeNewstack(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// The bug being tested here manifests as follows: // The bug being tested here manifests as follows:
// - set a breakpoint somewhere or interrupt the program with Ctrl-C // - set a breakpoint somewhere or interrupt the program with Ctrl-C
// - try to look at stacktraces of other goroutines // - try to look at stacktraces of other goroutines
@ -3474,9 +3429,6 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) {
} }
func TestIssue1034(t *testing.T) { func TestIssue1034(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace and Cgo-debug for now")
}
// The external linker on macOS produces an abbrev for DW_TAG_subprogram // The external linker on macOS produces an abbrev for DW_TAG_subprogram
// without the "has children" flag, we should support this. // without the "has children" flag, we should support this.
withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) {
@ -3494,9 +3446,6 @@ func TestIssue1034(t *testing.T) {
} }
func TestIssue1008(t *testing.T) { func TestIssue1008(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace and Cgo-debug for now")
}
// The external linker on macOS inserts "end of sequence" extended opcodes // The external linker on macOS inserts "end of sequence" extended opcodes
// in debug_line. which we should support correctly. // in debug_line. which we should support correctly.
withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) {
@ -3675,9 +3624,6 @@ func TestAllPCsForFileLines(t *testing.T) {
} }
func TestInlinedStacktraceAndVariables(t *testing.T) { func TestInlinedStacktraceAndVariables(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}) { if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}) {
// Versions of go before 1.10 do not have DWARF information for inlined calls // Versions of go before 1.10 do not have DWARF information for inlined calls
t.Skip("inlining not supported") t.Skip("inlining not supported")
@ -4124,7 +4070,7 @@ func TestNextUnknownInstr(t *testing.T) {
func TestReadDeferArgs(t *testing.T) { func TestReadDeferArgs(t *testing.T) {
if runtime.GOARCH == "arm64" { if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now") t.Skip("arm64 does not support ReadDeferArgs for now")
} }
var tests = []struct { var tests = []struct {
frame, deferCall int frame, deferCall int
@ -4475,9 +4421,6 @@ func TestIssue1615(t *testing.T) {
} }
func TestCgoStacktrace2(t *testing.T) { func TestCgoStacktrace2(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace and Cgo-debug for now")
}
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("fixture crashes go runtime on windows") t.Skip("fixture crashes go runtime on windows")
} }

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/constant" "go/constant"
"strings"
"github.com/go-delve/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
@ -127,7 +126,7 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
so := g.variable.bi.PCToImage(g.PC) so := g.variable.bi.PCToImage(g.PC)
return newStackIterator( return newStackIterator(
g.variable.bi, g.variable.mem, g.variable.bi, g.variable.mem,
g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP), g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
g.stackhi, stkbar, g.stkbarPos, g, opts), nil g.stackhi, stkbar, g.stkbarPos, g, opts), nil
} }
@ -242,6 +241,7 @@ func (it *stackIterator) Next() bool {
if it.err != nil || it.atend { if it.err != nil || it.atend {
return false return false
} }
callFrameRegs, ret, retaddr := it.advanceRegs() callFrameRegs, ret, retaddr := it.advanceRegs()
it.frame = it.newStackframe(ret, retaddr) it.frame = it.newStackframe(ret, retaddr)
@ -252,7 +252,7 @@ func (it *stackIterator) Next() bool {
} }
if it.opts&StacktraceSimple == 0 { if it.opts&StacktraceSimple == 0 {
if it.switchStack() { if it.bi.Arch.SwitchStack(it, &callFrameRegs) {
return true return true
} }
} }
@ -268,131 +268,15 @@ func (it *stackIterator) Next() bool {
return true return true
} }
// asmcgocallSPOffsetSaveSlot is the offset from systemstack.SP where
// (goroutine.SP - StackHi) is saved in runtime.asmcgocall after the stack
// switch happens.
const asmcgocallSPOffsetSaveSlot = 0x28
// 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 (it *stackIterator) switchStack() bool {
if it.frame.Current.Fn == nil {
return false
}
switch it.frame.Current.Fn.Name {
case "runtime.asmcgocall":
if it.top || !it.systemstack {
return false
}
// This function is called by a goroutine to execute a C function and
// switches from the goroutine stack to the system stack.
// Since we are unwinding the stack from callee to caller we have switch
// from the system stack to the goroutine stack.
off, _ := readIntRaw(it.mem, uintptr(it.regs.SP()+asmcgocallSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize())) // reads "offset of SP from StackHi" from where runtime.asmcgocall saved it
oldsp := it.regs.SP()
it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(int64(it.stackhi) - off)
// runtime.asmcgocall can also be called from inside the system stack,
// in that case no stack switch actually happens
if it.regs.SP() == oldsp {
return false
}
it.systemstack = false
// advances to the next frame in the call stack
it.frame.addrret = uint64(int64(it.regs.SP()) + int64(it.bi.Arch.PtrSize()))
it.frame.Ret, _ = readUintRaw(it.mem, uintptr(it.frame.addrret), int64(it.bi.Arch.PtrSize()))
it.pc = it.frame.Ret
it.top = false
return true
case "runtime.cgocallback_gofunc":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_amd64.s
//
// When a C functions calls back into go it will eventually call into
// runtime.cgocallback_gofunc which is the function that does the stack
// switch from the system stack back into the goroutine stack
// Since we are going backwards on the stack here we see the transition
// as goroutine stack -> system stack.
if it.top || it.systemstack {
return false
}
if it.g0_sched_sp <= 0 {
return false
}
// entering the system stack
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g0_sched_sp
// reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack
it.g0_sched_sp, _ = readUintRaw(it.mem, uintptr(it.regs.SP()), int64(it.bi.Arch.PtrSize()))
it.top = false
callFrameRegs, ret, retaddr := it.advanceRegs()
frameOnSystemStack := it.newStackframe(ret, retaddr)
it.pc = frameOnSystemStack.Ret
it.regs = callFrameRegs
it.systemstack = true
return true
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
}
}
func (it *stackIterator) switchToGoroutineStack() { func (it *stackIterator) switchToGoroutineStack() {
it.systemstack = false it.systemstack = false
it.top = false it.top = false
it.pc = it.g.PC it.pc = it.g.PC
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP
it.regs.Reg(it.regs.BPRegNum).Uint64Val = it.g.BP it.regs.Reg(it.regs.BPRegNum).Uint64Val = it.g.BP
if _, ok := it.bi.Arch.(*ARM64); ok {
it.regs.Reg(it.regs.LRRegNum).Uint64Val = it.g.LR
}
} }
// Frame returns the frame the iterator is pointing at. // Frame returns the frame the iterator is pointing at.
@ -552,7 +436,7 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
callimage := it.bi.PCToImage(it.pc) callimage := it.bi.PCToImage(it.pc)
callFrameRegs = op.DwarfRegisters{StaticBase: callimage.StaticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum} callFrameRegs = op.DwarfRegisters{StaticBase: callimage.StaticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum, LRRegNum: it.regs.LRRegNum}
// According to the standard the compiler should be responsible for emitting // According to the standard the compiler should be responsible for emitting
// rules for the RSP register so that it can then be used to calculate CFA, // rules for the RSP register so that it can then be used to calculate CFA,
@ -561,7 +445,11 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
// implicit. // implicit.
// See also the comment in dwarf2_frame_default_init in // See also the comment in dwarf2_frame_default_init in
// $GDB_SOURCE/dwarf2-frame.c // $GDB_SOURCE/dwarf2-frame.c
callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg) if _, ok := it.bi.Arch.(*ARM64); ok {
callFrameRegs.AddReg(uint64(arm64DwarfSPRegNum), cfareg)
} else {
callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg)
}
for i, regRule := range framectx.Regs { for i, regRule := range framectx.Regs {
reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA) reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
@ -579,6 +467,12 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
} }
} }
if _, ok := it.bi.Arch.(*ARM64); ok {
if ret == 0 && it.regs.Regs[it.regs.LRRegNum] != nil {
ret = it.regs.Regs[it.regs.LRRegNum].Uint64Val
}
}
return callFrameRegs, ret, retaddr return callFrameRegs, ret, retaddr
} }
@ -589,6 +483,9 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c
case frame.RuleUndefined: case frame.RuleUndefined:
return nil, nil return nil, nil
case frame.RuleSameVal: case frame.RuleSameVal:
if it.regs.Reg(regnum) == nil {
return nil, nil
}
reg := *it.regs.Reg(regnum) reg := *it.regs.Reg(regnum)
return &reg, nil return &reg, nil
case frame.RuleOffset: case frame.RuleOffset:

@ -2,7 +2,6 @@ package proc_test
import ( import (
"path/filepath" "path/filepath"
"runtime"
"testing" "testing"
"github.com/go-delve/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc"
@ -10,9 +9,6 @@ import (
) )
func TestGoroutineCreationLocation(t *testing.T) { func TestGoroutineCreationLocation(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support GetStackInfo for now")
}
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) {
bp := setFunctionBreakpoint(p, t, "main.agoroutine") bp := setFunctionBreakpoint(p, t, "main.agoroutine")

@ -190,6 +190,7 @@ type G struct {
PC uint64 // PC of goroutine when it was parked. PC uint64 // PC of goroutine when it was parked.
SP uint64 // SP of goroutine when it was parked. SP uint64 // SP of goroutine when it was parked.
BP uint64 // BP of goroutine when it was parked (go >= 1.7). BP uint64 // BP of goroutine when it was parked (go >= 1.7).
LR uint64 // LR of goroutine when it was parked.
GoPC uint64 // PC of 'go' statement that created this goroutine. GoPC uint64 // PC of 'go' statement that created this goroutine.
StartPC uint64 // PC of the first function run on this goroutine. StartPC uint64 // PC of the first function run on this goroutine.
WaitReason string // Reason for goroutine being parked. WaitReason string // Reason for goroutine being parked.
@ -550,10 +551,13 @@ func (v *Variable) parseG() (*G, error) {
schedVar := v.fieldVariable("sched") schedVar := v.fieldVariable("sched")
pc, _ := constant.Int64Val(schedVar.fieldVariable("pc").Value) pc, _ := constant.Int64Val(schedVar.fieldVariable("pc").Value)
sp, _ := constant.Int64Val(schedVar.fieldVariable("sp").Value) sp, _ := constant.Int64Val(schedVar.fieldVariable("sp").Value)
var bp int64 var bp, lr int64
if bpvar := schedVar.fieldVariable("bp"); bpvar != nil && bpvar.Value != nil { if bpvar := schedVar.fieldVariable("bp"); bpvar != nil && bpvar.Value != nil {
bp, _ = constant.Int64Val(bpvar.Value) bp, _ = constant.Int64Val(bpvar.Value)
} }
if bpvar := schedVar.fieldVariable("lr"); bpvar != nil && bpvar.Value != nil {
lr, _ = constant.Int64Val(bpvar.Value)
}
id, _ := constant.Int64Val(v.fieldVariable("goid").Value) id, _ := constant.Int64Val(v.fieldVariable("goid").Value)
gopc, _ := constant.Int64Val(v.fieldVariable("gopc").Value) gopc, _ := constant.Int64Val(v.fieldVariable("gopc").Value)
startpc, _ := constant.Int64Val(v.fieldVariable("startpc").Value) startpc, _ := constant.Int64Val(v.fieldVariable("startpc").Value)
@ -594,6 +598,7 @@ func (v *Variable) parseG() (*G, error) {
PC: uint64(pc), PC: uint64(pc),
SP: uint64(sp), SP: uint64(sp),
BP: uint64(bp), BP: uint64(bp),
LR: uint64(lr),
WaitReason: waitReason, WaitReason: waitReason,
Status: uint64(status), Status: uint64(status),
CurrentLoc: Location{PC: uint64(pc), File: f, Line: l, Fn: fn}, CurrentLoc: Location{PC: uint64(pc), File: f, Line: l, Fn: fn},

@ -295,9 +295,6 @@ func TestExitStatus(t *testing.T) {
} }
func TestScopePrefix(t *testing.T) { func TestScopePrefix(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
const goroutinesLinePrefix = " Goroutine " const goroutinesLinePrefix = " Goroutine "
const goroutinesCurLinePrefix = "* Goroutine " const goroutinesCurLinePrefix = "* Goroutine "
test.AllowRecording(t) test.AllowRecording(t)
@ -866,9 +863,6 @@ func TestIssue1090(t *testing.T) {
} }
func TestPrintContextParkedGoroutine(t *testing.T) { func TestPrintContextParkedGoroutine(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
term.MustExec("break stacktraceme") term.MustExec("break stacktraceme")
term.MustExec("continue") term.MustExec("continue")
@ -942,9 +936,6 @@ func TestOptimizationCheck(t *testing.T) {
} }
func TestTruncateStacktrace(t *testing.T) { func TestTruncateStacktrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
withTestTerminal("stacktraceprog", t, func(term *FakeTerminal) { withTestTerminal("stacktraceprog", t, func(term *FakeTerminal) {
term.MustExec("break main.stacktraceme") term.MustExec("break main.stacktraceme")
term.MustExec("continue") term.MustExec("continue")

@ -16,6 +16,7 @@ import (
const DelveMainPackagePath = "github.com/go-delve/delve/cmd/dlv" const DelveMainPackagePath = "github.com/go-delve/delve/cmd/dlv"
var Verbose bool var Verbose bool
var NOTimeout bool
var TestSet, TestRegex, TestBackend, TestBuildMode string var TestSet, TestRegex, TestBackend, TestBuildMode string
func NewMakeCommands() *cobra.Command { func NewMakeCommands() *cobra.Command {
@ -80,6 +81,7 @@ Use the flags -s, -r and -b to specify which tests to run. Specifying nothing is
Run: testCmd, Run: testCmd,
} }
test.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Verbose tests") test.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Verbose tests")
test.PersistentFlags().BoolVarP(&NOTimeout, "timeout", "t", false, "Set infinite timeouts")
test.PersistentFlags().StringVarP(&TestSet, "test-set", "s", "", `Select the set of tests to run, one of either: test.PersistentFlags().StringVarP(&TestSet, "test-set", "s", "", `Select the set of tests to run, one of either:
all tests all packages all tests all packages
basic tests proc, integration and terminal basic tests proc, integration and terminal
@ -259,6 +261,10 @@ func testFlags() []string {
if Verbose { if Verbose {
testFlags = append(testFlags, "-v") testFlags = append(testFlags, "-v")
} }
if NOTimeout {
testFlags = append(testFlags, "-timeout")
testFlags = append(testFlags, "0")
}
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" {
testFlags = append(testFlags, "-exec="+wd+"/scripts/testsign") testFlags = append(testFlags, "-exec="+wd+"/scripts/testsign")
} }

@ -724,9 +724,6 @@ func Test1ClientServer_SetVariable(t *testing.T) {
} }
func Test1ClientServer_FullStacktrace(t *testing.T) { func Test1ClientServer_FullStacktrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
withTestClient1("goroutinestackprog", t, func(c *rpc1.RPCClient) { withTestClient1("goroutinestackprog", t, func(c *rpc1.RPCClient) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1}) _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
assertNoError(err, t, "CreateBreakpoint()") assertNoError(err, t, "CreateBreakpoint()")
@ -799,9 +796,6 @@ func Test1ClientServer_FullStacktrace(t *testing.T) {
} }
func Test1Issue355(t *testing.T) { func Test1Issue355(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// After the target process has terminated should return an error but not crash // After the target process has terminated should return an error but not crash
withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1})

@ -803,9 +803,6 @@ func TestClientServer_SetVariable(t *testing.T) {
} }
func TestClientServer_FullStacktrace(t *testing.T) { func TestClientServer_FullStacktrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
protest.AllowRecording(t) protest.AllowRecording(t)
withTestClient2("goroutinestackprog", t, func(c service.Client) { withTestClient2("goroutinestackprog", t, func(c service.Client) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1}) _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
@ -879,9 +876,6 @@ func TestClientServer_FullStacktrace(t *testing.T) {
} }
func TestIssue355(t *testing.T) { func TestIssue355(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support Stacktrace for now")
}
// After the target process has terminated should return an error but not crash // After the target process has terminated should return an error but not crash
protest.AllowRecording(t) protest.AllowRecording(t)
withTestClient2("continuetestprog", t, func(c service.Client) { withTestClient2("continuetestprog", t, func(c service.Client) {