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")
} ```
列印結果: