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