diff options
author | Xiangdong Ji <xiangdong.ji@arm.com> | 2020-03-27 11:04:21 +0000 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2020-04-08 03:46:37 +0000 |
commit | 8e121b1587bd921ea84c7da49cab3f48dc6b8f36 (patch) | |
tree | 783c23c4aa58b8a916663e5268d777ac7f0f059e /src/runtime/runtime-gdb_test.go | |
parent | 0a820007e70fdd038950f28254c6269cd9588c02 (diff) | |
download | go-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.go | 80 |
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)) + } + } +} |