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