runtime: fix x86 stack trace for call to heap memory

Fixes #11656.

Change-Id: Ib81d583e4b004e67dc9d2f898fd798112434e7a9
Reviewed-on: https://go-review.googlesource.com/12026
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
diff --git a/src/runtime/signal_386.go b/src/runtime/signal_386.go
index 8fb1979..b6f55ff 100644
--- a/src/runtime/signal_386.go
+++ b/src/runtime/signal_386.go
@@ -67,21 +67,31 @@
 			}
 		}
 
-		// Only push runtime.sigpanic if rip != 0.
-		// If rip == 0, probably panicked because of a
+		pc := uintptr(c.eip())
+		sp := uintptr(c.esp())
+
+		// If we don't recognize the PC as code
+		// but we do recognize the top pointer on the stack as code,
+		// then assume this was a call to non-code and treat like
+		// pc == 0, to make unwinding show the context.
+		if pc != 0 && findfunc(pc) == nil && findfunc(*(*uintptr)(unsafe.Pointer(sp))) != nil {
+			pc = 0
+		}
+
+		// Only push runtime.sigpanic if pc != 0.
+		// If pc == 0, probably panicked because of a
 		// call to a nil func.  Not pushing that onto sp will
 		// make the trace look like a call to runtime.sigpanic instead.
 		// (Otherwise the trace will end at runtime.sigpanic and we
 		// won't get to see who faulted.)
-		if c.eip() != 0 {
-			sp := c.esp()
+		if pc != 0 {
 			if regSize > ptrSize {
 				sp -= ptrSize
-				*(*uintptr)(unsafe.Pointer(uintptr(sp))) = 0
+				*(*uintptr)(unsafe.Pointer(sp)) = 0
 			}
 			sp -= ptrSize
-			*(*uintptr)(unsafe.Pointer(uintptr(sp))) = uintptr(c.eip())
-			c.set_esp(sp)
+			*(*uintptr)(unsafe.Pointer(sp)) = pc
+			c.set_esp(uint32(sp))
 		}
 		c.set_eip(uint32(funcPC(sigpanic)))
 		return
diff --git a/src/runtime/signal_amd64x.go b/src/runtime/signal_amd64x.go
index 182b16e..13ee5af 100644
--- a/src/runtime/signal_amd64x.go
+++ b/src/runtime/signal_amd64x.go
@@ -101,21 +101,31 @@
 			}
 		}
 
-		// Only push runtime.sigpanic if rip != 0.
-		// If rip == 0, probably panicked because of a
+		pc := uintptr(c.rip())
+		sp := uintptr(c.rsp())
+
+		// If we don't recognize the PC as code
+		// but we do recognize the top pointer on the stack as code,
+		// then assume this was a call to non-code and treat like
+		// pc == 0, to make unwinding show the context.
+		if pc != 0 && findfunc(pc) == nil && findfunc(*(*uintptr)(unsafe.Pointer(sp))) != nil {
+			pc = 0
+		}
+
+		// Only push runtime.sigpanic if pc != 0.
+		// If pc == 0, probably panicked because of a
 		// call to a nil func.  Not pushing that onto sp will
 		// make the trace look like a call to runtime.sigpanic instead.
 		// (Otherwise the trace will end at runtime.sigpanic and we
 		// won't get to see who faulted.)
-		if c.rip() != 0 {
-			sp := c.rsp()
+		if pc != 0 {
 			if regSize > ptrSize {
 				sp -= ptrSize
-				*(*uintptr)(unsafe.Pointer(uintptr(sp))) = 0
+				*(*uintptr)(unsafe.Pointer(sp)) = 0
 			}
 			sp -= ptrSize
-			*(*uintptr)(unsafe.Pointer(uintptr(sp))) = uintptr(c.rip())
-			c.set_rsp(sp)
+			*(*uintptr)(unsafe.Pointer(sp)) = pc
+			c.set_rsp(uint64(sp))
 		}
 		c.set_rip(uint64(funcPC(sigpanic)))
 		return
diff --git a/test/fixedbugs/issue11656.go b/test/fixedbugs/issue11656.go
new file mode 100644
index 0000000..4bf657c
--- /dev/null
+++ b/test/fixedbugs/issue11656.go
@@ -0,0 +1,58 @@
+// run
+
+// Copyright 2015 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// darwin/386 seems to mangle the PC and SP before
+// it manages to invoke the signal handler, so this test fails there.
+// +build !darwin !386
+//
+// openbsd/386 and plan9/386 don't work, not sure why.
+// +build !openbsd !386
+// +build !plan9 !386
+//
+// windows doesn't work, because Windows exception handling
+// delivers signals based on the current PC, and that current PC
+// doesn't go into the Go runtime.
+// +build !windows
+
+package main
+
+import (
+	"runtime"
+	"runtime/debug"
+	"unsafe"
+)
+
+func main() {
+	debug.SetPanicOnFault(true)
+	defer func() {
+		if err := recover(); err == nil {
+			panic("not panicking")
+		}
+		pc, _, _, _ := runtime.Caller(10)
+		f := runtime.FuncForPC(pc)
+		if f == nil || f.Name() != "main.f" {
+			if f == nil {
+				println("no func for ", unsafe.Pointer(pc))
+			} else {
+				println("found func:", f.Name())
+			}
+			panic("cannot find main.f on stack")
+		}
+	}()
+	f(20)
+}
+
+func f(n int) {
+	if n > 0 {
+		f(n-1)
+	}
+	var f struct {
+		x uintptr
+	}
+	f.x = uintptr(unsafe.Pointer(&f))
+	fn := *(*func())(unsafe.Pointer(&f))
+	fn()
+}
diff --git a/test/run.go b/test/run.go
index 0a2f82d..6e1cde9 100644
--- a/test/run.go
+++ b/test/run.go
@@ -412,19 +412,13 @@
 		t.err = skipError("starts with newline")
 		return
 	}
+
+	// Execution recipe stops at first blank line.
 	pos := strings.Index(t.src, "\n\n")
 	if pos == -1 {
 		t.err = errors.New("double newline not found")
 		return
 	}
-	// Check for build constraints only upto the first blank line.
-	if ok, why := shouldTest(t.src[:pos], goos, goarch); !ok {
-		t.action = "skip"
-		if *showSkips {
-			fmt.Printf("%-20s %-20s: %s\n", t.action, t.goFileName(), why)
-		}
-		return
-	}
 	action := t.src[:pos]
 	if nl := strings.Index(action, "\n"); nl >= 0 && strings.Contains(action[:nl], "+build") {
 		// skip first line
@@ -434,6 +428,19 @@
 		action = action[2:]
 	}
 
+	// Check for build constraints only up to the actual code.
+	pkgPos := strings.Index(t.src, "\npackage")
+	if pkgPos == -1 {
+		pkgPos = pos // some files are intentionally malformed
+	}
+	if ok, why := shouldTest(t.src[:pkgPos], goos, goarch); !ok {
+		t.action = "skip"
+		if *showSkips {
+			fmt.Printf("%-20s %-20s: %s\n", t.action, t.goFileName(), why)
+		}
+		return
+	}
+
 	var args, flags []string
 	wantError := false
 	f := strings.Fields(action)