From 55154cf0b27e3c48e7cf7654c890868a95e7eed6 Mon Sep 17 00:00:00 2001 From: Michael Munday Date: Wed, 13 Apr 2016 13:34:41 -0400 Subject: [PATCH] cmd/link: fix gdb backtrace on architectures using a link register Also adds TestGdbBacktrace to the runtime package. Dwarf modifications written by Bryan Chan (@bryanpkc) who is also at IBM and covered by the same CLA. Fixes #14628 Change-Id: I106a1f704c3745a31f29cdadb0032e3905829850 Reviewed-on: https://go-review.googlesource.com/20193 Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot --- src/cmd/link/internal/ld/dwarf.go | 58 +++++++++----- src/runtime/runtime-gdb_test.go | 122 ++++++++++++++++++++++++++---- 2 files changed, 145 insertions(+), 35 deletions(-) diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 0202df664a9..bf1a7e74c14 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1619,14 +1619,13 @@ func writelines(prev *LSym) *LSym { * Emit .debug_frame */ const ( - CIERESERVE = 16 - DATAALIGNMENTFACTOR = -4 + dataAlignmentFactor = -4 ) // appendPCDeltaCFA appends per-PC CFA deltas to b and returns the final slice. func appendPCDeltaCFA(b []byte, deltapc, cfa int64) []byte { b = append(b, DW_CFA_def_cfa_offset_sf) - b = appendSleb128(b, cfa/DATAALIGNMENTFACTOR) + b = appendSleb128(b, cfa/dataAlignmentFactor) switch { case deltapc < 0x40: @@ -1654,36 +1653,42 @@ func writeframes(prev *LSym) *LSym { prev.Next = fs // Emit the CIE, Section 6.4.1 - Adduint32(Ctxt, fs, CIERESERVE) // initial length, must be multiple of thearch.ptrsize + cieReserve := uint32(16) + if haslinkregister() { + cieReserve = 32 + } + Adduint32(Ctxt, fs, cieReserve) // initial length, must be multiple of pointer size Adduint32(Ctxt, fs, 0xffffffff) // cid. Adduint8(Ctxt, fs, 3) // dwarf version (appendix F) Adduint8(Ctxt, fs, 0) // augmentation "" uleb128put(fs, 1) // code_alignment_factor - sleb128put(fs, DATAALIGNMENTFACTOR) // guess + sleb128put(fs, dataAlignmentFactor) // all CFI offset calculations include multiplication with this factor uleb128put(fs, int64(Thearch.Dwarfreglr)) // return_address_register - Adduint8(Ctxt, fs, DW_CFA_def_cfa) - - uleb128put(fs, int64(Thearch.Dwarfregsp)) // register SP (**ABI-dependent, defined in l.h) + Adduint8(Ctxt, fs, DW_CFA_def_cfa) // Set the current frame address.. + uleb128put(fs, int64(Thearch.Dwarfregsp)) // ...to use the value in the platform's SP register (defined in l.go)... if haslinkregister() { - uleb128put(fs, int64(0)) // offset - } else { - uleb128put(fs, int64(SysArch.PtrSize)) // offset - } + uleb128put(fs, int64(0)) // ...plus a 0 offset. - Adduint8(Ctxt, fs, DW_CFA_offset_extended) - uleb128put(fs, int64(Thearch.Dwarfreglr)) // return address - if haslinkregister() { - uleb128put(fs, int64(0)/DATAALIGNMENTFACTOR) // at cfa - 0 + Adduint8(Ctxt, fs, DW_CFA_same_value) // The platform's link register is unchanged during the prologue. + uleb128put(fs, int64(Thearch.Dwarfreglr)) + + Adduint8(Ctxt, fs, DW_CFA_val_offset) // The previous value... + uleb128put(fs, int64(Thearch.Dwarfregsp)) // ...of the platform's SP register... + uleb128put(fs, int64(0)) // ...is CFA+0. } else { - uleb128put(fs, int64(-SysArch.PtrSize)/DATAALIGNMENTFACTOR) // at cfa - x*4 + uleb128put(fs, int64(SysArch.PtrSize)) // ...plus the word size (because the call instruction implicitly adds one word to the frame). + + Adduint8(Ctxt, fs, DW_CFA_offset_extended) // The previous value... + uleb128put(fs, int64(Thearch.Dwarfreglr)) // ...of the return address... + uleb128put(fs, int64(-SysArch.PtrSize)/dataAlignmentFactor) // ...is saved at [CFA - (PtrSize/4)]. } // 4 is to exclude the length field. - pad := CIERESERVE + 4 - fs.Size + pad := int64(cieReserve) + 4 - fs.Size if pad < 0 { - Exitf("dwarf: CIERESERVE too small by %d bytes.", -pad) + Exitf("dwarf: cieReserve too small by %d bytes.", -pad) } Addbytes(Ctxt, fs, zeros[:pad]) @@ -1712,6 +1717,21 @@ func writeframes(prev *LSym) *LSym { } if haslinkregister() { + // TODO(bryanpkc): This is imprecise. In general, the instruction + // that stores the return address to the stack frame is not the + // same one that allocates the frame. + if pcsp.value > 0 { + // The return address is preserved at (CFA-frame_size) + // after a stack frame has been allocated. + deltaBuf = append(deltaBuf, DW_CFA_offset_extended_sf) + deltaBuf = appendUleb128(deltaBuf, uint64(Thearch.Dwarfreglr)) + deltaBuf = appendSleb128(deltaBuf, -int64(pcsp.value)/dataAlignmentFactor) + } else { + // The return address is restored into the link register + // when a stack frame has been de-allocated. + deltaBuf = append(deltaBuf, DW_CFA_same_value) + deltaBuf = appendUleb128(deltaBuf, uint64(Thearch.Dwarfreglr)) + } deltaBuf = appendPCDeltaCFA(deltaBuf, int64(nextpc)-int64(pcsp.pc), int64(pcsp.value)) } else { deltaBuf = appendPCDeltaCFA(deltaBuf, int64(nextpc)-int64(pcsp.pc), int64(SysArch.PtrSize)+int64(pcsp.value)) diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index 7cfcefc2c23..4f82646dbbe 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -3,6 +3,7 @@ package runtime_test import ( "bytes" "fmt" + "internal/testenv" "io/ioutil" "os" "os/exec" @@ -13,19 +14,22 @@ import ( "testing" ) -func checkGdbPython(t *testing.T) { - cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')") - out, err := cmd.CombinedOutput() - - if err != nil { - t.Skipf("skipping due to issue running gdb: %v", err) +func checkGdbEnvironment(t *testing.T) { + testenv.MustHaveGoBuild(t) + if runtime.GOOS == "darwin" { + t.Skip("gdb does not work on darwin") } - if string(out) != "go gdb python support\n" { - t.Skipf("skipping due to lack of python gdb support: %s", out) + if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final { + t.Skip("gdb test can fail with GOROOT_FINAL pending") } +} +func checkGdbVersion(t *testing.T) { // Issue 11214 reports various failures with older versions of gdb. - out, err = exec.Command("gdb", "--version").CombinedOutput() + out, err := exec.Command("gdb", "--version").CombinedOutput() + if err != nil { + t.Skipf("skipping: error executing gdb: %v", err) + } re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`) matches := re.FindSubmatch(out) if len(matches) < 3 { @@ -42,6 +46,18 @@ func checkGdbPython(t *testing.T) { t.Logf("gdb version %d.%d", major, minor) } +func checkGdbPython(t *testing.T) { + cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')") + out, err := cmd.CombinedOutput() + + if err != nil { + t.Skipf("skipping due to issue running gdb: %v", err) + } + if string(out) != "go gdb python support\n" { + t.Skipf("skipping due to lack of python gdb support: %s", out) + } +} + const helloSource = ` package main import "fmt" @@ -57,13 +73,8 @@ func main() { ` func TestGdbPython(t *testing.T) { - if runtime.GOOS == "darwin" { - t.Skip("gdb does not work on darwin") - } - if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final { - t.Skip("gdb test can fail with GOROOT_FINAL pending") - } - + checkGdbEnvironment(t) + checkGdbVersion(t) checkGdbPython(t) dir, err := ioutil.TempDir("", "go-build") @@ -162,3 +173,82 @@ func TestGdbPython(t *testing.T) { t.Logf("gdb cannot backtrace for GOARCH=%s, skipped goroutine backtrace test", runtime.GOARCH) } } + +const backtraceSource = ` +package main + +//go:noinline +func aaa() bool { return bbb() } + +//go:noinline +func bbb() bool { return ccc() } + +//go:noinline +func ccc() bool { return ddd() } + +//go:noinline +func ddd() bool { return f() } + +//go:noinline +func eee() bool { return true } + +var f = eee + +func main() { + _ = aaa() +} +` + +// TestGdbBacktrace tests that gdb can unwind the stack correctly +// using only the DWARF debug info. +func TestGdbBacktrace(t *testing.T) { + checkGdbEnvironment(t) + checkGdbVersion(t) + + dir, err := ioutil.TempDir("", "go-build") + if err != nil { + t.Fatalf("failed to create temp directory: %v", err) + } + defer os.RemoveAll(dir) + + // Build the source code. + src := filepath.Join(dir, "main.go") + err = ioutil.WriteFile(src, []byte(backtraceSource), 0644) + if err != nil { + t.Fatalf("failed to create file: %v", err) + } + cmd := exec.Command("go", "build", "-o", "a.exe") + cmd.Dir = dir + out, err := testEnv(cmd).CombinedOutput() + if err != nil { + t.Fatalf("building source %v\n%s", err, out) + } + + // Execute gdb commands. + args := []string{"-nx", "-batch", + "-ex", "break main.eee", + "-ex", "run", + "-ex", "backtrace", + "-ex", "continue", + filepath.Join(dir, "a.exe"), + } + got, _ := exec.Command("gdb", args...).CombinedOutput() + + // Check that the backtrace matches the source code. + bt := []string{ + "eee", + "ddd", + "ccc", + "bbb", + "aaa", + "main", + } + for i, name := range bt { + s := fmt.Sprintf("#%v.*main\\.%v", i, name) + re := regexp.MustCompile(s) + if found := re.Find(got) != nil; !found { + t.Errorf("could not find '%v' in backtrace", s) + t.Fatalf("gdb output:\n%v", string(got)) + } + } +}