xref: /xnu-10002.81.5/tools/lldbmacros/kmemory/btlog.py (revision 5e3eaea39dcf651e66cb99ba7d70e32cc4a99587)
1""" Some of the functionality here is a copy of existing macros from memory.py
2    with the VM specific stuff removed so the macros work with any btlog.
3    Eventually the zstack macros should be refactored to reuse the generic btlog
4    support added here.
5"""
6from __future__ import absolute_import, division, print_function
7
8from builtins import chr
9from builtins import hex
10from builtins import object
11from builtins import range
12
13import struct
14import sys
15
16from collections import namedtuple
17from core import caching, xnu_format
18
19# FIXME: should not import this from xnu
20from xnu import GetSourceInformationForAddress
21
22def _swap32(i):
23    return struct.unpack("<I", struct.pack(">I", i))[0]
24
25def _hash_ptr(ptr):
26    h  = ptr >> 4
27    h *= 0x5052acdb
28    h &= 0xffffffff
29    return (h ^ _swap32(h)) & 0xffffffff
30
31
32class BTStack(object):
33    """
34    Helper class to represent a backtrace in a library
35    """
36
37    BTS_REF_MASK   = 0x3fffffc0
38    BTS_FRAMES_MAX = 13
39
40    def __init__(self, library, ref):
41        ref &= BTStack.BTS_REF_MASK
42        slab = 0
43
44        while ref > (BTLibrary.SIZE_INIT << slab):
45            slab += 1
46
47        target   = library.target
48        slab     = library.sbv.chkGetChildMemberWithName('btl_slabs').xGetIntegerAtIndex(slab)
49        value    = target.xCreateValueFromAddress('stack', slab + ref, library.bts_type)
50        target.xReadBytes(value.GetLoadAddress(), value.GetByteSize())
51
52        self.sbv = value
53        self.bts_ref = ref
54        self.stext = library.stext
55
56    def __len__(self):
57        return self.sbv.xGetIntegerByName('bts_ref_len') & 0xf
58
59    @property
60    def bts_len(self):
61        return len(self)
62
63    @property
64    def refcount(self):
65        return self.sbv.xGetIntegerByName('bts_ref_len') >> 4
66
67    @property
68    def bts_hash(self):
69        return self.sbv.xGetIntegerByName('bts_hash')
70
71    @property
72    def bts_next(self):
73        return self.sbv.xGetIntegerByPath('.bts_next.__smr_ptr')
74
75    @property
76    def next_free(self):
77        return self.sbv.xGetIntegerByName('bts_free_next')
78
79    @property
80    def frames(self):
81        target = self.sbv.target
82        addr   = self.sbv.xGetLoadAddressByName('bts_frames')
83        stext  = self.stext
84        return (stext + offs for offs in target.xIterAsInt32(addr, len(self)))
85
86    def symbolicated_frames(self, prefix=" "):
87        return (prefix + GetSourceInformationForAddress(pc) for pc in self.frames)
88
89    def describe(self, verbose=False):
90        fmt  = (
91            "BTStack Info\n"
92            " address              : {&v:#x}\n"
93            " btref                : {0.bts_ref:#x}\n"
94            " refcount             : {0.refcount}\n"
95            " hash                 : {0.bts_hash:#010x}\n"
96            " next                 : {0.bts_next:#x}\n"
97            " backtrace"
98        )
99        print(xnu_format(fmt, self, v=self.sbv))
100
101        print(*self.symbolicated_frames(prefix="  "), sep="\n")
102        print()
103
104
105class BTLibrary(object):
106    """
107    Helper class to wrap backtrace libraries
108    """
109
110    PERMANENT      = 0x80000000
111    SIZE_INIT      = 1 << 20
112    BTL_HASH_COUNT = 256
113    BTL_SLABS      = 9
114
115    def __init__(self, target):
116        self.target = target
117
118        #
119        # Remember the types we will keep working with all the time
120        #
121        self.bts_type      = target.chkFindFirstType('union bt_stack')
122        self.btl_type      = target.chkFindFirstType('struct btlog')
123        self.btll_type     = target.chkFindFirstType('struct btlog_log')
124        self.btlh_type     = target.chkFindFirstType('struct btlog_hash')
125        self.bth_head_type = target.chkFindFirstType('struct bt_hash_head')
126
127        self.sbv           = target.chkFindFirstGlobalVariable('bt_library')
128        self.stext         = target.chkFindFirstGlobalVariable('vm_kernel_stext').xGetValueAsInteger()
129        target.xReadBytes(self.sbv.GetLoadAddress(), self.sbv.GetByteSize())
130
131    @staticmethod
132    @caching.cache_statically
133    def get_shared(target=None):
134        """ Returns a shared instance of the class """
135
136        return BTLibrary(target)
137
138    def __len__(self):
139        return self.sbv.xGetIntegerByName('btl_alloc_pos') // self.bts_type.GetByteSize()
140
141    @property
142    def size(self):
143        return len(self)
144
145    @property
146    def param(self):
147        return self.sbv.xGetIntegerByPath('.btl_param.__smr_ptr')
148
149    @property
150    def shift(self):
151        return 32 - (self.param & 0x3f)
152
153    @property
154    def parity(self):
155        return self.param >> 31
156
157    @property
158    def buckets(self):
159        return 1 << self.shift
160
161    @property
162    def hash_address(self):
163        path = ".btl_hash[{}]".format(self.parity)
164        return self.sbv.xGetScalarByPath(path)
165
166    @property
167    def free_head(self):
168        return self.sbv.xGetIntegerByName('btl_free_head')
169
170    def btlog_from_address(self, address):
171        return BTLog(self.sbv.xCreateValueFromAddress(
172            'btlog', address, self.btl_type))
173
174    def get_stack(self, ref):
175        return BTStack(self, ref)
176
177    def describe(self):
178        fmt = (
179            "BTLibrary Info\n"
180            " buckets              : {0.buckets}\n"
181            " stacks               : {0.size}\n"
182            " parity               : {0.parity}\n"
183            " shift                : {0.shift}\n"
184            " hash address         : {0.hash_address:#x}\n"
185        )
186        print(xnu_format(fmt, self))
187
188
189class BTLogEntry(namedtuple('BTLogEntry', ['index', 'address', 'op', 'ref'])):
190    """ Represents a btlog entry """
191
192    @classmethod
193    def _make(cls, pos, value, key_addr, key_where):
194        addr_mask = (1 << 8 * value.target.GetAddressByteSize()) - 1
195        op_mask   = BTLog.OP_MASK
196        addr      = value.xGetIntegerByName(key_addr)
197        where     = value.xGetIntegerByName(key_where)
198        if addr == 0:
199            return cls(pos, 0, 0, 0)
200        return cls(pos, addr ^ addr_mask, where & op_mask, where & ~op_mask)
201
202    def matches(self, desiredAddress, desiredRef):
203        if desiredAddress is not None and self.address != desiredAddress:
204            return False
205        if desiredRef is not None and self.ref != desiredRef:
206            return False
207        return self.address != 0
208
209
210class BTLog(object):
211    """
212    Helper class to abstract backtrace logs
213    """
214
215    OP_MASK = 0x3f
216    END     = 0xffffffff
217
218    def __init__(self, orig_value):
219        value = orig_value.chkDereference() if orig_value.TypeIsPointerType() else orig_value
220
221        if value.GetType() != BTLibrary.get_shared().btl_type:
222            raise TypeError("Argument is of unexpected type {}".format(
223                orig_value.GetType().GetDisplayTypeName()))
224
225        type_v = value.chkGetChildMemberWithName('btl_type')
226
227        self._index = None
228        self.sbv = value
229        self.btl_type = next((
230            e.GetName()[len('BTLOG_'):]
231            for e in type_v.GetType().get_enum_members_array()
232            if e.GetValueAsUnsigned() == type_v.GetValueAsUnsigned()
233        ), "???")
234
235    @property
236    def address(self):
237        return self.sbv.GetLoadAddress()
238
239    @property
240    def btl_count(self):
241        return self.sbv.xGetIntegerByName('btl_count')
242
243    def is_log(self):
244        return self.btl_type == 'LOG'
245
246    def is_hash(self):
247        return self.btl_type == 'HASH'
248
249    def index(self):
250        if self._index is None:
251            d = {}
252            for i, (_, _, op, ref) in enumerate(self.iter_records()):
253                if i % 1000 == 0:
254                    sys.stderr.write("Indexing {:d}\r".format(i))
255
256                d_ref = d.setdefault(ref, {})
257                d_ref[op] = d_ref.get(op, 0) + 1
258
259            self._index = list(
260                (ref, op, v)
261                for ref, d_ref in d.items()
262                for op, v in d_ref.items()
263            )
264
265        return self._index
266
267    def _iter_log_records(self, wantElement, wantBtref, reverse):
268        target = self.sbv.target
269        count  = self.btl_count
270
271        btll   = self.sbv.chkCast(BTLibrary.get_shared().btll_type)
272        pos    = btll.xGetIntegerByName('btll_pos')
273        arr    = btll.chkGetValueForExpressionPath('.btll_entries[0]')
274
275        base   = arr.GetLoadAddress()
276        ty     = arr.GetType()
277        tysz   = ty.GetByteSize()
278
279        target.xReadBytes(arr.GetLoadAddress(), count * tysz)
280
281        if reverse:
282            indexes = (
283                i if i < count else i - count
284                for i in range(pos, pos + count).__reversed__()
285            )
286        else:
287            indexes = (
288                i if i < count else i - count
289                for i in range(pos, pos + count)
290            )
291
292        entries = (
293            BTLogEntry._make(
294                idx,
295                arr.chkCreateValueFromAddress('e', base + idx * tysz, ty),
296                'btle_addr',
297                'btle_where'
298            )
299            for idx in indexes
300        )
301
302        return (
303            entry for entry in entries
304            if entry.matches(wantElement, wantBtref)
305        )
306
307    def _iter_hash_records(self, wantElement, wantBtref, reverse):
308        target  = self.sbv.target
309        library = BTLibrary.get_shared()
310        count   = self.btl_count
311
312        btlh    = self.sbv.chkCast(library.btlh_type)
313        e_arr   = btlh.chkGetValueForExpressionPath('.btlh_entries[0]')
314        e_base  = e_arr.GetLoadAddress()
315
316        e_ty    = e_arr.GetType()
317        e_tysz  = e_ty.GetByteSize()
318        h_ty    = library.bth_head_type
319        h_tysz  = h_ty.GetByteSize()
320
321        h_mask  = (count >> 2) - 1
322        h_base  = e_base + count * e_tysz
323
324        if wantElement is None:
325            target.xReadBytes(e_arr.GetLoadAddress(), count * e_tysz + (count >> 2) * h_tysz)
326            heads = (
327                target.xCreateValueFromAddress(
328                    None, h_base + i * h_tysz, h_ty).xGetIntegerByName('bthh_first')
329                for i in range(h_mask + 1)
330            )
331        else:
332            i = _hash_ptr(wantElement) & h_mask
333            heads = (target.xCreateValueFromAddress(
334                None, h_base + i * h_tysz, h_ty).xGetIntegerByName('bthh_first'), )
335
336        for idx in heads:
337            while idx != BTLog.END:
338                elt   = e_arr.chkCreateValueFromAddress(None, e_base + idx * e_tysz, e_ty)
339                entry = BTLogEntry._make(idx, elt, 'bthe_addr', 'bthe_where')
340                if entry.matches(wantElement, wantBtref):
341                    yield entry
342                idx   = elt.xGetIntegerByName('bthe_next')
343
344    def iter_records(self, wantElement=None, wantBtref=None, reverse=False):
345        if self.is_log():
346            return self._iter_log_records(wantElement, wantBtref, reverse)
347        return self._iter_hash_records(wantElement, wantBtref, reverse)
348
349
350__all__ = [
351    BTLibrary.__name__,
352    BTLog.__name__,
353]
354