304 lines
11 KiB
C++
304 lines
11 KiB
C++
#include "il2cpp-config.h"
|
|
|
|
#include "il2cpp-class-internals.h"
|
|
#include "os/Environment.h"
|
|
#include "os/File.h"
|
|
#include "os/Image.h"
|
|
#include "os/Initialize.h"
|
|
#include "os/LibraryLoader.h"
|
|
#include "os/Locale.h"
|
|
#include "os/Path.h"
|
|
#include "NativeSymbol.h"
|
|
#include "utils/Collections.h"
|
|
#include "utils/PathUtils.h"
|
|
#include "utils/MemoryMappedFile.h"
|
|
#include "utils/Runtime.h"
|
|
#include "vm-utils/MethodDefinitionKey.h"
|
|
#include <string>
|
|
#include <cstdlib>
|
|
|
|
#if RUNTIME_TINY
|
|
#include "vm/DebugMetadata.h"
|
|
#endif
|
|
|
|
namespace il2cpp
|
|
{
|
|
namespace utils
|
|
{
|
|
#if (IL2CPP_ENABLE_NATIVE_STACKTRACES && (!RUNTIME_TINY || IL2CPP_TINY_DEBUG_METADATA))
|
|
|
|
static Il2CppMethodPointer MaskSpareBits(const Il2CppMethodPointer method)
|
|
{
|
|
return (Il2CppMethodPointer)((size_t)method & ~IL2CPP_POINTER_SPARE_BITS);
|
|
}
|
|
|
|
struct MethodInfoToMethodPointerConverter
|
|
{
|
|
Il2CppMethodPointer operator()(const MethodDefinitionKey& methodInfo) const
|
|
{
|
|
return MaskSpareBits(methodInfo.method);
|
|
}
|
|
};
|
|
|
|
typedef il2cpp::utils::collections::ArrayValueMap<Il2CppMethodPointer, MethodDefinitionKey, MethodInfoToMethodPointerConverter> NativeMethodMap;
|
|
static NativeMethodMap s_NativeMethods;
|
|
|
|
struct NativeSymbolMutator
|
|
{
|
|
void operator()(MethodDefinitionKey* method)
|
|
{
|
|
// So, when a function is marked as noreturn, some compilers emit a call to that function and then
|
|
// put the next function immediately after call instruction which means the return address on the stack
|
|
// will appear to point to the wrong function. This messes up our stack walking as we now are confused
|
|
// which method is actually on the stack. To work around this, we add "1" to each of the function addresses,
|
|
// so each function appears to start 1 byte later which means the address on the stack will appear as if
|
|
// it is pointing to function that called the no return function. This is okay because no function will
|
|
// ever return to the first byte of another function.
|
|
method->method = reinterpret_cast<Il2CppMethodPointer>(reinterpret_cast<intptr_t>(method->method) + 1);
|
|
}
|
|
};
|
|
|
|
void NativeSymbol::RegisterMethods(const std::vector<MethodDefinitionKey>& managedMethods)
|
|
{
|
|
s_NativeMethods.assign(managedMethods);
|
|
|
|
#if IL2CPP_MUTATE_METHOD_POINTERS
|
|
NativeSymbolMutator mutator;
|
|
s_NativeMethods.mutate(mutator);
|
|
#endif
|
|
}
|
|
|
|
#pragma pack(push, p1, 4)
|
|
struct SymbolInfo
|
|
{
|
|
uint64_t address;
|
|
uint32_t length;
|
|
};
|
|
#pragma pack(pop, p1)
|
|
|
|
static int32_t s_SymbolCount;
|
|
static SymbolInfo* s_SymbolInfos;
|
|
static void* s_ImageBase;
|
|
|
|
#if !RUNTIME_TINY
|
|
static void* LoadSymbolInfoFileFrom(const std::string& path)
|
|
{
|
|
int error;
|
|
il2cpp::os::FileHandle* handle = il2cpp::os::File::Open(path, kFileModeOpen, kFileAccessRead, kFileShareRead, kFileOptionsNone, &error);
|
|
|
|
if (error != 0)
|
|
return NULL;
|
|
|
|
// Note that we won't unmap this file, we'll leave it open the entire lifetime of the process.
|
|
void* mappedFile = utils::MemoryMappedFile::Map(handle);
|
|
|
|
il2cpp::os::File::Close(handle, &error);
|
|
IL2CPP_ASSERT(error == 0);
|
|
|
|
return mappedFile;
|
|
}
|
|
|
|
#endif //!RUNTIME_TINY
|
|
|
|
static void* LoadSymbolInfoFile()
|
|
{
|
|
#if RUNTIME_TINY
|
|
return NULL;
|
|
#else
|
|
#if IL2CPP_TARGET_ANDROID
|
|
#if defined(__i386__)
|
|
std::string symbolMapFileName = "SymbolMap-x86";
|
|
#elif defined(__arm__)
|
|
std::string symbolMapFileName = "SymbolMap-ARMv7";
|
|
#elif defined(__aarch64__)
|
|
std::string symbolMapFileName = "SymbolMap-ARM64";
|
|
#elif defined(__x86_64__)
|
|
std::string symbolMapFileName = "SymbolMap-x86_64";
|
|
#else
|
|
#error Unknown symbol map file name
|
|
#endif
|
|
#else
|
|
#if !IL2CPP_CAN_USE_MULTIPLE_SYMBOL_MAPS
|
|
std::string symbolMapFileName = "SymbolMap";
|
|
#elif IL2CPP_SIZEOF_VOID_P == 4
|
|
std::string symbolMapFileName = "SymbolMap-32";
|
|
#elif IL2CPP_SIZEOF_VOID_P == 8
|
|
std::string symbolMapFileName = "SymbolMap-64";
|
|
#else
|
|
#error Unknown symbol map file name
|
|
#endif
|
|
#endif
|
|
|
|
void* result = LoadSymbolInfoFileFrom(il2cpp::utils::PathUtils::Combine(il2cpp::os::Path::GetApplicationFolder(), symbolMapFileName));
|
|
if (result != NULL)
|
|
return result;
|
|
|
|
return LoadSymbolInfoFileFrom(il2cpp::utils::PathUtils::Combine(utils::Runtime::GetDataDir(), symbolMapFileName));
|
|
#endif
|
|
}
|
|
|
|
static void InitializeSymbolInfos()
|
|
{
|
|
s_ImageBase = il2cpp::os::Image::GetImageBase();
|
|
|
|
if (!il2cpp::os::Image::ManagedSectionExists())
|
|
{
|
|
void* fileBuffer = LoadSymbolInfoFile();
|
|
if (fileBuffer == NULL)
|
|
return;
|
|
|
|
s_SymbolCount = *((int32_t*)fileBuffer);
|
|
s_SymbolInfos = (SymbolInfo*)((uint8_t*)fileBuffer + sizeof(s_SymbolCount));
|
|
}
|
|
}
|
|
|
|
static bool CompareEndOfSymbols(const SymbolInfo &a, const SymbolInfo &b)
|
|
{
|
|
return a.address + a.length < b.address + b.length;
|
|
}
|
|
|
|
static SymbolInfo* FindSymbolInfoForNativeMethod(Il2CppMethodPointer nativeMethod)
|
|
{
|
|
SymbolInfo* end = s_SymbolInfos + s_SymbolCount;
|
|
|
|
// our 'key' could be anywhere within a symbol. Our comparison function compares the end address
|
|
// of the symbols. By doing this, upper bound returns the first symbol whose end address is greater
|
|
// than our 'key'. This is our symbol since our end is the first end above an interior value.
|
|
SymbolInfo interiorSymbol = { (size_t)((char*)nativeMethod - (char*)s_ImageBase), 0 };
|
|
SymbolInfo* containingSymbol = std::upper_bound(s_SymbolInfos, end, interiorSymbol, &CompareEndOfSymbols);
|
|
|
|
if (containingSymbol == end)
|
|
return NULL;
|
|
|
|
// We only include managed methods in the symbol data. A lookup for a native method might find the
|
|
// previous or next managed method in the data. This will be incorrect, so check the start and the size,
|
|
// to make sure the interior symbol is really within the method found in the containing symbol.
|
|
if ((interiorSymbol.address != containingSymbol->address) &&
|
|
((interiorSymbol.address < containingSymbol->address) ||
|
|
(interiorSymbol.address - containingSymbol->address > containingSymbol->length)))
|
|
return NULL;
|
|
|
|
return containingSymbol;
|
|
}
|
|
|
|
static bool s_TriedToInitializeSymbolInfo = false;
|
|
|
|
static bool IsInstructionPointerProbablyInManagedMethod(Il2CppMethodPointer managedMethodStart, Il2CppMethodPointer instructionPointer)
|
|
{
|
|
const int probableMaximumManagedMethodSizeInBytes = 5000;
|
|
if (std::abs((intptr_t)managedMethodStart - (intptr_t)instructionPointer) < probableMaximumManagedMethodSizeInBytes)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
const VmMethod* NativeSymbol::GetMethodFromNativeSymbol(Il2CppMethodPointer nativeMethod)
|
|
{
|
|
if (!s_TriedToInitializeSymbolInfo)
|
|
{
|
|
// Only attempt to initialize the symbol information once. If it is not present the first time,
|
|
// it likely won't be there later either. Repeated checking can cause performance problems.
|
|
s_TriedToInitializeSymbolInfo = true;
|
|
InitializeSymbolInfos();
|
|
}
|
|
|
|
// address has to be above our base address
|
|
if ((void*)nativeMethod < (void*)s_ImageBase)
|
|
return NULL;
|
|
|
|
if (il2cpp::os::Image::ManagedSectionExists())
|
|
{
|
|
if (!il2cpp::os::Image::IsInManagedSection((void*)nativeMethod))
|
|
return NULL;
|
|
}
|
|
|
|
if (s_SymbolCount > 0)
|
|
{
|
|
SymbolInfo* containingSymbol = FindSymbolInfoForNativeMethod(nativeMethod);
|
|
if (containingSymbol == NULL)
|
|
return NULL;
|
|
|
|
nativeMethod = (Il2CppMethodPointer)((char*)s_ImageBase + containingSymbol->address);
|
|
|
|
// We can't assume that the map file is aligned.
|
|
// We must use the same masking/no masking logic used to insert into the data structure for the lookup.
|
|
// If we don't, the find will try to look up unmasked in a table full of masked values.
|
|
|
|
// do exact lookup based on the symbol start address, as that is our key
|
|
NativeMethodMap::iterator iter = s_NativeMethods.find_first(MaskSpareBits(nativeMethod));
|
|
if (iter != s_NativeMethods.end())
|
|
{
|
|
#if RUNTIME_TINY
|
|
return tiny::vm::DebugMetadata::GetMethodNameFromMethodDefinitionIndex(iter->methodIndex);
|
|
#else
|
|
return il2cpp::vm::MetadataCache::GetMethodInfoFromMethodHandle(iter->methodHandle);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get the first symbol greater than the one we want, because our instruction pointer
|
|
// probably won't be at the start of the method.
|
|
NativeMethodMap::iterator methodAfterNativeMethod = s_NativeMethods.upper_bound(nativeMethod);
|
|
|
|
// If method are all of the managed methods are in the same custom section of the binary, then assume we
|
|
// will find the proper method, so the end iterator means we found the last method in this list. If we
|
|
// don't have custom sections, then we may have actually not found the method. In that case, let's not
|
|
// return a method we are unsure of.
|
|
if (!il2cpp::os::Image::ManagedSectionExists())
|
|
{
|
|
if (methodAfterNativeMethod == s_NativeMethods.end())
|
|
return NULL;
|
|
|
|
if (!IsInstructionPointerProbablyInManagedMethod(methodAfterNativeMethod->method, nativeMethod))
|
|
return NULL;
|
|
}
|
|
|
|
// Go back one to get the method we actually want.
|
|
if (methodAfterNativeMethod != s_NativeMethods.begin())
|
|
methodAfterNativeMethod--;
|
|
|
|
#if RUNTIME_TINY
|
|
return tiny::vm::DebugMetadata::GetMethodNameFromMethodDefinitionIndex(methodAfterNativeMethod->methodIndex);
|
|
#else
|
|
return il2cpp::vm::MetadataCache::GetMethodInfoFromMethodHandle(methodAfterNativeMethod->methodHandle);
|
|
#endif
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool NativeSymbol::GetMethodDebugInfo(const MethodInfo *method, Il2CppMethodDebugInfo* methodDebugInfo)
|
|
{
|
|
Il2CppMethodPointer nativeMethod = method->virtualMethodPointer;
|
|
|
|
if (il2cpp::os::Image::ManagedSectionExists())
|
|
{
|
|
if (!il2cpp::os::Image::IsInManagedSection((void*)nativeMethod))
|
|
return false;
|
|
}
|
|
|
|
int32_t codeSize = 0;
|
|
if (s_SymbolCount > 0)
|
|
{
|
|
SymbolInfo* containingSymbol = FindSymbolInfoForNativeMethod(nativeMethod);
|
|
if (containingSymbol == NULL)
|
|
return false;
|
|
|
|
codeSize = containingSymbol->length;
|
|
}
|
|
|
|
if (methodDebugInfo != NULL)
|
|
{
|
|
methodDebugInfo->methodPointer = method->virtualMethodPointer;
|
|
methodDebugInfo->code_size = codeSize;
|
|
methodDebugInfo->file = NULL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
} /* namespace utils */
|
|
} /* namespace il2cpp */
|