123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- #if IL2CPP_ENABLE_WRITE_BARRIER_VALIDATION
- #include "gc_wrapper.h"
- #include "il2cpp-config.h"
- #include "il2cpp-api.h"
- #include "gc/WriteBarrierValidation.h"
- #include "gc/GarbageCollector.h"
- #include "os/Mutex.h"
- #include "vm/StackTrace.h"
- #include <algorithm>
- #include <functional>
- #include <map>
- #include <string>
- // On systems which have the posix backtrace API, you can turn on this define to get native stack traces.
- // This can make it easier to debug issues with missing barriers.
- #define HAS_POSIX_BACKTRACE IL2CPP_TARGET_OSX
- #define HAS_WINDOWS_BACKTRACE IL2CPP_TARGET_WINDOWS
- #if HAS_POSIX_BACKTRACE
- #include <execinfo.h>
- #endif
- #if HAS_WINDOWS_BACKTRACE
- #include <windows.h>
- #endif
- using namespace il2cpp::os;
- using namespace il2cpp::vm;
- #if !IL2CPP_GC_BOEHM
- #error "Write Barrier Validation is specific to Boehm GC"
- #endif
- #if IL2CPP_TINY && !IL2CPP_TINY_DEBUGGER
- #define IL2CPP_TINY_BACKEND 1
- uint8_t* Il2CppGetTinyTypeUniverse();
- typedef void* StackFrames;
- struct StackTrace
- {
- static StackFrames* GetStackFrames()
- {
- return NULL;
- }
- };
- #endif
- namespace il2cpp
- {
- namespace gc
- {
- enum AllocationKind
- {
- kPtrFree = 0,
- kNormal = 1,
- kUncollectable = 2,
- kObject = 3
- };
- struct AllocationInfo
- {
- size_t size;
- StackFrames stacktrace;
- #if HAS_POSIX_BACKTRACE || HAS_WINDOWS_BACKTRACE
- void *backtraceFrames[128];
- int frameCount;
- #endif
- AllocationKind kind;
- };
- static std::map<void*, AllocationInfo, std::greater<void*> > g_Allocations;
- static std::map<void**, void*> g_References;
- static void* g_MinHeap = (void*)0xffffffffffffffffULL;
- static void* g_MaxHeap = 0;
- static WriteBarrierValidation::ExternalAllocationTrackerFunction g_ExternalAllocationTrackerFunction = NULL;
- static WriteBarrierValidation::ExternalWriteBarrierTrackerFunction g_ExternalWriteBarrierTrackerFunction = NULL;
- static baselib::ReentrantLock s_AllocationMutex;
- static baselib::ReentrantLock s_WriteBarrierMutex;
- extern "C" void* GC_malloc_kind(size_t size, int k);
- #if HAS_POSIX_BACKTRACE
- std::string GetStackPosix(const AllocationInfo &info)
- {
- std::string result;
- char **frameStrings = backtrace_symbols(&info.backtraceFrames[0], info.frameCount);
- int frameCount = std::min(info.frameCount, 32);
- if (frameStrings != NULL)
- {
- for (int x = 0; x < frameCount; x++)
- {
- result += frameStrings[x];
- result += "\n";
- }
- free(frameStrings);
- }
- return result;
- }
- #endif
- void* GC_malloc_wrapper(size_t size, AllocationKind kind)
- {
- void* ptr = (char*)GC_malloc_kind(size, kind);
- memset(ptr, 0, size);
- if (g_ExternalAllocationTrackerFunction != NULL)
- g_ExternalAllocationTrackerFunction(ptr, size, kind);
- else
- {
- const StackFrames *trace = StackTrace::GetStackFrames();
- os::FastAutoLock lock(&s_AllocationMutex);
- AllocationInfo& allocation = g_Allocations[ptr];
- allocation.size = size;
- allocation.kind = kind;
- if (trace != NULL)
- allocation.stacktrace = *trace;
- #if HAS_POSIX_BACKTRACE
- allocation.frameCount = backtrace(&allocation.backtraceFrames[0], 128);
- #endif
- #if HAS_WINDOWS_BACKTRACE
- allocation.frameCount = CaptureStackBackTrace(0, 128, &allocation.backtraceFrames[0], NULL);
- #endif
- g_MinHeap = std::min(g_MinHeap, ptr);
- g_MaxHeap = std::max(g_MaxHeap, (void*)((char*)ptr + size));
- }
- return ptr;
- }
- extern "C" void GC_dirty_inner(void **ptr)
- {
- if (g_ExternalWriteBarrierTrackerFunction)
- g_ExternalWriteBarrierTrackerFunction(ptr);
- else
- {
- os::FastAutoLock lock(&s_WriteBarrierMutex);
- g_References[ptr] = *ptr;
- }
- }
- extern "C" void GC_free(void *ptr)
- {
- }
- extern "C" void* GC_malloc(size_t size)
- {
- return GC_malloc_wrapper(size, kNormal);
- }
- extern "C" void* GC_gcj_malloc(size_t size, void * ptr_to_struct_containing_descr)
- {
- void ** ptr = (void**)GC_malloc_wrapper(size, kObject);
- *ptr = ptr_to_struct_containing_descr;
- return ptr;
- }
- extern "C" void* GC_CALL GC_gcj_vector_malloc(size_t size, void * ptr_to_struct_containing_descr)
- {
- void ** ptr = (void**)GC_malloc_wrapper(size, kObject);
- *ptr = ptr_to_struct_containing_descr;
- return ptr;
- }
- extern "C" void* GC_malloc_uncollectable(size_t size)
- {
- return GC_malloc_wrapper(size, kUncollectable);
- }
- extern "C" void* GC_malloc_atomic(size_t size)
- {
- return GC_malloc_wrapper(size, kPtrFree);
- }
- static std::string ObjectName(void* object, AllocationKind kind)
- {
- if (kind != kObject)
- {
- switch (kind)
- {
- case kPtrFree: return "Kind: kPtrFree";
- case kNormal: return "Kind: kNormal";
- case kUncollectable: return "Kind: kUncollectable";
- default: return "?";
- }
- }
- #if IL2CPP_TINY_BACKEND
- ptrdiff_t typeOffset = reinterpret_cast<uint8_t*>(((Il2CppObject*)object)->klass) - Il2CppGetTinyTypeUniverse();
- return "Tiny Type Offset:" + std::to_string(typeOffset);
- #else
- Il2CppClass* klass = il2cpp_object_get_class((Il2CppObject*)(object));
- if (klass == NULL)
- return "";
- std::string name = il2cpp_class_get_name(klass);
- Il2CppClass* parent = il2cpp_class_get_declaring_type(klass);
- while (parent != NULL)
- {
- klass = parent;
- parent = il2cpp_class_get_declaring_type(klass);
- name = std::string(il2cpp_class_get_name(klass)) + "/" + name;
- }
- return std::string(il2cpp_class_get_namespace(klass)) + "::" + name;
- #endif
- }
- static std::string GetReadableStackTrace(const StackFrames &stackTrace)
- {
- #if IL2CPP_TINY_BACKEND
- return "No managed stack traces in Tiny";
- #else
- std::string str;
- for (StackFrames::const_iterator i = stackTrace.begin(); i != stackTrace.end(); i++)
- {
- Il2CppClass* parent = il2cpp_method_get_declaring_type(i->method);
- str += il2cpp_class_get_namespace(parent);
- str += '.';
- str += il2cpp_class_get_name(parent);
- str += ':';
- str += il2cpp_method_get_name(i->method);
- str += '\n';
- }
- return str;
- #endif
- }
- static std::string LogError(std::pair<void*, AllocationInfo> const & object, void** reference, void *refObject)
- {
- std::string msg;
- char chbuf[1024];
- snprintf(chbuf, 1024, "In object %p (%s) with size %zx at offset %zx, allocated at \n%s\n", object.first, ObjectName(object.first, object.second.kind).c_str(), object.second.size, (size_t)((char*)reference - (char*)object.first),
- #if HAS_POSIX_BACKTRACE
- GetStackPosix(object.second).c_str()
- #else
- GetReadableStackTrace(object.second.stacktrace).c_str()
- #endif
- );
- msg += chbuf;
- snprintf(chbuf, 1024, "Points to object %p of type (%s)\n", refObject, ObjectName(refObject, kPtrFree).c_str());
- msg += chbuf;
- return msg;
- }
- // Boehm internal constants
- #define GC_DS_TAG_BITS 2
- #define GC_DS_TAGS ((1 << GC_DS_TAG_BITS) - 1)
- #define GC_DS_LENGTH 0 /* The entire word is a length in bytes that */
- /* must be a multiple of 4. */
- #define GC_DS_BITMAP 1 /* 30 (62) bits are a bitmap describing pointer */
- #ifndef MARK_DESCR_OFFSET
- # define MARK_DESCR_OFFSET sizeof(void*)
- #endif
- void WriteBarrierValidation::SetExternalAllocationTracker(ExternalAllocationTrackerFunction func)
- {
- g_ExternalAllocationTrackerFunction = func;
- }
- void WriteBarrierValidation::SetExternalWriteBarrierTracker(ExternalWriteBarrierTrackerFunction func)
- {
- g_ExternalWriteBarrierTrackerFunction = func;
- }
- /* Taken from https://randomascii.wordpress.com/2012/02/14/64-bit-made-easy/
- Copyright 2012 Bruce Dawson. All Rights Reserved.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- void ReserveBottomMemory()
- {
- #if defined(_WIN64)
- static bool s_initialized = false;
- if (s_initialized)
- return;
- s_initialized = true;
- // Start by reserving large blocks of address space, and then
- // gradually reduce the size in order to capture all of the
- // fragments. Technically we should continue down to 64 KB but
- // stopping at 1 MB is sufficient to keep most allocators out.
- const size_t LOW_MEM_LINE = 0x1000000000LL; // Modified to reserve bottom 64 GB
- size_t totalReservation = 0;
- size_t numVAllocs = 0;
- size_t numHeapAllocs = 0;
- size_t oneMB = 1024 * 1024;
- size_t sixtyFourKB = 64 * 1024; // Modified to continue down to 64 KB as our GC allocates chunks in 256 KB
- for (size_t size = 256 * oneMB; size >= sixtyFourKB; size /= 2)
- {
- for (;;)
- {
- void* p = VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS);
- if (!p)
- break;
- if ((size_t)p >= LOW_MEM_LINE)
- {
- // We don't need this memory, so release it completely.
- VirtualFree(p, 0, MEM_RELEASE);
- break;
- }
- totalReservation += size;
- ++numVAllocs;
- }
- }
- // Now repeat the same process but making heap allocations, to use up
- // the already reserved heap blocks that are below the 4 GB line.
- HANDLE heap = GetProcessHeap();
- for (size_t blockSize = 64 * 1024; blockSize >= 16; blockSize /= 2)
- {
- for (;;)
- {
- void* p = HeapAlloc(heap, 0, blockSize);
- if (!p)
- break;
- if ((size_t)p >= LOW_MEM_LINE)
- {
- // We don't need this memory, so release it completely.
- HeapFree(heap, 0, p);
- break;
- }
- totalReservation += blockSize;
- ++numHeapAllocs;
- }
- }
- // Perversely enough the CRT doesn't use the process heap. Consume up
- // the memory the CRT heap has already reserved.
- for (size_t blockSize = 64 * 1024; blockSize >= 16; blockSize /= 2)
- {
- for (;;)
- {
- void* p = malloc(blockSize);
- if (!p)
- break;
- if ((size_t)p >= LOW_MEM_LINE)
- {
- // We don't need this memory, so release it completely.
- free(p);
- break;
- }
- totalReservation += blockSize;
- ++numHeapAllocs;
- }
- }
- // Print diagnostics showing how many allocations we had to make in
- // order to reserve all of low memory, typically less than 200.
- char buffer[1000];
- sprintf_s(buffer, "Reserved %1.3f MB (%d vallocs,"
- "%d heap allocs) of low-memory.\n",
- totalReservation / (1024 * 1024.0),
- (int)numVAllocs, (int)numHeapAllocs);
- OutputDebugStringA(buffer);
- #endif
- }
- void WriteBarrierValidation::Setup()
- {
- // Reserve bottom 64 GB of memory to force GC into high addresses
- // This prevents hashes colliding with heap addresses and causing false detections.
- ReserveBottomMemory();
- GarbageCollector::Disable();
- }
- void WriteBarrierValidation::Run()
- {
- if (g_ExternalAllocationTrackerFunction != NULL)
- return;
- std::string msg;
- msg = "<TestResult Name='WriteBarrierValidation'>\n<![CDATA[\n";
- size_t errors = 0;
- for (std::map<void*, AllocationInfo>::iterator i = g_Allocations.begin(); i != g_Allocations.end(); i++)
- {
- if (i->second.kind == kPtrFree)
- continue;
- intptr_t desc = (intptr_t)GC_NO_DESCRIPTOR;
- if (i->second.kind == kObject)
- {
- desc = *(intptr_t*)(((char*)(*(void**)i->first)) + MARK_DESCR_OFFSET);
- if ((desc & GC_DS_TAGS) != GC_DS_BITMAP)
- desc = (intptr_t)GC_NO_DESCRIPTOR;
- }
- for (void** ptr = ((void**)i->first) + 2; ptr < (void**)((char*)i->first + i->second.size); ptr++)
- {
- if (desc != (intptr_t)GC_NO_DESCRIPTOR)
- {
- // if we have a GC descriptor bitmap, check if this address can be a pointer, skip otherwise.
- size_t ptr_index = ptr - (void**)i->first;
- if (((desc >> (sizeof(void*) * 8 - ptr_index - 1)) & 1) == 0)
- continue;
- }
- void* ref = *ptr;
- if (ref < g_MinHeap || ref >= g_MaxHeap)
- continue;
- std::map<void*, AllocationInfo>::iterator j = g_Allocations.lower_bound(ref);
- if (j != g_Allocations.end() && j != i && j->second.kind != kUncollectable)
- {
- if (j->first <= ref && (void*)((char*)j->first + j->second.size) > ref)
- {
- std::map<void**, void*>::iterator trackedRef = g_References.find(ptr);
- if (trackedRef == g_References.end())
- {
- char chbuf[1024];
- snprintf(chbuf, 1024, "\n%p looks like a reference to %p, but was not found in tracked references\n", ptr, ref);
- msg += chbuf;
- errors++;
- msg += LogError(*i, ptr, j->first);
- }
- else if (trackedRef->second != ref)
- {
- char chbuf[1024];
- snprintf(chbuf, 1024, "\n%p does not match tracked value (%p!=%p).\n", ptr, trackedRef->second, ref);
- msg += chbuf;
- errors++;
- msg += LogError(*i, ptr, j->first);
- }
- }
- }
- }
- }
- msg += "]]>\n</TestResult>\n";
- if (errors > 0)
- printf("%s", msg.c_str());
- }
- } /* gc */
- } /* il2cpp */
- #endif
|