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