1""" 2XNU Collection iterators 3""" 4from .cvalue import gettype 5from .standard import xnu_format 6 7from abc import ABCMeta, abstractmethod, abstractproperty 8 9 10# 11# Note to implementers 12# ~~~~~~~~~~~~~~~~~~~~ 13# 14# Iterators must be named iter_* in accordance with typical python API naming 15# 16# The "root" or "head" of the collection must be passed by reference 17# and not by pointer. 18# 19# Returned elements must be references to the element type, 20# and not pointers. 21# 22# Intrusive data types should ask for an "element type" (as an SBType), 23# and a field name or path, and use SBType.xContainerOfTransform 24# to cache the transform to apply for the entire iteration. 25# 26 27 28def iter_linked_list(head_value, next_field_or_path, 29 first_field_or_path = None): 30 """ 31 Iterates a NULL-terminated linked list. 32 33 Assuming these C types: 34 35 struct container { 36 struct node *list1_head; 37 struct node *list2_head; 38 } 39 40 struct node { 41 struct node *next; 42 } 43 44 and "v" is an SBValue to a `struct container` type, 45 enumerating list1 is: 46 47 iter_linked_list(v, 'next', 'list1_head') 48 49 if "v" is a `struct node *` directly, then the enumeration 50 becomes: 51 52 iter_linked_list(v, 'next') 53 54 55 @param head_value (SBValue) 56 a reference to the list head. 57 58 @param next_field_or_path (str) 59 The name of (or path to if starting with '.') 60 the field linking to the next element. 61 62 @param first_field_or_path (str or None) 63 The name of (or path to if starting with '.') 64 the field from @c head_value holding the pointer 65 to the first element of the list. 66 """ 67 68 if first_field_or_path is None: 69 elt = head_value 70 elif first_field_or_path[0] == '.': 71 elt = head_value.chkGetValueForExpressionPath(first_field_or_path) 72 else: 73 elt = head_value.chkGetChildMemberWithName(first_field_or_path) 74 75 if next_field_or_path[0] == '.': 76 while elt.GetValueAsAddress(): 77 elt = elt.Dereference() 78 yield elt 79 elt = elt.chkGetValueForExpressionPath(next_field_or_path) 80 else: 81 while elt.GetValueAsAddress(): 82 elt = elt.Dereference() 83 yield elt 84 elt = elt.chkGetChildMemberWithName(next_field_or_path) 85 86 87def iter_queue_entries(head_value, elt_type, field_name_or_path, backwards=False): 88 """ 89 Iterate over a queue of entries (<kern/queue.h> method 1) 90 91 @param head_value (SBValue) 92 a reference to the list head (queue_head_t) 93 94 @param elt_type (SBType) 95 The type of the elements on the chain 96 97 @param field_name_or_path (str) 98 The name of (or path to if starting with '.') the field 99 containing the struct mpsc_queue_chain linkage. 100 101 @param backwards (bool) 102 Whether the walk is forward or backwards 103 """ 104 105 stop = head_value.GetLoadAddress() 106 elt = head_value 107 key = 'prev' if backwards else 'next' 108 109 transform = elt_type.xContainerOfTransform(field_name_or_path) 110 111 while True: 112 elt = elt.chkGetChildMemberWithName(key) 113 addr = elt.GetValueAsAddress() 114 if addr == 0 or addr == stop: 115 break 116 elt = elt.Dereference() 117 yield transform(elt) 118 119 120def iter_queue(head_value, elt_type, field_name_or_path, backwards=False, unpack=None): 121 """ 122 Iterate over queue of elements (<kern/queue.h> method 2) 123 124 @param head_value (SBValue) 125 A reference to the list head (queue_head_t) 126 127 @param elt_type (SBType) 128 The type of the elements on the chain 129 130 @param field_name_or_path (str) 131 The name of (or path to if starting with '.') the field 132 containing the struct mpsc_queue_chain linkage. 133 134 @param backwards (bool) 135 Whether the walk is forward or backwards 136 137 @param unpack (Function or None) 138 A function to unpack the pointer or None. 139 """ 140 141 stop = head_value.GetLoadAddress() 142 elt = head_value 143 key = '.prev' if backwards else '.next' 144 addr = elt.xGetScalarByPath(key) 145 if field_name_or_path[0] == '.': 146 path = field_name_or_path + key 147 else: 148 path = "." + field_name_or_path + key 149 150 while True: 151 if unpack is not None: 152 addr = unpack(addr) 153 if addr == 0 or addr == stop: 154 break 155 156 elt = elt.xCreateValueFromAddress('element', addr, elt_type) 157 addr = elt.xGetScalarByPath(path) 158 yield elt 159 160 161def iter_circle_queue(head_value, elt_type, field_name_or_path, backwards=False): 162 """ 163 Iterate over a queue of entries (<kern/circle_queue.h>) 164 165 @param head_value (SBValue) 166 a reference to the list head (circle_queue_head_t) 167 168 @param elt_type (SBType) 169 The type of the elements on the chain 170 171 @param field_name_or_path (str) 172 The name of (or path to if starting with '.') the field 173 containing the struct mpsc_queue_chain linkage. 174 175 @param backwards (bool) 176 Whether the walk is forward or backwards 177 """ 178 179 elt = head_value.chkGetChildMemberWithName('head') 180 stop = elt.GetValueAsAddress() 181 key = 'prev' if backwards else 'next' 182 183 transform = elt_type.xContainerOfTransform(field_name_or_path) 184 185 if stop: 186 if backwards: 187 elt = elt.chkGetValueForExpressionPath('->prev') 188 stop = elt.GetValueAsAddress() 189 190 while True: 191 elt = elt.Dereference() 192 yield transform(elt) 193 194 elt = elt.chkGetChildMemberWithName(key) 195 addr = elt.GetValueAsAddress() 196 if addr == 0 or addr == stop: 197 break 198 199 200def iter_mpsc_queue(head_value, elt_type, field_name_or_path): 201 """ 202 Iterates a struct mpsc_queue_head 203 204 @param head_value (SBValue) 205 A struct mpsc_queue_head value. 206 207 @param elt_type (SBType) 208 The type of the elements on the chain. 209 210 @param field_name_or_path (str) 211 The name of (or path to if starting with '.') the field 212 containing the struct mpsc_queue_chain linkage. 213 214 @returns (generator) 215 An iterator for the MPSC queue. 216 """ 217 218 transform = elt_type.xContainerOfTransform(field_name_or_path) 219 220 return (transform(e) for e in iter_linked_list( 221 head_value, 'mpqc_next', '.mpqh_head.mpqc_next' 222 )) 223 224 225class iter_priority_queue(object): 226 """ 227 Iterates any of the priority queues from <kern/priority_queue.h> 228 229 @param head_value (SBValue) 230 A struct priority_queue* value. 231 232 @param elt_type (SBType) 233 The type of the elements on the chain. 234 235 @param field_name_or_path (str) 236 The name of (or path to if starting with '.') the field 237 containing the struct priority_queue_entry* linkage. 238 239 @returns (generator) 240 An iterator for the priority queue. 241 """ 242 243 def __init__(self, head_value, elt_type, field_name_or_path): 244 self.transform = elt_type.xContainerOfTransform(field_name_or_path) 245 246 self.gen = iter_linked_list(head_value, 'next', 'pq_root') 247 self.queue = [] 248 249 def __iter__(self): 250 return self 251 252 def __next__(self): 253 queue = self.queue 254 elt = next(self.gen, None) 255 256 if elt is None: 257 if not len(queue): 258 raise StopIteration 259 elt = queue.pop() 260 self.gen = iter_linked_list(elt, 'next', 'next') 261 262 addr = elt.xGetScalarByName('child') 263 if addr: 264 queue.append(elt.xCreateValueFromAddress( 265 None, addr & 0xffffffffffffffff, elt.GetType())) 266 267 return self.transform(elt) 268 269def iter_SLIST_HEAD(head_value, link_field): 270 """ Specialized version of iter_linked_list() for <sys/queue.h> SLIST_HEAD """ 271 272 next_path = ".{}.sle_next".format(link_field) 273 return (e for e in iter_linked_list(head_value, next_path, "slh_first")) 274 275def iter_LIST_HEAD(head_value, link_field): 276 """ Specialized version of iter_linked_list() for <sys/queue.h> LIST_HEAD """ 277 278 next_path = ".{}.le_next".format(link_field) 279 return (e for e in iter_linked_list(head_value, next_path, "lh_first")) 280 281def iter_STAILQ_HEAD(head_value, link_field): 282 """ Specialized version of iter_linked_list() for <sys/queue.h> STAILQ_HEAD """ 283 284 next_path = ".{}.stqe_next".format(link_field) 285 return (e for e in iter_linked_list(head_value, next_path, "stqh_first")) 286 287def iter_TAILQ_HEAD(head_value, link_field): 288 """ Specialized version of iter_linked_list() for <sys/queue.h> TAILQ_HEAD """ 289 290 next_path = ".{}.tqe_next".format(link_field) 291 return (e for e in iter_linked_list(head_value, next_path, "tqh_first")) 292 293def iter_SYS_QUEUE_HEAD(head_value, link_field): 294 """ Specialized version of iter_linked_list() for any <sys/queue> *_HEAD """ 295 296 field = head_value.rawGetType().GetFieldAtIndex(0).GetName() 297 next_path = ".{}.{}e_next".format(link_field, field.partition('_')[0]) 298 return (e for e in iter_linked_list(head_value, next_path, field)) 299 300 301class RB_HEAD(object): 302 """ 303 Class providing utilities to manipulate a collection made with RB_HEAD() 304 """ 305 306 def __init__(self, rbh_root_value, link_field, cmp): 307 """ 308 Create an rb-tree wrapper 309 310 @param rbh_root_value (SBValue) 311 A value to an RB_HEAD() field 312 313 @param link_field (str) 314 The name of the RB_ENTRY() field in the elements 315 316 @param cmp (function) 317 The comparison function between a linked element, 318 and a key 319 """ 320 321 self.root_sbv = rbh_root_value.chkGetChildMemberWithName('rbh_root') 322 self.field = link_field 323 self.cmp = cmp 324 self.etype = self.root_sbv.GetType().GetPointeeType() 325 326 def _parent(self, elt): 327 RB_COLOR_MASK = 0x1 328 329 path = "." + self.field + ".rbe_parent" 330 parent = elt.chkGetValueForExpressionPath(path) 331 paddr = parent.GetValueAsAddress() 332 333 if paddr == 0: 334 return None, 0 335 336 if paddr & RB_COLOR_MASK == 0: 337 return parent.Dereference(), paddr 338 339 paddr &= ~RB_COLOR_MASK 340 return parent.xCreateValueFromAddress(None, paddr, self.etype), paddr 341 342 def _sibling(self, elt, rbe_left, rbe_right): 343 344 field = self.field 345 lpath = "." + field + rbe_left 346 rpath = "." + field + rbe_right 347 348 e_r = elt.chkGetValueForExpressionPath(rpath) 349 if e_r.GetValueAsAddress(): 350 # 351 # IF (RB_RIGHT(elm, field)) { 352 # elm = RB_RIGHT(elm, field); 353 # while (RB_LEFT(elm, field)) 354 # elm = RB_LEFT(elm, field); 355 # 356 357 path = "->" + field + rbe_left 358 res = e_r 359 e_l = res.chkGetValueForExpressionPath(path) 360 while e_l.GetValueAsAddress(): 361 res = e_l 362 e_l = res.chkGetValueForExpressionPath(path) 363 364 return res.Dereference() 365 366 eaddr = elt.GetLoadAddress() 367 e_p, paddr = self._parent(elt) 368 369 if paddr: 370 # 371 # IF (RB_GETPARENT(elm) && 372 # (elm == RB_LEFT(RB_GETPARENT(elm), field))) 373 # elm = RB_GETPARENT(elm) 374 # 375 376 if e_p.xGetScalarByPath(lpath) == eaddr: 377 return e_p 378 379 # 380 # WHILE (RB_GETPARENT(elm) && 381 # (elm == RB_RIGHT(RB_GETPARENT(elm), field))) 382 # elm = RB_GETPARENT(elm); 383 # elm = RB_GETPARENT(elm); 384 # 385 386 while paddr: 387 if e_p.xGetScalarByPath(rpath) != eaddr: 388 return e_p 389 390 eaddr = paddr 391 e_p, paddr = self._parent(e_p) 392 393 return None 394 395 def _find(self, key): 396 elt = self.root_sbv 397 f = self.field 398 le = None 399 ge = None 400 401 while elt.GetValueAsAddress(): 402 elt = elt.Dereference() 403 rc = self.cmp(elt, key) 404 if rc == 0: 405 return elt, elt, elt 406 407 if rc < 0: 408 ge = elt 409 elt = elt.chkGetValueForExpressionPath("->" + f + ".rbe_left") 410 else: 411 le = elt 412 elt = elt.chkGetValueForExpressionPath("->" + f + ".rbe_right") 413 414 return le, None, ge 415 416 def _minmax(self, direction): 417 """ Returns the first element in the tree """ 418 419 elt = self.root_sbv 420 res = None 421 path = "->" + self.field + direction 422 423 while elt.GetValueAsAddress(): 424 res = elt 425 elt = elt.chkGetValueForExpressionPath(path) 426 427 return res.Dereference() if res is not None else None 428 429 def find_lt(self, key): 430 """ Find the element smaller than the specified key """ 431 432 elt, _, _ = self._find(key) 433 return self.prev(elt) if elt is not None else None 434 435 def find_le(self, key): 436 """ Find the element smaller or equal to the specified key """ 437 438 elt, _, _ = self._find(key) 439 return elt 440 441 def find(self, key): 442 """ Find the element with the specified key """ 443 444 _, elt, _ = self._find(key) 445 return elt 446 447 def find_ge(self, key): 448 """ Find the element greater or equal to the specified key """ 449 450 _, _, elt = self._find(key) 451 return elt 452 453 def find_gt(self, key): 454 """ Find the element greater than the specified key """ 455 456 _, _, elt = self._find(key) 457 return self.next(elt.Dereference()) if elt is not None else None 458 459 def first(self): 460 """ Returns the first element in the tree """ 461 462 return self._minmax(".rbe_left") 463 464 def last(self): 465 """ Returns the last element in the tree """ 466 467 return self._minmax(".rbe_right") 468 469 def next(self, elt): 470 """ Returns the next element in rbtree order or None """ 471 472 return self._sibling(elt, ".rbe_left", ".rbe_right") 473 474 def prev(self, elt): 475 """ Returns the next element in rbtree order or None """ 476 477 return self._sibling(elt, ".rbe_right", ".rbe_left") 478 479 def iter(self, min_key=None, max_key=None): 480 """ 481 Iterates all elements in this red-black tree 482 with min_key <= key < max_key 483 """ 484 485 e = self.first() if min_key is None else self.find_ge(min_key) 486 487 if max_key is None: 488 while e is not None: 489 yield e 490 e = self.next(e) 491 else: 492 cmp = self.cmp 493 while e is not None and cmp(e, max_key) >= 0: 494 yield e 495 e = self.next(e) 496 497 def __iter__(self): 498 return self.iter() 499 500 501def iter_RB_HEAD(rbh_root_value, link_field): 502 """ Conveniency wrapper for RB_HEAD iteration """ 503 504 return RB_HEAD(rbh_root_value, link_field, None).iter() 505 506 507def iter_smr_queue(head_value, elt_type, field_name_or_path): 508 """ 509 Iterate over an SMR queue of entries (<kern/smr.h>) 510 511 @param head_value (SBValue) 512 a reference to the list head (struct smrq_*_head) 513 514 @param elt_type (SBType) 515 The type of the elements on the chain 516 517 @param field_name_or_path (str) 518 The name of (or path to if starting with '.') the field 519 containing the struct mpsc_queue_chain linkage. 520 """ 521 522 transform = elt_type.xContainerOfTransform(field_name_or_path) 523 524 return (transform(e) for e in iter_linked_list( 525 head_value, '.next.__smr_ptr', '.first.__smr_ptr' 526 )) 527 528class _Hash(object, metaclass=ABCMeta): 529 @abstractproperty 530 def buckets(self): 531 """ 532 Returns the number of buckets in the hash table 533 """ 534 pass 535 536 @abstractproperty 537 def count(self): 538 """ 539 Returns the number of elements in the hash table 540 """ 541 pass 542 543 @abstractproperty 544 def rehashing(self): 545 """ 546 Returns whether the hash is currently rehashing 547 """ 548 pass 549 550 @abstractmethod 551 def iter(self, detailed=False): 552 """ 553 @param detailed (bool) 554 whether to enumerate just elements, or show bucket info too 555 when bucket info is requested, enumeration returns a tuple of: 556 (0, bucket, index_in_bucket, element) 557 """ 558 pass 559 560 def __iter__(self): 561 return self.iter() 562 563 def describe(self): 564 fmt = ( 565 "Hash table info\n" 566 " address : {1:#x}\n" 567 " element count : {0.count}\n" 568 " bucket count : {0.buckets}\n" 569 " rehashing : {0.rehashing}" 570 ) 571 print(xnu_format(fmt, self, self.hash_value.GetLoadAddress())) 572 573 if self.rehashing: 574 print() 575 return 576 577 b_len = {} 578 for _, b_idx, e_idx, _ in self.iter(detailed=True): 579 b_len[b_idx] = e_idx + 1 580 581 stats = {i: 0 for i in range(max(b_len.values()) + 1) } 582 for v in b_len.values(): 583 stats[v] += 1 584 stats[0] = self.buckets - len(b_len) 585 586 fmt = ( 587 " histogram :\n" 588 " {:>4} {:>6} {:>6} {:>6} {:>5}" 589 ) 590 print(xnu_format(fmt, "size", "count", "(cum)", "%", "(cum)")) 591 592 fmt = " {:>4,d} {:>6,d} {:>6,d} {:>6.1%} {:>5.0%}" 593 tot = 0 594 for sz, n in stats.items(): 595 tot += n 596 print(xnu_format(fmt, sz, n, tot, n / self.buckets, tot / self.buckets)) 597 598 599class SMRHash(_Hash): 600 """ 601 Class providing utilities to manipulate SMR hash tables 602 """ 603 604 def __init__(self, hash_value, traits_value): 605 """ 606 Create an smr hash table iterator 607 608 @param hash_value (SBValue) 609 a reference to a struct smr_hash instance. 610 611 @param traits_value (SBValue) 612 a reference to the traits for this hash table 613 """ 614 super().__init__() 615 self.hash_value = hash_value 616 self.traits_value = traits_value 617 618 @property 619 def buckets(self): 620 hash_arr = self.hash_value.xGetScalarByName('smrh_array') 621 return 1 << (64 - ((hash_arr >> 48) & 0x00ff)) 622 623 @property 624 def count(self): 625 return self.hash_value.xGetScalarByName('smrh_count') 626 627 @property 628 def rehashing(self): 629 return self.hash_value.xGetScalarByName('smrh_resizing') 630 631 def iter(self, detailed=False): 632 obj_null = self.traits_value.chkGetChildMemberWithName('smrht_obj_type') 633 obj_ty = obj_null.GetType().GetArrayElementType().GetPointeeType() 634 lnk_offs = self.traits_value.xGetScalarByPath('.smrht.link_offset') 635 hash_arr = self.hash_value.xGetScalarByName('smrh_array') 636 hash_sz = 1 << (64 - ((hash_arr >> 48) & 0x00ff)) 637 hash_arr = obj_null.xCreateValueFromAddress(None, 638 hash_arr | 0xffff000000000000, gettype('struct smrq_slist_head')); 639 640 if detailed: 641 return ( 642 (0, head_idx, e_idx, e.xCreateValueFromAddress(None, e.GetLoadAddress() - lnk_offs, obj_ty)) 643 for head_idx, head in enumerate(hash_arr.xIterSiblings(0, hash_sz)) 644 for e_idx, e in enumerate(iter_linked_list(head, '.next.__smr_ptr', '.first.__smr_ptr')) 645 ) 646 647 return ( 648 e.xCreateValueFromAddress(None, e.GetLoadAddress() - lnk_offs, obj_ty) 649 for head in hash_arr.xIterSiblings(0, hash_sz) 650 for e in iter_linked_list(head, '.next.__smr_ptr', '.first.__smr_ptr') 651 ) 652 653 654class SMRScalableHash(_Hash): 655 656 def __init__(self, hash_value, traits_value): 657 """ 658 Create an smr hash table iterator 659 660 @param hash_value (SBValue) 661 a reference to a struct smr_shash instance. 662 663 @param traits_value (SBValue) 664 a reference to the traits for this hash table 665 """ 666 super().__init__() 667 self.hash_value = hash_value 668 self.traits_value = traits_value 669 670 @property 671 def buckets(self): 672 shift = self.hash_value.xGetScalarByPath('.smrsh_state.curshift') 673 return (0xffffffff >> shift) + 1; 674 675 @property 676 def count(self): 677 sbv = self.hash_value.chkGetChildMemberWithName('smrsh_count') 678 addr = sbv.GetValueAsAddress() | 0x8000000000000000 679 target = sbv.GetTarget() 680 ncpus = target.chkFindFirstGlobalVariable('zpercpu_early_count').xGetValueAsInteger() 681 pg_shift = target.chkFindFirstGlobalVariable('page_shift').xGetValueAsInteger() 682 683 return sum( 684 target.xReadInt64(addr + (cpu << pg_shift)) 685 for cpu in range(ncpus) 686 ) 687 688 @property 689 def rehashing(self): 690 curidx = self.hash_value.xGetScalarByPath('.smrsh_state.curidx'); 691 newidx = self.hash_value.xGetScalarByPath('.smrsh_state.newidx'); 692 return curidx != newidx 693 694 def iter(self, detailed=False): 695 """ 696 @param detailed (bool) 697 whether to enumerate just elements, or show bucket info too 698 when bucket info is requested, enumeration returns a tuple of: 699 (table_index, bucket, index_in_bucket, element) 700 """ 701 702 hash_value = self.hash_value 703 traits_value = self.traits_value 704 705 obj_null = traits_value.chkGetChildMemberWithName('smrht_obj_type') 706 obj_ty = obj_null.GetType().GetArrayElementType().GetPointeeType() 707 lnk_offs = traits_value.xGetScalarByPath('.smrht.link_offset') 708 hashes = [] 709 710 curidx = hash_value.xGetScalarByPath('.smrsh_state.curidx'); 711 newidx = hash_value.xGetScalarByPath('.smrsh_state.newidx'); 712 arrays = hash_value.chkGetChildMemberWithName('smrsh_array') 713 714 array = arrays.chkGetChildAtIndex(curidx) 715 shift = hash_value.xGetScalarByPath('.smrsh_state.curshift') 716 hashes.append((curidx, array.Dereference(), shift)) 717 if newidx != curidx: 718 array = arrays.chkGetChildAtIndex(newidx) 719 shift = hash_value.xGetScalarByPath('.smrsh_state.newshift') 720 hashes.append((newidx, array.Dereference(), shift)) 721 722 seen = set() 723 724 def _iter_smr_shash_bucket(head): 725 addr = head.xGetScalarByName('lck_ptr_bits') 726 tg = head.GetTarget() 727 728 while addr & 1 == 0: 729 addr &= 0xfffffffffffffffe 730 e = head.xCreateValueFromAddress(None, addr - lnk_offs, obj_ty) 731 if addr not in seen: 732 seen.add(addr) 733 yield e 734 addr = tg.xReadULong(addr); 735 736 if detailed: 737 return ( 738 (hash_idx, head_idx, e_idx, e) 739 for hash_idx, hash_arr, hash_shift in hashes 740 for head_idx, head in enumerate(hash_arr.xIterSiblings( 741 0, 1 + (0xffffffff >> hash_shift))) 742 for e_idx, e in enumerate(_iter_smr_shash_bucket(head)) 743 ) 744 745 return ( 746 e 747 for hash_idx, hash_arr, hash_shift in hashes 748 for head in hash_arr.xIterSiblings( 749 0, 1 + (0xffffffff >> hash_shift)) 750 for e in _iter_smr_shash_bucket(head) 751 ) 752 753 754__all__ = [ 755 iter_linked_list.__name__, 756 iter_queue_entries.__name__, 757 iter_queue.__name__, 758 iter_circle_queue.__name__, 759 760 iter_mpsc_queue.__name__, 761 762 iter_priority_queue.__name__, 763 764 iter_SLIST_HEAD.__name__, 765 iter_LIST_HEAD.__name__, 766 iter_STAILQ_HEAD.__name__, 767 iter_TAILQ_HEAD.__name__, 768 iter_SYS_QUEUE_HEAD.__name__, 769 iter_RB_HEAD.__name__, 770 RB_HEAD.__name__, 771 772 iter_smr_queue.__name__, 773 SMRHash.__name__, 774 SMRScalableHash.__name__, 775] 776