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