cmd/link: detect trampoline of deferreturn call

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 #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 a2024bc..d28af16 100644
--- a/src/cmd/link/internal/arm/asm.go
+++ b/src/cmd/link/internal/arm/asm.go
@@ -383,12 +383,16 @@
 			offset := (signext24(r.Add()&0xffffff) + 2) * 4
 			var tramp loader.Sym
 			for i := 0; ; i++ {
-				name := ldr.SymName(rs) + fmt.Sprintf("%+d-tramp%d", offset, i)
+				oName := ldr.SymName(rs)
+				name := oName + fmt.Sprintf("%+d-tramp%d", offset, i)
 				tramp = ldr.LookupOrCreateSym(name, int(ldr.SymVersion(rs)))
 				if ldr.SymType(tramp) == sym.SDYNIMPORT {
 					// don't reuse trampoline defined in other module
 					continue
 				}
+				if oName == "runtime.deferreturn" {
+					ldr.SetIsDeferReturnTramp(tramp, true)
+				}
 				if ldr.SymValue(tramp) == 0 {
 					// either the trampoline does not exist -- we need to create one,
 					// or found one the address which is not assigned -- this will be
diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go
index 00c29c6..5cbb7bb 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -161,7 +161,7 @@
 			// set the resumption point to PC_B.
 			lastWasmAddr = uint32(r.Add())
 		}
-		if r.Type().IsDirectCall() && r.Sym() == state.deferReturnSym {
+		if r.Type().IsDirectCall() && (r.Sym() == state.deferReturnSym || state.ldr.IsDeferReturnTramp(r.Sym())) {
 			if target.IsWasm() {
 				deferreturn = lastWasmAddr - 1
 			} else {
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index 2627218..8e6451d 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -236,7 +236,8 @@
 	outdata   [][]byte     // symbol's data in the output buffer
 	extRelocs [][]ExtReloc // symbol's external relocations
 
-	itablink map[Sym]struct{} // itablink[j] defined if j is go.itablink.*
+	itablink         map[Sym]struct{} // itablink[j] defined if j is go.itablink.*
+	deferReturnTramp map[Sym]bool     // whether the symbol is a trampoline of a deferreturn call
 
 	objByPkg map[string]*oReader // map package path to its Go object reader
 
@@ -362,6 +363,7 @@
 		attrCgoExportDynamic: make(map[Sym]struct{}),
 		attrCgoExportStatic:  make(map[Sym]struct{}),
 		itablink:             make(map[Sym]struct{}),
+		deferReturnTramp:     make(map[Sym]bool),
 		extStaticSyms:        make(map[nameVer]Sym),
 		builtinSyms:          make([]Sym, nbuiltin),
 		flags:                flags,
@@ -1062,6 +1064,16 @@
 	return false
 }
 
+// Return whether this is a trampoline of a deferreturn call.
+func (l *Loader) IsDeferReturnTramp(i Sym) bool {
+	return l.deferReturnTramp[i]
+}
+
+// Set that i is a trampoline of a deferreturn call.
+func (l *Loader) SetIsDeferReturnTramp(i Sym, v bool) {
+	l.deferReturnTramp[i] = v
+}
+
 // growValues grows the slice used to store symbol values.
 func (l *Loader) growValues(reqLen int) {
 	curLen := len(l.values)
diff --git a/src/cmd/link/internal/ppc64/asm.go b/src/cmd/link/internal/ppc64/asm.go
index bd4827e..fc714c9 100644
--- a/src/cmd/link/internal/ppc64/asm.go
+++ b/src/cmd/link/internal/ppc64/asm.go
@@ -686,16 +686,20 @@
 				// target is at some offset within the function.  Calls to duff+8 and duff+256 must appear as
 				// distinct trampolines.
 
-				name := ldr.SymName(rs)
+				oName := ldr.SymName(rs)
+				name := oName
 				if r.Add() == 0 {
-					name = name + fmt.Sprintf("-tramp%d", i)
+					name += fmt.Sprintf("-tramp%d", i)
 				} else {
-					name = name + fmt.Sprintf("%+x-tramp%d", r.Add(), i)
+					name += fmt.Sprintf("%+x-tramp%d", r.Add(), i)
 				}
 
 				// Look up the trampoline in case it already exists
 
 				tramp = ldr.LookupOrCreateSym(name, int(ldr.SymVersion(rs)))
+				if oName == "runtime.deferreturn" {
+					ldr.SetIsDeferReturnTramp(tramp, true)
+				}
 				if ldr.SymValue(tramp) == 0 {
 					break
 				}
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index 1c9e177..5ff9912 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -629,10 +629,23 @@
 	}
 }
 
-const helloSrc = `
+const testTrampSrc = `
 package main
 import "fmt"
-func main() { fmt.Println("hello") }
+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) {
@@ -655,7 +668,7 @@
 	defer os.RemoveAll(tmpdir)
 
 	src := filepath.Join(tmpdir, "hello.go")
-	err = ioutil.WriteFile(src, []byte(helloSrc), 0666)
+	err = ioutil.WriteFile(src, []byte(testTrampSrc), 0666)
 	if err != nil {
 		t.Fatal(err)
 	}