From: Cherry Zhang Date: Fri, 3 Jul 2020 18:28:15 +0000 (-0400) Subject: [release-branch.go1.14] cmd/link: detect trampoline of deferreturn call X-Git-Tag: go1.14.6~9 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=b6f70c0ec9bf92de91fd640fa3bd5206bacf1596;p=gostls13.git [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 TryBot-Result: Gobot Gobot Reviewed-by: Jeremy Faller Reviewed-by: Than McIntosh Reviewed-on: https://go-review.googlesource.com/c/go/+/240917 Run-TryBot: Dmitri Shuralyov Reviewed-by: Austin Clements Reviewed-by: Joel Sing --- diff --git a/src/cmd/link/internal/arm/asm.go b/src/cmd/link/internal/arm/asm.go index f2fb6543d0..c4f529a468 100644 --- a/src/cmd/link/internal/arm/asm.go +++ b/src/cmd/link/internal/arm/asm.go @@ -470,8 +470,12 @@ func trampoline(ctxt *ld.Link, r *sym.Reloc, s *sym.Symbol) { 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 3e8135c959..43e166191f 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -276,7 +276,7 @@ func (ctxt *Link) pclntab() { // 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 9fbcff551a..e84689d1a0 100644 --- a/src/cmd/link/internal/ppc64/asm.go +++ b/src/cmd/link/internal/ppc64/asm.go @@ -667,7 +667,8 @@ func trampoline(ctxt *ld.Link, r *sym.Reloc, s *sym.Symbol) { // 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 @@ func trampoline(ctxt *ld.Link, r *sym.Reloc, s *sym.Symbol) { // 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 4b69bf32d0..773b6a4ee7 100644 --- a/src/cmd/link/internal/sym/attribute.go +++ b/src/cmd/link/internal/sym/attribute.go @@ -81,7 +81,10 @@ const ( // 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) SubSymbol() bool { return a&AttrSubSymbol != 0 } 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 4f792bd1f1..f5efb51d29 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -447,3 +447,66 @@ func TestStrictDup(t *testing.T) { 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) + } +}