#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 #include #include #include // 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 #endif #if HAS_WINDOWS_BACKTRACE #include #endif using namespace il2cpp::os; using namespace il2cpp::vm; #if !IL2CPP_GC_BOEHM #error "Write Barrier Validation is specific to Boehm GC" #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 > g_Allocations; static std::map 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 FastMutex s_AllocationMutex; static FastMutex 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_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) return "?"; 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; } static std::string GetReadableStackTrace(const StackFrames &stackTrace) { 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; } static std::string LogError(std::pair 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; } void WriteBarrierValidation::Setup() { GarbageCollector::Disable(); } void WriteBarrierValidation::Run() { if (g_ExternalAllocationTrackerFunction != NULL) return; std::string msg; msg = "\n::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::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::iterator trackedRef = g_References.find(ptr); if (trackedRef == g_References.end()) { char chbuf[1024]; snprintf(chbuf, 1024, "%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, "%p does not match tracked value (%p!=%p).\n", ptr, trackedRef->second, ref); msg += chbuf; errors++; msg += LogError(*i, ptr, j->first); } } } } } msg += "]]>\n\n"; if (errors > 0) printf("%s", msg.c_str()); } } /* gc */ } /* il2cpp */ #endif