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