xref: /xnu-8020.121.3/tools/lldbmacros/btlog.py (revision fdd8201d7b966f0c3ea610489d29bd841d358941)
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 iter_records(self, wantElement=None, wantBtref=None):
196        if self.is_log():
197            cnt = self._cnt
198            pos = int(self._btll.btll_pos)
199            if pos:
200                for i in range(pos, cnt):
201                    e = self._btll.btll_entries[i]
202                    if e.btle_addr and self._entry_matches(e.btle_addr, e.btle_where, wantElement, wantBtref):
203                        yield self._make_entry(i, e.btle_addr, e.btle_where)
204
205            for i in range(0, pos):
206                e = self._btll.btll_entries[i]
207                if e.btle_addr and self._entry_matches(e.btle_addr, e.btle_where, wantElement, wantBtref):
208                    yield self._make_entry(i, e.btle_addr, e.btle_where)
209
210        elif self.is_hash():
211            mask = self._hash_mask()
212            arr  = self._hash_array()
213
214            if wantElement is None:
215                r = range(0, mask + 1)
216            else:
217                r = [_hash_ptr(int(wantElement)) & mask]
218
219            for i in r:
220                idx = int(arr[i].bthh_first)
221                while idx != BTLog.END:
222                    e   = self._btlh.btlh_entries[idx]
223                    idx = int(e.bthe_next)
224                    if self._entry_matches(e.bthe_addr, e.bthe_where, wantElement, wantBtref):
225                        yield self._make_entry(idx, e.bthe_addr, e.bthe_where)
226
227@lldb_command('showbtref', "A", fancy=True)
228def ShowBTRef(cmd_args=None, cmd_options={}, O=None):
229    """ Show a backtrace ref
230
231        usage: showbtref [-A] <ref...>
232
233            -A    arguments are raw addresses and not references
234    """
235
236    btl = BTLibrary()
237
238    for arg in cmd_args:
239        arg = int(kern.GetValueFromAddress(arg))
240        if "-A" in cmd_options:
241            bts = BTStack(btl, arg)
242        else:
243            bts = btl.get_stack(arg)
244        print("Stack {:#x} {:#20x} (len: {:d}, ref: {:d}, hash:{:#10x}, next:{:#x})".format(
245                arg, bts.address(), len(bts), bts.get_ref(), bts.hash(), bts.next()))
246
247        for s in bts.symbolicated_frames():
248            print(s)
249
250@lldb_command('_showbtlibrary', fancy=True)
251def ShowBTLibrary(cmd_args=None, cmd_options={}, O=None):
252    """ Dump the entire bt library (debugging tool for the bt library itself)
253
254        usage: showbtlibrary
255    """
256    btl = BTLibrary()
257    bth = btl.get_hash()
258
259    print("library {:#x}, buckets {:d}, stacks {:d}, parity {:d}, shift {:d}".format(
260            btl._btl, btl.get_size(), btl._btl.btl_alloc_pos // 64,
261            btl.get_parity(), btl.get_shift()))
262
263    hdr = "{:<12s} {:<12s} {:<12s} {:>3s}  {:>5s}  {:<20s}".format("ref", "hash", "next", "len", "ref", "stack")
264    hdr2 = hdr + "  {:<20s}".format("smr seq")
265
266    with O.table("{:<20s} {:>6s} {:>6s}".format("hash", "idx", "slot")):
267        for i in range(0, btl.get_size()):
268            for j in range(0, BTLibrary.BTL_HASH_COUNT):
269                ref = int(bth[i].bth_array[j].__smr_ptr)
270                if ref == 0: continue
271
272                print(O.format("{:<#20x} {:6d} {:6d}", bth[i], i, j))
273
274                with O.table(hdr, indent=True):
275                    while ref:
276                        bts = btl.get_stack(ref)
277                        err = ""
278                        if (bts.hash() & 0xff) != j:
279                            err = O.format(" {VT.DarkRed}wrong slot{VT.Default}")
280                        if (bts.hash() >> btl.get_shift()) != i:
281                            err += O.format(" {VT.DarkRed}wrong bucket{VT.Default}")
282
283                        print(O.format("{:#010x}   {:#010x}   {:#010x}   {:>3d}  {:>5d}  {:<#20x}{:s}",
284                            ref, bts.hash(), bts.next(), len(bts), bts.get_ref(), bts.address(), err))
285                        ref = bts.next()
286
287        print("freelist")
288        ref = int(btl._btl.btl_free_head)
289        with O.table(hdr2, indent=True):
290            while ref:
291                bts = btl.get_stack(ref)
292                print(O.format("{:#010x}   {:#010x}   {:#010x}   {:>3d}  {:>5d}  {:<#20x}  {:<#20x}",
293                    ref, bts.hash(), bts.next(), len(bts), bts.get_ref(), bts.address(), bts.smr_seq()))
294                ref = bts.next_free()
295
296@lldb_command('showbtlog', fancy=True)
297def ShowBTLog(cmd_args=None, cmd_options={}, O=None):
298    """ Display a summary of the specified btlog
299        Usage: showbtlog <btlog address>
300    """
301
302    with O.table(BTLog.__str__.header):
303        print(BTLog(cmd_args[0]))
304
305@lldb_command('showbtlogrecords', 'B:E:F', fancy=True)
306def ShowBTLogRecords(cmd_args=None, cmd_options={}, O=None):
307    """ Print all records in the btlog from head to tail.
308
309        Usage: showbtlogrecords <btlog addr> [-B <btref>] [-E <addr>] [-F]
310
311            -B <btref>      limit output to elements with backtrace <ref>
312            -E <addr>       limit output to elements with address <addr>
313            -F              show full backtraces
314    """
315
316    if not cmd_args:
317        return O.error('missing btlog argument')
318
319    if "-B" in cmd_options:
320        btref = int(kern.GetValueFromAddress(cmd_options["-B"]))
321    else:
322        btref = None
323
324    if "-E" in cmd_options:
325        element = kern.GetValueFromAddress(cmd_options["-E"])
326    else:
327        element = None
328
329    btlib = BTLibrary()
330    btlog = BTLog(cmd_args[0])
331
332    with O.table("{:<10s}  {:<20s} {:>3s}  {:<10s}".format("idx", "element", "OP", "backtrace")):
333        for idx, addr, op, ref in btlog.iter_records(wantElement=element, wantBtref=btref):
334            print(O.format("{:<10d}  {:<#20x} {:>3d}  {:#010x}", idx, addr, op, ref))
335            if "-F" in cmd_options:
336                for s in btlib.get_stack(ref).symbolicated_frames():
337                    print(O.format("    {:s}", s))
338