1""" 2A basic caching module for xnu debug macros to use. 3 4 5When to use caching? 6~~~~~~~~~~~~~~~~~~~~ 7 8Very often you do not need to: LLDB already provides extensive data caching. 9 10The most common things that need caching are: 11- types (the gettype() function provides this) 12- globals (kern.globals / kern.GetGlobalVariable() provides this) 13 14 15If your macro is slow to get some data, before slapping a caching decorator, 16please profile your code using `xnudebug profile`. Very often slowness happens 17due to the usage of CreateValueFromExpression() which spins a full compiler 18to parse relatively trivial expressions and is easily 10-100x as slow as 19alternatives like CreateValueFromAddress(). 20 21Only use caching once you have eliminated those obvious performance hogs 22and an A/B shows meaningful speed improvements over the base line. 23 24 25I really need caching how does it work? 26~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 27 28This module provides function decorators to easily cache the result of 29functions based on their parameters, while keeping the caches separated 30per lldb target. 31 32@cache_statically can be used to cache data once per target, 33because it won't change if the process is resumed and stopped again. 34For example: types, constant values, global addresses, ... 35 36@cache_dynamically can be used to cache data that is expensive to compute 37but which content depends on the state of the process. It will be 38automatically invalidated for you if the process is resumed and 39then stops or hits a breakpoint. 40 41@dyn_cached_property can be used on instances to turn a member into 42a per-target cached dynamic property that will be cleaned up when 43the object dies or if the process is resumed and then stops 44or hits a breakpoint. 45 46Functions using these decorators, must have a `target=None` named argument 47that no caller of those functions need to pass explicitly, the decorator 48will take care of passing the proper current target value (equivalent to 49LazyTarget.GetTarget() except more efficiently). 50 51 52Cache Invalidation 53~~~~~~~~~~~~~~~~~~ 54 55This module make the crucial assumption that no XNU lldb macro 56will step/resume/... processes as the invalidation only happens 57when a new command is being run. 58 59If some macro needs to step/resume it has to provide its own 60cache invalidation, by e.g. pushing its own ImplicitContext() 61around such process state manipulations. 62""" 63 64# Private Routines and objects 65from collections import namedtuple 66 67import functools 68import gc 69import inspect 70import sys 71import weakref 72 73import lldb 74 75from .configuration import config 76from . import lldbwrap 77 78 79class _Registry(list): 80 """ Private class that holds a list of _Cache instances """ 81 82 __slots__ = ('name') 83 84 def __init__(self, name): 85 super(_Registry, self).__init__() 86 self.name = name 87 88 @property 89 def size(self): 90 return sys.getsizeof(self) 91 92 def invalidate(self, pid): 93 for cache in self: 94 cache.invalidate(pid) 95 96 def clear(self): 97 for cache in self: 98 cache.clear() 99 100 def __repr__(self): 101 return "_Registry({}, {})".format( 102 self.name, super(_Registry, self).__repr__()) 103 104 def __str__(self): 105 return "_Registry({}, {} caches, size {})".format( 106 self.name, len(self), self.size) 107 108 109class _Cache(dict): 110 """ Private class that implements a given global function _Cache 111 112 Those are created with the @cache_statically/@cache_dynamically 113 decorators 114 """ 115 116 __slots__ = ('name') 117 118 def __init__(self, name, registry): 119 super(_Cache, self).__init__() 120 self.name = name 121 registry.append(self) 122 123 @property 124 def size(self): 125 return sys.getsizeof(self) 126 127 def invalidate(self, pid): 128 if pid in self: del self[pid] 129 130 def __repr__(self): 131 return "_Cache({}, {})".format( 132 self.name, super(_Cache, self).__repr__()) 133 134 def __str__(self): 135 return "_Cache({}, {} entries, size {})".format( 136 self.name, len(self), self.size) 137 138 139_static_registry = _Registry('static') 140_dynamic_registry = _Registry('dynamic') 141_dynamic_keys = {} 142 143_implicit_target = None 144_implicit_process = None 145_implicit_exe_id = None 146_implicit_dynkey = None 147 148 149class _DynamicKey(object): 150 """ Wraps a process StopID as a key that can be used as caches keys """ 151 152 def __init__(self, exe_id, stop_id): 153 self.exe_id = exe_id 154 self.stop_id = stop_id 155 156 def __hash__(self): 157 return id(self) 158 159 160class LazyTarget(object): 161 """ A common object that lazy-evaluates and caches the lldb.SBTarget 162 and lldb.SBProcess for the current interactive debugging session. 163 """ 164 165 @staticmethod 166 def _CacheUpdateAnchor(exe_id, process): 167 global _dynamic_keys, _dynamic_registry 168 169 stop_id = process.GetStopID() 170 dyn_key = _dynamic_keys.get(exe_id) 171 172 if dyn_key is None: 173 _dynamic_keys[exe_id] = dyn_key = _DynamicKey(exe_id, stop_id) 174 elif dyn_key.stop_id != stop_id: 175 _dynamic_registry.invalidate(exe_id) 176 _dynamic_keys[exe_id] = dyn_key = _DynamicKey(exe_id, stop_id) 177 gc.collect() 178 179 return dyn_key 180 181 @staticmethod 182 def _CacheGC(): 183 global _dynamic_keys, _dynamic_registry, _static_registry 184 185 exe_ids = _dynamic_keys.keys() - set( 186 tg.GetProcess().GetUniqueID() 187 for tg in lldb.debugger 188 ) 189 190 for exe_id in exe_ids: 191 _static_registry.invalidate(exe_id) 192 _dynamic_registry.invalidate(exe_id) 193 del _dynamic_keys[exe_id] 194 195 if len(exe_ids): 196 gc.collect() 197 198 @staticmethod 199 def _CacheGetDynamicKey(): 200 """ Get a _DynamicKey for the most likely current process 201 """ 202 global _implicit_dynkey 203 204 dyn_key = _implicit_dynkey 205 if dyn_key is None: 206 process = lldbwrap.GetProcess() 207 exe_id = process.GetUniqueID() 208 dyn_key = LazyTarget._CacheUpdateAnchor(exe_id, process) 209 210 return dyn_key 211 212 @staticmethod 213 def _CacheClear(): 214 """ remove all cached data. 215 """ 216 global _dynamic_registry, _static_registry, _dynamic_keys 217 218 _dynamic_registry.clear() 219 _static_registry.clear() 220 _dynamic_keys.clear() 221 222 @staticmethod 223 def _CacheSize(): 224 """ Returns number of bytes held in cache. 225 returns: 226 int - size of cache including static and dynamic 227 """ 228 global _dynamic_registry, _static_registry 229 230 return _dynamic_registry.size + _static_registry.size 231 232 233 @staticmethod 234 def GetTarget(): 235 """ Get the SBTarget that is the most likely current target 236 """ 237 global _implicit_target 238 239 return _implicit_target or lldbwrap.GetTarget() 240 241 @staticmethod 242 def GetProcess(): 243 """ Get an SBProcess for the most likely current process 244 """ 245 global _implicit_process 246 247 return _implicit_process or lldbwrap.GetProcess() 248 249 250class ImplicitContext(object): 251 """ This class sets up the implicit target/process 252 being used by the XNu lldb macros system. 253 254 In order for lldb macros to function properly, such a context 255 must be used around code being run, otherwise macros will try 256 to infer it from the current lldb selected target which is 257 incorrect in certain contexts. 258 259 typical usage is: 260 261 with ImplicitContext(thing): 262 # code 263 264 where @c thing is any of an SBExecutionContext, an SBValue, 265 an SBBreakpoint, an SBProcess, or an SBTarget. 266 """ 267 268 __slots__ = ('target', 'process', 'exe_id', 'old_ctx') 269 270 def __init__(self, arg): 271 if isinstance(arg, lldb.SBExecutionContext): 272 exe_ctx = lldbwrap.SBExecutionContext(arg) 273 target = exe_ctx.GetTarget() 274 process = exe_ctx.GetProcess() 275 elif isinstance(arg, lldb.SBValue): 276 target = lldbwrap.SBTarget(arg.GetTarget()) 277 process = target.GetProcess() 278 elif isinstance(arg, lldb.SBBreakpoint): 279 bpoint = lldbwrap.SBBreakpoint(arg) 280 target = bpoint.GetTarget() 281 process = target.GetProcess() 282 elif isinstance(arg, lldb.SBProcess): 283 process = lldbwrap.SBProcess(arg) 284 target = process.GetTarget() 285 elif isinstance(arg, lldb.SBTarget): 286 target = lldbwrap.SBTarget(arg) 287 process = target.GetProcess() 288 else: 289 raise TypeError("argument type unsupported {}".format( 290 arg.__class__.__name__)) 291 292 self.target = target 293 self.process = process 294 self.exe_id = process.GetUniqueID() 295 self.old_ctx = None 296 297 def __enter__(self): 298 global _implicit_target, _implicit_process, _implicit_exe_id 299 global _implicit_dynkey, _dynamic_keys 300 301 self.old_ctx = (_implicit_target, _implicit_process, _implicit_exe_id) 302 303 _implicit_target = self.target 304 _implicit_process = process = self.process 305 _implicit_exe_id = exe_id = self.exe_id 306 _implicit_dynkey = LazyTarget._CacheUpdateAnchor(exe_id, process) 307 308 if len(_dynamic_keys) > 1: 309 LazyTarget._CacheGC() 310 311 def __exit__(self, *args): 312 global _implicit_target, _implicit_process, _implicit_exe_id 313 global _implicit_dynkey, _dynamic_keys 314 315 target, process, exe_id = self.old_ctx 316 self.old_ctx = None 317 318 _implicit_target = target 319 _implicit_process = process 320 _implicit_exe_id = exe_id 321 322 if process: 323 _implicit_dynkey = LazyTarget._CacheUpdateAnchor(exe_id, process) 324 else: 325 _implicit_dynkey = None 326 327 328class _HashedSeq(list): 329 """ This class guarantees that hash() will be called no more than once 330 per element. This is important because the lru_cache() will hash 331 the key multiple times on a cache miss. 332 333 Inspired by python3's lru_cache decorator implementation 334 """ 335 336 __slots__ = 'hashvalue' 337 338 def __init__(self, tup, hash=hash): 339 self[:] = tup 340 self.hashvalue = hash(tup) 341 342 def __hash__(self): 343 return self.hashvalue 344 345 @classmethod 346 def make_key(cls, args, kwds, kwd_mark = (object(),), 347 fasttypes = {int, str}, tuple=tuple, type=type, len=len): 348 349 """ Inspired from python3's cache implementation """ 350 351 key = args 352 if kwds: 353 key += kwd_mark 354 key += tuple(kwd.items()) 355 elif len(key) == 0: 356 return None 357 elif len(key) == 1 and type(key[0]) in fasttypes: 358 return key[0] 359 return cls(key) 360 361 362def _cache_with_registry(fn, registry, maxsize=128, sentinel=object()): 363 """ Internal function """ 364 365 nokey = False 366 367 if hasattr(inspect, 'signature'): # PY3 368 sig = inspect.signature(fn) 369 tg = sig.parameters.get('target') 370 if not tg or tg.default is not None: 371 raise ValueError("function doesn't have a 'target=None' argument") 372 373 nokey = len(sig.parameters) == 1 374 cache = _Cache(fn.__qualname__, registry) 375 else: 376 spec = inspect.getargspec(fn) 377 try: 378 index = spec.args.index('target') 379 offs = len(spec.args) - len(spec.defaults) 380 if index < offs or spec.defaults[index - offs] is not None: 381 raise ValueError 382 except: 383 raise ValueError("function doesn't have a 'target=None' argument") 384 385 nokey = len(spec.args) == 1 and spec.varargs is None and spec.keywords is None 386 cache = _Cache(fn.__name__, registry) 387 388 c_setdef = cache.setdefault 389 c_get = cache.get 390 make_key = _HashedSeq.make_key 391 getdynk = LazyTarget._CacheGetDynamicKey 392 gettg = LazyTarget.GetTarget 393 394 if nokey: 395 def caching_wrapper(*args, **kwds): 396 global _implicit_exe_id, _implicit_target 397 398 key = _implicit_exe_id or getdynk().exe_id 399 result = c_get(key, sentinel) 400 if result is not sentinel: 401 return result 402 403 kwds['target'] = _implicit_target or gettg() 404 return c_setdef(key, fn(*args, **kwds)) 405 406 def cached(*args, **kwds): 407 global _implicit_exe_id 408 409 return c_get(_implicit_exe_id or getdynk().exe_id, sentinel) != sentinel 410 else: 411 def caching_wrapper(*args, **kwds): 412 global _implicit_exe_id, _implicit_target 413 414 tg_d = c_setdef(_implicit_exe_id or getdynk().exe_id, {}) 415 c_key = make_key(args, kwds) 416 result = tg_d.get(c_key, sentinel) 417 if result is not sentinel: 418 return result 419 420 # 421 # Blunt policy to avoid exploding memory, 422 # that is simpler than an actual LRU. 423 # 424 # TODO: be smarter? 425 # 426 if len(tg_d) >= maxsize: tg_d.clear() 427 428 kwds['target'] = _implicit_target or gettg() 429 return tg_d.setdefault(c_key, fn(*args, **kwds)) 430 431 def cached(*args, **kwds): 432 global _implicit_exe_id 433 434 tg_d = c_get(_implicit_exe_id or getdynk().exe_id) 435 return tg_d and tg_d.get(make_key(args, kwds), sentinel) != sentinel 436 437 caching_wrapper.cached = cached 438 return functools.update_wrapper(caching_wrapper, fn) 439 440 441def cache_statically(fn): 442 """ Decorator to cache the results statically 443 444 This basically makes the decorated function cache its result based 445 on its arguments with an automatic static per target cache 446 447 The function must have a named parameter called 'target' defaulting 448 to None, with no clients ever passing it explicitly. It will be 449 passed the proper SBTarget when called. 450 451 @cache_statically(user_function) 452 Cache the results of this function automatically per target, 453 using the arguments of the function as the cache key. 454 """ 455 456 return _cache_with_registry(fn, _static_registry) 457 458 459def cache_dynamically(fn): 460 """ Decorator to cache the results dynamically 461 462 This basically makes the decorated function cache its result based 463 on its arguments with an automatic dynamic cache that is reset 464 every time the process state changes 465 466 The function must have a named parameter called 'target' defaulting 467 to None, with no clients ever passing it explicitly. It will be 468 passed the proper SBTarget when called. 469 470 @cache_dynamically(user_function) 471 Cache the results of this function automatically per target, 472 using the arguments of the function as the cache key. 473 """ 474 475 return _cache_with_registry(fn, _dynamic_registry) 476 477 478def dyn_cached_property(fn, sentinel=object()): 479 """ Decorator to make a class or method property cached per instance 480 481 The method must have the prototype: 482 483 def foo(self, target=None) 484 485 and will generate the property "foo". 486 """ 487 488 if hasattr(inspect, 'signature'): # PY3 489 if list(inspect.signature(fn).parameters) != ['self', 'target']: 490 raise ValueError("function signature must be (self, target=None)") 491 else: 492 spec = inspect.getargspec(fn) 493 if spec.args != ['self', 'target'] or \ 494 spec.varargs is not None or spec.keywords is not None: 495 raise ValueError("function signature must be (self, target=None)") 496 497 getdynk = LazyTarget._CacheGetDynamicKey 498 gettg = LazyTarget.GetTarget 499 c_attr = "_dyn_key__" + fn.__name__ 500 501 def dyn_cached_property_wrapper(self, target=None): 502 global _implicit_dynkey, _implicit_target 503 504 cache = getattr(self, c_attr, None) 505 if cache is None: 506 cache = weakref.WeakKeyDictionary() 507 setattr(self, c_attr, cache) 508 509 c_key = _implicit_dynkey or getdynk() 510 result = cache.get(c_key, sentinel) 511 if result is not sentinel: 512 return result 513 514 return cache.setdefault(c_key, fn(self, _implicit_target or gettg())) 515 516 return property(functools.update_wrapper(dyn_cached_property_wrapper, fn)) 517 518 519ClearAllCache = LazyTarget._CacheClear 520 521GetSizeOfCache = LazyTarget._CacheSize 522 523__all__ = [ 524 LazyTarget.__name__, 525 ImplicitContext.__name__, 526 527 cache_statically.__name__, 528 cache_dynamically.__name__, 529 dyn_cached_property.__name__, 530 531 ClearAllCache.__name__, 532 GetSizeOfCache.__name__, 533] 534