il2cpp中的类型信息

Japan 乐队 Tin drum 封面

1. 从虚方法的调用看起

il2Cpp代码转换规则测试和整理 文章中介绍了 il2cpp 如何将 C# 代码转换成对应的 C++ 代码,其中介绍了C#中的虚函数会转成什么样的代码,以及运行时如何查找到对应的方法来进行调用,这里从虚方法的调用源码来进行分析。
当代码中有定义 virtual 函数时,C++代码会生成一个VirtActionInvoker结构体:

struct VirtActionInvoker0
{
    typedef void (*Action)(void*, const RuntimeMethod*);

    static inline void Invoke (Il2CppMethodSlot slot, RuntimeObject* obj)
    {
        const VirtualInvokeData& invokeData = il2cpp_codegen_get_virtual_invoke_data(slot, obj);
        ((Action)invokeData.methodPtr)(obj, invokeData.method);
    }
};

DisplayEntity 是一个虚函数是定义在 C# 类的一个虚函数,C# 的调用代码如下:

// 虚方法
entity.DisplayEntity();

生成的 C++ 调用代码如下:

BaseEntity_t10F88AF9625E7158E20269E2A8CEA034B0C8EEFC * L_8 = ___entity0;
NullCheck(L_8);
VirtActionInvoker0::Invoke(5 /* System.Void BaseEntity::DisplayEntity() */, L_8);

跟踪代码,最后可以看到虚函数的调用逻辑在 il2cpp 源码中的如下方法中实现:

FORCE_INLINE const VirtualInvokeData& il2cpp_codegen_get_virtual_invoke_data(Il2CppMethodSlot slot, const RuntimeObject* obj)
{
    Assert(slot != kInvalidIl2CppMethodSlot && "il2cpp_codegen_get_virtual_invoke_data got called on a non-virtual method");
    return obj->klass->vtable[slot];
}

objRuntimeObject*, 它的成员 klassIl2CppClass*, Il2CppObjectIl2CppClass 定义在文件 il2cpp-class-internals.h 中,Il2CppClass 中有一个 VirtualInvokeData 类型的数组,而虚方法就是从这个数组中拿到的 VirtualInvokeData

typedef struct Il2CppClass
{
    FieldInfo* fields; // Initialized in SetupFields
    const EventInfo* events; // Initialized in SetupEvents
    const PropertyInfo* properties; // Initialized in SetupProperties
    const MethodInfo** methods; // Initialized in SetupMethods
    Il2CppClass** nestedTypes; // Initialized in SetupNestedTypes
    Il2CppClass** implementedInterfaces; // Initialized in SetupInterfaces
    Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets; // Initialized in Init
    void* static_fields; // Initialized in Init
    const Il2CppRGCTXData* rgctx_data; // Initialized in Init
    // used for fast parent checks
    Il2CppClass** typeHierarchy; // Initialized in SetupTypeHierachy
    // ....
    // 省略一大堆成员
    VirtualInvokeData vtable[IL2CPP_ZERO_LEN_ARRAY];
} Il2CppClass;

我们需要看看 VirtualInvokeData 的定义:

typedef struct VirtualInvokeData
{
    Il2CppMethodPointer methodPtr;
#if RUNTIME_MONO
    const MonoMethod* method;
#else
    const MethodInfo* method;
#endif
} VirtualInvokeData;

其中 Il2CppMethodPointer就是一个函数指针,它的定义是

typedef void (*Il2CppMethodPointer)();

MethodInfo 的定义:

typedef struct MethodInfo
{
    Il2CppMethodPointer methodPointer;
    InvokerMethod invoker_method;
    const char* name;
    Il2CppClass *klass;
    const Il2CppType *return_type;
    const ParameterInfo* parameters;

    union
    {
        const Il2CppRGCTXData* rgctx_data; /* is_inflated is true and is_generic is false, i.e. a generic instance method */
        const Il2CppMethodDefinition* methodDefinition;
    };

    /* note, when is_generic == true and is_inflated == true the method represents an uninflated generic method on an inflated type. */
    union
    {
        const Il2CppGenericMethod* genericMethod; /* is_inflated is true */
        const Il2CppGenericContainer* genericContainer; /* is_inflated is false and is_generic is true */
    };

    uint32_t token;
    uint16_t flags;
    uint16_t iflags;
    uint16_t slot;
    uint8_t parameters_count;
    uint8_t is_generic : 1; /* true if method is a generic method definition */
    uint8_t is_inflated : 1; /* true if declaring_type is a generic instance or if method is a generic instance*/
    uint8_t wrapper_type : 1; /* always zero (MONO_WRAPPER_NONE) needed for the debugger */
    uint8_t is_marshaled_from_native : 1; /* a fake MethodInfo wrapping a native function pointer */
} MethodInfo;

定义中包含了函数入口地址、函数名、返回值类型、参数信息等数据。
脚本后端为 il2cpp 时,VirtualInvokeData 包括一个 Il2CppMethodPointer 和一个 MethodInfo 指针,我们在源码中搜索一下 methodPtr 在哪里被赋值,可以查找到代码 Class.cpp 中的 SetupVTable 方法,进一步查找 SetupVTable 方法的引用,可以查到 Class.cpp 中有一个 InitLocked 方法和一堆的 SetupXXXX 方法,其中 InitLocked 方法在 Class::Init 函数中调用了,这个应该是类的初始化方法。在 InitLocked的源码基本上都是对 klass 的数据进行设置,也就是初始化 Class 的信息了,带着两个问题去分析:

1.1 这些 class 信息包括哪些内容

查看 Class::Init 的实现

static bool InitLocked(Il2CppClass *klass, const FastAutoLock& lock)
    {
        if (klass->initialized)
            return true;

        if (klass->generic_class && (klass->flags & TYPE_ATTRIBUTE_EXPLICIT_LAYOUT))
        {
            std::string message;
            message += "Could not load type '";
            message += klass->namespaze;
            message += ":";
            message += klass->name;
            message += "' because generic types cannot have explicit layout.";
            klass->has_initialization_error = true;
            Class::UpdateInitializedAndNoError(klass);
            klass->initializationExceptionGCHandle = gc::GCHandle::New(il2cpp::vm::Exception::GetTypeLoadException(message.c_str()), false);
            return false;
        }

        IL2CPP_NOT_IMPLEMENTED_NO_ASSERT(Class::Init, "Audit and compare to mono version");

        klass->init_pending = true;

        klass->genericRecursionDepth++;

        if (klass->generic_class)
            // 初始化泛型类信息
            InitLocked(GenericClass::GetTypeDefinition(klass->generic_class), lock);

        if (klass->byval_arg.type == IL2CPP_TYPE_ARRAY || klass->byval_arg.type == IL2CPP_TYPE_SZARRAY)
        {
            Il2CppClass *element_class = klass->element_class;
            if (!element_class->initialized)
                // 初始化元素类型信息
                InitLocked(element_class, lock);
        }

        // 设置接口数据
        SetupInterfacesLocked(klass, lock);

        if (klass->parent && !klass->parent->initialized)
            // 基类信息
            InitLocked(klass->parent, lock);

        // 设置方法数据
        SetupMethodsLocked(klass, lock);

        // 继承链数据
        SetupTypeHierarchyLocked(klass, lock);

        // 设置虚表数据
        SetupVTable(klass, lock);
        if (!klass->size_inited)
            // 设置字段数据
            SetupFieldsLocked(klass, lock);

        if (klass->has_initialization_error)
            return false;

        // 设置委托数据
        SetupEventsLocked(klass, lock);

        // 设置属性数据
        SetupPropertiesLocked(klass, lock);

        // 设置嵌套类型数据
        SetupNestedTypesLocked(klass, lock);

        if (klass == il2cpp_defaults.object_class)
        {
            for (uint16_t slot = 0; slot < klass->vtable_count; slot++)
            {
                const MethodInfo* vmethod = klass->vtable[slot].method;
                if (!strcmp(vmethod->name, "GetHashCode"))
                    s_GetHashCodeSlot = slot;
                else if (!strcmp(vmethod->name, "Finalize"))
                    s_FinalizerSlot = slot;
            }
            IL2CPP_ASSERT(s_FinalizerSlot > 0);
            IL2CPP_ASSERT(s_GetHashCodeSlot > 0);
        }

        if (!Class::IsGeneric(klass))
            SetupGCDescriptor(klass);

        if (klass->generic_class)
        {
            // 设置泛型实例上下文数据
            const Il2CppTypeDefinition* typeDefinition = GenericClass::GetTypeDefinition(klass->generic_class)->typeDefinition;
            if (klass->genericRecursionDepth < GenericMetadata::MaximumRuntimeGenericDepth)
                klass->rgctx_data = GenericMetadata::InflateRGCTX(typeDefinition->rgctxStartIndex, typeDefinition->rgctxCount, &klass->generic_class->context);
        }

        klass->initialized = true;
        Class::UpdateInitializedAndNoError(klass);
        klass->init_pending = false;

        ++il2cpp_runtime_stats.initialized_class_count;

        return true;
    }

大体可以看出来,Class 信息中包含如下的数据

  • Methods 数据
typedef struct MethodInfo
{
    Il2CppMethodPointer methodPointer;
    InvokerMethod invoker_method;
    const char* name;
    Il2CppClass *klass;
    const Il2CppType *return_type;
    const ParameterInfo* parameters;

    union
    {
        const Il2CppRGCTXData* rgctx_data; /* is_inflated is true and is_generic is false, i.e. a generic instance method */
        const Il2CppMethodDefinition* methodDefinition;
    };

    /* note, when is_generic == true and is_inflated == true the method represents an uninflated generic method on an inflated type. */
    union
    {
        const Il2CppGenericMethod* genericMethod; /* is_inflated is true */
        const Il2CppGenericContainer* genericContainer; /* is_inflated is false and is_generic is true */
    };

    uint32_t token;
    uint16_t flags;
    uint16_t iflags;
    uint16_t slot;
    uint8_t parameters_count;
    uint8_t is_generic : 1; /* true if method is a generic method definition */
    uint8_t is_inflated : 1; /* true if declaring_type is a generic instance or if method is a generic instance*/
    uint8_t wrapper_type : 1; /* always zero (MONO_WRAPPER_NONE) needed for the debugger */
    uint8_t is_marshaled_from_native : 1; /* a fake MethodInfo wrapping a native function pointer */
} MethodInfo;

包括 函数指针、函数名、函数所属类指针、返回值类型、参数信息等,如果是泛型方法,还包括泛型上下文数据

  • Fields 数据
typedef struct FieldInfo
{
    const char* name;       // 字段名
    const Il2CppType* type; // 类型
    Il2CppClass *parent;    // 所属类
    // 相对偏移量
    int32_t offset; // If offset is -1, then it's thread static
    uint32_t token;
} FieldInfo;

包括字段名、字段类型、字段偏移值等数据

  • Properties 数据
typedef struct PropertyInfo
{
    Il2CppClass *parent;
    const char *name;
    const MethodInfo *get;
    const MethodInfo *set;
    uint32_t attrs;
    uint32_t token;
} PropertyInfo;
  • Events 数据
typedef struct EventInfo
{
    const char* name;
    const Il2CppType* eventType;
    Il2CppClass* parent;
    const MethodInfo* add;
    const MethodInfo* remove;
    const MethodInfo* raise;
    uint32_t token;
} EventInfo;
  • Interface 数据
    修改的是 Il2CppClass 中的 implementedInterfaces 字段的数据
Il2CppClass** implementedInterfaces; // Initialized in SetupInterfaces
  • VTable 虚表数据
    修改 Il2CppClass中的 vtable 字段
VirtualInvokeData vtable[IL2CPP_ZERO_LEN_ARRAY];
  • RGCTX Data 泛型上下文数据
    修改的是 Il2CppClass 中的 rgctx_data 字段数据
const Il2CppRGCTXData* rgctx_data; // Initialized in Init
1.2 数据怎么来的,有什么用

从代码的实现来看,这些数据是 Il2Cpp 在进行代码转换时放到了 global-metadata.dat 文件中,然后在 il2cpp 运行时初始化的时候,加载到 MetadataCache 中,然后在类调用 Class::Init 方法时调用各种 Setup 方法设置到 Class 中

初始化MetadataCache

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容