1from abc import ( 2 ABCMeta, 3 abstractmethod, 4 abstractproperty, 5) 6import argparse 7import re 8import struct 9from typing import ( 10 Optional, 11) 12 13from core import ( 14 SBValueFormatter, 15 caching, 16 gettype, 17 lldbwrap, 18 value, 19 xnu_format, 20) 21from core.standard import ( 22 ArgumentError, 23) 24from core.kernelcore import ( 25 KernelTarget, 26) 27from core.iterators import ( 28 RB_HEAD, 29) 30 31from .kmem import MemoryRange 32from .btlog import BTLog, BTLibrary 33from .whatis import * 34 35# FIXME: should not import this from xnu / utils 36from pmap import ( 37 PmapWalkARM64, 38 PmapWalkX86_64, 39 KVToPhysARM, 40) 41from utils import ( 42 GetEnumName, 43 print_hex_data, 44) 45from xnu import ( 46 lldb_command, 47) 48 49@SBValueFormatter.converter("vm_prot") 50def vm_prot_converter(prot): 51 PROT_STR = "-rw?x" 52 return PROT_STR[prot & 1] + PROT_STR[prot & 2] + PROT_STR[prot & 4] 53 54 55class Pmap(object, metaclass=ABCMeta): 56 """ Helper class to manipulate a pmap_t""" 57 58 def __new__(cls, pmap: lldbwrap.SBValue, name: Optional[str]=None): 59 target = pmap.GetTarget() 60 arch = target.triple[:target.triple.find('-')] 61 62 if cls is Pmap: 63 if arch.startswith('arm64'): 64 return _PmapARM64(pmap, name) 65 elif arch.startswith('x86_64'): 66 return _PmapX86(pmap, name) 67 else: 68 return None 69 70 return super(Pmap, cls).__new__(cls) 71 72 def __init__(self, pmap: lldbwrap.SBValue, name: Optional[str]=None): 73 self.sbv = pmap 74 self.name = name 75 self.kern = KernelTarget(pmap.GetTarget().GetDebugger()) 76 self.page_size = 4096 77 78 self._last_phytokv_paddr = None 79 self._last_phytokv_result = None 80 81 def describe(self, verbose=False): 82 fmt = ( 83 "Pmap Info\n" 84 " pmap : {&v:#x} \n" 85 ) 86 87 @staticmethod 88 @caching.cache_statically 89 def kernel_pmap(target=None): 90 """ 91 Returns an object for the kernel pmap 92 """ 93 94 pmap = target.FindFirstGlobalVariable('kernel_pmap').Dereference() 95 return Pmap(pmap, 'kernel_pmap') 96 97 def phystokv(self, paddr: int) -> int: 98 base = self.trunc_page(paddr) 99 100 if self._last_phytokv_paddr != base: 101 self._last_phytokv_paddr = base 102 self._last_phytokv_result = self.kern.PhysToKernelVirt(base) 103 104 return self._last_phytokv_result + self.page_offset(paddr) 105 106 def trunc_page(self, addr: int) -> int: 107 return addr & -self.page_size 108 109 def round_page(self, addr: int) -> int: 110 return (addr + self.page_size - 1) & -self.page_size 111 112 def page_offset(self, addr: int) -> int: 113 return addr & (self.page_size - 1) 114 115 @abstractmethod 116 def kvtophys(self, vaddr: int) -> int: 117 """ 118 resolves a kernel virtual address into a physical address 119 """ 120 pass 121 122 @abstractmethod 123 def walk(self, vaddr: int, extra: Optional[dict] = None) -> Optional[int]: 124 """ 125 resolves a virtual address to a physical address for this pmap 126 127 @param vaddr (int) 128 The address to resolve 129 130 @param extra (dict) 131 Extra pmap specific information about the mapping 132 """ 133 134 pass 135 136 def tag_storage(self, vaddr: int) -> (Optional[int], Optional[int], Optional[int]): 137 """ 138 Finds the tag storage parameters for the specified virtual address 139 140 @param vaddr (int) 141 the virtual address to resolve 142 143 @returns (tag_vaddr, tag_paddr, nibble_shift) 144 - tag_vaddr is the virtual address in the PAPT of the ATag storage 145 - tag_paddr is the physical address of the ATag storage 146 - nibble shift is 0xf0 or 0x0f to denote which nibble holds the tag 147 148 (None, None, None) is returned if the virtual address isn't tagged 149 or the memory isn't resident for this map. 150 """ 151 152 return (None, None, None) 153 154 def get_tag(self, vaddr: int) -> Optional[int]: 155 """ 156 Returns the tag for this virtual address or None 157 """ 158 159 return None 160 161 def ldg(self, vaddr: int) -> int: 162 """ 163 Fixes up a virtual address with the proper tag, emulating the arm LDG 164 instruction 165 """ 166 167 tag = self.get_tag(vaddr) 168 if tag is None: 169 return vaddr 170 return (vaddr & 0xf0ffffffffffffff) | (tag << 56) 171 172class _PmapARM64(Pmap): 173 """ 174 Specialization of Pmap for arm64 175 """ 176 177 def __init__(self, pmap: lldbwrap.SBValue, name: Optional[str]=None): 178 super().__init__(pmap, name) 179 180 target = pmap.GetTarget() 181 self.gVirtBase = target.FindFirstGlobalVariable('gVirtBase').xGetValueAsInteger() 182 self.gPhysBase = target.FindFirstGlobalVariable('gPhysBase').xGetValueAsInteger() 183 184 try: 185 self.pt_attr = pmap.chkGetChildMemberWithName('pmap_pt_attr') 186 except: 187 self.pt_attr = target.FindFirstGlobalVariable('native_pt_attr') 188 self.page_size = self.pt_attr.xGetIntegerByName('pta_page_size') 189 190 if target.FindFirstGlobalVariable('gARM_FEAT_MTE').IsValid(): 191 self.has_mte = target.FindFirstGlobalVariable('gARM_FEAT_MTE').xGetValueAsInteger() 192 else: 193 self.has_mte = False 194 195 self._last_walk_vaddr = None 196 self._last_walk_extra = None 197 self._last_walk_result = None 198 199 self._last_kvtophys_vaddr = None 200 self._last_kvtophys_result = None 201 202 def kvtophys(self, vaddr: int) -> int: 203 base = self.trunc_page(vaddr) 204 205 if self._last_kvtophys_vaddr != base: 206 self._last_walk_vaddr = base 207 self._last_walk_result = KVToPhysARM(base) 208 209 return self._last_walk_result + self.page_offset(base) 210 211 def walk(self, vaddr: int, extra: Optional[dict] = None) -> Optional[int]: 212 base = self.trunc_page(vaddr) 213 214 if self._last_walk_vaddr != base: 215 self._last_walk_vaddr = base 216 self._last_walk_extra = {} 217 218 tte = self.sbv.chkGetChildMemberWithName('tte') 219 self._last_walk_result = PmapWalkARM64( 220 value(self.pt_attr), value(tte), base, 221 0, self._last_walk_extra 222 ) 223 224 if extra is not None: 225 extra.update(self._last_walk_extra) 226 if self._last_walk_result: 227 return self._last_walk_result + self.page_offset(vaddr) 228 return None 229 230 @property 231 @caching.cache_statically 232 def tag_coverage_start_phys(self, target=None): 233 """ 234 The physical address of the start of the tag covered region 235 """ 236 return target.FindFirstGlobalVariable('gDramBase').xGetValueAsInteger() 237 238 @property 239 @caching.cache_statically 240 def tag_storage_start_phys(self, target=None): 241 """ 242 The physical address of the start of the tag storage region 243 """ 244 return target.FindFirstGlobalVariable('mte_tag_storage_start').xGetValueAsInteger() 245 246 @property 247 @caching.cache_statically 248 def tag_storage_end_phys(self, target=None): 249 """ 250 The physical address of the end of the tag storage region 251 """ 252 return target.FindFirstGlobalVariable('mte_tag_storage_end').xGetValueAsInteger() 253 254 def is_tagged(self, vaddr: int) -> bool: 255 """ 256 Returns whether the passed in virtual address is tagged 257 """ 258 if not self.has_mte: 259 return False 260 261 extra = {} 262 paddr = self.walk(vaddr, extra) 263 if paddr is None: 264 return False 265 266 return (extra['tte'][-1].value >> 2) & 0x7 == 4 267 268 def tag_storage(self, vaddr: int) -> (Optional[int], Optional[int], Optional[int]): 269 if not self.has_mte: 270 return (None, None, None) 271 272 extra = {} 273 paddr = self.walk(vaddr, extra) 274 if paddr is None: 275 return (None, None, None) 276 277 tte = extra['tte'][-1] 278 if (tte.value >> 2) & 0x7 != 4: 279 return (None, None, None) 280 281 offset = paddr - self.tag_coverage_start_phys 282 tag_paddr = self.tag_storage_start_phys + offset // 32 283 nibble_shift = (offset & 0x10) >> 2 284 return (self.phystokv(tag_paddr), tag_paddr, nibble_shift) 285 286 def get_tag(self, vaddr: int) -> Optional[int]: 287 addr, _, shift = self.tag_storage(vaddr) 288 if addr is not None: 289 return (self.sbv.GetTarget().xReadUInt8(addr) >> shift) & 0xf 290 return None 291 292 293class _PmapX86(Pmap): 294 """ 295 Specialization of Pmap for Intel 296 """ 297 298 def __init__(self, pmap: lldbwrap.SBValue, name: Optional[str]=None): 299 super().__init__(pmap, name) 300 301 target = pmap.GetTarget() 302 self.physmap_base = target.FindFirstGlobalVariable('physmap_base').xGetValueAsInteger() 303 304 @property 305 def page_size(self): 306 return 4096 307 308 def kvtophys(self, vaddr: int) -> int: 309 return vaddr - self.phsmap_base 310 311 def walk(self, vaddr: int, extra: Optional[dict] = None) -> Optional[int]: 312 return PmapWalkX86_64(value(self.sbv), vaddr, 0) 313 314 315class VMMap(object): 316 """ Helper class to manipulate a vm_map_t""" 317 318 def __init__(self, vm_map, name=None): 319 self.sbv = vm_map 320 self.name = name 321 self.rb = RB_HEAD( 322 vm_map.chkGetValueForExpressionPath(".hdr.rb_head_store"), 323 "entry", 324 self.entry_compare 325 ) 326 327 vme_type = gettype('struct vm_map_entry') 328 self.to_entry = vme_type.xContainerOfTransform('store') 329 330 def entry_compare(self, rb_entry, address): 331 vme = self.to_entry(rb_entry) 332 333 if vme.xGetScalarByPath(".links.end") <= address: 334 return 1 335 if address < vme.xGetScalarByPath(".links.start"): 336 return -1 337 return 0 338 339 def find(self, address): 340 ent = self.rb.find(address) 341 return self.to_entry(ent) if ent else None 342 343 def describe(self, verbose=False): 344 fmt = ( 345 "VM Map Info\n" 346 " vm map : {&v:#x} \n" 347 ) 348 if self.name: 349 fmt += ( 350 " vm map name : {m.name:s} \n" 351 ) 352 fmt += ( 353 " pmap : {$v.pmap:#x} \n" 354 " vm size : {$v.size|human_size} ({$v.size:,d} bytes) \n" 355 " entries : {$v.hdr.nentries} \n" 356 " map range : " 357 "{$v.hdr.links.start:#x} - {$v.hdr.links.end:#x}\n" 358 " map pgshift : {$v.hdr.page_shift}\n" 359 ) 360 print(xnu_format(fmt, m=self, v=self.sbv)) 361 362 363class VMMapEntry(MemoryObject): 364 """ Memory Object for a kernel map memory entry """ 365 366 MO_KIND = "kernel map entry" 367 368 def __init__(self, kmem, address, vm_map): 369 super().__init__(kmem, address) 370 self.vm_map = vm_map 371 self.sbv = vm_map.find(address) 372 373 @property 374 def object_range(self): 375 sbv = self.sbv 376 if sbv: 377 return MemoryRange( 378 sbv.xGetScalarByPath('.links.start'), 379 sbv.xGetScalarByPath('.links.end') 380 ) 381 382 base = self.address & ~self.kmem.page_mask 383 return MemoryRange(base, base + self.kmem.page_size) 384 385 @property 386 def vme_offset(self): 387 return self.sbv.xGetScalarByName('vme_offset') << 12 388 389 @property 390 def vme_object_type(self): 391 sbv = self.sbv 392 if sbv.xGetScalarByName('is_sub_map'): 393 return "submap" 394 if sbv.xGetScalarByName('vme_kernel_object'): 395 return "kobject" 396 return "vm object" 397 398 @property 399 def vme_object(self): 400 kmem = self.kmem 401 sbv = self.sbv 402 403 if sbv.xGetScalarByName('is_sub_map'): 404 addr = sbv.xGetScalarByName('vme_submap') << 2 405 return (addr, kmem.vm_map_type) 406 407 if sbv.xGetScalarByName('vme_kernel_object'): 408 return (kmem.vm_kobject.GetLoadAddress(), kmem.vmo_type) 409 410 packed = sbv.xGetScalarByName('vme_object_or_delta') 411 addr = kmem.vm_page_packing.unpack(packed) 412 return (addr, kmem.vmo_type) 413 414 @property 415 def pages(self): 416 return self.object_range.size >> self.kmem.page_shift 417 418 def describe(self, verbose=False): 419 420 self.vm_map.describe() 421 422 if not self.sbv: 423 fmt = ( 424 "Kernel Map Entry Info\n" 425 " No memory mapped at this address\n" 426 ) 427 print(xnu_format(fmt)) 428 return 429 430 fmt = ( 431 "VM Map Entry Info\n" 432 " vm entry : {&v:#x}\n" 433 " start / end : " 434 "{$v.links.start:#x} - {$v.links.end:#x} " 435 "({0.pages:,d} pages)\n" 436 " vm tag : {$v.vme_alias|vm_kern_tag}\n" 437 ) 438 range_id = next(( 439 i 440 for i, r in enumerate(self.kmem.kmem_ranges) 441 if r.contains(self.address) 442 ), None) 443 if range_id: 444 fmt += ( 445 " vm range id : {range_id}\n" 446 ) 447 fmt += ( 448 " protection : " 449 "{$v.protection|vm_prot}/{$v.max_protection|vm_prot}\n" 450 " vm object : " 451 "{0.vme_object_type} ({0.vme_object[0]:#x})\n" 452 " entry offset : {0.vme_offset:#x}\n" 453 ) 454 print(xnu_format(fmt, self, v=self.sbv, range_id=range_id)) 455 456 457@whatis_provider 458class KernelMapWhatisProvider(WhatisProvider): 459 """ 460 Whatis Provider for the kernel map ranges 461 """ 462 463 def claims(self, address): 464 kmem = self.kmem 465 466 return ( 467 any(r.contains(address) for r in kmem.kmem_ranges) 468 or kmem.iokit_range.contains(address) 469 ) 470 471 def lookup(self, address): 472 kmem = self.kmem 473 474 if any(r.contains(address) for r in kmem.kmem_ranges): 475 return VMMapEntry(kmem, address, VMMap(kmem.kernel_map, 'kernel_map')) 476 477 iokit_pageable_map_data = kmem.target.chkFindFirstGlobalVariable('gIOKitPageableMap') 478 iokit_pageable_vm_map = iokit_pageable_map_data.chkGetChildMemberWithName("map").Dereference() 479 return VMMapEntry(kmem, address, VMMap(iokit_pageable_vm_map, "gIOKitPageableMap.map")) 480 481 482@SBValueFormatter.converter("mte_cell_state") 483def mte_cell_state_converter(state): 484 return GetEnumName('cell_state_t', state, 'MTE_STATE_') 485 486class _MTEArgumentParser(argparse.ArgumentParser): 487 def error(self, message): 488 raise ArgumentError(message) 489 490class _MTEHelpFormatter(argparse.HelpFormatter): 491 """ 492 Class used to pretty print help for XNU commands 493 """ 494 495 def __init__(self, prog, **kwargs): 496 kwargs['width'] = 80 497 super(_MTEHelpFormatter, self).__init__(prog, **kwargs) 498 499 self._ws_re = re.compile(r'\s+', re.ASCII) 500 self._p_re = re.compile(r'\n\n+') 501 502 def _reflow(self, text, width, indent): 503 import textwrap 504 505 return textwrap.fill( 506 self._ws_re.sub(' ', text).strip(), 507 width=80, 508 initial_indent=indent, 509 subsequent_indent=indent, 510 ) 511 512 def _fill_text(self, text, width, indent): 513 return "\n\n".join( 514 self._reflow(s, width, indent if i == 0 else indent + " ") 515 for i, s in enumerate(self._p_re.split(text)) 516 ) 517 518class MTECommand(object, metaclass=ABCMeta): 519 """ 520 Inspect and debug MTE related problems 521 """ 522 523 COMMAND = "mte" 524 _sub_cmds = [ 'atag', 'atag-read', 'info', 'ldg' ] 525 526 # 527 # Initialization 528 # 529 530 def __init__(self): 531 cls = self.__class__ 532 533 self.target = None 534 self.cmd_name = cls.COMMAND 535 self.verbosity = 0 536 self.O = None 537 538 self._profile = None 539 self._coverage = None 540 541 opt_parser = _MTEArgumentParser( 542 prog=cls.COMMAND, 543 description=cls.__doc__, 544 formatter_class=_MTEHelpFormatter, 545 exit_on_error=False, 546 ) 547 548 cls.make_opts(opt_parser) 549 550 sub_parser = opt_parser.add_subparsers( 551 title='valid subcommands', 552 dest='subcommand', 553 required=True, 554 ) 555 556 for sub_cmd_name in cls._sub_cmds: 557 fn_name = sub_cmd_name.replace("-", "_") 558 fn = getattr(cls, "cmd_" + fn_name) 559 help = fn.__doc__.strip().split("\n", 1)[0].strip() 560 561 parser = sub_parser.add_parser( 562 sub_cmd_name, 563 help=help, 564 description=fn.__doc__, 565 formatter_class=_MTEHelpFormatter, 566 exit_on_error=False, 567 ) 568 569 gen_parser = getattr(cls, "make_opts_" + fn_name, None) 570 if gen_parser: gen_parser(parser) 571 572 self.opt_parser = opt_parser 573 574 # 575 # Help handling 576 # 577 578 def _print_help(self): 579 self.opt_parser.print_help() 580 print("") 581 582 def get_short_help(self): 583 """ Return a short help for the command (from your class short_help) """ 584 585 return self.__class__.__doc__.strip().split("\n", 1)[0] 586 587 def get_long_help(self): 588 """ Return a long help for the command (generated from the option parser) """ 589 590 return "\n" + self.opt_parser.format_help() 591 592 593 # 594 # Execution 595 # 596 597 def __call__(self, cmd_args, O=None): 598 self.target = caching.LazyTarget.GetTarget() 599 self.O = O 600 601 try: 602 args, argv = self.opt_parser.parse_known_args(cmd_args) 603 604 if argv: 605 raise ArgumentError( 606 f"unrecognized arguments: {' '.join(argv)}" 607 ) 608 except Exception as err: 609 self._print_help() 610 O.resultObj.SetError(str(err)) 611 return 612 613 getattr(self, f"cmd_{args.subcommand.replace('-', '_')}")(args) 614 615 616 # 617 # Helpers to extend option parsing 618 # 619 620 @staticmethod 621 def Int(arg: str) -> int: 622 """ 623 ArgumentParser type converter returning an Int out of lldb expressions 624 """ 625 626 return caching.LazyTarget.GetTarget().chkEvaluateExpression(arg).xGetValueAsInteger() 627 628 Address = Int 629 630 @staticmethod 631 def Value(type: str, structor: Optional = None): 632 """ 633 ArgumentParser type converter returning an SBValue of the specified type 634 """ 635 636 def convert(arg: str): 637 target = caching.LazyTarget.GetTarget() 638 arg = target.chkEvaluateExpression(arg).xGetValueAsInteger() 639 value = target.xCreateValueFromAddress(None, arg, gettype(type)) 640 return value if structor is None else structor(value) 641 642 return convert 643 644 645 # 646 # Actual command implementation 647 # 648 649 @property 650 def mte_cells(self): 651 return self.target.FindFirstGlobalVariable('mte_info_cells').Dereference() 652 653 @property 654 def mte_lists(self): 655 return self.target.FindFirstGlobalVariable('mte_info_lists') 656 657 @property 658 def mte_free_queues(self): 659 return self.target.FindFirstGlobalVariable('mte_free_queues') 660 661 @classmethod 662 def make_opts(cls, parser): 663 parser.add_argument( 664 "-P", "--pmap", 665 type=cls.Value("struct pmap", Pmap), 666 metavar='pmap', 667 default="kernel_pmap", 668 help="The pmap to use to resolve virtual addresses (default: kernel_pmap)", 669 ) 670 671 672 # 673 # mte atag 674 # 675 676 @classmethod 677 def make_opts_atag(cls, parser): 678 parser.add_argument( 679 "address", 680 type=cls.Address, 681 help="The virtual address to print atag information for" 682 ) 683 684 def cmd_atag(self, args): 685 """ 686 Print the MTE tag storage info for a covered virtual address 687 688 Print detailed information about the tag storage "cell" 689 corresponding to a given virtual address 690 """ 691 692 pmap = args.pmap 693 vaddr = args.address 694 695 tag_vaddr, tag_paddr, shift = pmap.tag_storage(vaddr) 696 697 if tag_vaddr is None: 698 print(f"{vaddr:#x} isn't tagged") 699 return 700 701 index = (tag_paddr - pmap.tag_storage_start_phys) >> 14 702 slot = (tag_paddr >> 9) & 0x1f 703 cell = self.mte_cells.xGetSiblingValueAtIndex(index) 704 705 cpaddr = pmap.tag_coverage_start_phys + index * (32 << 14) 706 cvaddr = pmap.phystokv(cpaddr) 707 708 slots = "".join('x' if pmap.is_tagged(cvaddr + (x << 14)) else '.' 709 for x in range(0, 32)) 710 711 print(xnu_format( 712 u"Tag information\n" 713 u" pmap : {&pmap:#x}\n" 714 f" virtual address : {vaddr:#x}\n" 715 f" tag storage address : {tag_paddr:#x}\n" 716 f" covered page index : {slot:d}\n" 717 f" tag storage nibble : {0xf0 >> shift:#04x}\n" 718 f" tag value : {pmap.get_tag(vaddr):#x}\n" 719 u"\n" 720 u"MTE Cell Info\n" 721 u" cell : {&c:#x}\n" 722 f" index : {index}\n" 723 u" state : {$c.state|mte_cell_state}\n" 724 u" free pages : {$c.free_page_count}\n" 725 u" mte slots used : {$c.mte_page_count}\n" 726 u" 1 2 3\n" 727 u" 0....5....0....5....0....5....0.\n" 728 f" mte enabled slots : {slots:s}\n" 729 f" {'':{slot}s}^\n", 730 pmap=pmap.sbv, index=index, c=cell, 731 )) 732 733 # 734 # mte atag-read 735 # 736 737 @classmethod 738 def make_opts_atag_read(cls, parser): 739 parser.add_argument( 740 "begin", 741 type=cls.Address, 742 help="The first virtual address to print tag storage for", 743 ) 744 parser.add_argument( 745 "end", 746 nargs="?", 747 type=cls.Address, 748 help="The last virtual address to printing tag storage for", 749 ) 750 751 def cmd_atag_read(self, args): 752 """ 753 Dump tags for a range of memory 754 755 Dumps the atag space corresponding to the range of addresses 756 from <begin> to <end>. If unspecified, <end> is taken as 512 757 bytes after <begin> in order to dump 32 tags. 758 """ 759 760 pmap = args.pmap 761 start_addr = args.begin 762 end_addr = args.end 763 764 stride = 16 * 32 765 start = start_addr & -stride; 766 marks = {} 767 768 if end_addr is None: 769 end = (start_addr + 2 * stride + stride - 1) & -stride 770 else: 771 end = (end_addr + stride - 1) & -stride 772 773 tag_vaddr, _, _ = pmap.tag_storage(start_addr) 774 if tag_vaddr is not None: 775 marks[tag_vaddr] = '>' 776 777 def print_extra(start): 778 return " {:#x}".format(addr) 779 780 print("{:18s} {:68s} {:18s}".format( 781 "atag address", "tag values", "virtual address")) 782 print("=" * 108) 783 784 for addr in range(start, end, stride): 785 tag_vaddr, _, _ = pmap.tag_storage(addr) 786 787 if addr != start and pmap.page_offset(addr) == 0: 788 print("-" * 108) 789 790 if tag_vaddr is None: 791 print("{:87s} {}".format( 792 "no tag information", print_extra(tag_vaddr))) 793 continue 794 795 try: 796 data = self.target.xReadBytes(tag_vaddr, 16) 797 print_hex_data(data, tag_vaddr, prefix="", marks=marks, extra=print_extra) 798 except: 799 print("{:87s} {}".format( 800 "** unable to read tag memory **", print_extra(tag_vaddr))) 801 802 # 803 # mte info 804 # 805 806 def _make_mask(self, mask, bits): 807 return "".join('x' if mask & (1 << i) else '.' for i in range(0, bits)) 808 809 def _describe_list(self, name, index, has_buckets = False): 810 l = self.mte_lists.chkGetChildAtIndex(index) 811 812 if has_buckets: 813 fmt = " {0:20s} : {$l.count:9,d} [ {counts:s} ]" 814 counts = " ".join( 815 "{:9,d}".format(l.xGetScalarByPath('.buckets[{}].head.cell_count'.format(i))) 816 for i in range(0,5) 817 ) 818 819 else: 820 fmt = " {0:20s} : {$l.count:9,d}" 821 counts = "" 822 823 print(xnu_format(fmt, name, counts=counts, l=l)) 824 return l.xGetIntegerByName('count') 825 826 def _describe_free_queue(self, name, index): 827 fmt = " {0:20s} : {$q.vmpfq_count:9,d}" 828 q = self.mte_free_queues.chkGetChildAtIndex(index) 829 print(xnu_format(fmt, name, q=q)) 830 return q.xGetIntegerByName('vmpfq_count') 831 832 833 def cmd_info(self, args): 834 """ 835 Dumps the state of the MTE info data structure 836 """ 837 838 free = self.target.FindFirstGlobalVariable('vm_page_free_count').xGetValueAsInteger() 839 tagged = self.target.FindFirstGlobalVariable('vm_page_tagged_count').xGetValueAsInteger() 840 taggable = self.target.FindFirstGlobalVariable('vm_page_free_taggable_count').xGetValueAsInteger() 841 ts_wired = self.target.FindFirstGlobalVariable('vm_page_wired_tag_storage_count').xGetValueAsInteger() 842 843 fmt = ( 844 "MTE Info Stats\n" 845 " cells address : {&cells:#x}\n" 846 " lists address : {&lists:#x}\n" 847 "\n" 848 "Lists count [ 0 1-8 9-16 17-24 25-32 ]") 849 850 print(xnu_format(fmt, lists=self.mte_lists, cells=self.mte_cells)) 851 852 self._describe_list("disabled", 0) 853 self._describe_list("pinned", 1) 854 self._describe_list("deactivating", 2) 855 self._describe_list("claimed", 3, True) 856 self._describe_list("inactive", 4, True) 857 self._describe_list("reclaiming", 5) 858 self._describe_list("activating", 6) 859 active0 = self._describe_list("active 0", 7, True) 860 active = self._describe_list("active 1+", 8) 861 if active == 0: 862 frag = 0 863 else: 864 frag = 100 - tagged * 100 / (32 * active) 865 866 fmt = ( 867 "\n" 868 "Free Queues" 869 ) 870 print(xnu_format(fmt)) 871 self._describe_free_queue("untaggable 0", 0) 872 self._describe_free_queue("untaggable 1", 1) 873 self._describe_free_queue("untaggable 2", 2) 874 self._describe_free_queue("active 0", 3) 875 self._describe_free_queue("active 1", 4) 876 self._describe_free_queue("active 2", 5) 877 self._describe_free_queue("active 3", 6) 878 self._describe_free_queue("activating", 7) 879 880 print( 881 "\n" 882 "Statistics\n" 883 f" taggable free pages : {taggable:9,d}\n" 884 f" free pages : {free:9,d}\n" 885 f" tagged pages : {tagged:9,d}\n" 886 f" tag storage active : {active + active0:9,d}\n" 887 f" tag storage wired : {ts_wired:9,d}\n" 888 f" fragmentation : {frag:9.1f}%\n" 889 ) 890 891 # 892 # mte ldg 893 # 894 895 @classmethod 896 def make_opts_ldg(cls, parser): 897 parser.add_argument( 898 "address", 899 nargs='+', 900 type=cls.Address, 901 help="An address to fix with the proper tag", 902 ) 903 904 def cmd_ldg(self, args): 905 """ 906 Fix a pointer with its proper tag 907 """ 908 909 for address in args.address: 910 print(f"{args.pmap.ldg(address):#x}") 911 912 913@lldb_command("mte", 'P:', fancy=True) 914def mte_dispatch(cmd_args=None, cmd_options={}, O=None): 915 """ 916 Inspect and debug MTE related problems 917 918 usage: mte [-P pmap] {atag,atag-read,info,ldg} ... 919 920 optional arguments: 921 -P pmap The pmap to use to resolve virtual addresses 922 (default: kernel_pmap) 923 924 valid subcommands: 925 atag Print the MTE tag storage info for a covered virtual 926 address 927 atag-read Dump tags for a range of memory 928 info Dumps the state of the MTE info data structure 929 ldg Fix a pointer with its proper tag 930 """ 931 932 if "-P" in cmd_options: 933 cmd_args = ["-P", cmd_options["-P"]] + cmd_args 934 MTECommand()(cmd_args, O=O) 935 936 937__all__ = [ 938 Pmap.__name__, 939 VMMap.__name__, 940 VMMapEntry.__name__, 941 KernelMapWhatisProvider.__name__, 942] 943