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