#!/usr/bin/python ############################################################################## # # Copyright 2014 Realm Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http:#www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## # In the lldb shell, load with: # command script import [Realm path]/plugin/rlm_lldb.py --allow-reload # To load automatically, add that line to your ~/.lldbinit file (which you will # have to create if you have not set up any previous lldb scripts), or run this # file as a Python script outside of Xcode to install it automatically if __name__ == '__main__': # Script is being run directly, so install it import errno import shutil import os source = os.path.realpath(__file__) destination = os.path.expanduser("~/Library/Application Support/Realm") # Copy the file into place try: os.makedirs(destination, 0o744) except os.error as e: # It's fine if the directory already exists if e.errno != errno.EEXIST: raise shutil.copy2(source, destination + '/rlm_lldb.py') # Add it to ~/.lldbinit load_line = 'command script import "~/Library/Application Support/Realm/rlm_lldb.py" --allow-reload\n' is_installed = False try: with open(os.path.expanduser('~/.lldbinit')) as f: for line in f: if line == load_line: is_installed = True break except IOError as e: if e.errno != errno.ENOENT: raise # File not existing yet is fine if not is_installed: with open(os.path.expanduser('~/.lldbinit'), 'a') as f: f.write('\n' + load_line) exit(0) import lldb property_types = { 0: 'int64_t', 10: 'double', 1: 'bool', 9: 'float', } def cache_lookup(cache, key, generator): value = cache.get(key, None) if not value: value = generator(key) cache[key] = value return value ivar_cache = {} def get_ivar_info(obj, ivar): def get_offset(ivar): class_name, ivar_name = ivar.split('.') frame = obj.GetThread().GetSelectedFrame() ptr = frame.EvaluateExpression("&(({} *)0)->{}".format(class_name, ivar_name)) return (ptr.GetValueAsUnsigned(), ptr.deref.type, ptr.deref.size) return cache_lookup(ivar_cache, ivar, get_offset) def get_ivar(obj, addr, ivar): offset, _, size = get_ivar_info(obj, ivar) if isinstance(addr, lldb.SBAddress): addr = addr.GetFileAddress() return obj.GetProcess().ReadUnsignedFromMemory(addr + offset, size, lldb.SBError()) object_table_ptr_offset = None def is_object_deleted(obj): addr = obj.GetAddress().GetFileAddress() global object_table_ptr_offset if not object_table_ptr_offset: row, _, _ = get_ivar_info(obj, 'RLMObject._row') table, _, _ = get_ivar_info(obj, 'realm::Row.m_table') ptr, _, _ = get_ivar_info(obj, 'realm::TableRef.m_ptr') object_table_ptr_offset = row + table + ptr ptr = obj.GetProcess().ReadUnsignedFromMemory(addr + object_table_ptr_offset, obj.target.addr_size, lldb.SBError()) return ptr == 0 class SyntheticChildrenProvider(object): def __init__(self, class_name): self._class_name = class_name def _eval(self, expr): frame = self.obj.GetThread().GetSelectedFrame() return frame.EvaluateExpression(expr) def _get_ivar(self, addr, ivar): return get_ivar(self.obj, addr, ivar) def _to_str(self, val): return self.obj.GetProcess().ReadCStringFromMemory(val, 1024, lldb.SBError()) def _value_from_ivar(self, ivar): offset, ivar_type, _ = get_ivar_info(self.obj, '{}._{}'.format(self._class_name, ivar)) return self.obj.CreateChildAtOffset(ivar, offset, ivar_type) def RLMObject_SummaryProvider(obj, _): if is_object_deleted(obj): return '[Deleted object]' return None schema_cache = {} class RLMObject_SyntheticChildrenProvider(SyntheticChildrenProvider): def __init__(self, obj, _): super(RLMObject_SyntheticChildrenProvider, self).__init__('RLMObject') self.obj = obj if not obj.GetAddress() or is_object_deleted(obj): self.props = [] return object_schema = self._get_ivar(self.obj.GetAddress(), 'RLMObject._objectSchema') def get_schema(object_schema): properties = self._get_ivar(object_schema, 'RLMObjectSchema._properties') if not properties: return None count = self._eval("(NSUInteger)[((NSArray *){}) count]".format(properties)).GetValueAsUnsigned() return [self._get_prop(properties, i) for i in range(count)] self.props = cache_lookup(schema_cache, object_schema, get_schema) def num_children(self): return len(self.props) + 2 def has_children(self): return not is_object_deleted(self.obj) def get_child_index(self, name): if name == 'realm': return 0 if name == 'objectSchema': return 1 return next(i for i, (prop_name, _) in enumerate(self.props) if prop_name == name) def get_child_at_index(self, index): if index == 0: return self._value_from_ivar('realm') if index == 1: return self._value_from_ivar('objectSchema') name, getter = self.props[index - 2] value = self._eval(getter) return self.obj.CreateValueFromData(name, value.GetData(), value.GetType()) def update(self): pass def _get_prop(self, props, i): prop = self._eval("(NSUInteger)[((NSArray *){}) objectAtIndex:{}]".format(props, i)).GetValueAsUnsigned() name = self._to_str(self._eval('[(NSString *){} UTF8String]'.format(self._get_ivar(prop, "RLMProperty._name"))).GetValueAsUnsigned()) type = self._get_ivar(prop, 'RLMProperty._type') getter = "({})[(id){} {}]".format(property_types.get(type, 'id'), self.obj.GetAddress(), name) return name, getter class_name_cache = {} def get_object_class_name(frame, obj, addr, ivar): class_name_ptr = get_ivar(obj, addr, ivar) def get_class_name(ptr): utf8_addr = frame.EvaluateExpression('(const char *)[(NSString *){} UTF8String]'.format(class_name_ptr)).GetValueAsUnsigned() return obj.GetProcess().ReadCStringFromMemory(utf8_addr, 1024, lldb.SBError()) return cache_lookup(class_name_cache, class_name_ptr, get_class_name) def RLMArray_SummaryProvider(obj, _): frame = obj.GetThread().GetSelectedFrame() class_name = get_object_class_name(frame, obj, obj.GetAddress(), 'RLMArray._objectClassName') count = frame.EvaluateExpression('(NSUInteger)[(RLMArray *){} count]'.format(obj.GetAddress())).GetValueAsUnsigned() return "({}[{}])".format(class_name, count) results_mode_offset = None mode_type = None mode_query_value = None def is_results_evaluated(obj): global results_mode_offset, mode_type, mode_query_value if not results_mode_offset: results_offset, _, _ = get_ivar_info(obj, 'RLMResults._results') mode_offset, mode_type, _ = get_ivar_info(obj, 'Results.m_mode') results_mode_offset = results_offset + mode_offset mode_query_value = next(m for m in mode_type.enum_members if m.name == 'Query').GetValueAsUnsigned() addr = obj.GetAddress().GetFileAddress() mode = obj.GetProcess().ReadUnsignedFromMemory(addr + results_mode_offset, mode_type.size, lldb.SBError()) return mode != mode_query_value def results_object_class_name(obj): class_info = get_ivar(obj, obj.GetAddress(), 'RLMResults._info') object_schema = get_ivar(obj, class_info, 'RLMClassInfo.rlmObjectSchema') return get_object_class_name(obj.GetThread().GetSelectedFrame(), obj, object_schema, 'RLMObjectSchema._className') def RLMResults_SummaryProvider(obj, _): class_name = results_object_class_name(obj) if not is_results_evaluated(obj): return 'Unevaluated query on ' + class_name frame = obj.GetThread().GetSelectedFrame() count = frame.EvaluateExpression('(NSUInteger)[(RLMResults *){} count]'.format(obj.GetAddress())).GetValueAsUnsigned() return "({}[{}])".format(class_name, count) class RLMCollection_SyntheticChildrenProvider(SyntheticChildrenProvider): def __init__(self, valobj, _): super(RLMCollection_SyntheticChildrenProvider, self).__init__(valobj.deref.type.name) self.obj = valobj self.addr = self.obj.GetAddress() def num_children(self): if not self.count: self.count = self._eval("(NSUInteger)[(id){} count]".format(self.addr)).GetValueAsUnsigned() return self.count + 1 def has_children(self): return True def get_child_index(self, name): if name == 'realm': return 0 if not name.startswith('['): return None return int(name.lstrip('[').rstrip(']')) + 1 def get_child_at_index(self, index): if index == 0: return self._value_from_ivar('realm') value = self._eval('(id)[(id){} objectAtIndex:{}]'.format(self.addr, index - 1)) return self.obj.CreateValueFromData('[' + str(index - 1) + ']', value.GetData(), value.GetType()) def update(self): self.count = None def __lldb_init_module(debugger, _): debugger.HandleCommand('type summary add RLMArray -F rlm_lldb.RLMArray_SummaryProvider') debugger.HandleCommand('type summary add RLMArrayLinkView -F rlm_lldb.RLMArray_SummaryProvider') debugger.HandleCommand('type summary add RLMResults -F rlm_lldb.RLMResults_SummaryProvider') debugger.HandleCommand('type summary add -x RLMAccessor_ -F rlm_lldb.RLMObject_SummaryProvider') debugger.HandleCommand('type synthetic add RLMArray --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider') debugger.HandleCommand('type synthetic add RLMArrayLinkView --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider') debugger.HandleCommand('type synthetic add RLMResults --python-class rlm_lldb.RLMCollection_SyntheticChildrenProvider') debugger.HandleCommand('type synthetic add -x RLMAccessor_.* --python-class rlm_lldb.RLMObject_SyntheticChildrenProvider')