mirror of
https://github.com/golang/go
synced 2024-11-24 05:10:19 -07:00
runtime: fix callwritebarrier
Given a call frame F of size N where the return values start at offset R, callwritebarrier was instructing heapBitsBulkBarrier to scan the block of memory [F+R, F+R+N). It should only scan [F+R, F+N). The extra N-R bytes scanned might lead into the next allocated block in memory. Because the scan was consulting the heap bitmap for type information, scanning into the next block normally "just worked" in the sense of not crashing. Scanning the extra N-R bytes of memory is a problem mainly because it causes the GC to consider pointers that might otherwise not be considered, leading it to retain objects that should actually be freed. This is very difficult to detect. Luckily, juju turned up a case where the heap bitmap and the memory were out of sync for the block immediately after the call frame, so that heapBitsBulkBarrier saw an obvious non-pointer where it expected a pointer, causing a loud crash. Why is there a non-pointer in memory that the heap bitmap records as a pointer? That is more difficult to answer. At least one way that it could happen is that allocations containing no pointers at all do not update the heap bitmap. So if heapBitsBulkBarrier walked out of the current object and into a no-pointer object and consulted those bitmap bits, it would be misled. This doesn't happen in general because all the paths to heapBitsBulkBarrier first check for the no-pointer case. This may or may not be what happened, but it's the only scenario I've been able to construct. I tried for quite a while to write a simple test for this and could not. It does fix the juju crash, and it is clearly an improvement over the old code. Fixes #10844. Change-Id: I53982c93ef23ef93155c4086bbd95a4c4fdaac9a Reviewed-on: https://go-review.googlesource.com/10317 Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
parent
a5c3bbe0b4
commit
001438bdfe
@ -195,7 +195,7 @@ func callwritebarrier(typ *_type, frame unsafe.Pointer, framesize, retoffset uin
|
||||
if !writeBarrierEnabled || typ == nil || typ.kind&kindNoPointers != 0 || framesize-retoffset < ptrSize || !inheap(uintptr(frame)) {
|
||||
return
|
||||
}
|
||||
heapBitsBulkBarrier(uintptr(add(frame, retoffset)), framesize)
|
||||
heapBitsBulkBarrier(uintptr(add(frame, retoffset)), framesize-retoffset)
|
||||
}
|
||||
|
||||
//go:nosplit
|
||||
|
@ -356,6 +356,12 @@ func (h heapBits) setCheckmarked(size uintptr) {
|
||||
// calling memmove(p, src, size). This function is marked nosplit
|
||||
// to avoid being preempted; the GC must not stop the goroutine
|
||||
// betwen the memmove and the execution of the barriers.
|
||||
//
|
||||
// The heap bitmap is not maintained for allocations containing
|
||||
// no pointers at all; any caller of heapBitsBulkBarrier must first
|
||||
// make sure the underlying allocation contains pointers, usually
|
||||
// by checking typ.kind&kindNoPointers.
|
||||
//
|
||||
//go:nosplit
|
||||
func heapBitsBulkBarrier(p, size uintptr) {
|
||||
if (p|size)&(ptrSize-1) != 0 {
|
||||
|
@ -168,7 +168,9 @@ func recordspan(vh unsafe.Pointer, p unsafe.Pointer) {
|
||||
|
||||
// inheap reports whether b is a pointer into a (potentially dead) heap object.
|
||||
// It returns false for pointers into stack spans.
|
||||
// Non-preemptible because it is used by write barriers.
|
||||
//go:nowritebarrier
|
||||
//go:nosplit
|
||||
func inheap(b uintptr) bool {
|
||||
if b == 0 || b < mheap_.arena_start || b >= mheap_.arena_used {
|
||||
return false
|
||||
|
Loading…
Reference in New Issue
Block a user