xref: /xnu-8792.81.2/tools/lldbmacros/btlog.py (revision 19c3b8c28c31cb8130e034cfb5df6bf9ba342d90)
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
7from builtins import range
8from builtins import object
9import struct
10from xnu import *
11
12def _swap32(i):
13    return struct.unpack("<I", struct.pack(">I", i))[0]
14
15def _hash_ptr(ptr):
16    h = unsigned(ptr)
17    h >>= 4
18    h *= 0x5052acdb
19    h &= 0xffffffff
20    return (h ^ _swap32(h)) & 0xffffffff
21
22class BTStack(object):
23    """
24    Helper class to represent a backtrace in a library
25    """
26
27    REF_MASK       = 0x3fffffc0
28
29    def __init__(self, btl, addr):
30        self._btl = btl
31        self._bts = kern.GetValueFromAddress(addr, 'bt_stack_t')
32
33    def __len__(self):
34        return int(self._bts.bts_ref_len) & 0xf
35
36    def get_ref(self):
37        return int(self._bts.bts_ref_len) >> 4
38
39    def address(self):
40        return int(self._bts)
41
42    def hash(self):
43        return int(self._bts.bts_hash)
44
45    def next(self):
46        # work around some weird cvalue.py bug
47        v = cast(addressof(self._bts.bts_next), 'uint32_t *')
48        return int(v[0])
49
50    def smr_seq(self):
51        return int(self._bts.bts_free_seq)
52
53    def next_free(self):
54        return int(self._bts.bts_free_next)
55
56    def frames(self):
57        base = unsigned(kern.GetGlobalVariable('vm_kernel_stext'))
58        for i in range(0, len(self)):
59            yield base + int(cast(self._bts.bts_frames[i], 'int'))
60
61    def symbolicated_frames(self):
62        for pc in self.frames():
63            try:
64                yield str(kern.SymbolicateFromAddress(pc, fullSymbol=True)[0])
65            except:
66                yield '{:<#x} ???>'.format(pc)
67
68class BTLibrary(object):
69    """
70    Helper class to wrap backtrace libraries
71    """
72
73    PERMANENT      = 0x80000000
74    SIZE_INIT      = 1 << 20
75    BTL_HASH_COUNT = 256
76
77    def __init__(self):
78        self._btl = addressof(kern.GetGlobalVariable('bt_library'))
79
80    def get_param(self):
81        # work around some weird cvalue.py bug
82        v = cast(addressof(self._btl.btl_param), 'uint32_t *')
83        return int(v[0])
84
85    def get_shift(self):
86        return self.get_param() & 0x3f
87
88    def get_parity(self):
89        return self.get_param() >> 31
90
91    def get_size(self):
92        return 1 << int(32 - self.get_shift())
93
94    def get_hash(self):
95        return self._btl.btl_hash[self.get_parity()]
96
97    def get_stack(self, ref):
98        ref &= BTStack.REF_MASK
99        if ref == 0:
100            return BTStack(self, 0)
101
102        for slab in range(0, 10):
103            if ref < (BTLibrary.SIZE_INIT << slab):
104                break
105
106        return BTStack(self, int(self._btl.btl_slabs[slab] + ref))
107
108class BTLog(object):
109    """
110    Helper class to abstract backtrace logs
111    """
112
113    OP_MASK = 0x3f
114    END = 0xffffffff
115
116    def __init__(self, addr):
117        self._btl  = kern.GetValueFromAddress(addr, 'struct btlog *')
118        self._cnt  = self._btl.btl_count
119        self._btlh = None
120        self._btll = None
121        self._type = int(self._btl.btl_type)
122        if self._type == GetEnumValue('btlog_type_t', 'BTLOG_LOG'):
123            self._btll = kern.GetValueFromAddress(addr, 'struct btlog_log *')
124        elif self._type == GetEnumValue('btlog_type_t', 'BTLOG_HASH'):
125            self._btlh = kern.GetValueFromAddress(addr, 'struct btlog_hash *')
126        self._index = None
127
128    @header("{:<20s} {:<6s} {:>9s}".format("btlog", "type", "count"))
129    def __str__(self):
130        return "{:<#20x} {:<6s} {:>9d}".format(self._btl, self.get_type_str(), self._cnt)
131
132    def is_log(self):
133        return self._btll is not None
134
135    def is_hash(self):
136        return self._btlh is not None
137
138    def _hash_mask(self):
139        if self.is_hash():
140            return (self._cnt >> 2) - 1
141
142    def _hash_array(self):
143        if self.is_hash():
144            addr = int(addressof(self._btlh.btlh_entries[self._cnt]))
145            return kern.GetValueFromAddress(addr, 'struct bt_hash_head *')
146
147    def address(self):
148        return int(self._btl)
149
150    def get_type_str(self):
151        return GetEnumName('btlog_type_t', self._type, 'BTLOG_')
152
153    def index(self):
154        if self._index is None:
155            d = {}
156            i = 0
157            for _, _, op, ref in self.iter_records():
158                i += 1
159                if i % 1000 == 0:
160                    sys.stderr.write("Indexing {:d}\r".format(i))
161
162                k = (ref, op)
163                if k in d:
164                    d[k] += 1
165                else:
166                    d[k] = 1
167
168            l = []
169            for k in d.keys():
170                l.append((k[0], k[1], d[k]))
171
172            self._index = l
173
174        return self._index
175
176    def _decode(self, addr):
177        addr = int(addr)
178        if addr > 0xffffffff:
179            addr ^= 0xffffffffffffffff
180        else:
181            addr ^= 0xffffffff
182        return addr
183
184    def _make_entry(self, idx, addr, where):
185        addr = self._decode(addr)
186        return (idx, addr, where & BTLog.OP_MASK, where & ~BTLog.OP_MASK)
187
188    def _entry_matches(self, addr, where, wE, wB):
189        if wE is not None and self._decode(addr) != wE:
190            return False
191        if wB is not None and (where & ~BTLog.OP_MASK) != wB:
192            return False
193        return True
194
195    def _records_indices(self, reverse=False):
196        step = -1 if reverse else 1
197        count = step * self._cnt
198        return ((int(self._btll.btll_pos) + i) % self._cnt for i in range(0, count, step))
199
200    def iter_records(self, wantElement=None, wantBtref=None, reverse=False):
201        if self.is_log():
202            if int(self._btll.btll_pos):
203                for i in self._records_indices(reverse=reverse):
204                    e = self._btll.btll_entries[i]
205                    if e.btle_addr and self._entry_matches(e.btle_addr, e.btle_where, wantElement, wantBtref):
206                        yield self._make_entry(i, e.btle_addr, e.btle_where)
207
208        elif self.is_hash():
209            mask = self._hash_mask()
210            arr  = self._hash_array()
211
212            if wantElement is None:
213                r = range(0, mask + 1)
214            else:
215                r = [_hash_ptr(int(wantElement)) & mask]
216
217            for i in r:
218                idx = int(arr[i].bthh_first)
219                while idx != BTLog.END:
220                    e   = self._btlh.btlh_entries[idx]
221                    idx = int(e.bthe_next)
222                    if self._entry_matches(e.bthe_addr, e.bthe_where, wantElement, wantBtref):
223                        yield self._make_entry(idx, e.bthe_addr, e.bthe_where)
224
225@lldb_command('showbtref', "A", fancy=True)
226def ShowBTRef(cmd_args=None, cmd_options={}, O=None):
227    """ Show a backtrace ref
228
229        usage: showbtref [-A] <ref...>
230
231            -A    arguments are raw addresses and not references
232    """
233
234    btl = BTLibrary()
235
236    for arg in cmd_args:
237        arg = int(kern.GetValueFromAddress(arg))
238        if "-A" in cmd_options:
239            bts = BTStack(btl, arg)
240        else:
241            bts = btl.get_stack(arg)
242        print("Stack {:#x} {:#20x} (len: {:d}, ref: {:d}, hash:{:#10x}, next:{:#x})".format(
243                arg, bts.address(), len(bts), bts.get_ref(), bts.hash(), bts.next()))
244
245        for s in bts.symbolicated_frames():
246            print(s)
247
248@lldb_command('_showbtlibrary', fancy=True)
249def ShowBTLibrary(cmd_args=None, cmd_options={}, O=None):
250    """ Dump the entire bt library (debugging tool for the bt library itself)
251
252        usage: showbtlibrary
253    """
254    btl = BTLibrary()
255    bth = btl.get_hash()
256
257    print("library {:#x}, buckets {:d}, stacks {:d}, parity {:d}, shift {:d}".format(
258            btl._btl, btl.get_size(), btl._btl.btl_alloc_pos // 64,
259            btl.get_parity(), btl.get_shift()))
260
261    hdr = "{:<12s} {:<12s} {:<12s} {:>3s}  {:>5s}  {:<20s}".format("ref", "hash", "next", "len", "ref", "stack")
262    hdr2 = hdr + "  {:<20s}".format("smr seq")
263
264    with O.table("{:<20s} {:>6s} {:>6s}".format("hash", "idx", "slot")):
265        for i in range(0, btl.get_size()):
266            for j in range(0, BTLibrary.BTL_HASH_COUNT):
267                ref = int(bth[i].bth_array[j].__smr_ptr)
268                if ref == 0: continue
269
270                print(O.format("{:<#20x} {:6d} {:6d}", bth[i], i, j))
271
272                with O.table(hdr, indent=True):
273                    while ref:
274                        bts = btl.get_stack(ref)
275                        err = ""
276                        if (bts.hash() & 0xff) != j:
277                            err = O.format(" {VT.DarkRed}wrong slot{VT.Default}")
278                        if (bts.hash() >> btl.get_shift()) != i:
279                            err += O.format(" {VT.DarkRed}wrong bucket{VT.Default}")
280
281                        print(O.format("{:#010x}   {:#010x}   {:#010x}   {:>3d}  {:>5d}  {:<#20x}{:s}",
282                            ref, bts.hash(), bts.next(), len(bts), bts.get_ref(), bts.address(), err))
283                        ref = bts.next()
284
285        print("freelist")
286        ref = int(btl._btl.btl_free_head)
287        with O.table(hdr2, indent=True):
288            while ref:
289                bts = btl.get_stack(ref)
290                print(O.format("{:#010x}   {:#010x}   {:#010x}   {:>3d}  {:>5d}  {:<#20x}  {:<#20x}",
291                    ref, bts.hash(), bts.next(), len(bts), bts.get_ref(), bts.address(), bts.smr_seq()))
292                ref = bts.next_free()
293
294@lldb_command('showbtlog', fancy=True)
295def ShowBTLog(cmd_args=None, cmd_options={}, O=None):
296    """ Display a summary of the specified btlog
297        Usage: showbtlog <btlog address>
298    """
299
300    if not cmd_args:
301        return O.error('missing btlog address argument')
302
303    with O.table(BTLog.__str__.header):
304        print(BTLog(cmd_args[0]))
305
306@lldb_command('showbtlogrecords', 'B:E:C:FR', fancy=True)
307def ShowBTLogRecords(cmd_args=None, cmd_options={}, O=None):
308    """ Print all records in the btlog from head to tail.
309
310        Usage: showbtlogrecords <btlog addr> [-B <btref>] [-E <addr>] [-F]
311
312            -B <btref>      limit output to elements with backtrace <ref>
313            -E <addr>       limit output to elements with address <addr>
314            -C <num>        number of elements to show
315            -F              show full backtraces
316            -R              reverse order
317    """
318
319    if not cmd_args:
320        return O.error('missing btlog argument')
321
322    if "-B" in cmd_options:
323        btref = int(kern.GetValueFromAddress(cmd_options["-B"]))
324    else:
325        btref = None
326
327    if "-E" in cmd_options:
328        element = kern.GetValueFromAddress(cmd_options["-E"])
329    else:
330        element = None
331
332    if "-C" in cmd_options:
333        count = kern.GetValueFromAddress(cmd_options["-C"])
334    else:
335        count = None
336
337    reversed = "-R" in cmd_options
338
339    btlib = BTLibrary()
340    btlog = BTLog(cmd_args[0])
341
342    with O.table("{:<10s}  {:<20s} {:>3s}  {:<10s}".format("idx", "element", "OP", "backtrace")):
343        for idx, addr, op, ref in btlog.iter_records(wantElement=element, wantBtref=btref, reverse=reversed):
344            print(O.format("{:<10d}  {:<#20x} {:>3d}  {:#010x}", idx, addr, op, ref))
345            if "-F" in cmd_options:
346                for s in btlib.get_stack(ref).symbolicated_frames():
347                    print(O.format("    {:s}", s))
348            if count:
349                count -= 1
350                if count == 0:
351                    break
352