rlm_lldb.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. #!/usr/bin/python
  2. ##############################################################################
  3. #
  4. # Copyright 2014 Realm Inc.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http:#www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. #
  18. ##############################################################################
  19. # In the lldb shell, load with:
  20. # command script import [Realm path]/plugin/rlm_lldb.py --allow-reload
  21. # To load automatically, add that line to your ~/.lldbinit file (which you will
  22. # have to create if you have not set up any previous lldb scripts), or run this
  23. # file as a Python script outside of Xcode to install it automatically
  24. if __name__ == '__main__':
  25. # Script is being run directly, so install it
  26. import errno
  27. import shutil
  28. import os
  29. source = os.path.realpath(__file__)
  30. destination = os.path.expanduser("~/Library/Application Support/Realm")
  31. # Copy the file into place
  32. try:
  33. os.makedirs(destination, 0744)
  34. except os.error as e:
  35. # It's fine if the directory already exists
  36. if e.errno != errno.EEXIST:
  37. raise
  38. shutil.copy2(source, destination + '/rlm_lldb.py')
  39. # Add it to ~/.lldbinit
  40. load_line = 'command script import "~/Library/Application Support/Realm/rlm_lldb.py" --allow-reload\n'
  41. is_installed = False
  42. try:
  43. with open(os.path.expanduser('~/.lldbinit')) as f:
  44. for line in f:
  45. if line == load_line:
  46. is_installed = True
  47. break
  48. except IOError as e:
  49. if e.errno != errno.ENOENT:
  50. raise
  51. # File not existing yet is fine
  52. if not is_installed:
  53. with open(os.path.expanduser('~/.lldbinit'), 'a') as f:
  54. f.write('\n' + load_line)
  55. exit(0)
  56. import lldb
  57. property_types = {
  58. 0: 'int64_t',
  59. 10: 'double',
  60. 1: 'bool',
  61. 9: 'float',
  62. }
  63. def cache_lookup(cache, key, generator):
  64. value = cache.get(key, None)
  65. if not value:
  66. value = generator(key)
  67. cache[key] = value
  68. return value
  69. ivar_cache = {}
  70. def get_ivar_info(obj, ivar):
  71. def get_offset(ivar):
  72. class_name, ivar_name = ivar.split('.')
  73. frame = obj.GetThread().GetSelectedFrame()
  74. ptr = frame.EvaluateExpression("&(({} *)0)->{}".format(class_name, ivar_name))
  75. return (ptr.GetValueAsUnsigned(), ptr.deref.type, ptr.deref.size)
  76. return cache_lookup(ivar_cache, ivar, get_offset)
  77. def get_ivar(obj, addr, ivar):
  78. offset, _, size = get_ivar_info(obj, ivar)
  79. if isinstance(addr, lldb.SBAddress):
  80. addr = addr.GetFileAddress()
  81. return obj.GetProcess().ReadUnsignedFromMemory(addr + offset, size, lldb.SBError())
  82. object_table_ptr_offset = None
  83. def is_object_deleted(obj):
  84. addr = obj.GetAddress().GetFileAddress()
  85. global object_table_ptr_offset
  86. if not object_table_ptr_offset:
  87. row, _, _ = get_ivar_info(obj, 'RLMObject._row')
  88. table, _, _ = get_ivar_info(obj, 'realm::Row.m_table')
  89. ptr, _, _ = get_ivar_info(obj, 'realm::TableRef.m_ptr')
  90. object_table_ptr_offset = row + table + ptr
  91. ptr = obj.GetProcess().ReadUnsignedFromMemory(addr + object_table_ptr_offset,
  92. obj.target.addr_size, lldb.SBError())
  93. return ptr == 0
  94. class SyntheticChildrenProvider(object):
  95. def __init__(self, class_name):
  96. self._class_name = class_name
  97. def _eval(self, expr):
  98. frame = self.obj.GetThread().GetSelectedFrame()
  99. return frame.EvaluateExpression(expr)
  100. def _get_ivar(self, addr, ivar):
  101. return get_ivar(self.obj, addr, ivar)
  102. def _to_str(self, val):
  103. return self.obj.GetProcess().ReadCStringFromMemory(val, 1024, lldb.SBError())
  104. def _value_from_ivar(self, ivar):
  105. offset, ivar_type, _ = get_ivar_info(self.obj, '{}._{}'.format(self._class_name, ivar))
  106. return self.obj.CreateChildAtOffset(ivar, offset, ivar_type)
  107. def RLMObject_SummaryProvider(obj, _):
  108. if is_object_deleted(obj):
  109. return '[Deleted object]'
  110. return None
  111. schema_cache = {}
  112. class RLMObject_SyntheticChildrenProvider(SyntheticChildrenProvider):
  113. def __init__(self, obj, _):
  114. super(RLMObject_SyntheticChildrenProvider, self).__init__('RLMObject')
  115. self.obj = obj
  116. if not obj.GetAddress() or is_object_deleted(obj):
  117. self.props = []
  118. return
  119. object_schema = self._get_ivar(self.obj.GetAddress(), 'RLMObject._objectSchema')
  120. def get_schema(object_schema):
  121. properties = self._get_ivar(object_schema, 'RLMObjectSchema._properties')
  122. if not properties:
  123. return None
  124. count = self._eval("(NSUInteger)[((NSArray *){}) count]".format(properties)).GetValueAsUnsigned()
  125. return [self._get_prop(properties, i) for i in range(count)]
  126. self.props = cache_lookup(schema_cache, object_schema, get_schema)
  127. def num_children(self):
  128. return len(self.props) + 2
  129. def has_children(self):
  130. return not is_object_deleted(self.obj)
  131. def get_child_index(self, name):
  132. if name == 'realm':
  133. return 0
  134. if name == 'objectSchema':
  135. return 1
  136. return next(i for i, (prop_name, _) in enumerate(self.props) if prop_name == name)
  137. def get_child_at_index(self, index):
  138. if index == 0:
  139. return self._value_from_ivar('realm')
  140. if index == 1:
  141. return self._value_from_ivar('objectSchema')
  142. name, getter = self.props[index - 2]
  143. value = self._eval(getter)
  144. return self.obj.CreateValueFromData(name, value.GetData(), value.GetType())
  145. def update(self):
  146. pass
  147. def _get_prop(self, props, i):
  148. prop = self._eval("(NSUInteger)[((NSArray *){}) objectAtIndex:{}]".format(props, i)).GetValueAsUnsigned()
  149. name = self._to_str(self._eval('[(NSString *){} UTF8String]'.format(self._get_ivar(prop, "RLMProperty._name"))).GetValueAsUnsigned())
  150. type = self._get_ivar(prop, 'RLMProperty._type')
  151. getter = "({})[(id){} {}]".format(property_types.get(type, 'id'), self.obj.GetAddress(), name)
  152. return name, getter
  153. class_name_cache = {}
  154. def get_object_class_name(frame, obj, addr, ivar):
  155. class_name_ptr = get_ivar(obj, addr, ivar)
  156. def get_class_name(ptr):
  157. utf8_addr = frame.EvaluateExpression('(const char *)[(NSString *){} UTF8String]'.format(class_name_ptr)).GetValueAsUnsigned()
  158. return obj.GetProcess().ReadCStringFromMemory(utf8_addr, 1024, lldb.SBError())
  159. return cache_lookup(class_name_cache, class_name_ptr, get_class_name)
  160. def RLMArray_SummaryProvider(obj, _):
  161. frame = obj.GetThread().GetSelectedFrame()
  162. class_name = get_object_class_name(frame, obj, obj.GetAddress(), 'RLMArray._objectClassName')
  163. count = frame.EvaluateExpression('(NSUInteger)[(RLMArray *){} count]'.format(obj.GetAddress())).GetValueAsUnsigned()
  164. return "({}[{}])".format(class_name, count)
  165. results_mode_offset = None
  166. mode_type = None
  167. mode_query_value = None
  168. def is_results_evaluated(obj):
  169. global results_mode_offset, mode_type, mode_query_value
  170. if not results_mode_offset:
  171. results_offset, _, _ = get_ivar_info(obj, 'RLMResults._results')
  172. mode_offset, mode_type, _ = get_ivar_info(obj, 'Results.m_mode')
  173. results_mode_offset = results_offset + mode_offset
  174. mode_query_value = next(m for m in mode_type.enum_members if m.name == 'Query').GetValueAsUnsigned()
  175. addr = obj.GetAddress().GetFileAddress()
  176. mode = obj.GetProcess().ReadUnsignedFromMemory(addr + results_mode_offset, mode_type.size, lldb.SBError())
  177. return mode != mode_query_value
  178. def results_object_class_name(obj):
  179. class_info = get_ivar(obj, obj.GetAddress(), 'RLMResults._info')
  180. object_schema = get_ivar(obj, class_info, 'RLMClassInfo.rlmObjectSchema')
  181. return get_object_class_name(obj.GetThread().GetSelectedFrame(), obj, object_schema, 'RLMObjectSchema._className')
  182. def RLMResults_SummaryProvider(obj, _):
  183. class_name = results_object_class_name(obj)
  184. if not is_results_evaluated(obj):
  185. return 'Unevaluated query on ' + class_name
  186. frame = obj.GetThread().GetSelectedFrame()
  187. count = frame.EvaluateExpression('(NSUInteger)[(RLMResults *){} count]'.format(obj.GetAddress())).GetValueAsUnsigned()
  188. return "({}[{}])".format(class_name, count)
  189. class RLMCollection_SyntheticChildrenProvider(SyntheticChildrenProvider):
  190. def __init__(self, valobj, _):
  191. super(RLMCollection_SyntheticChildrenProvider, self).__init__(valobj.deref.type.name)
  192. self.obj = valobj
  193. self.addr = self.obj.GetAddress()
  194. def num_children(self):
  195. if not self.count:
  196. self.count = self._eval("(NSUInteger)[(id){} count]".format(self.addr)).GetValueAsUnsigned()
  197. return self.count + 1
  198. def has_children(self):
  199. return True
  200. def get_child_index(self, name):
  201. if name == 'realm':
  202. return 0
  203. if not name.startswith('['):
  204. return None
  205. return int(name.lstrip('[').rstrip(']')) + 1
  206. def get_child_at_index(self, index):
  207. if index == 0:
  208. return self._value_from_ivar('realm')
  209. value = self._eval('(id)[(id){} objectAtIndex:{}]'.format(self.addr, index - 1))
  210. return self.obj.CreateValueFromData('[' + str(index - 1) + ']', value.GetData(), value.GetType())
  211. def update(self):
  212. self.count = None
  213. def __lldb_init_module(debugger, _):
  214. debugger.HandleCommand('type summary add RLMArray -F rlm_lldb.RLMArray_SummaryProvider')
  215. debugger.HandleCommand('type summary add RLMArrayLinkView -F rlm_lldb.RLMArray_SummaryProvider')
  216. debugger.HandleCommand('type summary add RLMResults -F rlm_lldb.RLMResults_SummaryProvider')
  217. debugger.HandleCommand('type summary add -x RLMAccessor_ -F rlm_lldb.RLMObject_SummaryProvider')
  218. debugger.HandleCommand('type synthetic add RLMArray --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider')
  219. debugger.HandleCommand('type synthetic add RLMArrayLinkView --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider')
  220. debugger.HandleCommand('type synthetic add RLMResults --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider')
  221. debugger.HandleCommand('type synthetic add -x RLMAccessor_.* --python-class rlm_lldb.RLMObject_SyntheticChildrenProvider')