#include "il2cpp-config.h"
|
#include "MemoryInformation.h"
|
#include "gc/GarbageCollector.h"
|
#include "gc/GCHandle.h"
|
#include "metadata/ArrayMetadata.h"
|
#include "metadata/GenericMetadata.h"
|
#include "vm/Assembly.h"
|
#include "vm/Class.h"
|
#include "vm/MetadataCache.h"
|
#include "vm/Type.h"
|
#include "utils/Memory.h"
|
#include "il2cpp-class-internals.h"
|
#include "il2cpp-object-internals.h"
|
#include "il2cpp-tabledefs.h"
|
|
#include <map>
|
#include <limits>
|
|
namespace il2cpp
|
{
|
namespace vm
|
{
|
namespace MemoryInformation
|
{
|
struct GatherMetadataContext
|
{
|
uint32_t currentIndex;
|
std::map<Il2CppClass*, uint32_t> allTypes;
|
};
|
|
static void GatherMetadataCallback(Il2CppClass* type, void* context)
|
{
|
if (type->initialized)
|
{
|
GatherMetadataContext* ctx = static_cast<GatherMetadataContext*>(context);
|
ctx->allTypes.insert(std::make_pair(type, ctx->currentIndex++));
|
}
|
}
|
|
static inline int FindTypeInfoIndexInMap(const std::map<Il2CppClass*, uint32_t>& allTypes, Il2CppClass* typeInfo)
|
{
|
std::map<Il2CppClass*, uint32_t>::const_iterator it = allTypes.find(typeInfo);
|
|
if (it == allTypes.end())
|
return -1;
|
|
return it->second;
|
}
|
|
static inline void GatherMetadata(Il2CppMetadataSnapshot& metadata)
|
{
|
GatherMetadataContext gatherMetadataContext = { 0 };
|
const AssemblyVector* allAssemblies = Assembly::GetAllAssemblies();
|
|
for (AssemblyVector::const_iterator it = allAssemblies->begin(); it != allAssemblies->end(); it++)
|
{
|
const Il2CppImage& image = *(*it)->image;
|
|
for (uint32_t i = 0; i < image.typeCount; i++)
|
{
|
Il2CppClass* type = MetadataCache::GetTypeInfoFromTypeDefinitionIndex(image.typeStart + i);
|
if (type->initialized)
|
gatherMetadataContext.allTypes.insert(std::make_pair(type, gatherMetadataContext.currentIndex++));
|
}
|
}
|
|
metadata::ArrayMetadata::WalkArrays(GatherMetadataCallback, &gatherMetadataContext);
|
metadata::ArrayMetadata::WalkSZArrays(GatherMetadataCallback, &gatherMetadataContext);
|
metadata::GenericMetadata::WalkAllGenericClasses(GatherMetadataCallback, &gatherMetadataContext);
|
MetadataCache::WalkPointerTypes(GatherMetadataCallback, &gatherMetadataContext);
|
|
const std::map<Il2CppClass*, uint32_t>& allTypes = gatherMetadataContext.allTypes;
|
metadata.typeCount = static_cast<uint32_t>(allTypes.size());
|
metadata.types = static_cast<Il2CppMetadataType*>(IL2CPP_CALLOC(metadata.typeCount, sizeof(Il2CppMetadataType)));
|
|
for (std::map<Il2CppClass*, uint32_t>::const_iterator it = allTypes.begin(); it != allTypes.end(); it++)
|
{
|
Il2CppClass* typeInfo = it->first;
|
|
uint32_t index = it->second;
|
Il2CppMetadataType& type = metadata.types[index];
|
|
if (typeInfo->rank > 0)
|
{
|
type.flags = static_cast<Il2CppMetadataTypeFlags>(kArray | (kArrayRankMask & (typeInfo->rank << 16)));
|
type.baseOrElementTypeIndex = FindTypeInfoIndexInMap(allTypes, typeInfo->element_class);
|
}
|
else
|
{
|
type.flags = (typeInfo->valuetype || typeInfo->byval_arg.type == IL2CPP_TYPE_PTR) ? kValueType : kNone;
|
type.fieldCount = 0;
|
|
if (typeInfo->field_count > 0)
|
{
|
type.fields = static_cast<Il2CppMetadataField*>(IL2CPP_CALLOC(typeInfo->field_count, sizeof(Il2CppMetadataField)));
|
|
for (int i = 0; i < typeInfo->field_count; i++)
|
{
|
Il2CppMetadataField& field = metadata.types[index].fields[type.fieldCount];
|
FieldInfo* fieldInfo = typeInfo->fields + i;
|
field.typeIndex = FindTypeInfoIndexInMap(allTypes, Class::FromIl2CppType(fieldInfo->type));
|
|
// This will happen if fields type is not initialized
|
// It's OK to skip it, because it means the field is guaranteed to be null on any object
|
if (field.typeIndex == -1)
|
continue;
|
|
//literals have no actual storage, and are not relevant in this context.
|
if ((fieldInfo->type->attrs & FIELD_ATTRIBUTE_LITERAL) != 0)
|
continue;
|
|
field.isStatic = (fieldInfo->type->attrs & FIELD_ATTRIBUTE_STATIC) != 0;
|
field.offset = fieldInfo->offset;
|
field.name = fieldInfo->name;
|
type.fieldCount++;
|
}
|
}
|
|
type.staticsSize = typeInfo->static_fields_size;
|
|
if (type.staticsSize > 0 && typeInfo->static_fields != NULL)
|
{
|
type.statics = static_cast<uint8_t*>(IL2CPP_MALLOC(type.staticsSize));
|
memcpy(type.statics, typeInfo->static_fields, type.staticsSize);
|
}
|
|
Il2CppClass* baseType = Class::GetParent(typeInfo);
|
type.baseOrElementTypeIndex = baseType != NULL ? FindTypeInfoIndexInMap(allTypes, baseType) : -1;
|
}
|
|
type.assemblyName = typeInfo->image->assembly->aname.name;
|
|
std::string typeName = Type::GetName(&typeInfo->byval_arg, IL2CPP_TYPE_NAME_FORMAT_IL);
|
type.name = static_cast<char*>(IL2CPP_CALLOC(typeName.length() + 1, sizeof(char)));
|
memcpy(type.name, typeName.c_str(), typeName.length() + 1);
|
|
type.typeInfoAddress = reinterpret_cast<uint64_t>(typeInfo);
|
type.size = (typeInfo->valuetype) != 0 ? (typeInfo->instance_size - sizeof(Il2CppObject)) : typeInfo->instance_size;
|
}
|
}
|
|
struct SectionIterationContext
|
{
|
Il2CppManagedMemorySection* currentSection;
|
};
|
|
static void AllocateMemoryForSection(void* context, void* sectionStart, void* sectionEnd)
|
{
|
SectionIterationContext* ctx = static_cast<SectionIterationContext*>(context);
|
Il2CppManagedMemorySection& section = *ctx->currentSection;
|
|
section.sectionStartAddress = reinterpret_cast<uint64_t>(sectionStart);
|
|
ptrdiff_t sectionSize = static_cast<uint8_t*>(sectionEnd) - static_cast<uint8_t*>(sectionStart);
|
|
if (sizeof(void*) > 4) // This assert is only valid on 64-bit
|
IL2CPP_ASSERT(sectionSize <= static_cast<ptrdiff_t>(std::numeric_limits<uint32_t>::max()));
|
|
section.sectionSize = static_cast<uint32_t>(sectionSize);
|
section.sectionBytes = static_cast<uint8_t*>(IL2CPP_MALLOC(section.sectionSize));
|
|
ctx->currentSection++;
|
}
|
|
static void CopyHeapSection(void* context, void* sectionStart, void* sectionEnd)
|
{
|
SectionIterationContext* ctx = static_cast<SectionIterationContext*>(context);
|
Il2CppManagedMemorySection& section = *ctx->currentSection;
|
|
IL2CPP_ASSERT(section.sectionStartAddress == reinterpret_cast<uint64_t>(sectionStart));
|
IL2CPP_ASSERT(section.sectionSize == static_cast<uint8_t*>(sectionEnd) - static_cast<uint8_t*>(sectionStart));
|
memcpy(section.sectionBytes, sectionStart, section.sectionSize);
|
|
ctx->currentSection++;
|
}
|
|
static void* CaptureHeapInfo(void* voidManagedHeap)
|
{
|
Il2CppManagedHeap& heap = *(Il2CppManagedHeap*)voidManagedHeap;
|
|
heap.sectionCount = static_cast<uint32_t>(il2cpp::gc::GarbageCollector::GetSectionCount());
|
heap.sections = static_cast<Il2CppManagedMemorySection*>(IL2CPP_CALLOC(heap.sectionCount, sizeof(Il2CppManagedMemorySection)));
|
|
SectionIterationContext iterationContext = { heap.sections };
|
il2cpp::gc::GarbageCollector::ForEachHeapSection(&iterationContext, AllocateMemoryForSection);
|
|
return NULL;
|
}
|
|
static void FreeIL2CppManagedHeap(Il2CppManagedHeap& heap)
|
{
|
for (uint32_t i = 0; i < heap.sectionCount; i++)
|
{
|
IL2CPP_FREE(heap.sections[i].sectionBytes);
|
}
|
|
IL2CPP_FREE(heap.sections);
|
}
|
|
struct VerifyHeapSectionStillValidIterationContext
|
{
|
Il2CppManagedMemorySection* currentSection;
|
bool wasValid;
|
};
|
|
static void VerifyHeapSectionIsStillValid(void* context, void* sectionStart, void* sectionEnd)
|
{
|
VerifyHeapSectionStillValidIterationContext* iterationContext = (VerifyHeapSectionStillValidIterationContext*)context;
|
if (iterationContext->currentSection->sectionSize != static_cast<uint8_t*>(sectionEnd) - static_cast<uint8_t*>(sectionStart))
|
iterationContext->wasValid = false;
|
else if (iterationContext->currentSection->sectionStartAddress != reinterpret_cast<uint64_t>(sectionStart))
|
iterationContext->wasValid = false;
|
|
iterationContext->currentSection++;
|
}
|
|
static bool IsIL2CppManagedHeapStillValid(Il2CppManagedHeap& heap)
|
{
|
if (heap.sectionCount != static_cast<uint32_t>(il2cpp::gc::GarbageCollector::GetSectionCount()))
|
return false;
|
|
VerifyHeapSectionStillValidIterationContext iterationContext = { heap.sections, true };
|
il2cpp::gc::GarbageCollector::ForEachHeapSection(&iterationContext, VerifyHeapSectionIsStillValid);
|
|
return iterationContext.wasValid;
|
}
|
|
// The difficulty in capturing the managed snapshot is that we need to do quite some work with the world stopped,
|
// to make sure that our snapshot is "valid", and didn't change as we were copying it. However, stopping the world,
|
// makes it so you cannot take any lock or allocations. We deal with it like this:
|
//
|
// 1) We take note of the amount of heap sections and their sizes, and we allocate memory to copy them into.
|
// 2) We stop the world.
|
// 3) We check if the amount of heapsections and their sizes didn't change in the mean time. If they did, try again.
|
// 4) Now, with the world still stopped, we memcpy() the memory from the real heapsections, into the memory that we
|
// allocated for their copies.
|
// 5) Start the world again.
|
|
static inline void CaptureManagedHeap(Il2CppManagedHeap& heap)
|
{
|
for (;;)
|
{
|
il2cpp::gc::GarbageCollector::CallWithAllocLockHeld(CaptureHeapInfo, &heap);
|
|
il2cpp::gc::GarbageCollector::StopWorld();
|
|
if (IsIL2CppManagedHeapStillValid(heap))
|
break;
|
|
il2cpp::gc::GarbageCollector::StartWorld();
|
|
FreeIL2CppManagedHeap(heap);
|
}
|
|
SectionIterationContext iterationContext = { heap.sections };
|
il2cpp::gc::GarbageCollector::ForEachHeapSection(&iterationContext, CopyHeapSection);
|
|
il2cpp::gc::GarbageCollector::StartWorld();
|
}
|
|
struct GCHandleTargetIterationContext
|
{
|
std::vector<Il2CppObject*> managedObjects;
|
};
|
|
static void GCHandleIterationCallback(Il2CppObject* managedObject, void* context)
|
{
|
GCHandleTargetIterationContext* ctx = static_cast<GCHandleTargetIterationContext*>(context);
|
ctx->managedObjects.push_back(managedObject);
|
}
|
|
static inline void CaptureGCHandleTargets(Il2CppGCHandles& gcHandles)
|
{
|
GCHandleTargetIterationContext gcHandleTargetIterationContext;
|
il2cpp::gc::GCHandle::WalkStrongGCHandleTargets(GCHandleIterationCallback, &gcHandleTargetIterationContext);
|
|
const std::vector<Il2CppObject*>& trackedObjects = gcHandleTargetIterationContext.managedObjects;
|
gcHandles.trackedObjectCount = static_cast<uint32_t>(trackedObjects.size());
|
gcHandles.pointersToObjects = static_cast<uint64_t*>(IL2CPP_CALLOC(gcHandles.trackedObjectCount, sizeof(uint64_t)));
|
|
for (uint32_t i = 0; i < gcHandles.trackedObjectCount; i++)
|
gcHandles.pointersToObjects[i] = reinterpret_cast<uint64_t>(trackedObjects[i]);
|
}
|
|
void FillRuntimeInformation(Il2CppRuntimeInformation& runtimeInfo)
|
{
|
runtimeInfo.pointerSize = static_cast<uint32_t>(sizeof(void*));
|
runtimeInfo.objectHeaderSize = static_cast<uint32_t>(sizeof(Il2CppObject));
|
runtimeInfo.arrayHeaderSize = static_cast<uint32_t>(kIl2CppSizeOfArray);
|
runtimeInfo.arraySizeOffsetInHeader = kIl2CppOffsetOfArrayLength;
|
runtimeInfo.arrayBoundsOffsetInHeader = kIl2CppOffsetOfArrayBounds;
|
runtimeInfo.allocationGranularity = static_cast<uint32_t>(2 * sizeof(void*));
|
}
|
|
struct il2cpp_heap_chunk
|
{
|
void* start;
|
size_t size;
|
};
|
|
void ReportIL2CppClasses(ClassReportFunc callback, void* context)
|
{
|
const AssemblyVector* allAssemblies = Assembly::GetAllAssemblies();
|
|
for (AssemblyVector::const_iterator it = allAssemblies->begin(); it != allAssemblies->end(); it++)
|
{
|
const Il2CppImage& image = *(*it)->image;
|
|
for (uint32_t i = 0; i < image.typeCount; i++)
|
{
|
Il2CppClass* type = MetadataCache::GetTypeInfoFromTypeDefinitionIndex(image.typeStart + i);
|
if (type->initialized)
|
callback(type, context);
|
}
|
}
|
|
metadata::ArrayMetadata::WalkArrays(callback, context);
|
metadata::ArrayMetadata::WalkSZArrays(callback, context);
|
metadata::GenericMetadata::WalkAllGenericClasses(callback, context);
|
MetadataCache::WalkPointerTypes(callback, context);
|
}
|
|
void ReportGcHeapSection(void * context, void * start, void * end)
|
{
|
il2cpp_heap_chunk chunk;
|
chunk.start = start;
|
chunk.size = (uint8_t *)end - (uint8_t *)start;
|
IterationContext* ctxPtr = reinterpret_cast<IterationContext*>(context);
|
ctxPtr->callback(&chunk, ctxPtr->userData);
|
}
|
|
void ReportGcHandleTarget(Il2CppObject * obj, void * context)
|
{
|
IterationContext* ctxPtr = reinterpret_cast<IterationContext*>(context);
|
ctxPtr->callback(obj, ctxPtr->userData);
|
}
|
|
Il2CppManagedMemorySnapshot* CaptureManagedMemorySnapshot()
|
{
|
Il2CppManagedMemorySnapshot* snapshot = static_cast<Il2CppManagedMemorySnapshot*>(IL2CPP_MALLOC_ZERO(sizeof(Il2CppManagedMemorySnapshot)));
|
|
GatherMetadata(snapshot->metadata);
|
CaptureManagedHeap(snapshot->heap);
|
CaptureGCHandleTargets(snapshot->gcHandles);
|
FillRuntimeInformation(snapshot->runtimeInformation);
|
|
return snapshot;
|
}
|
|
void FreeCapturedManagedMemorySnapshot(Il2CppManagedMemorySnapshot* snapshot)
|
{
|
FreeIL2CppManagedHeap(snapshot->heap);
|
|
IL2CPP_FREE(snapshot->gcHandles.pointersToObjects);
|
|
Il2CppMetadataSnapshot& metadata = snapshot->metadata;
|
|
for (uint32_t i = 0; i < metadata.typeCount; i++)
|
{
|
if ((metadata.types[i].flags & kArray) == 0)
|
{
|
IL2CPP_FREE(metadata.types[i].fields);
|
IL2CPP_FREE(metadata.types[i].statics);
|
}
|
|
IL2CPP_FREE(metadata.types[i].name);
|
}
|
|
IL2CPP_FREE(metadata.types);
|
IL2CPP_FREE(snapshot);
|
}
|
} // namespace MemoryInformation
|
} // namespace vm
|
} // namespace il2cpp
|