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