Home | History | Annotate | Download | only in cocoa
      1 """
      2 LLDB AppKit formatters
      3 
      4 part of The LLVM Compiler Infrastructure
      5 This file is distributed under the University of Illinois Open Source
      6 License. See LICENSE.TXT for details.
      7 """
      8 # example synthetic children and summary provider for CFString (and related NSString class)
      9 # the real code is part of the LLDB core
     10 import lldb
     11 import lldb.runtime.objc.objc_runtime
     12 import lldb.formatters.Logger
     13 
     14 def CFString_SummaryProvider (valobj,dict):
     15 	logger = lldb.formatters.Logger.Logger()
     16 	provider = CFStringSynthProvider(valobj,dict);
     17 	if provider.invalid == False:
     18 		try:
     19 			summary = provider.get_child_at_index(provider.get_child_index("content"))
     20 			if type(summary) == lldb.SBValue:
     21 				summary = summary.GetSummary()
     22 			else:
     23 				summary = '"' + summary + '"'
     24 		except:
     25 			summary = None
     26 		if summary == None:
     27 			summary = '<variable is not NSString>'
     28 		return '@'+summary
     29 	return ''
     30 
     31 def CFAttributedString_SummaryProvider (valobj,dict):
     32 	logger = lldb.formatters.Logger.Logger()
     33 	offset = valobj.GetTarget().GetProcess().GetAddressByteSize()
     34 	pointee = valobj.GetValueAsUnsigned(0)
     35 	summary = '<variable is not NSAttributedString>'
     36 	if pointee != None and pointee != 0:
     37 		pointee = pointee + offset
     38 		child_ptr = valobj.CreateValueFromAddress("string_ptr",pointee,valobj.GetType())
     39 		child = child_ptr.CreateValueFromAddress("string_data",child_ptr.GetValueAsUnsigned(),valobj.GetType()).AddressOf()
     40 		provider = CFStringSynthProvider(child,dict);
     41 		if provider.invalid == False:
     42 			try:
     43 				summary = provider.get_child_at_index(provider.get_child_index("content")).GetSummary();
     44 			except:
     45 				summary = '<variable is not NSAttributedString>'
     46 	if summary == None:
     47 		summary = '<variable is not NSAttributedString>'
     48 	return '@'+summary
     49 
     50 
     51 def __lldb_init_module(debugger,dict):
     52 	debugger.HandleCommand("type summary add -F CFString.CFString_SummaryProvider NSString CFStringRef CFMutableStringRef")
     53 	debugger.HandleCommand("type summary add -F CFString.CFAttributedString_SummaryProvider NSAttributedString")
     54 
     55 class CFStringSynthProvider:
     56 	def __init__(self,valobj,dict):
     57 		logger = lldb.formatters.Logger.Logger()
     58 		self.valobj = valobj;
     59 		self.update()
     60 
     61 	# children other than "content" are for debugging only and must not be used in production code
     62 	def num_children(self):
     63 		logger = lldb.formatters.Logger.Logger()
     64 		if self.invalid:
     65 			return 0;
     66 		return 6;
     67 
     68 	def read_unicode(self, pointer,max_len=2048):
     69 		logger = lldb.formatters.Logger.Logger()
     70 		process = self.valobj.GetTarget().GetProcess()
     71 		error = lldb.SBError()
     72 		pystr = u''
     73 		# cannot do the read at once because the length value has
     74 		# a weird encoding. better play it safe here
     75 		while max_len > 0:
     76 			content = process.ReadMemory(pointer, 2, error)
     77 			new_bytes = bytearray(content)
     78 			b0 = new_bytes[0]
     79 			b1 = new_bytes[1]
     80 			pointer = pointer + 2
     81 			if b0 == 0 and b1 == 0:
     82 				break
     83 			# rearrange bytes depending on endianness
     84 			# (do we really need this or is Cocoa going to
     85 			#  use Windows-compatible little-endian even
     86 			#  if the target is big endian?)
     87 			if self.is_little:
     88 				value = b1 * 256 + b0
     89 			else:
     90 				value = b0 * 256 + b1
     91 			pystr = pystr + unichr(value)
     92 			# read max_len unicode values, not max_len bytes
     93 			max_len = max_len - 1
     94 		return pystr
     95 
     96 	# handle the special case strings
     97 	# only use the custom code for the tested LP64 case
     98 	def handle_special(self):
     99 		logger = lldb.formatters.Logger.Logger()
    100 		if self.is_64_bit == False:
    101 			# for 32bit targets, use safe ObjC code
    102 			return self.handle_unicode_string_safe()
    103 		offset = 12
    104 		pointer = self.valobj.GetValueAsUnsigned(0) + offset
    105 		pystr = self.read_unicode(pointer)
    106 		return self.valobj.CreateValueFromExpression("content",
    107 			"(char*)\"" + pystr.encode('utf-8') + "\"")
    108 
    109 	# last resort call, use ObjC code to read; the final aim is to
    110 	# be able to strip this call away entirely and only do the read
    111 	# ourselves
    112 	def handle_unicode_string_safe(self):
    113 		return self.valobj.CreateValueFromExpression("content",
    114 			"(char*)\"" + self.valobj.GetObjectDescription() + "\"");
    115 
    116 	def handle_unicode_string(self):
    117 		logger = lldb.formatters.Logger.Logger()
    118 		# step 1: find offset
    119 		if self.inline:
    120 			pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base();
    121 			if self.explicit == False:
    122 				# untested, use the safe code path
    123 				return self.handle_unicode_string_safe();
    124 			else:
    125 				# a full pointer is skipped here before getting to the live data
    126 				pointer = pointer + self.pointer_size
    127 		else:
    128 			pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()
    129 			# read 8 bytes here and make an address out of them
    130 			try:
    131 				char_type = self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType()
    132 				vopointer = self.valobj.CreateValueFromAddress("dummy",pointer,char_type);
    133 				pointer = vopointer.GetValueAsUnsigned(0)
    134 			except:
    135 				return self.valobj.CreateValueFromExpression("content",
    136                                                              '(char*)"@\"invalid NSString\""')
    137 		# step 2: read Unicode data at pointer
    138 		pystr = self.read_unicode(pointer)
    139 		# step 3: return it
    140 		return pystr.encode('utf-8')
    141 
    142 	def handle_inline_explicit(self):
    143 		logger = lldb.formatters.Logger.Logger()
    144 		offset = 3*self.pointer_size
    145 		offset = offset + self.valobj.GetValueAsUnsigned(0)
    146 		return self.valobj.CreateValueFromExpression("content",
    147 				"(char*)(" + str(offset) + ")")
    148 
    149 	def handle_mutable_string(self):
    150 		logger = lldb.formatters.Logger.Logger()
    151 		offset = 2 * self.pointer_size
    152 		data = self.valobj.CreateChildAtOffset("content",
    153 			offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType());
    154 		data_value = data.GetValueAsUnsigned(0)
    155 		if self.explicit and self.unicode:
    156 			return self.read_unicode(data_value).encode('utf-8')
    157 		else:
    158 			data_value = data_value + 1
    159 			return self.valobj.CreateValueFromExpression("content", "(char*)(" + str(data_value) + ")")
    160 
    161 	def handle_UTF8_inline(self):
    162 		logger = lldb.formatters.Logger.Logger()
    163 		offset = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base();
    164 		if self.explicit == False:
    165 			offset = offset + 1;
    166 		return self.valobj.CreateValueFromAddress("content",
    167 				offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar)).AddressOf();
    168 
    169 	def handle_UTF8_not_inline(self):
    170 		logger = lldb.formatters.Logger.Logger()
    171 		offset = self.size_of_cfruntime_base();
    172 		return self.valobj.CreateChildAtOffset("content",
    173 				offset,self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType());
    174 
    175 	def get_child_at_index(self,index):
    176 		logger = lldb.formatters.Logger.Logger()
    177 		logger >> "Querying for child [" + str(index) + "]"
    178 		if index == 0:
    179 			return self.valobj.CreateValueFromExpression("mutable",
    180 				str(int(self.mutable)));
    181 		if index == 1:
    182 			return self.valobj.CreateValueFromExpression("inline",
    183 				str(int(self.inline)));
    184 		if index == 2:
    185 			return self.valobj.CreateValueFromExpression("explicit",
    186 				str(int(self.explicit)));
    187 		if index == 3:
    188 			return self.valobj.CreateValueFromExpression("unicode",
    189 				str(int(self.unicode)));
    190 		if index == 4:
    191 			return self.valobj.CreateValueFromExpression("special",
    192 				str(int(self.special)));
    193 		if index == 5:
    194 			# we are handling the several possible combinations of flags.
    195 			# for each known combination we have a function that knows how to
    196 			# go fetch the data from memory instead of running code. if a string is not
    197 			# correctly displayed, one should start by finding a combination of flags that
    198 			# makes it different from these known cases, and provide a new reader function
    199 			# if this is not possible, a new flag might have to be made up (like the "special" flag
    200 			# below, which is not a real flag in CFString), or alternatively one might need to use
    201 			# the ObjC runtime helper to detect the new class and deal with it accordingly
    202 			#print 'mutable = ' + str(self.mutable)
    203 			#print 'inline = ' + str(self.inline)
    204 			#print 'explicit = ' + str(self.explicit)
    205 			#print 'unicode = ' + str(self.unicode)
    206 			#print 'special = ' + str(self.special)
    207 			if self.mutable == True:
    208 				return self.handle_mutable_string()
    209 			elif self.inline == True and self.explicit == True and \
    210 			   self.unicode == False and self.special == False and \
    211 			   self.mutable == False:
    212 				return self.handle_inline_explicit()
    213 			elif self.unicode == True:
    214 				return self.handle_unicode_string();
    215 			elif self.special == True:
    216 				return self.handle_special();
    217 			elif self.inline == True:
    218 				return self.handle_UTF8_inline();
    219 			else:
    220 				return self.handle_UTF8_not_inline();
    221 
    222 	def get_child_index(self,name):
    223 		logger = lldb.formatters.Logger.Logger()
    224 		logger >> "Querying for child ['" + str(name) + "']"
    225 		if name == "content":
    226 			return self.num_children() - 1;
    227 		if name == "mutable":
    228 			return 0;
    229 		if name == "inline":
    230 			return 1;
    231 		if name == "explicit":
    232 			return 2;
    233 		if name == "unicode":
    234 			return 3;
    235 		if name == "special":
    236 			return 4;
    237 
    238 	# CFRuntimeBase is defined as having an additional
    239 	# 4 bytes (padding?) on LP64 architectures
    240 	# to get its size we add up sizeof(pointer)+4
    241 	# and then add 4 more bytes if we are on a 64bit system
    242 	def size_of_cfruntime_base(self):
    243 		logger = lldb.formatters.Logger.Logger()
    244 		return self.pointer_size+4+(4 if self.is_64_bit else 0)
    245 
    246 	# the info bits are part of the CFRuntimeBase structure
    247 	# to get at them we have to skip a uintptr_t and then get
    248 	# at the least-significant byte of a 4 byte array. If we are
    249 	# on big-endian this means going to byte 3, if we are on
    250 	# little endian (OSX & iOS), this means reading byte 0
    251 	def offset_of_info_bits(self):
    252 		logger = lldb.formatters.Logger.Logger()
    253 		offset = self.pointer_size
    254 		if self.is_little == False:
    255 			offset = offset + 3;
    256 		return offset;
    257 
    258 	def read_info_bits(self):
    259 		logger = lldb.formatters.Logger.Logger()
    260 		cfinfo = self.valobj.CreateChildAtOffset("cfinfo",
    261 					self.offset_of_info_bits(),
    262 					self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar));
    263 		cfinfo.SetFormat(11)
    264 		info = cfinfo.GetValue();
    265 		if info != None:
    266 			self.invalid = False;
    267 			return int(info,0);
    268 		else:
    269 			self.invalid = True;
    270 			return None;
    271 
    272 	# calculating internal flag bits of the CFString object
    273 	# this stuff is defined and discussed in CFString.c
    274 	def is_mutable(self):
    275 		logger = lldb.formatters.Logger.Logger()
    276 		return (self.info_bits & 1) == 1;
    277 
    278 	def is_inline(self):
    279 		logger = lldb.formatters.Logger.Logger()
    280 		return (self.info_bits & 0x60) == 0;
    281 
    282 	# this flag's name is ambiguous, it turns out
    283 	# we must skip a length byte to get at the data
    284 	# when this flag is False
    285 	def has_explicit_length(self):
    286 		logger = lldb.formatters.Logger.Logger()
    287 		return (self.info_bits & (1 | 4)) != 4;
    288 
    289 	# probably a subclass of NSString. obtained this from [str pathExtension]
    290 	# here info_bits = 0 and Unicode data at the start of the padding word
    291 	# in the long run using the isa value might be safer as a way to identify this
    292 	# instead of reading the info_bits
    293 	def is_special_case(self):
    294 		logger = lldb.formatters.Logger.Logger()
    295 		return self.info_bits == 0;
    296 
    297 	def is_unicode(self):
    298 		logger = lldb.formatters.Logger.Logger()
    299 		return (self.info_bits & 0x10) == 0x10;
    300 
    301 	# preparing ourselves to read into memory
    302 	# by adjusting architecture-specific info
    303 	def adjust_for_architecture(self):
    304 		logger = lldb.formatters.Logger.Logger()
    305 		self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
    306 		self.is_64_bit = self.pointer_size == 8
    307 		self.is_little = self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle
    308 
    309 	# reading info bits out of the CFString and computing
    310 	# useful values to get at the real data
    311 	def compute_flags(self):
    312 		logger = lldb.formatters.Logger.Logger()
    313 		self.info_bits = self.read_info_bits();
    314 		if self.info_bits == None:
    315 			return;
    316 		self.mutable = self.is_mutable();
    317 		self.inline = self.is_inline();
    318 		self.explicit = self.has_explicit_length();
    319 		self.unicode = self.is_unicode();
    320 		self.special = self.is_special_case();
    321 
    322 	def update(self):
    323 		logger = lldb.formatters.Logger.Logger()
    324 		self.adjust_for_architecture();
    325 		self.compute_flags();
    326