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")
+}
+`
// If p does not point into a heap object,
// return base == 0
// 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
if p < arenaStart || p >= mheap_.arena_used {
return
// The following ensures that we are rigorous about what data
// structures hold valid pointers.
- // TODO(rsc): Check if this still happens.
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()
- print("runtime:objectstart Span weird: p=", hex(p), " k=", hex(k))
- if s == nil {
- print(" s=nil\n")
+ print("runtime: pointer ", hex(p))
+ if s.state != mSpanInUse {
+ print(" to unallocated span")
} 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("objectstart: bad pointer in unexpected span")
+ throw("found bad pointer in Go heap (incorrect use of unsafe or cgo?)")
}
return
}
// Same work as in scanobject; see comments there.
obj := *(*uintptr)(unsafe.Pointer(b + i))
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)
}
}
// 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 {
// 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)
}
}
// Preemption must be disabled.
//go:nowritebarrier
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
greyobject(obj, 0, 0, hbits, span, gcw)
if gcphase == _GCmarktermination || gcBlackenPromptly {
// field at byte offset off in obj.
func gcDumpObject(label string, obj, off uintptr) {
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
}
k := obj >> _PageShift