xref: /xnu-12377.81.4/tools/lldbmacros/kmemory/vm.py (revision 043036a2b3718f7f0be807e2870f8f47d3fa0796)
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