Commit b7c55ba4 authored by Austin Clements's avatar Austin Clements

runtime: improve invalid pointer error message

By default, the runtime panics if it detects a pointer to an
unallocated span. At this point, this usually catches bad uses of
unsafe or cgo in user code (though it could also catch runtime bugs).
Unfortunately, the rather cryptic error misleads users, offers users
little help with debugging their own problem, and offers the Go
developers little help with root-causing.

Improve the error message in various ways. First, the wording is
improved to make it clearer what condition was detected and to suggest
that this may be the result of incorrect use of unsafe or cgo. Second,
we add a dump of the object containing the bad pointer so that there's
at least some hope of figuring out why a bad pointer was stored in the
Go heap.

Change-Id: I57b91b12bc3cb04476399d7706679e096ce594b9
Reviewed-on: https://go-review.googlesource.com/14763Reviewed-by: default avatarRick Hudson <rlh@golang.org>
parent 7360638d
...@@ -587,3 +587,40 @@ func main() { ...@@ -587,3 +587,40 @@ func main() {
fmt.Println("done") fmt.Println("done")
} }
` `
func TestInvalidptrCrash(t *testing.T) {
output := executeTest(t, invalidptrCrashSource, nil)
// Check that the bad pointer was detected.
want1 := "found bad pointer in Go heap"
if !strings.Contains(output, want1) {
t.Fatalf("failed to detect bad pointer; output does not contain %q:\n%s", want1, output)
}
// Check that we dumped the object containing the bad pointer.
want2 := "*(object+0) = 0x12345678"
if !strings.Contains(output, want2) {
t.Fatalf("failed to dump source object; output does not contain %q:\n%s", want2, output)
}
}
const invalidptrCrashSource = `
package main
import (
"runtime"
"unsafe"
)
var x = new(struct {
magic uintptr
y *byte
})
func main() {
runtime.GC()
x.magic = 0x12345678
x.y = &make([]byte, 64*1024)[0]
weasel := uintptr(unsafe.Pointer(x.y))
x.y = nil
runtime.GC()
x.y = (*byte)(unsafe.Pointer(weasel))
runtime.GC()
println("failed to detect bad pointer")
}
`
...@@ -182,7 +182,11 @@ func heapBitsForSpan(base uintptr) (hbits heapBits) { ...@@ -182,7 +182,11 @@ func heapBitsForSpan(base uintptr) (hbits heapBits) {
// If p does not point into a heap object, // If p does not point into a heap object,
// return base == 0 // return base == 0
// otherwise return the base of the object. // otherwise return the base of the object.
func heapBitsForObject(p uintptr) (base uintptr, hbits heapBits, s *mspan) { //
// refBase and refOff optionally give the base address of the object
// in which the pointer p was found and the byte offset at which it
// was found. These are used for error reporting.
func heapBitsForObject(p, refBase, refOff uintptr) (base uintptr, hbits heapBits, s *mspan) {
arenaStart := mheap_.arena_start arenaStart := mheap_.arena_start
if p < arenaStart || p >= mheap_.arena_used { if p < arenaStart || p >= mheap_.arena_used {
return return
...@@ -203,18 +207,28 @@ func heapBitsForObject(p uintptr) (base uintptr, hbits heapBits, s *mspan) { ...@@ -203,18 +207,28 @@ func heapBitsForObject(p uintptr) (base uintptr, hbits heapBits, s *mspan) {
// The following ensures that we are rigorous about what data // The following ensures that we are rigorous about what data
// structures hold valid pointers. // structures hold valid pointers.
// TODO(rsc): Check if this still happens.
if debug.invalidptr != 0 { if debug.invalidptr != 0 {
// Still happens sometimes. We don't know why. // Typically this indicates an incorrect use
// of unsafe or cgo to store a bad pointer in
// the Go heap. It may also indicate a runtime
// bug.
//
// TODO(austin): We could be more aggressive
// and detect pointers to unallocated objects
// in allocated spans.
printlock() printlock()
print("runtime:objectstart Span weird: p=", hex(p), " k=", hex(k)) print("runtime: pointer ", hex(p))
if s == nil { if s.state != mSpanInUse {
print(" s=nil\n") print(" to unallocated span")
} else { } else {
print(" s.start=", hex(s.start<<_PageShift), " s.limit=", hex(s.limit), " s.state=", s.state, "\n") print(" to unused region of span")
}
print("idx=", hex(idx), " span.start=", hex(s.start<<_PageShift), " span.limit=", hex(s.limit), " span.state=", s.state, "\n")
if refBase != 0 {
print("runtime: found in object at *(", hex(refBase), "+", hex(off), ")\n")
gcDumpObject("object", refBase, refOff)
} }
printunlock() throw("found bad pointer in Go heap (incorrect use of unsafe or cgo?)")
throw("objectstart: bad pointer in unexpected span")
} }
return return
} }
......
...@@ -659,7 +659,7 @@ func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) { ...@@ -659,7 +659,7 @@ func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) {
// Same work as in scanobject; see comments there. // Same work as in scanobject; see comments there.
obj := *(*uintptr)(unsafe.Pointer(b + i)) obj := *(*uintptr)(unsafe.Pointer(b + i))
if obj != 0 && arena_start <= obj && obj < arena_used { if obj != 0 && arena_start <= obj && obj < arena_used {
if obj, hbits, span := heapBitsForObject(obj); obj != 0 { if obj, hbits, span := heapBitsForObject(obj, b, i); obj != 0 {
greyobject(obj, b, i, hbits, span, gcw) greyobject(obj, b, i, hbits, span, gcw)
} }
} }
...@@ -725,7 +725,7 @@ func scanobject(b uintptr, gcw *gcWork) { ...@@ -725,7 +725,7 @@ func scanobject(b uintptr, gcw *gcWork) {
// Check if it points into heap and not back at the current object. // Check if it points into heap and not back at the current object.
if obj != 0 && arena_start <= obj && obj < arena_used && obj-b >= n { if obj != 0 && arena_start <= obj && obj < arena_used && obj-b >= n {
// Mark the object. // Mark the object.
if obj, hbits, span := heapBitsForObject(obj); obj != 0 { if obj, hbits, span := heapBitsForObject(obj, b, i); obj != 0 {
greyobject(obj, b, i, hbits, span, gcw) greyobject(obj, b, i, hbits, span, gcw)
} }
} }
...@@ -739,7 +739,7 @@ func scanobject(b uintptr, gcw *gcWork) { ...@@ -739,7 +739,7 @@ func scanobject(b uintptr, gcw *gcWork) {
// Preemption must be disabled. // Preemption must be disabled.
//go:nowritebarrier //go:nowritebarrier
func shade(b uintptr) { func shade(b uintptr) {
if obj, hbits, span := heapBitsForObject(b); obj != 0 { if obj, hbits, span := heapBitsForObject(b, 0, 0); obj != 0 {
gcw := &getg().m.p.ptr().gcw gcw := &getg().m.p.ptr().gcw
greyobject(obj, 0, 0, hbits, span, gcw) greyobject(obj, 0, 0, hbits, span, gcw)
if gcphase == _GCmarktermination || gcBlackenPromptly { if gcphase == _GCmarktermination || gcBlackenPromptly {
...@@ -810,7 +810,7 @@ func greyobject(obj, base, off uintptr, hbits heapBits, span *mspan, gcw *gcWork ...@@ -810,7 +810,7 @@ func greyobject(obj, base, off uintptr, hbits heapBits, span *mspan, gcw *gcWork
// field at byte offset off in obj. // field at byte offset off in obj.
func gcDumpObject(label string, obj, off uintptr) { func gcDumpObject(label string, obj, off uintptr) {
if obj < mheap_.arena_start || obj >= mheap_.arena_used { if obj < mheap_.arena_start || obj >= mheap_.arena_used {
print(label, "=", hex(obj), " is not a heap object\n") print(label, "=", hex(obj), " is not in the Go heap\n")
return return
} }
k := obj >> _PageShift k := obj >> _PageShift
......
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