swift进阶StructMetadata分析和还原

语言: CN / TW / HK

本文主要介绍StructMetadata源码的分析,然后还原StructMetadata,最后打印属性信息和属性值

一、StructMetadata源码分析

1.1 TargetStructMetadata

  • 首先我们搜索TargetStructMetadata,进入TargetStructMetadata的定义

``` template struct TargetStructMetadata : public TargetValueMetadata {

// 此处会返回一个TargetStructDescriptor类型的Description const TargetStructDescriptor *getDescription() const { return llvm::cast>(this->Description); } } ```

001

可以看到TargetStructMetadata继承自TargetValueMetadata

  • 继续搜索TargetValueMetadata

``` template struct TargetValueMetadata : public TargetMetadata { TargetSignedPointer * __ptrauth_swift_type_descriptor> Description;

ConstTargetMetadataPointer getDescription() const { return Description; } }; using ValueMetadata = TargetValueMetadata; ```

002

我们只看到有一个Description属性,它的类型是TargetValueTypeDescriptorTargetValueMetadata继承自TargetMetadata

  • 继续搜索TargetMetadata

template <typename Runtime> struct TargetMetadata { private: /// The kind. Only valid for non-class metadata; getKind() must be used to get /// the kind value. StoredPointer Kind; }

003

我们只看到有一个Kind属性

接下来我们搜索TargetValueTypeDescriptor

1.2 TargetValueTypeDescriptor

``` template class TargetStructDescriptor final : public TargetValueTypeDescriptor {

public: uint32_t NumFields; uint32_t FieldOffsetVectorOffset; } ```

004

看到TargetStructDescriptor继承自TargetValueTypeDescriptor

005

我们还可以发现两个属性,分别是NumFieldsFieldOffsetVectorOffset

  1. NumFields主要表示结构体中属性的个数,如果只有一个字段偏移量则表示偏移量的长度
  2. FieldOffsetVectorOffset表示这个结构体元数据中存储的属性的字段偏移向量的偏移量,如果是0则表示没有

  3. 搜索TargetValueTypeDescriptor

006

​ 没有找到太多有用的信息,我们继续向父类寻找。

  • 搜索TargetTypeContextDescriptor

007

  1. 该类继承自TargetContextDescriptor
  2. NameAccessFunctionPtrFields三个属性
  3. Name就是类型的名称
  4. AccessFunctionPtr是该类型元数据访问函数的指针
  5. Fields是一个指向该类型的字段描述符的指针

  6. TargetContextDescriptor

接下来我们再看看TargetTypeContextDescriptor的父类中还有什么有用的信息

008

这里我们可以看到:

  1. 这就是descriptors的基类
  2. 有两个属性,分别是FlagsParent
  3. Flags是描述上下文的标志,包括它的种类和格式版本。
  4. Parent是记录父类上下文的,如果是顶级则为null

1.3 Description中的属性

  • Flags

009

从以上的代码中我们可以看到这个FLags实际是个uint32_t的值,按位存储着kindisGenericisUniqueversion等信息。

  • Parent

Parent的类型是TargetRelativeContextPointer<Runtime>,我们看看TargetRelativeContextPointer,点击跳转过去:

010

我们可以看到TargetRelativeContextPointer是取自RelativeIndirectablePointer的别名,继续点击进行查看:

011

根据注释知道这个类的主要作用是存储在内存中的对象的相对引用。通过RelativeOffsetPlusIndirect属性存储相对的地址偏移量

012

在通过get()函数获取,在get()函数中,会调用applyRelativeOffset函数,进行地址的偏移,applyRelativeOffset源码:

013

最后返回的时候我们可以看到base + extendOffset;基地址加上偏移的值,最后得到真实的地址。

  • Fields

TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor, /*nullable*/ true> Fields;

这里看看FieldDescriptor点击跳转到其源码处,部分源码如下:

014

这里有5个属性:

  1. MangledTypeName

  2. Superclass

  3. kind

  4. FieldRecordSize

  5. NumFields

关于getFieldRecordBuffer函数的返回值FieldRecord源码如下:

015

FieldRecord主要是封装了一些属性,用于存储这些值。

1.5 type

首先我们看看type是怎么取的:

首先是调用swift_reflectionMirror_normalizedType函数

016

比如说这是个结构体,此时的impl就是个StructImpl类型,所以这里的typeStructImpl父类ReflectionMirrorImpl的属性type

1.6 count

关于count的获取首先是调用swift_reflectionMirror_count函数

017

同样还以结构体为例,此时的implStructImpl,内部的count()函数:

018

这里的Struct就是个TargetStructMetadata类型,通过getDescription()函数获取到一个TargetStructDescriptor类型的Description,然后取NumFields的值就是我们要的count

1.7 属性名和属性值

我们知道在Mirror中通过其初始化方法返回一个提供该值子元素的AnyCollection<Child>类型的children集合,Child是一个元组(label: String?, value: Any),label是一个可选类型的属性名,value是属性值。

020

在分析internalReflecting函数的时候,我们说children是懒加载的,而加载的时候会调用getChild方法,getChild方法源码入下:

021

getChild方法中还会调用_getChild方法,源码如下

022

_getChild方法同样是使用@_silgen_name修饰符最终调用的C++中的swift_reflectionMirror_subscript函数。

023

这里我们可以看到是调用了implsubscript函数,同样以结构体为例,我们在StructImpl中找到该函数,源码如下:

024

通过subscript函数我们可以看到这里面还会调用childMetadata获取到fieldInfo,其实这里就是获取type,也就是属性名,通过childOffset函数和index获取到对于的偏移量,最后根据内存偏移去到属性值。childMetadata核心点是调用getFieldAt函数获取属性名称。

025

我们可以看到在上面这个方法中:

  • 首先通过getTypeContextDescriptor获取baseDesc,也就是我们说的Description
  • 然后通过Fields.get()获取到fields
  • 接着通过getFields()[index]或取对应的field
  • 最后通过getFieldName()函数获取到属性名称
  • getTypeContextDescriptor函数在struct TargetMetadata中,通过这个函数获取到一个TargetStructDescriptor,它的父类的父类TargetTypeContextDescriptor中的Fields属性
  • Fields属性的类型TargetRelativeDirectPointer中有get方法
  • 实际中使用的FieldDescriptor类中getFieldRecordBuffer方法返回的FieldRecord中的getFieldName函数

getFields 源码:

026

关于getFields我们可以看到这是一块连续的空间,在beginend中:

  • begin就是getFieldRecordBuffer
  • getFieldRecordBuffer就是Begin + NumFields
  • 所以这就是一块连续内存的访问

childOffset 源码:

分析完了属性名的获取,我们来看看偏移量的获取

027

这里面是调用TargetStructMetadata中的getFieldOffsets函数源码如下:

028

我们可以看到这里统一是通过获取Description中的属性,这里使用的属性是FieldOffsetVectorOffset。获取到偏移值后通过内存偏移即可获取到属性值。

二、还原StructMetadata

2.1 TargetStructMetadata

首先我们需要拥有一个结构体的元数据结构,这里我们命名为StructMetadata,里面有继承的kindDescriptor属性,这里的Descriptor属性是一个TargetStructDescriptor类型的指针。

struct TargetStructMetadata { var Kind: Int var typeDescription: UnsafeMutablePointer<TargetStructDescriptor> }

2.2 TargetStructDescriptor

对于结构体来说其内部有7个属性

  1. flag是个32位的整形,我们用Int32代替

  2. parent是记录父类的,类型是TargetRelativeDirectPointer<Runtime>,这里也可以用Int32代替

  3. name记录类型的,它的类型是TargetRelativeDirectPointer<char>,所以我们需要实现一个TargetRelativeDirectPointer

  4. AccessFunctionPtrname类似,内部是个指针

  5. Fields也与name类似,内部是个FieldDescriptor

  6. NumFields使用Int32

  7. FieldOffsetVectorOffset也是用Int32

仿写实现如下:

``` struct TargetStructDescriptor { var Flags: Int32 var Parent: Int32 var Name: TargetRelativeDirectPointer var AccessFunctionPtr: TargetRelativeDirectPointer var fieldDescriptor: TargetRelativeDirectPointer var NumFields: Int32 // 每一个属性距离当前实例对象地址的偏移量 var FieldOffsetVectorOffset: Int32

var genericArgumentOffset: Int {
    return 2
}

} ```

2.3 TargetRelativeDirectPointer

  • TargetRelativeDirectPointerRelativeDirectPointer的别名,其内部有一个继承的RelativeOffset属性,是int32_t类型,我们可以用Int32代替。
  • 还有一个get方法,内部通过指针偏移获取值。

仿写实现如下:

``` struct TargetRelativeDirectPointer { var offset: Int32 //模拟RelativeDirectPointerImpl类中的get方法 this+offset指针 mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer { let offset = self.offset return withUnsafePointer(to: &self) { p in /* 获得self,变为raw,然后+offset

         - UnsafeRawPointer(p) 表示this
         - advanced(by: numericCast(offset) 表示移动的步长,即offset
         - assumingMemoryBound(to: T.self) 表示假定类型是T,即自己制定的类型
         - UnsafeMutablePointer(mutating:) 表示返回的指针类型
        */
        return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
    }
}

} ```

2.4 FieldDescriptor

FieldDescriptorMirror反射中有着很重要的作用,其内部有5个属性:

  1. MangledTypeNameRelativeDirectPointer<const char>类型,我们使用TargetRelativeDirectPointer<CChar>代替
  2. SuperclassMangledTypeName一样
  3. KindFieldDescriptorKind类型,实际是uint16_t,这里我们使用UInt16代替
  4. FieldRecordSizeuint16_t也使用使用UInt16代替
  5. NumFields使用Int32代替
  6. fields,其实从属性上看不到有这个,但是这里面有个getFieldRecordBuffer方法,通过this+1的方式一个一个的访问属性,所以这是一块连续的内存空间,我们使用fields代替

struct FieldDescriptor { var MangledTypeName: TargetRelativeDirectPointer<CChar> var Superclass: TargetRelativeDirectPointer<CChar> var Kind: UInt16 var FieldRecordSize:UInt16 var NumFields: UInt32 var fields: FieldRecordBuffer<FieldRecord> }

2.5 FieldRecord

FieldRecord存储着属性的相关信息,其内部有三个属性

  1. FlagsFieldRecordFlags类型实际是uint32_t,这里我们使用Int32代替
  2. MangledTypeName使用TargetRelativeDirectPointer<CChar>代替
  3. FieldName使用TargetRelativeDirectPointer<CChar>代替

仿写如下:

struct FieldRecord { var Flags: UInt32 var MangledTypeName: TargetRelativeDirectPointer<CChar> var FieldName: TargetRelativeDirectPointer<CChar> }

三、打印属性信息和属性值

定义一个结构体:

struct Person { var age: Int = 18 var name: String = "tony" } var p = Person()

3.1 绑定结构体内存

使用unsafeBitCast按位强转,将Person绑定到StructMetadata上,这个操作非常危险,没有任何校验和修饰

let ptr = unsafeBitCast(Person.self as Any.Type, to: UnsafeMutablePointer<TargetStructMetadata>.self)

3.2 打印类名和属性个数

``` let typeDescription = ptr.pointee.typeDescription

let namePtr = typeDescription.pointee.Name.getmeasureRelativeOffset() print("current class name: (String(cString: namePtr))")

let numFields = typeDescription.pointee.NumFields print("当前类属性的个数:(numFields)") ```

3.3 打印属性名称和属性值

  1. 打印一下属性的名称,首先是获取到FieldDescriptor的指针,然后通过内存偏移的方式访问每一个FieldRecord,最后在访问FieldRecord中的属性名。
  2. 打印属性值:
  3. 首先获取FieldOffsetVectorOffset的值
  4. 然后在加上this也就是当前Metadata的指针
  5. 这里我们将仿写的StructMetadata的指针ptr重绑定为Int
  6. 源码中加上FieldOffsetVectorOffset,这里我们就移动FieldOffsetVectorOffset
  7. 然后将上述移动后的绑定为一个Int32的指针
  8. 最后使用UnsafeBufferPointer和属性个数创建一个buffer数组指针
  9. 接下来我们就可以从数组中取出每个属性的偏移值
  10. 然后取出结构体实例p的内存地址
  11. 然后按照buffer数组中的偏移值进行偏移,重绑定为属性的类型
  12. 最后就可以打印出属性值了

实现代码:

``` var bufferPtr = UnsafeBufferPointer(start: UnsafeRawPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self).advanced(by: numericCast(ptr.pointee.typeDescription.pointee.FieldOffsetVectorOffset))).assumingMemoryBound(to: Int32.self), count: Int(ptr.pointee.typeDescription.pointee.NumFields))

for i in 0..<numFields { let fieldDespritor = typeDescription.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.FieldName.getmeasureRelativeOffset() print("--- fixed (String(cString: fieldDespritor)) info begin ---")

let mangledTypeName = typeDescription.pointee.fieldDescriptor.getmeasureRelativeOffset().pointee.fields.index(of: Int(i)).pointee.MangledTypeName.getmeasureRelativeOffset()

let genericVector = UnsafeRawPointer(ptr).advanced(by: typeDescription.pointee.genericArgumentOffset * MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: Any.Type.self)

let fieldType = swift_getTypeByMangledNameInContext(mangledTypeName, 256, UnsafeRawPointer(typeDescription), UnsafeRawPointer(genericVector).assumingMemoryBound(to: Optional<UnsafeRawPointer>.self))

let type = unsafeBitCast(fieldType, to: Any.Type.self)
let value = customCast(type: type)    
let fieldOffset = bufferPtr[Int(i)]
let valuePtr = withUnsafeMutablePointer(to: &p) { $0 }
print("fieldType: \(type) \nfieldValue: \(value.get(from: UnsafeRawPointer(UnsafeRawPointer(valuePtr).advanced(by: numericCast(fieldOffset)))))")
print("--- field: \(String(cString: fieldDespritor)) info end ---\n")

} ```

打印结果:

019