Skip to content

Commit 24256fb

Browse files
committed
[1.8>master] [MERGE #4290 @dlibby-] Fix RecyclerVisited related crashes during marking
Merge pull request #4290 from dlibby-:build/dlibby/dummyvtable_fix_1.8 (courtesy of Bo Cupp) Fixing two issues in marking. 1. If there are only precisely traced objects allocated, the markStack used for non-precisely traced objects will be empty and crash when Pop is called. An IsEmpty check was added to prevent popping from an empty stack. 2. A race between allocating and background marking can lead to calling a virtual Trace method on an object that has a null v-table pointer. It isn't possible to call trace until after a flag has been set by the allocator, so prior to that happening a dummy v-table pointer is now installed until control returns to the caller of the recycler's alloc method where a final v-table pointer will overwrite the dummy one. This must be done with a MemoryBarrier on ARM to guarantee the writing of the v-table happens before the attribute updates. We also need to fixup the address in FillCheckPad We were tripping an assert that the v-table doesn't match the padding pattern. Because we install the v-table earlier, there's a new mode for FillCheckPad where the first void* bytes should be skipped. The object is not initialized, but already has a valid v-table that doesn't need to be verified and shouldn't be 0-filled.
2 parents 98a2470 + 93a8495 commit 24256fb

File tree

6 files changed

+92
-38
lines changed

6 files changed

+92
-38
lines changed

lib/Common/Core/FinalizableObject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class FinalizableObject : public IRecyclerVisitedObject
1515
Mark(static_cast<Recycler*>(recycler));
1616
}
1717

18-
void Trace(IRecyclerHeapMarkingContext* markingContext) final
18+
void Trace(IRecyclerHeapMarkingContext* markingContext)
1919
{
2020
AssertMsg(false, "Trace called on object that isn't implemented by the host");
2121
}

lib/Common/Memory/LargeHeapBlock.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,16 @@ LargeHeapBlock::AllocFreeListEntry(DECLSPEC_GUARD_OVERFLOW size_t size, ObjectIn
553553
#ifdef RECYCLER_WRITE_BARRIER
554554
header->hasWriteBarrier = (attributes & WithBarrierBit) == WithBarrierBit;
555555
#endif
556+
557+
if ((attributes & (FinalizeBit | TrackBit)) != 0)
558+
{
559+
// Make sure a valid vtable is installed as once the attributes have been set this allocation may be traced by background marking
560+
allocObject = (char *)new (allocObject) DummyVTableObject();
561+
#if defined(_M_ARM32_OR_ARM64)
562+
// On ARM, make sure the v-table write is performed before setting the attributes
563+
MemoryBarrier();
564+
#endif
565+
}
556566
header->SetAttributes(this->heapInfo->recycler->Cookie, (attributes & StoredObjectInfoBitMask));
557567
header->markOnOOMRescan = false;
558568
header->SetNext(this->heapInfo->recycler->Cookie, nullptr);
@@ -618,6 +628,15 @@ LargeHeapBlock::Alloc(DECLSPEC_GUARD_OVERFLOW size_t size, ObjectInfoBits attrib
618628
#ifdef RECYCLER_WRITE_BARRIER
619629
header->hasWriteBarrier = (attributes&WithBarrierBit) == WithBarrierBit;
620630
#endif
631+
if ((attributes & (FinalizeBit | TrackBit)) != 0)
632+
{
633+
// Make sure a valid vtable is installed as once the attributes have been set this allocation may be traced by background marking
634+
allocObject = (char *)new (allocObject) DummyVTableObject();
635+
#if defined(_M_ARM32_OR_ARM64)
636+
// On ARM, make sure the v-table write is performed before setting the attributes
637+
MemoryBarrier();
638+
#endif
639+
}
621640
header->SetAttributes(recycler->Cookie, (attributes & StoredObjectInfoBitMask));
622641
HeaderList()[allocCount++] = header;
623642
finalizeCount += ((attributes & FinalizeBit) != 0);

lib/Common/Memory/MarkContext.inl

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -234,41 +234,46 @@ void MarkContext::ProcessMark()
234234
while (!markStack.IsEmpty() || !preciseStack.IsEmpty())
235235
#endif
236236
{
237+
// It is possible that when the stacks were split, only one of them had any chunks to process.
238+
// If that is the case, one of the stacks might not be initialized, so we must check !IsEmpty before popping.
239+
if (!markStack.IsEmpty())
240+
{
237241
#if defined(_M_IX86) || defined(_M_X64)
238-
MarkCandidate current, next;
242+
MarkCandidate current, next;
239243

240-
while (markStack.Pop(&current))
241-
{
242-
// Process entries and prefetch as we go.
243-
while (markStack.Pop(&next))
244+
while (markStack.Pop(&current))
244245
{
245-
// Prefetch the next entry so it's ready when we need it.
246-
_mm_prefetch((char *)next.obj, _MM_HINT_T0);
246+
// Process entries and prefetch as we go.
247+
while (markStack.Pop(&next))
248+
{
249+
// Prefetch the next entry so it's ready when we need it.
250+
_mm_prefetch((char *)next.obj, _MM_HINT_T0);
247251

248-
// Process the previously retrieved entry.
249-
ScanObject<parallel, interior>(current.obj, current.byteCount);
252+
// Process the previously retrieved entry.
253+
ScanObject<parallel, interior>(current.obj, current.byteCount);
250254

251-
_mm_prefetch((char *)*(next.obj), _MM_HINT_T0);
255+
_mm_prefetch((char *)*(next.obj), _MM_HINT_T0);
252256

253-
current = next;
254-
}
257+
current = next;
258+
}
255259

256-
// The stack is empty, but we still have a previously retrieved entry; process it now.
257-
ScanObject<parallel, interior>(current.obj, current.byteCount);
260+
// The stack is empty, but we still have a previously retrieved entry; process it now.
261+
ScanObject<parallel, interior>(current.obj, current.byteCount);
258262

259-
// Processing that entry may have generated more entries in the mark stack, so continue the loop.
260-
}
263+
// Processing that entry may have generated more entries in the mark stack, so continue the loop.
264+
}
261265
#else
262-
// _mm_prefetch intrinsic is specific to Intel platforms.
263-
// CONSIDER: There does seem to be a compiler intrinsic for prefetch on ARM,
264-
// however, the information on this is scarce, so for now just don't do prefetch on ARM.
265-
MarkCandidate current;
266+
// _mm_prefetch intrinsic is specific to Intel platforms.
267+
// CONSIDER: There does seem to be a compiler intrinsic for prefetch on ARM,
268+
// however, the information on this is scarce, so for now just don't do prefetch on ARM.
269+
MarkCandidate current;
266270

267-
while (markStack.Pop(&current))
268-
{
269-
ScanObject<parallel, interior>(current.obj, current.byteCount);
270-
}
271+
while (markStack.Pop(&current))
272+
{
273+
ScanObject<parallel, interior>(current.obj, current.byteCount);
274+
}
271275
#endif
276+
}
272277

273278
Assert(markStack.IsEmpty());
274279

lib/Common/Memory/Recycler.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7314,6 +7314,21 @@ Recycler::FillCheckPad(void * address, size_t size, size_t alignedAllocSize, boo
73147314
addressToVerify = ((char*) address + size);
73157315
sizeToVerify = (alignedAllocSize - size);
73167316
}
7317+
else
7318+
{
7319+
// It could be the case that an uninitialized object already has a dummy vtable installed
7320+
// at the beginning of the address. If that is the case, we can't verify the fill pattern
7321+
// on that memory, since it's already been initialized.
7322+
// Note that FillPadNoCheck will skip over the first sizeof(FreeObject) bytes, which
7323+
// prevents overwriting of the vtable.
7324+
static_assert(sizeof(DummyVTableObject) == sizeof(void*), "Incorrect size for a DummyVTableObject - it must contain a single v-table pointer");
7325+
DummyVTableObject dummy;
7326+
if ((*(void**)(&dummy)) == *((void**)address))
7327+
{
7328+
addressToVerify = (char*)address + sizeof(DummyVTableObject);
7329+
sizeToVerify = alignedAllocSize - sizeof(DummyVTableObject);
7330+
}
7331+
}
73177332

73187333
// Actually this is filling the non-pad to zero
73197334
VerifyCheckFill(addressToVerify, sizeToVerify - sizeof(size_t));

lib/Common/Memory/Recycler.inl

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ namespace Memory
3636
class DummyVTableObject : public FinalizableObject
3737
{
3838
public:
39-
virtual void Finalize(bool isShutdown) {}
40-
virtual void Dispose(bool isShutdown) {}
41-
virtual void Mark(Recycler * recycler) {}
39+
virtual void Finalize(bool isShutdown) final {}
40+
virtual void Dispose(bool isShutdown) final {}
41+
virtual void Mark(Recycler * recycler) final {}
42+
virtual void Trace(IRecyclerHeapMarkingContext* markingContext) final {}
4243
};
4344
}
4445

@@ -171,13 +172,6 @@ Recycler::AllocWithAttributesInlined(DECLSPEC_GUARD_OVERFLOW size_t size)
171172
#endif
172173

173174

174-
#pragma prefast(suppress:6313, "attributes is a template parameter and can be 0")
175-
if (attributes & (FinalizeBit | TrackBit))
176-
{
177-
// Make sure a valid vtable is installed in case of OOM before the real vtable is set
178-
memBlock = (char *)new (memBlock) DummyVTableObject();
179-
}
180-
181175
#ifdef RECYCLER_WRITE_BARRIER
182176
SwbVerboseTrace(this->GetRecyclerFlagsTable(), _u("Allocated SWB memory: 0x%p\n"), memBlock);
183177

lib/Common/Memory/SmallHeapBlockAllocator.h

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ SmallHeapBlockAllocator<TBlockType>::InlinedAllocImpl(Recycler * recycler, DECLS
129129

130130
if (NeedSetAttributes(attributes))
131131
{
132+
if ((attributes & (FinalizeBit | TrackBit)) != 0)
133+
{
134+
// Make sure a valid vtable is installed as once the attributes have been set this allocation may be traced by background marking
135+
memBlock = (char *)new (memBlock) DummyVTableObject();
136+
#if defined(_M_ARM32_OR_ARM64)
137+
// On ARM, make sure the v-table write is performed before setting the attributes
138+
MemoryBarrier();
139+
#endif
140+
}
132141
heapBlock->SetAttributes(memBlock, (attributes & StoredObjectInfoBitMask));
133142
}
134143

@@ -138,6 +147,11 @@ SmallHeapBlockAllocator<TBlockType>::InlinedAllocImpl(Recycler * recycler, DECLS
138147
if (memBlock != nullptr && endAddress == nullptr)
139148
{
140149
// Free list allocation
150+
freeObjectList = ((FreeObject *)memBlock)->GetNext();
151+
#ifdef RECYCLER_MEMORY_VERIFY
152+
((FreeObject *)memBlock)->DebugFillNext();
153+
#endif
154+
141155
Assert(!this->IsBumpAllocMode());
142156
if (NeedSetAttributes(attributes))
143157
{
@@ -149,13 +163,20 @@ SmallHeapBlockAllocator<TBlockType>::InlinedAllocImpl(Recycler * recycler, DECLS
149163
Assert(allocationHeapBlock != nullptr);
150164
Assert(!allocationHeapBlock->IsLargeHeapBlock());
151165
}
166+
167+
if ((attributes & (FinalizeBit | TrackBit)) != 0)
168+
{
169+
// Make sure a valid vtable is installed as once the attributes have been set this allocation may be traced by background marking
170+
memBlock = (char *)new (memBlock) DummyVTableObject();
171+
#if defined(_M_ARM32_OR_ARM64)
172+
// On ARM, make sure the v-table write is performed before setting the attributes
173+
MemoryBarrier();
174+
#endif
175+
}
152176
allocationHeapBlock->SetAttributes(memBlock, (attributes & StoredObjectInfoBitMask));
153177
}
154-
freeObjectList = ((FreeObject *)memBlock)->GetNext();
155178

156179
#ifdef RECYCLER_MEMORY_VERIFY
157-
((FreeObject *)memBlock)->DebugFillNext();
158-
159180
if (this->IsExplicitFreeObjectListAllocMode())
160181
{
161182
HeapBlock* heapBlock = recycler->FindHeapBlock(memBlock);

0 commit comments

Comments
 (0)