File indexing completed on 2024-05-12 04:40:07

0001 #
0002 # LLDB data formatter helpers
0003 #
0004 # SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz>
0005 #
0006 # SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 #
0008 
0009 # BEGIN: Utilities for wrapping differences of Python 2.x and Python 3
0010 # Inspired by https://pythonhosted.org/six/
0011 from __future__ import print_function
0012 import sys
0013 import lldb
0014 # Useful for very coarse version differentiation.
0015 PY2 = sys.version_info[0] == 2
0016 PY3 = sys.version_info[0] == 3
0017 
0018 # create Python 2.x & 3.x compatible iterator base
0019 if PY3:
0020     Iterator = object
0021 else:
0022     class Iterator(object):
0023 
0024         def next(self):
0025             return type(self).__next__(self)
0026 if PY3:
0027     unichr = chr
0028     unicode = str
0029 else:
0030     unichr = unichr
0031 # END
0032 
0033 
0034 def canonicalized_type_name(name):
0035     """Canonicalize the type name for FindFirstType usage.
0036         + 1 space between template arguments (after comma)
0037         + no space before pointer *
0038     otherwise FindFirstType returns None
0039     """
0040     return name.replace(' ', '').replace(',', ', ')
0041 
0042 
0043 def quote(string, quote='"'):
0044     """Quote a string so it's suitable to be used in quote"""
0045     if isinstance(string, unicode):
0046         ls = []
0047         for uch in string:
0048             code = ord(uch)
0049             if code > 255:
0050                 ls += '\\u{:04x}'.format(code)
0051             elif code >= 127:
0052                 ls += '\\x{:02x}'.format(code)
0053             elif uch == quote or uch == '\\':
0054                 ls += '\\' + chr(code)
0055             elif code == 0:
0056                 ls += '\\x00'
0057             else:
0058                 ls += chr(code)
0059         return quote + ''.join(ls) + quote
0060     else:
0061         return '{q}{s}{q}'.format(s=string.replace('\\', '\\\\').replace(quote, '\\' + quote),
0062                                   q=quote)
0063 
0064 
0065 def unquote(data, quote='"'):
0066     """Unquote a string"""
0067     if data.startswith(quote) and data.endswith(quote):
0068         data = data[1:-1]
0069         ls = []
0070         esc = False
0071         for ch in data:
0072             if esc:
0073                 ls.append(ch)
0074                 esc = False
0075             else:
0076                 if ch == '\\':
0077                     esc = True
0078                 else:
0079                     ls.append(ch)
0080         if esc:
0081             print('WARNING: unpaired escape')
0082         data = ''.join(ls)
0083     return data
0084 
0085 
0086 def invoke(val, method, args=''):
0087     """Try to invoke a method on val, args are passed in as an expression string"""
0088     # first try to get a valid frame
0089     frame = None
0090     for f in [val.frame, lldb.frame, val.process.selected_thread.GetFrameAtIndex(0)]:
0091         if f.IsValid():
0092             frame = f
0093             break
0094     if frame is None:
0095         return lldb.SBValue()
0096 
0097     # second try to get a pointer to val
0098     if val.GetType().IsPointerType():
0099         ptype = val.GetType()
0100         addr = val.GetValueAsUnsigned(0)
0101     else:
0102         ptype = val.GetType().GetPointerType()
0103         addr = val.AddressOf().GetValueAsUnsigned(0)
0104 
0105     # third, build expression
0106     expr = 'reinterpret_cast<const {}>({})->{}({})'.format(ptype.GetName(), addr, method, args)
0107     res = frame.EvaluateExpression(expr)
0108     # if not res.IsValid():
0109     #     print 'Expr {} on value {} failed'.format(expr, val.GetName())
0110     return res
0111 
0112 
0113 def rename(name, val):
0114     """Rename a SBValue"""
0115     return val.CreateValueFromData(name, val.GetData(), val.GetType())
0116 
0117 
0118 def toSBPointer(valobj, addr, pointee_type):
0119     """Convert a addr integer to SBValue"""
0120     addr = addr & 0xFFFFFFFFFFFFFFFF  # force unsigned
0121     return valobj.CreateValueFromAddress(None, addr, pointee_type).AddressOf()
0122 
0123 
0124 def validAddr(valobj, addr):
0125     """Test if a address is valid"""
0126     return toSBPointer(valobj, addr,
0127                        valobj.GetType().GetBasicType(lldb.eBasicTypeVoid).GetPointerType()).IsValid()
0128 
0129 
0130 def validPointer(pointer):
0131     """Test if a SBValue pointer is valid"""
0132     if not pointer.IsValid():
0133         return False
0134     if pointer.GetValueAsUnsigned(0) == 0:
0135         return False
0136     return toSBPointer(pointer, pointer.GetValueAsUnsigned(0), pointer.GetType().GetPointeeType()).IsValid()
0137 
0138 
0139 class AutoCacheValue(object):
0140     """An object that can create itself when needed and cache the result"""
0141     def __init__(self, creator):
0142         super(AutoCacheValue, self).__init__()
0143         self.creator = creator
0144         self.cache = None
0145         self.cached = False
0146 
0147     def get(self):
0148         if not self.cached:
0149             self.cache = self.creator()
0150             self.cached = True
0151         return self.cache
0152 
0153 
0154 class HiddenMemberProvider(object):
0155     """A lldb synthetic provider that can provide hidden children.
0156        Original children is exposed in this way"""
0157 
0158     @staticmethod
0159     def _capping_size():
0160         return 255
0161 
0162     def __init__(self, valobj, internal_dict):
0163         self.valobj = valobj
0164         # number of normally visible children
0165         self._num_children = 0
0166         # cache for visible children
0167         self._members = []
0168         # cache for hidden children
0169         self._hiddens = []
0170         # child name to index
0171         self._name2idx = {}
0172         # whether to add original children
0173         self._add_original = True
0174         # some useful info
0175         process = self.valobj.GetProcess()
0176         self._endianness = process.GetByteOrder()
0177         self._pointer_size = process.GetAddressByteSize()
0178         self._char_type = valobj.GetType().GetBasicType(lldb.eBasicTypeChar)
0179 
0180     def has_children(self):
0181         return self._num_children != 0
0182 
0183     def num_children(self):
0184         return self._num_children
0185 
0186     def get_child_index(self, name):
0187         if name in self._name2idx:
0188             return self._name2idx[name]
0189         return None
0190 
0191     def get_child_at_index(self, idx):
0192         if not self.valobj.IsValid():
0193             return None
0194         if idx < 0:
0195             return None
0196         elif idx < self._num_children:
0197             child = self._members[idx]
0198         # These are hidden children, which won't be queried by lldb, but we know
0199         # they are there, so we can use them in summary provider, to avoid another
0200         # fetch from the inferior, and don't shadow original children
0201         elif idx < self._num_children + len(self._hiddens):
0202             child = self._hiddens[idx - self._num_children]
0203         else:
0204             return None
0205 
0206         if isinstance(child, AutoCacheValue):
0207             child = child.get()
0208 
0209         return child
0210 
0211     @staticmethod
0212     def _getName(var):
0213         if isinstance(var, lldb.SBValue):
0214             return var.GetName()
0215         else:
0216             return var[0]
0217 
0218     def update(self):
0219         self._num_children = -1
0220         self._members = []
0221         self._hiddens = []
0222         self._name2idx = {}
0223 
0224         if not self.valobj.IsValid():
0225             return
0226 
0227         # call _update on subclass
0228         self._update()
0229 
0230         # add valobj's original children as hidden children,
0231         # must be called after self._update, so subclass has chance
0232         # to disable it.
0233         if self._add_original:
0234             for v in self.valobj:
0235                 self._addChild(v, hidden=True)
0236 
0237         # update num_children
0238         if self._num_children < 0:
0239             self._num_children = len(self._members)
0240 
0241         # build name to index lookup, hidden value first, so normal value takes precedence
0242         self._name2idx = {
0243             self._getName(self._hiddens[idx]): idx + self._num_children
0244             for idx in range(0, len(self._hiddens))
0245         }
0246         self._name2idx.update({
0247             self._getName(self._members[idx]): idx
0248             for idx in range(0, self._num_children)
0249         })
0250 
0251     def _update(self):
0252         """override in subclass"""
0253         pass
0254 
0255     def _addChild(self, var, hidden=False):
0256         if not isinstance(var, lldb.SBValue):
0257             # special handling for (name, expr) tuple of string constants
0258             if len(var) != 2:
0259                 print('error, const char[] value should be a tuple with two elements, it is', var)
0260             name, content = var
0261 
0262             if isinstance(content, unicode):
0263                 content = content.encode()
0264 
0265             try:
0266                 char_arr_type = self._char_type.GetArrayType(len(content));
0267                 strdata = lldb.SBData.CreateDataFromCString(self._endianness, self._pointer_size, content)
0268                 var = self.valobj.CreateValueFromData(name, strdata, char_arr_type)
0269             except:
0270                 pass
0271 
0272         cache = self._hiddens if hidden else self._members
0273         cache.append(var)