aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/runtime-gdb_test.go
diff options
context:
space:
mode:
authorXiangdong Ji <xiangdong.ji@arm.com>2020-03-27 11:04:21 +0000
committerIan Lance Taylor <iant@golang.org>2020-04-08 03:46:37 +0000
commit8e121b1587bd921ea84c7da49cab3f48dc6b8f36 (patch)
tree783c23c4aa58b8a916663e5268d777ac7f0f059e /src/runtime/runtime-gdb_test.go
parent0a820007e70fdd038950f28254c6269cd9588c02 (diff)
downloadgo-8e121b1587bd921ea84c7da49cab3f48dc6b8f36.tar.gz
go-8e121b1587bd921ea84c7da49cab3f48dc6b8f36.zip
runtime: fix infinite callstack of cgo on arm64
This change adds CFA information to the assembly function 'crosscall1' and reorgnizes its code to establish well-formed prologue and epilogue. It will fix an infinite callstack issue when debugging cgo program with GDB on arm64. Brief root cause analysis: GDB's aarch64 unwinder parses prologue to determine current frame's size and previous PC&SP if CFA information is not available. The unwinder parses the prologue of 'crosscall1' to determine a frame size of 0x10, then turns to its next frame trying to compute its previous PC&SP as they are not saved on current frame's stack as per its 'traditional frame unwind' rules, which ends up getting an endless frame chain like: [callee] : pc:<pc0>, sp:<sp0> crosscall1: pc:<pc1>, sp:<sp0>+0x10 [caller] : pc:<pc1>, sp:<sp0>+0x10+0x10 [caller] : pc:<pc1>, sp:<sp0>+0x10+0x10+0x10 ... GDB fails to detect the 'caller' frame is same as 'crosscall1' and terminate unwinding since SP increases everytime. Fixes #37238 Change-Id: Ia6bd8555828541a3a61f7dc9b94dfa00775ec52a Reviewed-on: https://go-review.googlesource.com/c/go/+/226999 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'src/runtime/runtime-gdb_test.go')
-rw-r--r--src/runtime/runtime-gdb_test.go80
1 files changed, 80 insertions, 0 deletions
diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go
index 5dbe4bf88a..4639e2fcb8 100644
--- a/src/runtime/runtime-gdb_test.go
+++ b/src/runtime/runtime-gdb_test.go
@@ -607,3 +607,83 @@ func TestGdbPanic(t *testing.T) {
}
}
}
+
+const InfCallstackSource = `
+package main
+import "C"
+import "time"
+
+func loop() {
+ for i := 0; i < 1000; i++ {
+ time.Sleep(time.Millisecond*5)
+ }
+}
+
+func main() {
+ go loop()
+ time.Sleep(time.Second * 1)
+}
+`
+// TestGdbInfCallstack tests that gdb can unwind the callstack of cgo programs
+// on arm64 platforms without endless frames of function 'crossfunc1'.
+// https://golang.org/issue/37238
+func TestGdbInfCallstack(t *testing.T) {
+ checkGdbEnvironment(t)
+
+ testenv.MustHaveCGO(t)
+ if runtime.GOARCH != "arm64" {
+ t.Skip("skipping infinite callstack test on non-arm64 arches")
+ }
+
+ t.Parallel()
+ 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(InfCallstackSource), 0644)
+ if err != nil {
+ t.Fatalf("failed to create file: %v", err)
+ }
+ cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
+ cmd.Dir = dir
+ out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
+ if err != nil {
+ t.Fatalf("building source %v\n%s", err, out)
+ }
+
+ // Execute gdb commands.
+ // 'setg_gcc' is the first point where we can reproduce the issue with just one 'run' command.
+ args := []string{"-nx", "-batch",
+ "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
+ "-ex", "set startup-with-shell off",
+ "-ex", "break setg_gcc",
+ "-ex", "run",
+ "-ex", "backtrace 3",
+ "-ex", "disable 1",
+ "-ex", "continue",
+ filepath.Join(dir, "a.exe"),
+ }
+ got, _ := exec.Command("gdb", args...).CombinedOutput()
+
+ // Check that the backtrace matches
+ // We check the 3 inner most frames only as they are present certainly, according to gcc_<OS>_arm64.c
+ bt := []string{
+ `setg_gcc`,
+ `crosscall1`,
+ `threadentry`,
+ }
+ for i, name := range bt {
+ s := fmt.Sprintf("#%v.*%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))
+ }
+ }
+}