179 lines
5.9 KiB
Diff
179 lines
5.9 KiB
Diff
|
From 457c3cea934db4b8883c9b932912367e02170a61 Mon Sep 17 00:00:00 2001
|
||
|
From: Cherry Zhang <cherryyz@google.com>
|
||
|
Date: Fri, 03 Jul 2020 14:28:15 -0400
|
||
|
Subject: [PATCH] [release-branch.go1.14] cmd/link: detect trampoline of deferreturn call
|
||
|
|
||
|
This is a backport of CL 234105. This is not a clean cherry-pick,
|
||
|
as CL 234105 is for the new linker, whereas we still use the old
|
||
|
linker here. This CL backports the logic.
|
||
|
|
||
|
The runtime needs to find the PC of the deferreturn call in a few
|
||
|
places. So for functions that have defer, we record the PC of
|
||
|
deferreturn call in its funcdata.
|
||
|
|
||
|
For very large binaries, the deferreturn call could be made
|
||
|
through a trampoline. The current code of finding deferreturn PC
|
||
|
fails in this case. This CL handles the trampoline as well.
|
||
|
|
||
|
Fixes #39991.
|
||
|
Updates #39049.
|
||
|
|
||
|
Change-Id: I929be54d6ae436f5294013793217dc2a35f080d4
|
||
|
Reviewed-on: https://go-review.googlesource.com/c/go/+/234105
|
||
|
Run-TryBot: Cherry Zhang <cherryyz@google.com>
|
||
|
TryBot-Result: Gobot Gobot <gobot@golang.org>
|
||
|
Reviewed-by: Jeremy Faller <jeremy@golang.org>
|
||
|
Reviewed-by: Than McIntosh <thanm@google.com>
|
||
|
---
|
||
|
|
||
|
diff --git a/src/cmd/link/internal/arm/asm.go b/src/cmd/link/internal/arm/asm.go
|
||
|
index f2fb654..c4f529a 100644
|
||
|
--- a/src/cmd/link/internal/arm/asm.go
|
||
|
+++ b/src/cmd/link/internal/arm/asm.go
|
||
|
@@ -470,8 +470,12 @@
|
||
|
offset := (signext24(r.Add&0xffffff) + 2) * 4
|
||
|
var tramp *sym.Symbol
|
||
|
for i := 0; ; i++ {
|
||
|
- name := r.Sym.Name + fmt.Sprintf("%+d-tramp%d", offset, i)
|
||
|
+ oName := r.Sym.Name
|
||
|
+ name := oName + fmt.Sprintf("%+d-tramp%d", offset, i)
|
||
|
tramp = ctxt.Syms.Lookup(name, int(r.Sym.Version))
|
||
|
+ if oName == "runtime.deferreturn" {
|
||
|
+ tramp.Attr.Set(sym.AttrDeferReturnTramp, true)
|
||
|
+ }
|
||
|
if tramp.Type == sym.SDYNIMPORT {
|
||
|
// don't reuse trampoline defined in other module
|
||
|
continue
|
||
|
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
|
||
|
index 3e8135c..43e1661 100644
|
||
|
--- a/src/cmd/link/internal/ld/pcln.go
|
||
|
+++ b/src/cmd/link/internal/ld/pcln.go
|
||
|
@@ -276,7 +276,7 @@
|
||
|
// set the resumption point to PC_B.
|
||
|
lastWasmAddr = uint32(r.Add)
|
||
|
}
|
||
|
- if r.Type.IsDirectCall() && r.Sym != nil && r.Sym.Name == "runtime.deferreturn" {
|
||
|
+ if r.Type.IsDirectCall() && r.Sym != nil && (r.Sym.Name == "runtime.deferreturn" || r.Sym.Attr.DeferReturnTramp()) {
|
||
|
if ctxt.Arch.Family == sys.Wasm {
|
||
|
deferreturn = lastWasmAddr - 1
|
||
|
} else {
|
||
|
diff --git a/src/cmd/link/internal/ppc64/asm.go b/src/cmd/link/internal/ppc64/asm.go
|
||
|
index 9fbcff5..e84689d 100644
|
||
|
--- a/src/cmd/link/internal/ppc64/asm.go
|
||
|
+++ b/src/cmd/link/internal/ppc64/asm.go
|
||
|
@@ -667,7 +667,8 @@
|
||
|
// target is at some offset within the function. Calls to duff+8 and duff+256 must appear as
|
||
|
// distinct trampolines.
|
||
|
|
||
|
- name := r.Sym.Name
|
||
|
+ oName := r.Sym.Name
|
||
|
+ name := oName
|
||
|
if r.Add == 0 {
|
||
|
name = name + fmt.Sprintf("-tramp%d", i)
|
||
|
} else {
|
||
|
@@ -677,6 +678,9 @@
|
||
|
// Look up the trampoline in case it already exists
|
||
|
|
||
|
tramp = ctxt.Syms.Lookup(name, int(r.Sym.Version))
|
||
|
+ if oName == "runtime.deferreturn" {
|
||
|
+ tramp.Attr.Set(sym.AttrDeferReturnTramp, true)
|
||
|
+ }
|
||
|
if tramp.Value == 0 {
|
||
|
break
|
||
|
}
|
||
|
diff --git a/src/cmd/link/internal/sym/attribute.go b/src/cmd/link/internal/sym/attribute.go
|
||
|
index 4b69bf3..773b6a4 100644
|
||
|
--- a/src/cmd/link/internal/sym/attribute.go
|
||
|
+++ b/src/cmd/link/internal/sym/attribute.go
|
||
|
@@ -81,7 +81,10 @@
|
||
|
// AttrReadOnly indicates whether the symbol's content (Symbol.P) is backed by
|
||
|
// read-only memory.
|
||
|
AttrReadOnly
|
||
|
- // 19 attributes defined so far.
|
||
|
+ // AttrDeferReturnTramp indicates the symbol is a trampoline of a deferreturn
|
||
|
+ // call.
|
||
|
+ AttrDeferReturnTramp
|
||
|
+ // 20 attributes defined so far.
|
||
|
)
|
||
|
|
||
|
func (a Attribute) DuplicateOK() bool { return a&AttrDuplicateOK != 0 }
|
||
|
@@ -103,6 +106,7 @@
|
||
|
func (a Attribute) Container() bool { return a&AttrContainer != 0 }
|
||
|
func (a Attribute) TopFrame() bool { return a&AttrTopFrame != 0 }
|
||
|
func (a Attribute) ReadOnly() bool { return a&AttrReadOnly != 0 }
|
||
|
+func (a Attribute) DeferReturnTramp() bool { return a&AttrDeferReturnTramp != 0 }
|
||
|
|
||
|
func (a Attribute) CgoExport() bool {
|
||
|
return a.CgoExportDynamic() || a.CgoExportStatic()
|
||
|
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
|
||
|
index 4f792bd..f5efb51 100644
|
||
|
--- a/src/cmd/link/link_test.go
|
||
|
+++ b/src/cmd/link/link_test.go
|
||
|
@@ -447,3 +447,66 @@
|
||
|
t.Errorf("unexpected output:\n%s", out)
|
||
|
}
|
||
|
}
|
||
|
+
|
||
|
+const testTrampSrc = `
|
||
|
+package main
|
||
|
+import "fmt"
|
||
|
+func main() {
|
||
|
+ fmt.Println("hello")
|
||
|
+
|
||
|
+ defer func(){
|
||
|
+ if e := recover(); e == nil {
|
||
|
+ panic("did not panic")
|
||
|
+ }
|
||
|
+ }()
|
||
|
+ f1()
|
||
|
+}
|
||
|
+
|
||
|
+// Test deferreturn trampolines. See issue #39049.
|
||
|
+func f1() { defer f2() }
|
||
|
+func f2() { panic("XXX") }
|
||
|
+`
|
||
|
+
|
||
|
+func TestTrampoline(t *testing.T) {
|
||
|
+ // Test that trampoline insertion works as expected.
|
||
|
+ // For stress test, we set -debugtramp=2 flag, which sets a very low
|
||
|
+ // threshold for trampoline generation, and essentially all cross-package
|
||
|
+ // calls will use trampolines.
|
||
|
+ switch runtime.GOARCH {
|
||
|
+ case "arm", "ppc64", "ppc64le":
|
||
|
+ default:
|
||
|
+ t.Skipf("trampoline insertion is not implemented on %s", runtime.GOARCH)
|
||
|
+ }
|
||
|
+ if runtime.GOOS == "aix" {
|
||
|
+ t.Skip("trampolines on AIX doesn't work in Go 1.14") // fixed in Go 1.15
|
||
|
+ }
|
||
|
+
|
||
|
+ testenv.MustHaveGoBuild(t)
|
||
|
+
|
||
|
+ tmpdir, err := ioutil.TempDir("", "TestTrampoline")
|
||
|
+ if err != nil {
|
||
|
+ t.Fatal(err)
|
||
|
+ }
|
||
|
+ defer os.RemoveAll(tmpdir)
|
||
|
+
|
||
|
+ src := filepath.Join(tmpdir, "hello.go")
|
||
|
+ err = ioutil.WriteFile(src, []byte(testTrampSrc), 0666)
|
||
|
+ if err != nil {
|
||
|
+ t.Fatal(err)
|
||
|
+ }
|
||
|
+ exe := filepath.Join(tmpdir, "hello.exe")
|
||
|
+
|
||
|
+ cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2", "-o", exe, src)
|
||
|
+ out, err := cmd.CombinedOutput()
|
||
|
+ if err != nil {
|
||
|
+ t.Fatalf("build failed: %v\n%s", err, out)
|
||
|
+ }
|
||
|
+ cmd = exec.Command(exe)
|
||
|
+ out, err = cmd.CombinedOutput()
|
||
|
+ if err != nil {
|
||
|
+ t.Errorf("executable failed to run: %v\n%s", err, out)
|
||
|
+ }
|
||
|
+ if string(out) != "hello\n" {
|
||
|
+ t.Errorf("unexpected output:\n%s", out)
|
||
|
+ }
|
||
|
+}
|