源战役客户端
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

277 行
14 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor.MemoryProfiler;
  5. namespace MemoryProfilerWindow
  6. {
  7. internal class Crawler
  8. {
  9. private Dictionary<UInt64, TypeDescription> _typeInfoToTypeDescription;
  10. private Dictionary<int, UInt64> _pointer2Backups = new Dictionary<int, ulong>();
  11. private VirtualMachineInformation _virtualMachineInformation;
  12. private TypeDescription[] _typeDescriptions;
  13. private FieldDescription[][] _instanceFields;
  14. private FieldDescription[][] _staticFields;
  15. public PackedCrawlerData Crawl(PackedMemorySnapshot input)
  16. {
  17. _typeInfoToTypeDescription = input.typeDescriptions.ToDictionary(td => td.typeInfoAddress, td => td);
  18. _virtualMachineInformation = input.virtualMachineInformation;
  19. _typeDescriptions = input.typeDescriptions;
  20. _instanceFields = new FieldDescription[_typeDescriptions.Length][];
  21. _staticFields = new FieldDescription[_typeDescriptions.Length][];
  22. foreach (var type in _typeDescriptions)
  23. {
  24. _instanceFields[type.typeIndex] = TypeTools.AllFieldsOf(type, _typeDescriptions, TypeTools.FieldFindOptions.OnlyInstance).ToArray();
  25. _staticFields[type.typeIndex] = TypeTools.AllFieldsOf(type, _typeDescriptions, TypeTools.FieldFindOptions.OnlyStatic).ToArray();
  26. }
  27. var result = new PackedCrawlerData(input);
  28. var managedObjects = new List<PackedManagedObject>(result.startIndices.OfFirstManagedObject * 3);
  29. var connections = new List<Connection>(managedObjects.Count * 3);
  30. //we will be adding a lot of connections, but the input format also already had connections. (nativeobject->nativeobject and nativeobject->gchandle). we'll add ours to the ones already there.
  31. connections.AddRange(input.connections);
  32. for (int i = 0; i != input.gcHandles.Length; i++)
  33. CrawlPointer(input, result.startIndices, input.gcHandles[i].target, result.startIndices.OfFirstGCHandle + i, connections, managedObjects);
  34. for (int i = 0; i < result.typesWithStaticFields.Length; i++)
  35. {
  36. var typeDescription = result.typesWithStaticFields[i];
  37. CrawlRawObjectData(input, result.startIndices, new BytesAndOffset {bytes = typeDescription.staticFieldBytes, offset = 0, pointerSize = _virtualMachineInformation.pointerSize}, typeDescription, true, result.startIndices.OfFirstStaticFields + i, connections, managedObjects);
  38. }
  39. result.managedObjects = managedObjects.ToArray();
  40. connections.AddRange(AddManagedToNativeConnectionsAndRestoreObjectHeaders(input, result.startIndices, result));
  41. result.connections = connections.ToArray();
  42. return result;
  43. }
  44. private IEnumerable<Connection> AddManagedToNativeConnectionsAndRestoreObjectHeaders(PackedMemorySnapshot packedMemorySnapshot, StartIndices startIndices, PackedCrawlerData packedCrawlerData)
  45. {
  46. if (packedMemorySnapshot.typeDescriptions.Length == 0)
  47. yield break;
  48. var unityEngineObjectTypeDescription = packedMemorySnapshot.typeDescriptions.First(td => td.name == "UnityEngine.Object");
  49. bool unityEngineObjectHasInstanceIDField = unityEngineObjectTypeDescription.fields.Any(f => f.name == "m_InstanceID");
  50. int instanceIDOffset = -1;
  51. if (unityEngineObjectHasInstanceIDField)
  52. instanceIDOffset = unityEngineObjectTypeDescription.fields.Single(f => f.name == "m_InstanceID").offset;
  53. #if UNITY_5_4_OR_NEWER
  54. var cachedPtrOffset = unityEngineObjectTypeDescription.fields.Single(f => f.name == "m_CachedPtr").offset;
  55. #endif
  56. for (int i = 0; i != packedCrawlerData.managedObjects.Length; i++)
  57. {
  58. var managedObjectIndex = i + startIndices.OfFirstManagedObject;
  59. var address = packedCrawlerData.managedObjects[i].address;
  60. var typeInfoAddress = RestoreObjectHeader(packedMemorySnapshot.managedHeapSections, address, managedObjectIndex);
  61. if (!DerivesFrom(packedMemorySnapshot.typeDescriptions, _typeInfoToTypeDescription[typeInfoAddress].typeIndex, unityEngineObjectTypeDescription.typeIndex))
  62. continue;
  63. int indexOfNativeObject = -1;
  64. if (unityEngineObjectHasInstanceIDField)
  65. {
  66. var instanceID = packedMemorySnapshot.managedHeapSections.Find(address + (UInt64)instanceIDOffset, packedMemorySnapshot.virtualMachineInformation).ReadInt32();
  67. indexOfNativeObject = Array.FindIndex(packedMemorySnapshot.nativeObjects, no => no.instanceId == instanceID);
  68. }
  69. #if UNITY_5_4_OR_NEWER // Since Unity 5.4, UnityEngine.Object no longer stores instance id inside when running in the player. Use cached ptr instead to find the index of native object
  70. else
  71. {
  72. // If you get a compilation error on the following 2 lines, update to Unity 5.4b14.
  73. var cachedPtr = packedMemorySnapshot.managedHeapSections.Find(address + (UInt64)cachedPtrOffset, packedMemorySnapshot.virtualMachineInformation).ReadPointer();
  74. indexOfNativeObject = Array.FindIndex(packedMemorySnapshot.nativeObjects, no => (ulong)no.nativeObjectAddress == cachedPtr);
  75. }
  76. #endif
  77. if (indexOfNativeObject != -1)
  78. yield return new Connection { @from = managedObjectIndex, to = indexOfNativeObject + startIndices.OfFirstNativeObject };
  79. }
  80. }
  81. private bool DerivesFrom(TypeDescription[] typeDescriptions, int typeIndex, int potentialBase)
  82. {
  83. if (typeIndex == potentialBase)
  84. return true;
  85. var baseIndex = typeDescriptions[typeIndex].baseOrElementTypeIndex;
  86. if (baseIndex == -1)
  87. return false;
  88. return DerivesFrom(typeDescriptions, baseIndex, potentialBase);
  89. }
  90. private ulong RestoreObjectHeader(MemorySection[] heaps, ulong address, int managedObjectIndex)
  91. {
  92. var bo = heaps.Find(address, _virtualMachineInformation);
  93. var mask = this._virtualMachineInformation.pointerSize == 8 ? System.UInt64.MaxValue - 1 : System.UInt32.MaxValue - 1;
  94. var pointer = bo.ReadPointer();
  95. var typeInfoAddress = pointer & mask;
  96. bo.WritePointer(typeInfoAddress);
  97. UInt64 restoreValue = 0;
  98. _pointer2Backups.TryGetValue(managedObjectIndex, out restoreValue);
  99. bo.NextPointer().WritePointer(restoreValue);
  100. return typeInfoAddress;
  101. }
  102. private void CrawlRawObjectData(PackedMemorySnapshot packedMemorySnapshot, StartIndices startIndices, BytesAndOffset bytesAndOffset, TypeDescription typeDescription, bool useStaticFields, int indexOfFrom, List<Connection> out_connections, List<PackedManagedObject> out_managedObjects)
  103. {
  104. var fields = useStaticFields ? _staticFields[typeDescription.typeIndex] : _instanceFields[typeDescription.typeIndex];
  105. foreach (var field in fields)
  106. {
  107. var fieldType = packedMemorySnapshot.typeDescriptions[field.typeIndex];
  108. var fieldLocation = bytesAndOffset.Add(field.offset - (useStaticFields ? 0 : _virtualMachineInformation.objectHeaderSize));
  109. if (fieldType.isValueType)
  110. {
  111. CrawlRawObjectData(packedMemorySnapshot, startIndices, fieldLocation, fieldType, false, indexOfFrom, out_connections, out_managedObjects);
  112. continue;
  113. }
  114. //temporary workaround for a bug in 5.3b4 and earlier where we would get literals returned as fields with offset 0. soon we'll be able to remove this code.
  115. bool gotException = false;
  116. try
  117. {
  118. fieldLocation.ReadPointer();
  119. }
  120. catch (ArgumentException)
  121. {
  122. UnityEngine.Debug.LogWarningFormat("Skipping field {0} on type {1}", field.name, typeDescription.name);
  123. UnityEngine.Debug.LogWarningFormat("FieldType.name: {0}", fieldType.name);
  124. gotException = true;
  125. }
  126. if (!gotException)
  127. {
  128. CrawlPointer(packedMemorySnapshot, startIndices, fieldLocation.ReadPointer(), indexOfFrom, out_connections, out_managedObjects);
  129. }
  130. }
  131. }
  132. private void CrawlPointer(PackedMemorySnapshot packedMemorySnapshot, StartIndices startIndices, ulong pointer, int indexOfFrom, List<Connection> out_connections, List<PackedManagedObject> out_managedObjects)
  133. {
  134. var bo = packedMemorySnapshot.managedHeapSections.Find(pointer, _virtualMachineInformation);
  135. if (!bo.IsValid)
  136. return;
  137. UInt64 typeInfoAddress;
  138. int indexOfObject;
  139. bool wasAlreadyCrawled;
  140. try
  141. {
  142. ParseObjectHeader(startIndices, packedMemorySnapshot.managedHeapSections, pointer, out typeInfoAddress, out indexOfObject, out wasAlreadyCrawled, out_managedObjects);
  143. }
  144. catch (Exception e)
  145. {
  146. UnityEngine.Debug.LogWarningFormat("Exception parsing object header. Skipping. {0}", e);
  147. return;
  148. }
  149. out_connections.Add(new Connection() {from = indexOfFrom, to = indexOfObject});
  150. if (wasAlreadyCrawled)
  151. return;
  152. var typeDescription = _typeInfoToTypeDescription[typeInfoAddress];
  153. if (!typeDescription.isArray)
  154. {
  155. CrawlRawObjectData(packedMemorySnapshot, startIndices, bo.Add(_virtualMachineInformation.objectHeaderSize), typeDescription, false, indexOfObject, out_connections, out_managedObjects);
  156. return;
  157. }
  158. var arrayLength = ArrayTools.ReadArrayLength(packedMemorySnapshot.managedHeapSections, pointer, typeDescription, _virtualMachineInformation);
  159. var elementType = packedMemorySnapshot.typeDescriptions[typeDescription.baseOrElementTypeIndex];
  160. var cursor = bo.Add(_virtualMachineInformation.arrayHeaderSize);
  161. for (int i = 0; i != arrayLength; i++)
  162. {
  163. if (elementType.isValueType)
  164. {
  165. CrawlRawObjectData(packedMemorySnapshot, startIndices, cursor, elementType, false, indexOfObject, out_connections, out_managedObjects);
  166. cursor = cursor.Add(elementType.size);
  167. }
  168. else
  169. {
  170. CrawlPointer(packedMemorySnapshot, startIndices, cursor.ReadPointer(), indexOfObject, out_connections, out_managedObjects);
  171. cursor = cursor.NextPointer();
  172. }
  173. }
  174. }
  175. int SizeOfObjectInBytes(TypeDescription typeDescription, BytesAndOffset bo, MemorySection[] heap, ulong address)
  176. {
  177. if (typeDescription.isArray)
  178. return ArrayTools.ReadArrayObjectSizeInBytes(heap, address, typeDescription, _typeDescriptions, _virtualMachineInformation);
  179. if (typeDescription.name == "System.String")
  180. return StringTools.ReadStringObjectSizeInBytes(bo, _virtualMachineInformation);
  181. //array and string are the only types that are special, all other types just have one size, which is stored in the typedescription
  182. return typeDescription.size;
  183. }
  184. private void ParseObjectHeader(StartIndices startIndices, MemorySection[] heap, ulong originalHeapAddress, out ulong typeInfoAddress, out int indexOfObject, out bool wasAlreadyCrawled, List<PackedManagedObject> outManagedObjects)
  185. {
  186. var bo = heap.Find(originalHeapAddress, _virtualMachineInformation);
  187. var pointer1 = bo.ReadPointer();
  188. var pointer2 = bo.NextPointer();
  189. if (HasMarkBit(pointer1) == 0)
  190. {
  191. wasAlreadyCrawled = false;
  192. indexOfObject = outManagedObjects.Count + startIndices.OfFirstManagedObject;
  193. typeInfoAddress = pointer1;
  194. var typeDescription = _typeInfoToTypeDescription[pointer1];
  195. var size = SizeOfObjectInBytes(typeDescription, bo, heap, originalHeapAddress);
  196. outManagedObjects.Add(new PackedManagedObject() { address = originalHeapAddress, size = size, typeIndex = typeDescription.typeIndex });
  197. //okay, we gathered all information, now lets set the mark bit, and store the index for this object in the 2nd pointer of the header, which is rarely used.
  198. bo.WritePointer(pointer1 | 1);
  199. //test writepointer implementation
  200. ulong magic = bo.pointerSize == 8 ? 0x12345678deadbeefUL : 0xdeadbeef;
  201. pointer2.WritePointer(magic);
  202. var check = pointer2.ReadPointer();
  203. if (check != magic)
  204. throw new Exception("writepointer broken");
  205. pointer2.WritePointer((ulong)indexOfObject);
  206. return;
  207. }
  208. //give typeinfo address back without the markbit
  209. typeInfoAddress = ClearMarkBit(pointer1);
  210. wasAlreadyCrawled = true;
  211. //read the index for this object that we stored in the 2ndpointer field of the header
  212. indexOfObject = (int)pointer2.ReadPointer();
  213. }
  214. private static ulong HasMarkBit(ulong pointer1)
  215. {
  216. return pointer1 & 1;
  217. }
  218. private static ulong ClearMarkBit(ulong pointer1)
  219. {
  220. return pointer1 & ~(1UL);
  221. }
  222. }
  223. }