#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
|
|
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 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<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;
|
}
|
|
void WriteBarrierValidation::Setup()
|
{
|
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, "%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</TestResult>\n";
|
if (errors > 0)
|
printf("%s", msg.c_str());
|
}
|
} /* gc */
|
} /* il2cpp */
|
|
#endif
|