xref: /xnu-10002.61.3/tools/lldbmacros/core/kernelcore.py (revision 0f4c859e951fba394238ab619495c4e1d54d0f34)
1
2""" Please make sure you read the README COMPLETELY BEFORE reading anything below.
3    It is very critical that you read coding guidelines in Section E in README file.
4"""
5from __future__ import absolute_import, division
6
7from builtins import range
8from builtins import object
9
10from .cvalue import value
11from .compat import valueint as int
12from . import collections as ccol
13from .caching import (
14    LazyTarget,
15    dyn_cached_property,
16    cache_dynamically,
17    cache_statically,
18)
19from utils import *
20
21import lldb
22import six
23
24class UnsupportedArchitectureError(RuntimeError):
25    def __init__(self, arch, msg="Unsupported architecture"):
26        self._arch = arch
27        self._msg = msg
28        super().__init__(msg)
29
30    def __str__(self):
31        return '%s: %s' % (self._arch, self._msg)
32
33
34def IterateTAILQ_HEAD(headval, element_name, list_prefix=''):
35    """ iterate over a TAILQ_HEAD in kernel. refer to bsd/sys/queue.h
36        params:
37            headval      - value : value object representing the head of the list
38            element_name - str   :  string name of the field which holds the list links.
39            list_prefix  - str   : use 's' here to iterate STAILQ_HEAD instead
40        returns:
41            A generator does not return. It is used for iterating.
42            value : an object that is of type as headval->tqh_first. Always a pointer object
43        example usage:
44          list_head = kern.GetGlobalVariable('mountlist')
45          for entryobj in IterateTAILQ_HEAD(list_head, 'mnt_list'):
46            print GetEntrySummary(entryobj)
47    """
48
49    next_path = ".{}.{}tqe_next".format(element_name, list_prefix)
50    head = headval.GetSBValue()
51
52    return (value(e.AddressOf()) for e in ccol.iter_linked_list(
53        head.Dereference() if head.TypeIsPointerType() else head,
54        next_path,
55        list_prefix + 'tqh_first',
56    ))
57
58
59def IterateLinkedList(headval, field_name):
60    """ iterate over a linked list.
61        This is equivalent to elt = headval; while(elt) { do_work(elt); elt = elt-><field_name>; }
62        params:
63            headval - value : value object representing element in the list.
64            field_name - str       : name of field that holds pointer to next element
65        returns: Nothing. This is used as iterable
66        example usage:
67            first_zone = kern.GetGlobalVariable('first_zone')
68            for zone in IterateLinkedList(first_zone, 'next_zone'):
69                print GetZoneSummary(zone)
70    """
71
72    head = headval.GetSBValue()
73
74    return (value(e.AddressOf()) for e in ccol.iter_linked_list(head, field_name))
75
76
77def IterateListEntry(headval, field_name, list_prefix=''):
78    """ iterate over a list as defined with LIST_HEAD in bsd/sys/queue.h
79        params:
80            headval      - value : Value object for lh_first
81            field_name   - str   : Name of the field in next element's structure
82            list_prefix  - str   : use 's' here to iterate SLIST_HEAD instead
83        returns:
84            A generator does not return. It is used for iterating
85            value  : an object thats of type (element_type) head->le_next. Always a pointer object
86        example usage:
87            headp = kern.globals.initproc.p_children
88            for pp in IterateListEntry(headp, 'p_sibling'):
89                print GetProcInfo(pp)
90    """
91
92    next_path = ".{}.{}le_next".format(field_name, list_prefix)
93    head = headval.GetSBValue()
94
95    return (value(e.AddressOf()) for e in ccol.iter_linked_list(
96        head.Dereference() if head.TypeIsPointerType() else head,
97        next_path,
98        list_prefix + 'lh_first',
99    ))
100
101
102def IterateLinkageChain(queue_head, element_type, field_name):
103    """ Iterate over a Linkage Chain queue in kernel of type queue_head_t. (osfmk/kern/queue.h method 1)
104        This is equivalent to the qe_foreach_element() macro
105        params:
106            queue_head   - value       : Value object for queue_head.
107            element_type - lldb.SBType : pointer type of the element which contains the queue_chain_t. Typically its structs like thread, task etc..
108                         - str         : OR a string describing the type. ex. 'task *'
109            field_name   - str         : Name of the field (in element) which holds a queue_chain_t
110        returns:
111            A generator does not return. It is used for iterating.
112            value  : An object thats of type (element_type). Always a pointer object
113        example usage:
114            coalq = kern.GetGlobalVariable('coalitions_q')
115            for coal in IterateLinkageChain(coalq, 'struct coalition *', 'coalitions'):
116                print GetCoalitionInfo(coal)
117    """
118
119    if isinstance(element_type, six.string_types):
120        element_type = gettype(element_type)
121
122    head = queue_head.GetSBValue()
123
124    return (value(e.AddressOf()) for e in ccol.iter_queue_entries(
125        head.Dereference() if head.TypeIsPointerType() else head,
126        element_type.GetPointeeType(),
127        field_name,
128    ))
129
130
131def IterateCircleQueue(queue_head, element_type, field_name):
132    """ iterate over a circle queue in kernel of type circle_queue_head_t. refer to osfmk/kern/circle_queue.h
133        params:
134            queue_head    - lldb.SBValue : Value object for queue_head.
135            element_type  - lldb.SBType : a type of the element 'next' points to. Typically its structs like thread, task etc..
136            field_name    - str : name of the field in target struct.
137        returns:
138            A generator does not return. It is used for iterating.
139            SBValue  : an object thats of type (element_type) queue_head->next. Always a pointer object
140    """
141
142    if isinstance(element_type, six.string_types):
143        element_type = gettype(element_type)
144
145    head = queue_head.GetSBValue()
146
147    return (value(e.AddressOf()) for e in ccol.iter_circle_queue(
148        head.Dereference() if head.TypeIsPointerType() else head,
149        element_type,
150        field_name,
151    ))
152
153
154def IterateQueue(queue_head, element_ptr_type, element_field_name, backwards=False, unpack_ptr_fn=None):
155    """ Iterate over an Element Chain queue in kernel of type queue_head_t. (osfmk/kern/queue.h method 2)
156        params:
157            queue_head         - value : Value object for queue_head.
158            element_ptr_type   - lldb.SBType : a pointer type of the element 'next' points to. Typically its structs like thread, task etc..
159                               - str         : OR a string describing the type. ex. 'task *'
160            element_field_name - str : name of the field in target struct.
161            backwards          - backwards : traverse the queue backwards
162            unpack_ptr_fn      - function : a function ptr of signature def unpack_ptr(long v) which returns long.
163        returns:
164            A generator does not return. It is used for iterating.
165            value  : an object thats of type (element_type) queue_head->next. Always a pointer object
166        example usage:
167            for page_meta in IterateQueue(kern.globals.first_zone.pages.all_free, 'struct zone_page_metadata *', 'pages'):
168                print page_meta
169    """
170
171    if isinstance(element_ptr_type, six.string_types):
172        element_ptr_type = gettype(element_ptr_type)
173
174    head = queue_head.GetSBValue()
175
176    return (value(e.AddressOf()) for e in ccol.iter_queue(
177        head.Dereference() if head.TypeIsPointerType() else head,
178        element_ptr_type.GetPointeeType(),
179        element_field_name,
180        backwards=backwards,
181        unpack=unpack_ptr_fn,
182    ))
183
184
185def IterateRBTreeEntry(rootelt, field_name):
186    """ iterate over a rbtree as defined with RB_HEAD in libkern/tree.h
187            rootelt      - value : Value object for rbh_root
188            field_name   - str   : Name of the field in link element's structure
189        returns:
190            A generator does not return. It is used for iterating
191            value  : an object thats of type (element_type) head->sle_next. Always a pointer object
192    """
193
194    return (value(e.AddressOf()) for e in ccol.iter_RB_HEAD(rootelt.GetSBValue(), field_name))
195
196
197def IterateSchedPriorityQueue(root, element_type, field_name):
198    """ iterate over a priority queue as defined with struct priority_queue from osfmk/kern/priority_queue.h
199            root         - value : Value object for the priority queue
200            element_type - str   : Type of the link element
201            field_name   - str   : Name of the field in link element's structure
202        returns:
203            A generator does not return. It is used for iterating
204            value  : an object thats of type (element_type). Always a pointer object
205    """
206
207    if isinstance(element_type, six.string_types):
208        element_type = gettype(element_type)
209
210    root = root.GetSBValue()
211
212    return (value(e.AddressOf()) for e in ccol.iter_priority_queue(
213        root.Dereference() if root.TypeIsPointerType() else root,
214        element_type,
215        field_name,
216    ))
217
218
219def IterateMPSCQueue(root, element_type, field_name):
220    """ iterate over an MPSC queue as defined with struct mpsc_queue_head from osfmk/kern/mpsc_queue.h
221            root         - value : Value object for the mpsc queue
222            element_type - str   : Type of the link element
223            field_name   - str   : Name of the field in link element's structure
224        returns:
225            A generator does not return. It is used for iterating
226            value  : an object thats of type (element_type). Always a pointer object
227    """
228    if isinstance(element_type, six.string_types):
229        element_type = gettype(element_type)
230
231    return (value(e.AddressOf()) for e in ccol.iter_mpsc_queue(
232        root.GetSBValue(), element_type, field_name
233    ))
234
235
236class KernelTarget(object):
237    """ A common kernel object that provides access to kernel objects and information.
238        The class holds global lists for  task, terminated_tasks, procs, zones, zombroc etc.
239        It also provides a way to symbolicate an address or create a value from an address.
240    """
241    def __init__(self, debugger):
242        """ Initialize the kernel debugging environment.
243            Target properties like architecture and connectedness are lazy-evaluted.
244        """
245
246        self.symbolicator = None
247
248        class _GlobalVariableFind(object):
249            def __init__(self, kern):
250                self._xnu_kernobj_12obscure12 = kern
251            def __getattr__(self, name):
252                v = self._xnu_kernobj_12obscure12.GetGlobalVariable(name)
253                if not v.GetSBValue().IsValid():
254                    # Python 2 swallows all exceptions in hasattr(). That makes it work
255                    # even when global variable is not found. Python 3 has fixed the behavior
256                    # and we can raise only AttributeError here to keep original behavior.
257                    raise AttributeError('No such global variable by name: %s '%str(name))
258                return v
259        self.globals = _GlobalVariableFind(self)
260
261    def _GetSymbolicator(self):
262        """ Internal function: To initialize the symbolication from lldb.utils
263        """
264        if not self.symbolicator is None:
265            return self.symbolicator
266
267        from lldb.utils import symbolication
268        symbolicator = symbolication.Symbolicator()
269        symbolicator.target = LazyTarget.GetTarget()
270        self.symbolicator = symbolicator
271        return self.symbolicator
272
273    def Symbolicate(self, addr):
274        """ simple method to get name of function/variable from an address. this is equivalent of gdb 'output /a 0xaddress'
275            params:
276                addr - int : typically hex value like 0xffffff80002c0df0
277            returns:
278                str - '' if no symbol found else the symbol name.
279            Note: this function only finds the first symbol. If you expect multiple symbol conflict please use SymbolicateFromAddress()
280        """
281        ret_str = ''
282        syms = self.SymbolicateFromAddress(addr)
283        if len(syms) > 0:
284            ret_str +=syms[0].GetName()
285        return ret_str
286
287    def SymbolicateFromAddress(self, addr, fullSymbol=False):
288        """ symbolicates any given address based on modules loaded in the target.
289            params:
290                addr - int : typically hex value like 0xffffff80002c0df0
291            returns:
292                [] of SBSymbol: In case we don't find anything than empty array is returned.
293                      Note: a type of symbol can be figured out by gettype() function of SBSymbol.
294            example usage:
295                syms = kern.Symbolicate(0xffffff80002c0df0)
296                for s in syms:
297                  if s.GetType() == lldb.eSymbolTypeCode:
298                    print "Function", s.GetName()
299                  if s.GetType() == lldb.eSymbolTypeData:
300                    print "Variable", s.GetName()
301        """
302        if type(int(1)) != type(addr):
303            if str(addr).strip().find("0x") == 0 :
304                addr = int(addr, 16)
305            else:
306                addr = int(addr)
307        addr = self.StripKernelPAC(addr)
308        ret_array = []
309        symbolicator = self._GetSymbolicator()
310        syms = symbolicator.symbolicate(addr)
311        if not syms:
312            return ret_array
313        for s in syms:
314            if fullSymbol:
315                ret_array.append(s)
316            else:
317                ret_array.append(s.get_symbol_context().symbol)
318        return ret_array
319
320    def IsDebuggerConnected(self):
321        proc_state = LazyTarget.GetProcess().state
322        if proc_state == lldb.eStateInvalid : return False
323        if proc_state in [lldb.eStateStopped, lldb.eStateSuspended] : return True
324
325    @staticmethod
326    @cache_statically
327    def GetGlobalVariable(name, target=None):
328        """ Get the value object representation for a kernel global variable
329            params:
330              name : str - name of the variable. ex. version
331            returns: value - python object representing global variable.
332            raises : Exception in case the variable is not found.
333        """
334
335        return value(target.FindGlobalVariables(name, 1).GetValueAtIndex(0))
336
337    def PERCPU_BASE(self, cpu):
338        """ Get the PERCPU base for the given cpu number
339            params:
340              cpu  : int - the cpu# for this variable
341            returns: int - the base for PERCPU for this cpu index
342        """
343        if self.arch == 'x86_64':
344            return unsigned(self.globals.cpu_data_ptr[cpu].cpu_pcpu_base)
345        elif self.arch.startswith('arm'):
346            data_entries = self.GetGlobalVariable('CpuDataEntries')
347            BootCpuData = addressof(self.GetGlobalVariable('percpu_slot_cpu_data'))
348            return unsigned(data_entries[cpu].cpu_data_vaddr) - unsigned(BootCpuData)
349
350    def PERCPU_GET(self, name, cpu):
351        """ Get the value object representation for a kernel percpu global variable
352            params:
353              name : str - name of the variable. ex. version
354              cpu  : int - the cpu# for this variable
355            returns: value - python object representing global variable.
356            raises : Exception in case the variable is not found.
357        """
358        var = addressof(self.GetGlobalVariable('percpu_slot_' + name))
359        addr = unsigned(var) + self.PERCPU_BASE(cpu)
360        return dereference(self.GetValueFromAddress(addr, var))
361
362    def GetLoadAddressForSymbol(self, name):
363        """ Get the load address of a symbol in the kernel.
364            params:
365              name : str - name of the symbol to lookup
366            returns: int - the load address as an integer. Use GetValueFromAddress to cast to a value.
367            raises : LookupError - if the symbol is not found.
368        """
369        name = str(name)
370        target = LazyTarget.GetTarget()
371        syms_arr = target.FindSymbols(name)
372        if syms_arr.IsValid() and len(syms_arr) > 0:
373            symbol = syms_arr[0].GetSymbol()
374            if symbol.IsValid():
375                return int(symbol.GetStartAddress().GetLoadAddress(target))
376
377        raise LookupError("Symbol not found: " + name)
378
379    def GetValueFromAddress(self, addr, type_str = 'void *'):
380        """ convert a address to value
381            params:
382                addr - int : typically hex value like 0xffffff80008dc390
383                type_str - str: type to cast to. Default type will be void *
384            returns:
385                value : a value object which has address as addr and type is type_str
386        """
387        obj = value(self.globals.version.GetSBValue().CreateValueFromExpression(None,'(void *)'+str(addr)))
388        obj = cast(obj, type_str)
389        return obj
390
391    def CreateTypedPointerFromAddress(self, addr, type_str = "char"):
392        """ convert a address to pointer value
393
394            Note: This is obsolete and here as a temporary solution
395                  for people to migrate to using references instead.
396
397            params:
398                addr - int : typically hex value like 0xffffff80008dc390
399                type_str - str: type to cast to, must not be a pointer type.
400            returns:
401                value : a value object which has address as addr
402                        and type is `type_str *`
403        """
404
405        target = LazyTarget.GetTarget()
406        sbv    = target.xCreateValueFromAddress(None, addr, gettype(type_str))
407        return value(sbv.AddressOf())
408
409
410    def GetValueAsType(self, v, t):
411        """ Retrieves a global variable 'v' of type 't' wrapped in a vue object.
412            If 'v' is an address, creates a vue object of the appropriate type.
413            If 'v' is a name, looks for the global variable and asserts its type.
414            Throws:
415                NameError - If 'v' cannot be found
416                TypeError - If 'v' is of the wrong type
417        """
418        if islong(v):
419            return self.GetValueFromAddress(v, t)
420        else:
421            var = LazyTarget.GetTarget().FindGlobalVariables(v, 1)[0]
422            if not var:
423                raise NameError("Failed to find global variable '{0}'".format(v))
424            if var.GetTypeName() != t:
425                raise TypeError("{0} must be of type '{1}', not '{2}'".format(v, t, var.GetTypeName()))
426            return value(var)
427
428    def _GetIterator(self, iter_head_name, next_element_name='next', iter_head_type=None):
429        """ returns an iterator for a collection in kernel memory.
430            params:
431                iter_head_name - str : name of queue_head or list head variable.
432                next_element_name - str : name of the element that leads to next element.
433                                          for ex. in struct zone list 'next_zone' is the linking element.
434            returns:
435                iterable : typically used in conjunction with "for varname in iterable:"
436        """
437        head_element = self.GetGlobalVariable(iter_head_name)
438        return head_element.GetSBValue().linked_list_iter(next_element_name)
439
440    def TruncPage(self, addr):
441        return (addr & ~(unsigned(self.GetGlobalVariable("page_size")) - 1))
442
443    def RoundPage(self, addr):
444        return trunc_page(addr + unsigned(self.GetGlobalVariable("page_size")) - 1)
445
446    def StraddlesPage(self, addr, size):
447        if size > unsigned(self.GetGlobalVariable("page_size")):
448            return True
449        val = ((addr + size) & (unsigned(self.GetGlobalVariable("page_size"))-1))
450        return (val < size and val > 0)
451
452    def StripUserPAC(self, addr):
453        if self.arch != 'arm64e':
454            return addr
455        T0Sz = self.GetGlobalVariable('gT0Sz')
456        return StripPAC(addr, T0Sz)
457
458    def StripKernelPAC(self, addr):
459        if self.arch != 'arm64e':
460            return addr
461        T1Sz = self.GetGlobalVariable('gT1Sz')
462        return StripPAC(addr, T1Sz)
463
464    PAGE_PROTECTION_TYPE_NONE = 0
465    PAGE_PROTECTION_TYPE_PPL = 1
466
467    def PhysToKVARM64(self, addr):
468        if self.globals.page_protection_type <= self.PAGE_PROTECTION_TYPE_PPL:
469            ptov_table = self.globals.ptov_table
470            for i in range(0, self.globals.ptov_index):
471                if (addr >= int(unsigned(ptov_table[i].pa))) and (addr < (int(unsigned(ptov_table[i].pa)) + int(unsigned(ptov_table[i].len)))):
472                    return (addr - int(unsigned(ptov_table[i].pa)) + int(unsigned(ptov_table[i].va)))
473        else:
474            raise ValueError("PA {:#x} not found in physical region lookup table".format(addr))
475        return (addr - unsigned(self.globals.gPhysBase) + unsigned(self.globals.gVirtBase))
476
477    def PhysToKernelVirt(self, addr):
478        if self.arch == 'x86_64':
479            return (addr + unsigned(self.GetGlobalVariable('physmap_base')))
480        elif self.arch.startswith('arm64'):
481            return self.PhysToKVARM64(addr)
482        elif self.arch.startswith('arm'):
483            return (addr - unsigned(self.GetGlobalVariable("gPhysBase")) + unsigned(self.GetGlobalVariable("gVirtBase")))
484        else:
485            raise ValueError("PhysToVirt does not support {0}".format(self.arch))
486
487    @cache_statically
488    def GetUsecDivisor(self, target=None):
489        if self.arch == 'x86_64':
490            return 1000
491
492        rtclockdata_addr = self.GetLoadAddressForSymbol('RTClockData')
493        rtc = self.GetValueFromAddress(rtclockdata_addr, 'struct _rtclock_data_ *')
494        return unsigned(rtc.rtc_usec_divisor)
495
496    def GetNanotimeFromAbstime(self, abstime):
497        """ convert absolute time (which is in MATUs) to nano seconds.
498            Since based on architecture the conversion may differ.
499            params:
500                abstime - int absolute time as shown by mach_absolute_time
501            returns:
502                int - nanosecs of time
503        """
504        return (abstime * 1000) // self.GetUsecDivisor()
505
506    @property
507    @cache_statically
508    def zones(self, target=None):
509        za = target.chkFindFirstGlobalVariable('zone_array')
510        zs = target.chkFindFirstGlobalVariable('zone_security_array')
511        n  = target.chkFindFirstGlobalVariable('num_zones').xGetValueAsInteger()
512
513        iter_za = za.chkGetChildAtIndex(0).xIterSiblings(0, n)
514        iter_zs = zs.chkGetChildAtIndex(0).xIterSiblings(0, n)
515
516        return [
517            (value(next(iter_za).AddressOf()), value(next(iter_zs).AddressOf()))
518            for i in range(n)
519        ]
520
521    @property
522    def threads(self):
523        target = LazyTarget.GetTarget()
524
525        return (value(t.AddressOf()) for t in ccol.iter_queue(
526            target.chkFindFirstGlobalVariable('threads'),
527            gettype('thread'),
528            'threads',
529        ))
530
531    @dyn_cached_property
532    def tasks(self, target=None):
533        return [value(t.AddressOf()) for t in ccol.iter_queue(
534            target.chkFindFirstGlobalVariable('tasks'),
535            gettype('task'),
536            'tasks',
537        )]
538
539    @property
540    def coalitions(self):
541        target = LazyTarget.GetTarget()
542
543        return (value(coal.AddressOf()) for coal in ccol.SMRHash(
544            target.chkFindFirstGlobalVariable('coalition_hash'),
545            target.chkFindFirstGlobalVariable('coal_hash_traits'),
546        ))
547
548    @property
549    def thread_groups(self):
550        target = LazyTarget.GetTarget()
551
552        return (value(tg.AddressOf()) for tg in ccol.iter_queue_entries(
553            target.chkFindFirstGlobalVariable('tg_queue'),
554            gettype('thread_group'),
555            'tg_queue_chain',
556        ))
557
558    @property
559    def terminated_tasks(self):
560        target = LazyTarget.GetTarget()
561
562        return (value(t.AddressOf()) for t in ccol.iter_queue(
563            target.chkFindFirstGlobalVariable('terminated_tasks'),
564            gettype('task'),
565            'tasks',
566        ))
567
568    @property
569    def terminated_threads(self):
570        target = LazyTarget.GetTarget()
571
572        return (value(t.AddressOf()) for t in ccol.iter_queue(
573            target.chkFindFirstGlobalVariable('terminated_threads'),
574            gettype('thread'),
575            'threads',
576        ))
577
578    @property
579    def procs(self):
580        target = LazyTarget.GetTarget()
581
582        return (value(p.AddressOf()) for p in ccol.iter_LIST_HEAD(
583            target.chkFindFirstGlobalVariable('allproc'),
584            'p_list',
585        ))
586
587    @property
588    def interrupt_stats(self):
589        target = LazyTarget.GetTarget()
590
591        return (value(stat.AddressOf()) for stat in ccol.iter_queue(
592            target.chkFindFirstGlobalVariable('gInterruptAccountingDataList'),
593            gettype('IOInterruptAccountingData'),
594            'chain',
595        ))
596
597    @property
598    def zombprocs(self):
599        target = LazyTarget.GetTarget()
600
601        return (value(p.AddressOf()) for p in ccol.iter_LIST_HEAD(
602            target.chkFindFirstGlobalVariable('zombproc'),
603            'p_list',
604        ))
605
606    @property
607    def version(self):
608        return str(self.globals.version)
609
610    @property
611    def arch(self):
612        return LazyTarget.GetTarget().triple.split('-', 1)[0]
613
614    @property
615    def ptrsize(self):
616        return LazyTarget.GetTarget().GetAddressByteSize()
617
618    @property
619    def VM_MIN_KERNEL_ADDRESS(self):
620        if self.arch == 'x86_64':
621            return 0xffffff8000000000
622        else:
623            return 0xffffffe00000000
624
625    @property
626    def VM_MIN_KERNEL_AND_KEXT_ADDRESS(self):
627        if self.arch == 'x86_64':
628            return 0xffffff8000000000 - 0x80000000
629        else:
630            return 0xffffffe00000000
631