Commit af8f4062 authored by Cherry Zhang's avatar Cherry Zhang

runtime: scan gp._panic in stack scan

In runtime.gopanic, the _panic object p is stack allocated and
referenced from gp._panic. With stack objects, p on stack is dead
at the point preprintpanics runs. gp._panic points to p, but
stack scan doesn't look at gp. Heap scan of gp does look at
gp._panic, but it stops and ignores the pointer as it points to
the stack. So whatever p points to may be collected and clobbered.
We need to scan gp._panic explicitly during stack scan.

To test it reliably, we introduce a GODEBUG mode "clobberfree",
which clobbers the memory content when the GC frees an object.

Fixes #30150.

Change-Id: I11128298f03a89f817faa221421a9d332b41dced
Reviewed-on: https://go-review.googlesource.com/c/161778
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarKeith Randall <khr@golang.org>
Reviewed-by: default avatarAustin Clements <austin@google.com>
parent ffd096db
...@@ -728,3 +728,15 @@ func TestG0StackOverflow(t *testing.T) { ...@@ -728,3 +728,15 @@ func TestG0StackOverflow(t *testing.T) {
runtime.G0StackOverflow() runtime.G0StackOverflow()
} }
// Test that panic message is not clobbered.
// See issue 30150.
func TestDoublePanic(t *testing.T) {
output := runTestProg(t, "testprog", "DoublePanic", "GODEBUG=clobberfree=1")
wants := []string{"panic: XXX", "panic: YYY"}
for _, want := range wants {
if !strings.Contains(output, want) {
t.Errorf("output:\n%s\n\nwant output containing: %s", output, want)
}
}
}
...@@ -27,6 +27,10 @@ It is a comma-separated list of name=val pairs setting these named variables: ...@@ -27,6 +27,10 @@ It is a comma-separated list of name=val pairs setting these named variables:
allocfreetrace: setting allocfreetrace=1 causes every allocation to be allocfreetrace: setting allocfreetrace=1 causes every allocation to be
profiled and a stack trace printed on each object's allocation and free. profiled and a stack trace printed on each object's allocation and free.
clobberfree: setting clobberfree=1 causes the garbage collector to
clobber the memory content of an object with bad content when it frees
the object.
cgocheck: setting cgocheck=0 disables all checks for packages cgocheck: setting cgocheck=0 disables all checks for packages
using cgo to incorrectly pass Go pointers to non-Go code. using cgo to incorrectly pass Go pointers to non-Go code.
Setting cgocheck=1 (the default) enables relatively cheap Setting cgocheck=1 (the default) enables relatively cheap
......
...@@ -709,7 +709,13 @@ func scanstack(gp *g, gcw *gcWork) { ...@@ -709,7 +709,13 @@ func scanstack(gp *g, gcw *gcWork) {
return true return true
} }
gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0) gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)
// Find additional pointers that point into the stack from the heap.
// Currently this includes defers and panics. See also function copystack.
tracebackdefers(gp, scanframe, nil) tracebackdefers(gp, scanframe, nil)
if gp._panic != nil {
state.putPtr(uintptr(unsafe.Pointer(gp._panic)))
}
// Find and scan all reachable stack objects. // Find and scan all reachable stack objects.
state.buildIndex() state.buildIndex()
......
...@@ -291,7 +291,7 @@ func (s *mspan) sweep(preserve bool) bool { ...@@ -291,7 +291,7 @@ func (s *mspan) sweep(preserve bool) bool {
} }
} }
if debug.allocfreetrace != 0 || raceenabled || msanenabled { if debug.allocfreetrace != 0 || debug.clobberfree != 0 || raceenabled || msanenabled {
// Find all newly freed objects. This doesn't have to // Find all newly freed objects. This doesn't have to
// efficient; allocfreetrace has massive overhead. // efficient; allocfreetrace has massive overhead.
mbits := s.markBitsForBase() mbits := s.markBitsForBase()
...@@ -302,6 +302,9 @@ func (s *mspan) sweep(preserve bool) bool { ...@@ -302,6 +302,9 @@ func (s *mspan) sweep(preserve bool) bool {
if debug.allocfreetrace != 0 { if debug.allocfreetrace != 0 {
tracefree(unsafe.Pointer(x), size) tracefree(unsafe.Pointer(x), size)
} }
if debug.clobberfree != 0 {
clobberfree(unsafe.Pointer(x), size)
}
if raceenabled { if raceenabled {
racefree(unsafe.Pointer(x), size) racefree(unsafe.Pointer(x), size)
} }
...@@ -446,3 +449,12 @@ retry: ...@@ -446,3 +449,12 @@ retry:
traceGCSweepDone() traceGCSweepDone()
} }
} }
// clobberfree sets the memory content at x to bad content, for debugging
// purposes.
func clobberfree(x unsafe.Pointer, size uintptr) {
// size (span.elemsize) is always a multiple of 4.
for i := uintptr(0); i < size; i += 4 {
*(*uint32)(add(x, i)) = 0xdeadbeef
}
}
...@@ -301,6 +301,7 @@ type dbgVar struct { ...@@ -301,6 +301,7 @@ type dbgVar struct {
var debug struct { var debug struct {
allocfreetrace int32 allocfreetrace int32
cgocheck int32 cgocheck int32
clobberfree int32
efence int32 efence int32
gccheckmark int32 gccheckmark int32
gcpacertrace int32 gcpacertrace int32
...@@ -318,6 +319,7 @@ var debug struct { ...@@ -318,6 +319,7 @@ var debug struct {
var dbgvars = []dbgVar{ var dbgvars = []dbgVar{
{"allocfreetrace", &debug.allocfreetrace}, {"allocfreetrace", &debug.allocfreetrace},
{"clobberfree", &debug.clobberfree},
{"cgocheck", &debug.cgocheck}, {"cgocheck", &debug.cgocheck},
{"efence", &debug.efence}, {"efence", &debug.efence},
{"gccheckmark", &debug.gccheckmark}, {"gccheckmark", &debug.gccheckmark},
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
func init() { func init() {
register("Crash", Crash) register("Crash", Crash)
register("DoublePanic", DoublePanic)
} }
func test(name string) { func test(name string) {
...@@ -43,3 +44,23 @@ func Crash() { ...@@ -43,3 +44,23 @@ func Crash() {
testInNewThread("second-new-thread") testInNewThread("second-new-thread")
test("main-again") test("main-again")
} }
type P string
func (p P) String() string {
// Try to free the "YYY" string header when the "XXX"
// panic is stringified.
runtime.GC()
runtime.GC()
runtime.GC()
return string(p)
}
// Test that panic message is not clobbered.
// See issue 30150.
func DoublePanic() {
defer func() {
panic(P("YYY"))
}()
panic(P("XXX"))
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment