mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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: Keith Randall <khr@golang.org> Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
parent
ffd096db2b
commit
af8f4062c2
6 changed files with 58 additions and 1 deletions
|
|
@ -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:
|
||||||
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) {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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:
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
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},
|
||||||
|
|
|
||||||
21
src/runtime/testdata/testprog/crash.go
vendored
21
src/runtime/testdata/testprog/crash.go
vendored
|
|
@ -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() {
|
||||||
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"))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue