# This implements the "diagnose-nsstring" command, usually installed in the debug session like
#   command script import lldb.diagnose
# it is used when NSString summary formatter fails to replicate the logic that went into LLDB making the
# decisions it did and  providing some useful context information that can be used for improving the formatter

import lldb

def read_memory(process,location,size):
	data = ""
	error = lldb.SBError()
	for x in range(0,size-1):
		byte = process.ReadUnsignedFromMemory(x+location,1,error)
		if error.fail:
			data = data + "err%s" % "" if x == size-2 else ":"
		else:
			try:
				data = data + "0x%x" % byte
				if byte == 0:
					data = data + "(\\0)"
				elif byte == 0xa:
					data = data + "(\\a)"
				elif byte == 0xb:
					data = data + "(\\b)"
				elif byte == 0xc:
					data = data + "(\\c)"
				elif byte == '\n':
					data = data + "(\\n)"
				else:
					data = data + "(%s)" % chr(byte)
				if x < size-2:
					data = data + ":"
			except Exception as e:
				print e
	return data

def diagnose_nsstring_Command_Impl(debugger,command,result,internal_dict):
	"""
	A command to diagnose the LLDB NSString data formatter
	invoke as
	(lldb) diagnose-nsstring <expr returning NSString>
	e.g.
	(lldb) diagnose-nsstring @"Hello world"
	"""
	target = debugger.GetSelectedTarget()
	process = target.GetProcess()
	thread = process.GetSelectedThread()
	frame = thread.GetSelectedFrame()
	if not target.IsValid() or not process.IsValid():
		return "unable to get target/process - cannot proceed"
	options = lldb.SBExpressionOptions()
	options.SetFetchDynamicValue()
	error = lldb.SBError()
	if frame.IsValid():
		nsstring = frame.EvaluateExpression(command,options)
	else:
		nsstring = target.EvaluateExpression(command,options)
	print >>result,str(nsstring)
	nsstring_address = nsstring.GetValueAsUnsigned(0)
	if nsstring_address == 0:
		return "unable to obtain the string - cannot proceed"
	expression = "\
struct $__lldb__notInlineMutable {\
    char* buffer;\
    signed long length;\
    signed long capacity;\
    unsigned int hasGap:1;\
    unsigned int isFixedCapacity:1;\
    unsigned int isExternalMutable:1;\
    unsigned int capacityProvidedExternally:1;\n\
#if __LP64__\n\
    unsigned long desiredCapacity:60;\n\
#else\n\
    unsigned long desiredCapacity:28;\n\
#endif\n\
    void* contentsAllocator;\
};\
\
struct $__lldb__CFString {\
    void* _cfisa;\
    uint8_t _cfinfo[4];\
    uint32_t _rc;\
    union {\
        struct __inline1 {\
            signed long length;\
        } inline1;\
        struct __notInlineImmutable1 {\
            char* buffer;\
            signed long length;\
            void* contentsDeallocator;\
        } notInlineImmutable1;\
        struct __notInlineImmutable2 {\
            char* buffer;\
            void* contentsDeallocator;\
        } notInlineImmutable2;\
        struct $__lldb__notInlineMutable notInlineMutable;\
    } variants;\
};\
"

	expression = expression + "*(($__lldb__CFString*) %d)" % nsstring_address
	# print expression
	dumped = target.EvaluateExpression(expression,options)
	print >>result, str(dumped)
	
	little_endian = (target.byte_order == lldb.eByteOrderLittle)
	ptr_size = target.addr_size
	
	info_bits = dumped.GetChildMemberWithName("_cfinfo").GetChildAtIndex(0 if little_endian else 3).GetValueAsUnsigned(0)
	is_mutable = (info_bits & 1) == 1
	is_inline = (info_bits & 0x60) == 0
	has_explicit_length = (info_bits & (1 | 4)) != 4
	is_unicode = (info_bits & 0x10) == 0x10
	is_special = (nsstring.GetDynamicValue(lldb.eDynamicCanRunTarget).GetTypeName() == "NSPathStore2")
	has_null = (info_bits & 8) == 8
    
	print >>result,"\nInfo=%d\nMutable=%s\nInline=%s\nExplicit=%s\nUnicode=%s\nSpecial=%s\nNull=%s\n" % \
		(info_bits, "yes" if is_mutable else "no","yes" if is_inline else "no","yes" if has_explicit_length else "no","yes" if is_unicode else "no","yes" if is_special else "no","yes" if has_null else "no")


	explicit_length_offset = 0
	if not has_null and has_explicit_length and not is_special:
		explicit_length_offset = 2*ptr_size
		if is_mutable and not is_inline:
			explicit_length_offset = explicit_length_offset + ptr_size
		elif is_inline:
			pass
		elif not is_inline and not is_mutable:
			explicit_length_offset = explicit_length_offset + ptr_size
		else:
			explicit_length_offset = 0

	if explicit_length_offset == 0:
		print >>result,"There is no explicit length marker - skipping this step\n"
	else:
		explicit_length_offset = nsstring_address + explicit_length_offset
		explicit_length = process.ReadUnsignedFromMemory(explicit_length_offset, 4, error)
		print >>result,"Explicit length location is at 0x%x - read value is %d\n" % (explicit_length_offset,explicit_length)

	if is_mutable:
		location = 2 * ptr_size + nsstring_address
		location = process.ReadPointerFromMemory(location,error)
	elif is_inline and has_explicit_length and not is_unicode and not is_special and not is_mutable:
		location = 3 * ptr_size + nsstring_address
	elif is_unicode:
		location = 2 * ptr_size + nsstring_address
		if is_inline:
			if not has_explicit_length:
				print >>result,"Unicode & Inline & !Explicit is a new combo - no formula for it"
			else:
				location += ptr_size
		else:
			location = process.ReadPointerFromMemory(location,error)
	elif is_special:
		location = nsstring_address + ptr_size + 4
	elif is_inline:
		location = 2 * ptr_size + nsstring_address
		if not has_explicit_length:
			location += 1
	else:
		location = 2 * ptr_size + nsstring_address
		location = process.ReadPointerFromMemory(location,error)
	print >>result,"Expected data location: 0x%x\n" % (location)
	print >>result,"1K of data around location: %s\n" % read_memory(process,location,1024)
	print >>result,"5K of data around string pointer: %s\n" % read_memory(process,nsstring_address,1024*5)

def __lldb_init_module(debugger, internal_dict):
	debugger.HandleCommand("command script add -f %s.diagnose_nsstring_Command_Impl diagnose-nsstring" % __name__)
	print 'The "diagnose-nsstring" command has been installed, type "help diagnose-nsstring" for detailed help.'

__lldb_init_module(lldb.debugger,None)
__lldb_init_module = None