swift進階StructMetadata分析和還原
本文主要介紹StructMetadata源碼的分析,然後還原StructMetadata,最後打印屬性信息和屬性值
一、StructMetadata源碼分析
1.1 TargetStructMetadata
- 首先我們搜索
TargetStructMetadata
,進入TargetStructMetadata
的定義
```
template
// 此處會返回一個TargetStructDescriptor類型的Description
const TargetStructDescriptor
可以看到TargetStructMetadata
繼承自TargetValueMetadata
- 繼續搜索
TargetValueMetadata
```
template
ConstTargetMetadataPointer
我們只看到有一個Description
屬性,它的類型是TargetValueTypeDescriptor
。TargetValueMetadata
繼承自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;
}
我們只看到有一個Kind
屬性
接下來我們搜索TargetValueTypeDescriptor
1.2 TargetValueTypeDescriptor
```
template
public: uint32_t NumFields; uint32_t FieldOffsetVectorOffset; } ```
看到TargetStructDescriptor
繼承自TargetValueTypeDescriptor
我們還可以發現兩個屬性,分別是NumFields
和FieldOffsetVectorOffset
NumFields
主要表示結構體中屬性的個數,如果只有一個字段偏移量則表示偏移量的長度-
FieldOffsetVectorOffset
表示這個結構體元數據中存儲的屬性的字段偏移向量的偏移量,如果是0
則表示沒有 -
搜索
TargetValueTypeDescriptor
沒有找到太多有用的信息,我們繼續向父類尋找。
- 搜索
TargetTypeContextDescriptor
- 該類繼承自
TargetContextDescriptor
- 有
Name
、AccessFunctionPtr
、Fields
三個屬性 Name
就是類型的名稱AccessFunctionPtr
是該類型元數據訪問函數的指針-
Fields
是一個指向該類型的字段描述符的指針 -
TargetContextDescriptor
接下來我們再看看TargetTypeContextDescriptor
的父類中還有什麼有用的信息
這裏我們可以看到:
- 這就是
descriptors
的基類 - 有兩個屬性,分別是
Flags
和Parent
Flags
是描述上下文的標誌,包括它的種類和格式版本。Parent
是記錄父類上下文的,如果是頂級則為null
1.3 Description中的屬性
- Flags
從以上的代碼中我們可以看到這個FLags實際是個uint32_t
的值,按位存儲着kind
、isGeneric
、isUnique
、version
等信息。
- Parent
Parent
的類型是TargetRelativeContextPointer<Runtime>
,我們看看TargetRelativeContextPointer
,點擊跳轉過去:
我們可以看到TargetRelativeContextPointer
是取自RelativeIndirectablePointer
的別名,繼續點擊進行查看:
根據註釋知道這個類的主要作用是存儲在內存中的對象的相對引用。通過RelativeOffsetPlusIndirect
屬性存儲相對的地址偏移量
在通過get()
函數獲取,在get()
函數中,會調用applyRelativeOffset
函數,進行地址的偏移,applyRelativeOffset
源碼:
最後返回的時候我們可以看到base + extendOffset
;基地址加上偏移的值,最後得到真實的地址。
- Fields
TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
/*nullable*/ true> Fields;
這裏看看FieldDescriptor
點擊跳轉到其源碼處,部分源碼如下:
這裏有5個屬性:
-
MangledTypeName
-
Superclass
-
kind
-
FieldRecordSize
-
NumFields
關於getFieldRecordBuffer
函數的返回值FieldRecord
源碼如下:
FieldRecord
主要是封裝了一些屬性,用於存儲這些值。
1.5 type
首先我們看看type
是怎麼取的:
首先是調用swift_reflectionMirror_normalizedType
函數
比如説這是個結構體,此時的impl
就是個StructImpl
類型,所以這裏的type
是StructImpl
父類ReflectionMirrorImpl
的屬性type
。
1.6 count
關於count
的獲取首先是調用swift_reflectionMirror_count
函數
同樣還以結構體為例,此時的impl
為StructImpl
,內部的count()
函數:
這裏的Struct
就是個TargetStructMetadata
類型,通過getDescription()
函數獲取到一個TargetStructDescriptor
類型的Description
,然後取NumFields
的值就是我們要的count
。
1.7 屬性名和屬性值
我們知道在Mirror
中通過其初始化方法返回一個提供該值子元素的AnyCollection<Child>
類型的children
集合,Child
是一個元組(label: String?
, value: Any
),label
是一個可選類型的屬性名,value是屬性值。
在分析internalReflecting
函數的時候,我們説children
是懶加載的,而加載的時候會調用getChild
方法,getChild
方法源碼入下:
在getChild
方法中還會調用_getChild
方法,源碼如下
_getChild
方法同樣是使用@_silgen_name
修飾符最終調用的C++
中的swift_reflectionMirror_subscript
函數。
這裏我們可以看到是調用了impl
的subscript
函數,同樣以結構體為例,我們在StructImpl
中找到該函數,源碼如下:
通過subscript
函數我們可以看到這裏面還會調用childMetadata
獲取到fieldInfo
,其實這裏就是獲取type
,也就是屬性名,通過childOffset
函數和index
獲取到對於的偏移量,最後根據內存偏移去到屬性值。childMetadata核心點是調用getFieldAt
函數獲取屬性名稱。
我們可以看到在上面這個方法中:
- 首先通過
getTypeContextDescriptor
獲取baseDesc
,也就是我們説的Description
- 然後通過
Fields.get()
獲取到fields
- 接着通過
getFields()[index]
或取對應的field
- 最後通過
getFieldName()
函數獲取到屬性名稱 getTypeContextDescriptor
函數在struct TargetMetadata
中,通過這個函數獲取到一個TargetStructDescriptor
,它的父類的父類TargetTypeContextDescriptor
中的Fields
屬性Fields
屬性的類型TargetRelativeDirectPointer
中有get
方法- 實際中使用的
FieldDescriptor
類中getFieldRecordBuffer
方法返回的FieldRecord
中的getFieldName
函數
getFields 源碼:
關於getFields
我們可以看到這是一塊連續的空間,在begin
和end
中:
begin
就是getFieldRecordBuffer
getFieldRecordBuffer
就是Begin + NumFields
- 所以這就是一塊連續內存的訪問
childOffset 源碼:
分析完了屬性名的獲取,我們來看看偏移量的獲取
這裏面是調用TargetStructMetadata
中的getFieldOffsets
函數源碼如下:
我們可以看到這裏統一是通過獲取Description
中的屬性,這裏使用的屬性是FieldOffsetVectorOffset
。獲取到偏移值後通過內存偏移即可獲取到屬性值。
二、還原StructMetadata
2.1 TargetStructMetadata
首先我們需要擁有一個結構體的元數據結構,這裏我們命名為StructMetadata
,裏面有繼承的kind
和Descriptor
屬性,這裏的Descriptor
屬性是一個TargetStructDescriptor
類型的指針。
struct TargetStructMetadata {
var Kind: Int
var typeDescription: UnsafeMutablePointer<TargetStructDescriptor>
}
2.2 TargetStructDescriptor
對於結構體來説其內部有7個屬性
-
flag
是個32
位的整形,我們用Int32代替 -
parent
是記錄父類的,類型是TargetRelativeDirectPointer<Runtime>
,這裏也可以用Int32代替 -
name
記錄類型的,它的類型是TargetRelativeDirectPointer<char>
,所以我們需要實現一個TargetRelativeDirectPointer
-
AccessFunctionPtr
與name
類似,內部是個指針 -
Fields
也與name
類似,內部是個FieldDescriptor
-
NumFields
使用Int32
-
FieldOffsetVectorOffset
也是用Int32
仿寫實現如下:
```
struct TargetStructDescriptor {
var Flags: Int32
var Parent: Int32
var Name: TargetRelativeDirectPointer
var genericArgumentOffset: Int {
return 2
}
} ```
2.3 TargetRelativeDirectPointer
TargetRelativeDirectPointer
是RelativeDirectPointer
的別名,其內部有一個繼承的RelativeOffset
屬性,是int32_t
類型,我們可以用Int32
代替。- 還有一個
get
方法,內部通過指針偏移獲取值。
仿寫實現如下:
```
struct TargetRelativeDirectPointer
- 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
FieldDescriptor
在Mirror
反射中有着很重要的作用,其內部有5個屬性:
MangledTypeName
是RelativeDirectPointer<const char>
類型,我們使用TargetRelativeDirectPointer<CChar>
代替Superclass
與MangledTypeName
一樣Kind
是FieldDescriptorKind
類型,實際是uint16_t
,這裏我們使用UInt16
代替FieldRecordSize
是uint16_t
也使用使用UInt16
代替NumFields
使用Int32
代替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
存儲着屬性的相關信息,其內部有三個屬性
Flags
是FieldRecordFlags
類型實際是uint32_t
,這裏我們使用Int32
代替MangledTypeName
使用TargetRelativeDirectPointer<CChar>
代替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 打印屬性名稱和屬性值
- 打印一下屬性的名稱,首先是獲取到
FieldDescriptor
的指針,然後通過內存偏移的方式訪問每一個FieldRecord
,最後在訪問FieldRecord
中的屬性名。 - 打印屬性值:
- 首先獲取
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")
} ```
打印結果: