llvm-project/lldb/examples/summaries/cocoa/CFArray.py

322 lines
10 KiB
Python

# synthetic children provider for NSArray
import lldb
import ctypes
import objc_runtime
import metrics
statistics = metrics.Metrics()
statistics.add_metric('invalid_isa')
statistics.add_metric('invalid_pointer')
statistics.add_metric('unknown_class')
statistics.add_metric('code_notrun')
# much less functional than the other two cases below
# just runs code to get to the count and then returns
# no children
class NSArrayKVC_SynthProvider:
def adjust_for_architecture(self):
self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
def __init__(self, valobj, dict):
self.valobj = valobj;
self.update()
def update(self):
self.adjust_for_architecture();
self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
def num_children(self):
stream = lldb.SBStream()
self.valobj.GetExpressionPath(stream)
num_children_vo = self.valobj.CreateValueFromExpression("count","(int)[" + stream.GetData() + " count]");
return num_children_vo.GetValueAsUnsigned(0)
def get_child_index(self,name):
if name == "len":
return self.num_children();
else:
return None
def get_child_at_index(self, index):
return None
# much less functional than the other two cases below
# just runs code to get to the count and then returns
# no children
class NSArrayCF_SynthProvider:
def adjust_for_architecture(self):
self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
self.cfruntime_size = self.size_of_cfruntime_base()
# CFRuntimeBase is defined as having an additional
# 4 bytes (padding?) on LP64 architectures
# to get its size we add up sizeof(pointer)+4
# and then add 4 more bytes if we are on a 64bit system
def size_of_cfruntime_base(self):
if self.is_64_bit == True:
return 8+4+4;
else:
return 4+4;
def __init__(self, valobj, dict):
self.valobj = valobj;
self.update()
def update(self):
self.adjust_for_architecture();
self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
def num_children(self):
num_children_vo = self.valobj.CreateChildAtOffset("count",
self.cfruntime_size,
self.valobj.GetType().GetBasicType(lldb.eBasicTypeUnsignedLong))
return num_children_vo.GetValueAsUnsigned(0)
def get_child_index(self,name):
if name == "len":
return self.num_children();
else:
return None
def get_child_at_index(self, index):
return None
class NSArrayI_SynthProvider:
def adjust_for_architecture(self):
self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
def __init__(self, valobj, dict):
self.valobj = valobj;
self.update()
def update(self):
self.adjust_for_architecture();
self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
# skip the isa pointer and get at the size
def num_children(self):
offset = self.pointer_size;
datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)
count = self.valobj.CreateChildAtOffset("count",
offset,
datatype);
return int(count.GetValue(), 0)
def get_child_index(self,name):
if name == "len":
return self.num_children();
else:
return int(name.lstrip('[').rstrip(']'), 0)
def get_child_at_index(self, index):
if index == self.num_children():
return self.valobj.CreateValueFromExpression("len",
str(index))
offset = 2 * self.pointer_size + self.id_type.GetByteSize()*index
return self.valobj.CreateChildAtOffset('[' + str(index) + ']',
offset,
self.id_type)
class NSArrayM_SynthProvider:
def adjust_for_architecture(self):
self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
def __init__(self, valobj, dict):
self.valobj = valobj;
self.update();
def update(self):
self.adjust_for_architecture();
self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
# skip the isa pointer and get at the size
def num_children(self):
offset = self.pointer_size;
datatype = self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong)
count = self.valobj.CreateChildAtOffset("count",
offset,
datatype);
return int(count.GetValue(), 0)
def get_child_index(self,name):
if name == "len":
return self.num_children();
else:
return int(name.lstrip('[').rstrip(']'), 0)
def data_offset(self):
offset = self.pointer_size; # isa
offset += self.pointer_size; # _used
offset += self.pointer_size; # _doHardRetain, _doWeakAccess, _size
offset += self.pointer_size; # _hasObjects, _hasStrongReferences, _offset
offset += self.pointer_size; # _mutations
return offset;
# the _offset field is used to calculate the actual offset
# when reading a value out of the array. we need to read it
# to do so we read a whole pointer_size of data from the
# right spot, and then zero out the two LSB
def read_offset_field(self):
disp = self.pointer_size; # isa
disp += self.pointer_size; # _used
disp += self.pointer_size; # _doHardRetain, _doWeakAccess, _size
offset = self.valobj.CreateChildAtOffset("offset",
disp,
self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong))
offset_value = int(offset.GetValue(), 0)
offset_value = ctypes.c_uint32((offset_value & 0xFFFFFFFC) >> 2).value
return offset_value
# the _used field tells how many items are in the array
# but since this is a mutable array, it allocates more space
# for performance reasons. we need to get the real _size of
# the array to calculate the actual offset of each element
# in get_child_at_index() (see NSArray.m for details)
def read_size_field(self):
disp = self.pointer_size; # isa
disp += self.pointer_size; # _used
size = self.valobj.CreateChildAtOffset("size",
disp,
self.valobj.GetType().GetBasicType(lldb.eBasicTypeLong))
size_value = int(size.GetValue(), 0)
size_value = ctypes.c_uint32((size_value & 0xFFFFFFFA) >> 2).value
return size_value
def get_child_at_index(self, index):
if index == self.num_children():
return self.valobj.CreateValueFromExpression("len",
str(index))
size = self.read_size_field()
offset = self.read_offset_field()
phys_idx = offset + index
if size <= phys_idx:
phys_idx -=size;
# we still need to multiply by element size to do a correct pointer read
phys_idx *= self.id_type.GetByteSize()
list_ptr = self.valobj.CreateChildAtOffset("_list",
self.data_offset(),
self.id_type.GetBasicType(lldb.eBasicTypeUnsignedLongLong))
list_addr = int(list_ptr.GetValue(), 0)
return self.valobj.CreateValueFromAddress('[' + str(index) + ']',
list_addr + phys_idx,
self.id_type)
# this is the actual synth provider, but is just a wrapper that checks
# whether valobj is an instance of __NSArrayI or __NSArrayM and sets up an
# appropriate backend layer to do the computations
class NSArray_SynthProvider:
def adjust_for_architecture(self):
self.is_64_bit = (self.valobj.GetTarget().GetProcess().GetAddressByteSize() == 8)
self.is_little = (self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle)
self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
self.id_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeObjCID)
def __init__(self, valobj, dict):
self.valobj = valobj;
self.adjust_for_architecture()
self.wrapper = self.make_wrapper(valobj,dict)
self.invalid = (self.wrapper == None)
def get_child_at_index(self, index):
if self.wrapper == None:
return None;
return self.wrapper.get_child_at_index(index)
def get_child_index(self,name):
if self.wrapper == None:
return None;
return self.wrapper.get_child_index(name)
def num_children(self):
if self.wrapper == None:
return 0;
return self.wrapper.num_children()
def update(self):
if self.wrapper == None:
return None;
return self.wrapper.update()
def read_ascii(self, pointer):
process = self.valobj.GetTarget().GetProcess()
error = lldb.SBError()
pystr = ''
# cannot do the read at once because there is no length byte
while True:
content = process.ReadMemory(pointer, 1, error)
new_bytes = bytearray(content)
b0 = new_bytes[0]
pointer = pointer + 1
if b0 == 0:
break
pystr = pystr + chr(b0)
return pystr
# this code acts as our defense against NULL and unitialized
# NSArray pointers, which makes it much longer than it would be otherwise
def make_wrapper(self,valobj,dict):
global statistics
class_data = objc_runtime.ObjCRuntime(valobj)
if class_data.is_valid() == False:
statistics.metric_hit('invalid_pointer',valobj)
wrapper = None
return
class_data = class_data.read_class_data()
if class_data.is_valid() == False:
statistics.metric_hit('invalid_isa',valobj)
wrapper = None
return
if class_data.is_kvo():
class_data = class_data.get_superclass()
if class_data.is_valid() == False:
statistics.metric_hit('invalid_isa',valobj)
wrapper = None
return
name_string = class_data.class_name()
if name_string == '__NSArrayI':
wrapper = NSArrayI_SynthProvider(valobj, dict)
statistics.metric_hit('code_notrun',valobj)
elif name_string == '__NSArrayM':
wrapper = NSArrayM_SynthProvider(valobj, dict)
statistics.metric_hit('code_notrun',valobj)
elif name_string == '__NSCFArray':
wrapper = NSArrayCF_SynthProvider(valobj, dict)
statistics.metric_hit('code_notrun',valobj)
else:
wrapper = NSArrayKVC_SynthProvider(valobj, dict)
statistics.metric_hit('unknown_class',str(valobj) + " seen as " + name_string)
return wrapper;
def CFArray_SummaryProvider (valobj,dict):
provider = NSArray_SynthProvider(valobj,dict);
if provider.invalid == False:
try:
summary = str(provider.num_children());
except:
summary = None
if summary == None:
summary = 'no valid array here'
return summary + " objects"
return ''
def __lldb_init_module(debugger,dict):
debugger.HandleCommand("type summary add -F CFArray.CFArray_SummaryProvider NSArray CFArrayRef CFMutableArrayRef")