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