swift进阶StructMetadata分析和还原

1,184 阅读8分钟

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

一、StructMetadata源码分析

1.1 TargetStructMetadata

  • 首先我们搜索TargetStructMetadata,进入TargetStructMetadata的定义
template <typename Runtime>
struct TargetStructMetadata : public TargetValueMetadata<Runtime> {

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

001

可以看到TargetStructMetadata继承自TargetValueMetadata

  • 继续搜索TargetValueMetadata
template <typename Runtime>
struct TargetValueMetadata : public TargetMetadata<Runtime> {
  TargetSignedPointer<Runtime, const TargetValueTypeDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;

  ConstTargetMetadataPointer<Runtime, TargetValueTypeDescriptor>
  getDescription() const {
    return Description;
  }
};
using ValueMetadata = TargetValueMetadata<InProcess>;

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 <typename Runtime>
class TargetStructDescriptor final
    : public TargetValueTypeDescriptor<Runtime> {
    
public:
  uint32_t NumFields;
  uint32_t FieldOffsetVectorOffset;
}

004

看到TargetStructDescriptor继承自 TargetValueTypeDescriptor

005

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

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

006

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

  • 搜索TargetTypeContextDescriptor

007

  1. 该类继承自TargetContextDescriptor
  2. NameAccessFunctionPtrFields三个属性
  3. Name就是类型的名称
  4. AccessFunctionPtr是该类型元数据访问函数的指针
  5. Fields是一个指向该类型的字段描述符的指针
  • 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<CChar>
    var AccessFunctionPtr: TargetRelativeDirectPointer<UnsafeRawPointer>
    var fieldDescriptor: TargetRelativeDirectPointer<FieldDescriptor>
    var NumFields: Int32
    // 每一个属性距离当前实例对象地址的偏移量
    var FieldOffsetVectorOffset: Int32
        
    var genericArgumentOffset: Int {
        return 2
    }
}

2.3 TargetRelativeDirectPointer

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

仿写实现如下:

struct TargetRelativeDirectPointer<Pointee> {
    var offset: Int32
    //模拟RelativeDirectPointerImpl类中的get方法 this+offset指针
    mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee> {
        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. 打印属性值:
    • 首先获取FieldOffsetVectorOffset的值
    • 然后在加上this也就是当前Metadata的指针
    • 这里我们将仿写的StructMetadata的指针ptr重绑定为Int
    • 源码中加上FieldOffsetVectorOffset,这里我们就移动FieldOffsetVectorOffset
    • 然后将上述移动后的绑定为一个Int32的指针
    • 最后使用UnsafeBufferPointer和属性个数创建一个buffer数组指针
    • 接下来我们就可以从数组中取出每个属性的偏移值
    • 然后取出结构体实例p的内存地址
    • 然后按照buffer数组中的偏移值进行偏移,重绑定为属性的类型
    • 最后就可以打印出属性值了

实现代码:

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