diff --git a/_fixtures/cgosigsegvstack.go b/_fixtures/cgosigsegvstack.go index 84e455d6..a32ff925 100644 --- a/_fixtures/cgosigsegvstack.go +++ b/_fixtures/cgosigsegvstack.go @@ -1,7 +1,6 @@ package main // #cgo CFLAGS: -g -Wall -O0 - /* void sigsegv(int x) { int *p = NULL; diff --git a/_fixtures/cgostacktest/hello.c b/_fixtures/cgostacktest/hello.c index fd904a9e..7e179be1 100644 --- a/_fixtures/cgostacktest/hello.c +++ b/_fixtures/cgostacktest/hello.c @@ -2,7 +2,11 @@ #include "_cgo_export.h" +#ifdef __amd64__ #define BREAKPOINT asm("int3;") +#elif __aarch64__ +#define BREAKPOINT asm("brk 0;") +#endif void helloworld_pt2(int x) { BREAKPOINT; diff --git a/pkg/dwarf/op/regs.go b/pkg/dwarf/op/regs.go index 1de5e0c4..aa19a30d 100644 --- a/pkg/dwarf/op/regs.go +++ b/pkg/dwarf/op/regs.go @@ -17,6 +17,7 @@ type DwarfRegisters struct { PCRegNum uint64 SPRegNum uint64 BPRegNum uint64 + LRRegNum uint64 } type DwarfRegister struct { diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go index e7ea8335..ac65cf3a 100644 --- a/pkg/proc/amd64_arch.go +++ b/pkg/proc/amd64_arch.go @@ -2,6 +2,7 @@ package proc import ( "encoding/binary" + "strings" "github.com/go-delve/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/op" @@ -159,6 +160,124 @@ func (a *AMD64) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi * 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. // The mapping between hardware registers and DWARF registers is specified // 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 // 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[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc) dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp) diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index c7ce86f1..e6490d22 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -17,9 +17,10 @@ type Arch interface { BreakpointSize() int DerefTLS() bool FixFrameUnwindContext(*frame.FrameContext, uint64, *BinaryInfo) *frame.FrameContext + SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool RegSize(uint64) int RegistersToDwarfRegisters(uint64, Registers) op.DwarfRegisters - AddrAndStackRegsToDwarfRegisters(uint64, uint64, uint64, uint64) op.DwarfRegisters + AddrAndStackRegsToDwarfRegisters(uint64, uint64, uint64, uint64, uint64) op.DwarfRegisters } const ( diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go index 4368e09b..fad53a86 100644 --- a/pkg/proc/arm64_arch.go +++ b/pkg/proc/arm64_arch.go @@ -2,6 +2,7 @@ package proc import ( "encoding/binary" + "strings" "github.com/go-delve/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/op" @@ -28,6 +29,7 @@ type ARM64 struct { const ( arm64DwarfIPRegNum uint64 = 32 arm64DwarfSPRegNum uint64 = 31 + arm64DwarfLRRegNum uint64 = 30 arm64DwarfBPRegNum uint64 = 29 ) @@ -154,10 +156,116 @@ func (a *ARM64) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi * Offset: 0, } } + if fctxt.Regs[arm64DwarfLRRegNum].Rule == frame.RuleUndefined { + fctxt.Regs[arm64DwarfLRRegNum] = frame.DWRule{ + Rule: frame.RuleFramePointer, + Reg: arm64DwarfLRRegNum, + Offset: 0, + } + } 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 { // fp registers 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[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(regs.SP()) 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 { v, err := regs.Get(int(asmReg)) @@ -272,16 +383,18 @@ func (a *ARM64) RegistersToDwarfRegisters(staticBase uint64, regs Registers) op. PCRegNum: arm64DwarfIPRegNum, SPRegNum: arm64DwarfSPRegNum, BPRegNum: arm64DwarfBPRegNum, + LRRegNum: arm64DwarfLRRegNum, } } // AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in // 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[arm64DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc) dregs[arm64DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp) dregs[arm64DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp) + dregs[arm64DwarfLRRegNum] = op.DwarfRegisterFromUint64(lr) return op.DwarfRegisters{ StaticBase: staticBase, @@ -290,5 +403,6 @@ func (a *ARM64) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp uint64) PCRegNum: arm64DwarfIPRegNum, SPRegNum: arm64DwarfSPRegNum, BPRegNum: arm64DwarfBPRegNum, + LRRegNum: arm64DwarfLRRegNum, } } diff --git a/pkg/proc/linutil/regs_arm64_arch.go b/pkg/proc/linutil/regs_arm64_arch.go index f823f699..469d9278 100644 --- a/pkg/proc/linutil/regs_arm64_arch.go +++ b/pkg/proc/linutil/regs_arm64_arch.go @@ -103,7 +103,7 @@ func (r *ARM64Registers) GAddr() (uint64, bool) { 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) { reg := arm64asm.Reg(n) diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index 88504d7f..f42cbdac 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -1,6 +1,7 @@ package proc import ( + "bytes" "encoding/binary" "errors" "fmt" @@ -212,13 +213,14 @@ func Continue(dbp Process) error { return conditionErrors(threads) } g, _ := GetG(curthread) + arch := dbp.BinInfo().Arch switch { case loc.Fn.Name == "runtime.breakpoint": // In linux-arm64, PtraceSingleStep seems cannot step over BRK instruction // (linux-arm64 feature or kernel bug maybe). - if !curthread.Arch().BreakInstrMovesPC() { - curthread.SetPC(loc.PC + uint64(curthread.Arch().BreakpointSize())) + if !arch.BreakInstrMovesPC() { + curthread.SetPC(loc.PC + uint64(arch.BreakpointSize())) } // Single-step current thread until we exit runtime.breakpoint and // runtime.Breakpoint. @@ -229,7 +231,15 @@ func Continue(dbp Process) error { } return conditionErrors(threads) 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) } case curbp.Active && curbp.Internal: diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 864be8ab..9fb3e268 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -820,9 +820,6 @@ func (l1 *loc) match(l2 proc.Stackframe) bool { } func TestStacktrace(t *testing.T) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support Stacktrace for now") - } stacks := [][]loc{ {{4, "main.stacktraceme"}, {8, "main.func1"}, {16, "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) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support Stacktrace for now") - } withTestProcess("retstack", t, func(p proc.Process, fixture protest.Fixture) { 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) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support Stacktrace for now") - } mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}} if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { mainStack[0].line = 15 @@ -1235,9 +1226,6 @@ func TestVariableEvaluation(t *testing.T) { } func TestFrameEvaluation(t *testing.T) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support Stacktrace for now") - } protest.AllowRecording(t) withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.stacktraceme") @@ -1718,9 +1706,6 @@ func TestIssue384(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 protest.AllowRecording(t) 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) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support Stacktrace for now") - } // Step should skip a function's prologue // 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: " @@ -1902,9 +1884,6 @@ func TestCmdLineArgs(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 if runtime.GOOS == "windows" { return @@ -1934,9 +1913,6 @@ func TestNextParked(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 Stacktrace for now") - } protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p proc.Process, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") @@ -1987,9 +1963,6 @@ func TestNextParked(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" { t.Skip("test is not valid on FreeBSD") } @@ -2335,9 +2308,6 @@ func TestStepConcurrentDirect(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 Stacktrace for now") - } protest.AllowRecording(t) withTestProcess("teststepconcurrent", t, func(p proc.Process, fixture protest.Fixture) { 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) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support Stacktrace for now") - } // Go's Garbage Collector will insert stack barriers into stacks. // This stack barrier is inserted by overwriting the return address for the // 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) { - if runtime.GOARCH != "amd64" { - t.Skip("amd64 only") - } if runtime.GOOS == "windows" { ver, _ := goversion.Parse(runtime.Version()) 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) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support Cgo-debug for now") - } if runtime.GOOS == "windows" { ver, _ := goversion.Parse(runtime.Version()) 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) { - 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() withTestProcess("panic", t, func(p proc.Process, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "runtime.startpanic_m") @@ -3438,9 +3396,6 @@ func TestSystemstackStacktrace(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: // - set a breakpoint somewhere or interrupt the program with Ctrl-C // - try to look at stacktraces of other goroutines @@ -3474,9 +3429,6 @@ func TestSystemstackOnRuntimeNewstack(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 // without the "has children" flag, we should support this. withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) { @@ -3494,9 +3446,6 @@ func TestIssue1034(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 // in debug_line. which we should support correctly. withTestProcess("cgostacktest/", t, func(p proc.Process, fixture protest.Fixture) { @@ -3675,9 +3624,6 @@ func TestAllPCsForFileLines(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, ""}) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") @@ -4124,7 +4070,7 @@ func TestNextUnknownInstr(t *testing.T) { func TestReadDeferArgs(t *testing.T) { 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 { frame, deferCall int @@ -4475,9 +4421,6 @@ func TestIssue1615(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" { t.Skip("fixture crashes go runtime on windows") } diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index eec9f6de..9f87d93b 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "go/constant" - "strings" "github.com/go-delve/delve/pkg/dwarf/frame" "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) return newStackIterator( 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 } @@ -242,6 +241,7 @@ func (it *stackIterator) Next() bool { if it.err != nil || it.atend { return false } + callFrameRegs, ret, retaddr := it.advanceRegs() it.frame = it.newStackframe(ret, retaddr) @@ -252,7 +252,7 @@ func (it *stackIterator) Next() bool { } if it.opts&StacktraceSimple == 0 { - if it.switchStack() { + if it.bi.Arch.SwitchStack(it, &callFrameRegs) { return true } } @@ -268,131 +268,15 @@ func (it *stackIterator) Next() bool { 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() { it.systemstack = false it.top = false it.pc = it.g.PC it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g.SP 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. @@ -552,7 +436,7 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin 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 // 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. // See also the comment in dwarf2_frame_default_init in // $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 { 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 } @@ -589,6 +483,9 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c case frame.RuleUndefined: return nil, nil case frame.RuleSameVal: + if it.regs.Reg(regnum) == nil { + return nil, nil + } reg := *it.regs.Reg(regnum) return ®, nil case frame.RuleOffset: diff --git a/pkg/proc/variable_test.go b/pkg/proc/variable_test.go index f68f4831..750dd1d4 100644 --- a/pkg/proc/variable_test.go +++ b/pkg/proc/variable_test.go @@ -2,7 +2,6 @@ package proc_test import ( "path/filepath" - "runtime" "testing" "github.com/go-delve/delve/pkg/proc" @@ -10,9 +9,6 @@ import ( ) func TestGoroutineCreationLocation(t *testing.T) { - if runtime.GOARCH == "arm64" { - t.Skip("arm64 does not support GetStackInfo for now") - } protest.AllowRecording(t) withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.agoroutine") diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 2877a5f5..ee8468a1 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -190,6 +190,7 @@ type G struct { PC uint64 // PC 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). + LR uint64 // LR of goroutine when it was parked. GoPC uint64 // PC of 'go' statement that created this goroutine. StartPC uint64 // PC of the first function run on this goroutine. WaitReason string // Reason for goroutine being parked. @@ -550,10 +551,13 @@ func (v *Variable) parseG() (*G, error) { schedVar := v.fieldVariable("sched") pc, _ := constant.Int64Val(schedVar.fieldVariable("pc").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 { 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) gopc, _ := constant.Int64Val(v.fieldVariable("gopc").Value) startpc, _ := constant.Int64Val(v.fieldVariable("startpc").Value) @@ -594,6 +598,7 @@ func (v *Variable) parseG() (*G, error) { PC: uint64(pc), SP: uint64(sp), BP: uint64(bp), + LR: uint64(lr), WaitReason: waitReason, Status: uint64(status), CurrentLoc: Location{PC: uint64(pc), File: f, Line: l, Fn: fn}, diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index e2ab3f09..911c7c7d 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -295,9 +295,6 @@ func TestExitStatus(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 goroutinesCurLinePrefix = "* Goroutine " test.AllowRecording(t) @@ -866,9 +863,6 @@ func TestIssue1090(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) { term.MustExec("break stacktraceme") term.MustExec("continue") @@ -942,9 +936,6 @@ func TestOptimizationCheck(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) { term.MustExec("break main.stacktraceme") term.MustExec("continue") diff --git a/scripts/make.go b/scripts/make.go index bdba288d..dc077647 100644 --- a/scripts/make.go +++ b/scripts/make.go @@ -16,6 +16,7 @@ import ( const DelveMainPackagePath = "github.com/go-delve/delve/cmd/dlv" var Verbose bool +var NOTimeout bool var TestSet, TestRegex, TestBackend, TestBuildMode string 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, } 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: all tests all packages basic tests proc, integration and terminal @@ -259,6 +261,10 @@ func testFlags() []string { if Verbose { testFlags = append(testFlags, "-v") } + if NOTimeout { + testFlags = append(testFlags, "-timeout") + testFlags = append(testFlags, "0") + } if runtime.GOOS == "darwin" { testFlags = append(testFlags, "-exec="+wd+"/scripts/testsign") } diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index 33e551b2..27e3d051 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -724,9 +724,6 @@ func Test1ClientServer_SetVariable(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) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") @@ -799,9 +796,6 @@ func Test1ClientServer_FullStacktrace(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 withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 618f4d92..9d09f3b9 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -803,9 +803,6 @@ func TestClientServer_SetVariable(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) withTestClient2("goroutinestackprog", t, func(c service.Client) { _, 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) { - 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 protest.AllowRecording(t) withTestClient2("continuetestprog", t, func(c service.Client) {