You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
11 KiB

"""
LLDB AppKit formatters
Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
See https://llvm.org/LICENSE.txt for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
# example summary provider for NSNumber
# the real summary is now C++ code built into LLDB
from __future__ import print_function
import lldb
import ctypes
import lldb.runtime.objc.objc_runtime
import lldb.formatters.metrics
import struct
import lldb.formatters.Logger
statistics = lldb.formatters.metrics.Metrics()
statistics.add_metric('invalid_isa')
statistics.add_metric('invalid_pointer')
statistics.add_metric('unknown_class')
statistics.add_metric('code_notrun')
# despite the similary to synthetic children providers, these classes are not
# trying to provide anything but the port number of an NSNumber, so they need not
# obey the interface specification for synthetic children providers
class NSTaggedNumber_SummaryProvider:
def adjust_for_architecture(self):
pass
def __init__(self, valobj, info_bits, data, params):
logger = lldb.formatters.Logger.Logger()
self.valobj = valobj
self.sys_params = params
self.info_bits = info_bits
self.data = data
self.update()
def update(self):
logger = lldb.formatters.Logger.Logger()
self.adjust_for_architecture()
def value(self):
logger = lldb.formatters.Logger.Logger()
# in spite of the plenty of types made available by the public NSNumber API
# only a bunch of these are actually used in the internal implementation
# unfortunately, the original type information appears to be lost
# so we try to at least recover the proper magnitude of the data
if self.info_bits == 0:
return '(char)' + \
str(ord(ctypes.c_char(chr(self.data % 256)).value))
if self.info_bits == 4:
return '(short)' + \
str(ctypes.c_short(self.data % (256 * 256)).value)
if self.info_bits == 8:
return '(int)' + str(ctypes.c_int(self.data %
(256 * 256 * 256 * 256)).value)
if self.info_bits == 12:
return '(long)' + str(ctypes.c_long(self.data).value)
else:
return 'unexpected value:(info=' + str(self.info_bits) + \
", value = " + str(self.data) + ')'
class NSUntaggedNumber_SummaryProvider:
def adjust_for_architecture(self):
pass
def __init__(self, valobj, params):
logger = lldb.formatters.Logger.Logger()
self.valobj = valobj
self.sys_params = params
if not(self.sys_params.types_cache.char):
self.sys_params.types_cache.char = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeChar)
if not(self.sys_params.types_cache.short):
self.sys_params.types_cache.short = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeShort)
if not(self.sys_params.types_cache.ushort):
self.sys_params.types_cache.ushort = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeUnsignedShort)
if not(self.sys_params.types_cache.int):
self.sys_params.types_cache.int = self.valobj.GetType().GetBasicType(lldb.eBasicTypeInt)
if not(self.sys_params.types_cache.long):
self.sys_params.types_cache.long = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeLong)
if not(self.sys_params.types_cache.ulong):
self.sys_params.types_cache.ulong = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeUnsignedLong)
if not(self.sys_params.types_cache.longlong):
self.sys_params.types_cache.longlong = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeLongLong)
if not(self.sys_params.types_cache.ulonglong):
self.sys_params.types_cache.ulonglong = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeUnsignedLongLong)
if not(self.sys_params.types_cache.float):
self.sys_params.types_cache.float = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeFloat)
if not(self.sys_params.types_cache.double):
self.sys_params.types_cache.double = self.valobj.GetType(
).GetBasicType(lldb.eBasicTypeDouble)
self.update()
def update(self):
logger = lldb.formatters.Logger.Logger()
self.adjust_for_architecture()
def value(self):
logger = lldb.formatters.Logger.Logger()
global statistics
# we need to skip the ISA, then the next byte tells us what to read
# we then skip one other full pointer worth of data and then fetch the contents
# if we are fetching an int64 value, one more pointer must be skipped
# to get at our data
data_type_vo = self.valobj.CreateChildAtOffset(
"dt", self.sys_params.pointer_size, self.sys_params.types_cache.char)
data_type = ((data_type_vo.GetValueAsUnsigned(0) % 256) & 0x1F)
data_offset = 2 * self.sys_params.pointer_size
if data_type == 0B00001:
data_vo = self.valobj.CreateChildAtOffset(
"data", data_offset, self.sys_params.types_cache.char)
statistics.metric_hit('code_notrun', self.valobj)
return '(char)' + \
str(ord(ctypes.c_char(chr(data_vo.GetValueAsUnsigned(0))).value))
elif data_type == 0B0010:
data_vo = self.valobj.CreateChildAtOffset(
"data", data_offset, self.sys_params.types_cache.short)
statistics.metric_hit('code_notrun', self.valobj)
return '(short)' + str(
ctypes.c_short(
data_vo.GetValueAsUnsigned(0) %
(256 * 256)).value)
# IF tagged pointers are possible on 32bit+v2 runtime
# (of which the only existing instance should be iOS)
# then values of this type might be tagged
elif data_type == 0B0011:
data_vo = self.valobj.CreateChildAtOffset(
"data", data_offset, self.sys_params.types_cache.int)
statistics.metric_hit('code_notrun', self.valobj)
return '(int)' + str(ctypes.c_int(data_vo.GetValueAsUnsigned(0) %
(256 * 256 * 256 * 256)).value)
# apparently, on is_64_bit architectures, these are the only values that will ever
# be represented by a non tagged pointers
elif data_type == 0B10001:
data_offset = data_offset + 8 # 8 is needed even if we are on 32bit
data_vo = self.valobj.CreateChildAtOffset(
"data", data_offset, self.sys_params.types_cache.longlong)
statistics.metric_hit('code_notrun', self.valobj)
return '(long)' + \
str(ctypes.c_long(data_vo.GetValueAsUnsigned(0)).value)
elif data_type == 0B0100:
if self.sys_params.is_64_bit:
data_offset = data_offset + self.sys_params.pointer_size
data_vo = self.valobj.CreateChildAtOffset(
"data", data_offset, self.sys_params.types_cache.longlong)
statistics.metric_hit('code_notrun', self.valobj)
return '(long)' + \
str(ctypes.c_long(data_vo.GetValueAsUnsigned(0)).value)
elif data_type == 0B0101:
data_vo = self.valobj.CreateChildAtOffset(
"data", data_offset, self.sys_params.types_cache.longlong)
data_plain = int(
str(data_vo.GetValueAsUnsigned(0) & 0x00000000FFFFFFFF))
packed = struct.pack('I', data_plain)
data_float = struct.unpack('f', packed)[0]
statistics.metric_hit('code_notrun', self.valobj)
return '(float)' + str(data_float)
elif data_type == 0B0110:
data_vo = self.valobj.CreateChildAtOffset(
"data", data_offset, self.sys_params.types_cache.longlong)
data_plain = data_vo.GetValueAsUnsigned(0)
data_double = struct.unpack('d', struct.pack('Q', data_plain))[0]
statistics.metric_hit('code_notrun', self.valobj)
return '(double)' + str(data_double)
statistics.metric_hit(
'unknown_class', str(
valobj.GetName()) + " had unknown data_type " + str(data_type))
return 'unexpected: dt = ' + str(data_type)
class NSUnknownNumber_SummaryProvider:
def adjust_for_architecture(self):
pass
def __init__(self, valobj, params):
logger = lldb.formatters.Logger.Logger()
self.valobj = valobj
self.sys_params = params
self.update()
def update(self):
logger = lldb.formatters.Logger.Logger()
self.adjust_for_architecture()
def value(self):
logger = lldb.formatters.Logger.Logger()
stream = lldb.SBStream()
self.valobj.GetExpressionPath(stream)
expr = "(NSString*)[" + stream.GetData() + " stringValue]"
num_children_vo = self.valobj.CreateValueFromExpression("str", expr)
if num_children_vo.IsValid():
return num_children_vo.GetSummary()
return '<variable is not NSNumber>'
def GetSummary_Impl(valobj):
logger = lldb.formatters.Logger.Logger()
global statistics
class_data, wrapper = lldb.runtime.objc.objc_runtime.Utilities.prepare_class_detection(
valobj, statistics)
if wrapper:
return wrapper
name_string = class_data.class_name()
logger >> "class name is: " + str(name_string)
if name_string == 'NSNumber' or name_string == '__NSCFNumber':
if class_data.is_tagged():
wrapper = NSTaggedNumber_SummaryProvider(
valobj, class_data.info_bits(), class_data.value(), class_data.sys_params)
statistics.metric_hit('code_notrun', valobj)
else:
# the wrapper might be unable to decipher what is into the NSNumber
# and then have to run code on it
wrapper = NSUntaggedNumber_SummaryProvider(
valobj, class_data.sys_params)
else:
wrapper = NSUnknownNumber_SummaryProvider(
valobj, class_data.sys_params)
statistics.metric_hit(
'unknown_class',
valobj.GetName() +
" seen as " +
name_string)
return wrapper
def NSNumber_SummaryProvider(valobj, dict):
logger = lldb.formatters.Logger.Logger()
provider = GetSummary_Impl(valobj)
if provider is not None:
if isinstance(
provider,
lldb.runtime.objc.objc_runtime.SpecialSituation_Description):
return provider.message()
try:
summary = provider.value()
except Exception as foo:
print(foo)
# except:
summary = None
logger >> "got summary " + str(summary)
if summary is None:
summary = '<variable is not NSNumber>'
return str(summary)
return 'Summary Unavailable'
def __lldb_init_module(debugger, dict):
debugger.HandleCommand(
"type summary add -F NSNumber.NSNumber_SummaryProvider NSNumber")
debugger.HandleCommand(
"type summary add -F NSNumber.NSNumber_SummaryProvider __NSCFBoolean")
debugger.HandleCommand(
"type summary add -F NSNumber.NSNumber_SummaryProvider __NSCFNumber")