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