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