xref: /xnu-10002.61.3/tools/lldbmacros/kext.py (revision 0f4c859e951fba394238ab619495c4e1d54d0f34)
1from __future__ import absolute_import, division, print_function
2
3from builtins import object
4from builtins import hex
5from builtins import map
6from builtins import filter
7
8from collections import namedtuple
9import os
10import io
11import core
12from uuid import UUID
13
14from core.cvalue import (
15    unsigned,
16    signed,
17    addressof
18)
19from core.caching import (
20    cache_dynamically,
21    LazyTarget,
22)
23from core.io import SBProcessRawIO
24from macho import MachOSegment, MemMachO, VisualMachoMap
25
26from xnu import (
27    IterateLinkedList,
28    lldb_alias,
29    lldb_command,
30    lldb_run_command,
31    lldb_type_summary,
32    kern,
33    Cast,
34    header,
35    GetLongestMatchOption,
36    debuglog,
37    dsymForUUID,
38    addDSYM,
39    loadDSYM,
40    ArgumentError,
41    ArgumentStringToInt,
42    GetObjectAtIndexFromArray,
43    ResolveFSPath,
44    uuid_regex
45)
46
47import kmemory
48import macho
49import lldb
50
51
52#
53# Summary of information available about a kext.
54#
55#   uuid     - UUID of the object
56#   vmaddr   - VA of the text segment
57#   name     - Name of the kext
58#   address  - Kext address
59#   segments - Mach-O segments (if available)
60#   summary  - OSKextLoadedSummary
61#   kmod     - kmod_info_t
62KextSummary = namedtuple(
63    'KextSummary',
64    'uuid vmaddr name address segments summary kmod'
65)
66
67
68# Segment helpers
69
70
71def text_segment(segments):
72    """ Return TEXT segment if present in the list of first one.
73        segments: List of MachOSegment.
74    """
75
76    text_segments = {
77        s.name: s
78        for s in segments
79        if s.name in ('__TEXT_EXEC', '__TEXT')
80    }
81
82    # Pick text segment based on our prefered order.
83    for name in ['__TEXT_EXEC', '__TEXT']:
84        if name in text_segments:
85            return text_segments[name]
86
87    return segments[0]
88
89
90def seg_contains(segments, addr):
91    """ Returns generator of all segments that contains given address. """
92
93    return (
94        s for s in segments
95        if s.vmaddr <= addr < (s.vmaddr + s.vmsize)
96    )
97
98
99def sec_contains(sections, addr):
100    """ Returns generator of all sections that contains given address. """
101
102    return (
103        s for s in sections
104        if s.addr <= addr < (s.addr + s.size)
105    )
106
107def sbsec_contains(target, sbsections, addr):
108    """ Returns generator of all SBSections that contains given address. """
109
110    return (
111        s for s in sbsections
112        if s.GetLoadAddress(target) <= addr < s.GetLoadAddress(target) + s.GetByteSize()
113    )
114
115
116# Summary helpers
117
118def LoadMachO(address, size):
119    """ Parses Mach-O headers in given VA range.
120
121        return: MemMachO instance.
122    """
123
124    process = LazyTarget.GetProcess()
125    procio = SBProcessRawIO(process, address, size)
126    bufio = io.BufferedRandom(procio)
127    return macho.MemMachO(bufio)
128
129
130def IterateKextSummaries(target):
131    """ Generator walking over all kext summaries. """
132
133    hdr   = target.chkFindFirstGlobalVariable('gLoadedKextSummaries').Dereference()
134    arr   = hdr.GetValueForExpressionPath('.summaries[0]')
135    total = hdr.xGetIntegerByName('numSummaries')
136
137    for kext in (core.value(e.AddressOf()) for e in arr.xIterSiblings(0, total)):
138        # Load Mach-O segments/sections.
139        mobj = LoadMachO(unsigned(kext.address), unsigned(kext.size))
140
141        # Construct kext summary.
142        yield KextSummary(
143            uuid=GetUUIDSummary(kext.uuid),
144            vmaddr=text_segment(mobj.segments).vmaddr,
145            name=str(kext.name),
146            address=unsigned(kext.address),
147            segments=mobj.segments,
148            summary=kext,
149            kmod=GetKmodWithAddr(unsigned(kext.address))
150        )
151
152
153@cache_dynamically
154def GetAllKextSummaries(target=None):
155    """ Return all kext summaries. (cached) """
156
157    return list(IterateKextSummaries(target))
158
159
160def FindKextSummary(kmod_addr):
161    """ Returns summary for given kmod_info_t. """
162
163    for mod in GetAllKextSummaries():
164        if mod.address == kmod_addr or mod.vmaddr == kmod_addr:
165            return mod
166
167    return None
168
169
170# Keep this around until DiskImages2 migrate over to new methods above.
171def GetKextLoadInformation(addr=0, show_progress=False):
172    """ Original wrapper kept for backwards compatibility. """
173    if addr:
174        return [FindKextSummary(addr)]
175    else:
176        return GetAllKextSummaries()
177
178
179@lldb_command('showkextmacho')
180def ShowKextMachO(cmd_args=[]):
181    """ Show visual Mach-O layout.
182
183        Syntax: (lldb) showkextmacho <name of a kext>
184    """
185    if len(cmd_args) != 1:
186        raise ArgumentError("kext name is missing")
187
188    for kext in GetAllKextSummaries():
189
190        # Skip not matching kexts.
191        if kext.name.find(cmd_args[0]) == -1:
192            continue
193
194        # Load Mach-O segments/sections.
195        mobj = LoadMachO(unsigned(kext.kmod.address), unsigned(kext.kmod.size))
196
197        p = VisualMachoMap(kext.name)
198        p.printMachoMap(mobj)
199        print(" \n")
200
201
202_UNKNOWN_UUID = "........-....-....-....-............"
203
204
205@lldb_type_summary(['uuid_t'])
206@header("")
207def GetUUIDSummary(uuid):
208    """ returns a UUID string in form CA50DA4C-CA10-3246-B8DC-93542489AA26
209
210        uuid - Address of a memory where UUID is stored.
211    """
212
213    err = lldb.SBError()
214    addr = unsigned(addressof(uuid))
215    data = LazyTarget.GetProcess().ReadMemory(addr, 16, err)
216
217    if not err.Success():
218        return _UNKNOWN_UUID
219
220    return str(UUID(bytes=data)).upper()
221
222
223@lldb_type_summary(['kmod_info_t *'])
224@header((
225    "{0: <20s} {1: <20s} {2: <20s} {3: >3s} {4: >5s} {5: <20s} {6: <20s} "
226    "{7: >20s} {8: <30s}"
227).format('kmod_info', 'address', 'size', 'id', 'refs', 'TEXT exec', 'size',
228         'version', 'name'))
229def GetKextSummary(kmod):
230    """ returns a string representation of kext information """
231
232    format_string = (
233        "{mod: <#020x} {mod.address: <#020x} {mod.size: <#020x} "
234        "{mod.id: >3d} {mod.reference_count: >5d} {seg.vmaddr: <#020x} "
235        "{seg.vmsize: <#020x} {mod.version: >20s} {mod.name: <30s}"
236    )
237
238    # Try to obtain text segment from kext summary
239    summary = FindKextSummary(unsigned(kmod.address))
240    if summary:
241        seg = text_segment(summary.segments)
242    else:
243        # Fake text segment for pseudo kexts.
244        seg = MachOSegment('__TEXT', kmod.address, kmod.size, 0, kmod.size, [])
245
246    return format_string.format(mod=kmod, seg=seg)
247
248
249def GetKmodWithAddr(addr):
250    """ Go through kmod list and find one with begin_addr as addr.
251        returns: None if not found else a cvalue of type kmod.
252    """
253
254    for kmod in IterateLinkedList(kern.globals.kmod, 'next'):
255        if addr == unsigned(kmod.address):
256            return kmod
257
258    return None
259
260
261@lldb_command('showkmodaddr')
262def ShowKmodAddr(cmd_args=[]):
263    """ Given an address, print the offset and name for the kmod containing it
264        Syntax: (lldb) showkmodaddr <addr>
265    """
266    if len(cmd_args) < 1:
267        raise ArgumentError("Insufficient arguments")
268
269    addr = ArgumentStringToInt(cmd_args[0])
270
271    # Find first summary/segment pair that covers given address.
272    sumseg = (
273        (m, next(seg_contains(m.segments, addr), None))
274        for m in GetAllKextSummaries()
275    )
276
277    print(GetKextSummary.header)
278    for ksum, segment in (t for t in sumseg if t[1] is not None):
279        summary = GetKextSummary(ksum.kmod)
280        print(summary + " segment: {} offset = {:#0x}".format(
281            segment.name, (addr - segment.vmaddr)))
282
283    return True
284
285
286def GetOSKextVersion(version_num):
287    """ returns a string of format 1.2.3x from the version_num
288        params: version_num - int
289        return: str
290    """
291    if version_num == -1:
292        return "invalid"
293
294    (MAJ_MULT, MIN_MULT) = (1000000000000, 100000000)
295    (REV_MULT, STAGE_MULT) = (10000, 1000)
296
297    version = version_num
298
299    vers_major = version // MAJ_MULT
300    version = version - (vers_major * MAJ_MULT)
301
302    vers_minor = version // MIN_MULT
303    version = version - (vers_minor * MIN_MULT)
304
305    vers_revision = version // REV_MULT
306    version = version - (vers_revision * REV_MULT)
307
308    vers_stage = version // STAGE_MULT
309    version = version - (vers_stage * STAGE_MULT)
310
311    vers_stage_level = version
312
313    out_str = "%d.%d" % (vers_major, vers_minor)
314    if vers_revision > 0:
315        out_str += ".%d" % vers_revision
316    if vers_stage == 1:
317        out_str += "d%d" % vers_stage_level
318    if vers_stage == 3:
319        out_str += "a%d" % vers_stage_level
320    if vers_stage == 5:
321        out_str += "b%d" % vers_stage_level
322    if vers_stage == 6:
323        out_str += "fc%d" % vers_stage_level
324
325    return out_str
326
327
328def FindKmodNameForAddr(addr):
329    """ Given an address, return the name of the kext containing that address.
330    """
331
332    names = (
333        mod.kmod.name
334        for mod in GetAllKextSummaries()
335        if (any(seg_contains(mod.segments, unsigned(addr))))
336    )
337
338    return next(names, None)
339
340
341@lldb_command('showallkmods')
342def ShowAllKexts(cmd_args=None):
343    """ Display a summary listing of all loaded kexts (alias: showallkmods) """
344
345    print("{: <36s} ".format("UUID") + GetKextSummary.header)
346
347    for kmod in IterateLinkedList(kern.globals.kmod, 'next'):
348        sum = FindKextSummary(unsigned(kmod.address))
349
350        if sum:
351            _ksummary = GetKextSummary(sum.kmod)
352            uuid = sum.uuid
353        else:
354            _ksummary = GetKextSummary(kmod)
355            uuid = _UNKNOWN_UUID
356
357        print(uuid + " " + _ksummary)
358
359
360@lldb_command('showallknownkmods')
361def ShowAllKnownKexts(cmd_args=None):
362    """ Display a summary listing of all kexts known in the system.
363        This is particularly useful to find if some kext was unloaded
364        before this crash'ed state.
365    """
366    kext_ptr = kern.globals.sKextsByID
367    kext_count = unsigned(kext_ptr.count)
368
369    print("%d kexts in sKextsByID:" % kext_count)
370    print("{0: <20s} {1: <20s} {2: >5s} {3: >20s} {4: <30s}".format('OSKEXT *', 'load_addr', 'id', 'version', 'name'))
371    format_string = "{0: <#020x} {1: <20s} {2: >5s} {3: >20s} {4: <30s}"
372
373    for kext_dict in (GetObjectAtIndexFromArray(kext_ptr.dictionary, i)
374                      for i in range(kext_count)):
375
376        kext_name = str(kext_dict.key.string)
377        osk = Cast(kext_dict.value, 'OSKext *')
378
379        load_addr = "------"
380        id = "--"
381
382        if int(osk.flags.loaded):
383            load_addr = "{0: <#020x}".format(osk.kmod_info)
384            id = "{0: >5d}".format(osk.loadTag)
385
386        version_num = signed(osk.version)
387        version = GetOSKextVersion(version_num)
388        print(format_string.format(osk, load_addr, id, version, kext_name))
389
390
391def FetchDSYM(kinfo):
392    """ Obtains and adds dSYM based on kext summary. """
393
394    # No op for built-in modules.
395    kernel_uuid = str(kern.globals.kernel_uuid_string)
396    if kernel_uuid == kinfo.uuid:
397        print("(built-in)")
398        return
399
400    # Obtain and load binary from dSYM.
401    print("Fetching dSYM for %s" % kinfo.uuid)
402    info = dsymForUUID(kinfo.uuid)
403    if info and 'DBGSymbolRichExecutable' in info:
404        print("Adding dSYM (%s) for %s" % (kinfo.uuid, info['DBGSymbolRichExecutable']))
405        addDSYM(kinfo.uuid, info)
406        loadDSYM(kinfo.uuid, kinfo.vmaddr, kinfo.segments)
407    else:
408        print("Failed to get symbol info for %s" % kinfo.uuid)
409
410
411def AddKextSymsByFile(filename, slide):
412    """ Add kext based on file name and slide. """
413    sections = None
414
415    filespec = lldb.SBFileSpec(filename, False)
416    print("target modules add \"{:s}\"".format(filename))
417    print(lldb_run_command("target modules add \"{:s}\"".format(filename)))
418
419    loaded_module = LazyTarget.GetTarget().FindModule(filespec)
420    if loaded_module.IsValid():
421        uuid_str = loaded_module.GetUUIDString()
422        debuglog("added module {:s} with uuid {:s}".format(filename, uuid_str))
423
424        if slide is None:
425            for k in GetAllKextSummaries():
426                debuglog(k.uuid)
427                if k.uuid.lower() == uuid_str.lower():
428                    slide = k.vmaddr
429                    sections = k.segments
430                    debuglog("found the slide {:#0x} for uuid {:s}".format(k.vmaddr, k.uuid))
431    if slide is None:
432        raise ArgumentError("Unable to find load address for module described at {:s} ".format(filename))
433
434    if not sections:
435        cmd_str = "target modules load --file \"{:s}\" --slide {:s}".format(filename, str(slide))
436        debuglog(cmd_str)
437    else:
438        cmd_str = "target modules load --file \"{:s}\"".format(filename)
439        for s in sections:
440            cmd_str += " {:s} {:#0x} ".format(s.name, s.vmaddr)
441        debuglog(cmd_str)
442
443    lldb.debugger.HandleCommand(cmd_str)
444
445    kern.symbolicator = None
446    return True
447
448
449def AddKextSymsByName(kextname, all=False):
450    """ Add kext based on longest name match"""
451
452    kexts = GetLongestMatchOption(kextname, [x.name for x in GetAllKextSummaries()], True)
453    if not kexts:
454        print("No matching kext found.")
455        return False
456
457    if len(kexts) != 1 and not all:
458        print("Ambiguous match for name: {:s}".format(kextname))
459        if len(kexts) > 0:
460            print("Options are:\n\t" + "\n\t".join(kexts))
461        return False
462
463    # Load all matching dSYMs
464    for sum in GetAllKextSummaries():
465        if sum.name in kexts:
466            debuglog("matched the kext to name {:s} "
467                     "and uuid {:s}".format(sum.name, sum.uuid))
468            FetchDSYM(sum)
469
470    kern.symbolicator = None
471    return True
472
473
474@lldb_command('addkext', 'AF:N:')
475def AddKextSyms(cmd_args=[], cmd_options={}):
476    """ Add kext symbols into lldb.
477        This command finds symbols for a uuid and load the required executable
478        Usage:
479            addkext <uuid> : Load one kext based on uuid. eg. (lldb)addkext 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
480            addkext -F <abs/path/to/executable> <load_address> : Load kext executable at specified load address
481            addkext -N <name> : Load one kext that matches the name provided. eg. (lldb) addkext -N corecrypto
482            addkext -N <name> -A: Load all kext that matches the name provided. eg. to load all kext with Apple in name do (lldb) addkext -N Apple -A
483            addkext all    : Will load all the kext symbols - SLOW
484    """
485
486    # Load kext by file name.
487    if "-F" in cmd_options:
488        exec_path = cmd_options["-F"]
489        exec_full_path = ResolveFSPath(exec_path)
490        if not os.path.exists(exec_full_path):
491            raise ArgumentError("Unable to resolve {:s}".format(exec_path))
492
493        if not os.path.isfile(exec_full_path):
494            raise ArgumentError(
495                """Path is {:s} not a filepath.
496                Please check that path points to executable.
497                For ex. path/to/Symbols/IOUSBFamily.kext/Contents/PlugIns/AppleUSBHub.kext/Contents/MacOS/AppleUSBHub.
498                Note: LLDB does not support adding kext based on directory paths like gdb used to.""".format(exec_path))
499
500        slide_value = None
501        if cmd_args:
502            slide_value = cmd_args[0]
503            debuglog("loading slide value from user input {:s}".format(cmd_args[0]))
504
505        return AddKextSymsByFile(exec_full_path, slide_value)
506
507    # Load kext by name.
508    if "-N" in cmd_options:
509        kext_name = cmd_options["-N"]
510        return AddKextSymsByName(kext_name, "-A" in cmd_options)
511
512    # Load kexts by UUID or "all"
513    if len(cmd_args) < 1:
514        raise ArgumentError("No arguments specified.")
515
516    kernel_uuid = str(kern.globals.kernel_uuid_string).lower()
517    uuid = cmd_args[0].lower()
518
519    load_all_kexts = (uuid == "all")
520    if not load_all_kexts and len(uuid_regex.findall(uuid)) == 0:
521        raise ArgumentError("Unknown argument {:s}".format(uuid))
522
523    for sum in GetAllKextSummaries():
524        cur_uuid = sum.uuid.lower()
525        if load_all_kexts or (uuid == cur_uuid):
526            if kernel_uuid != cur_uuid:
527                FetchDSYM(sum)
528
529    kern.symbolicator = None
530    return True
531
532
533@lldb_command('addkextaddr')
534def AddKextAddr(cmd_args=[]):
535    """ Given an address, load the kext which contains that address
536        Syntax: (lldb) addkextaddr <addr>
537    """
538    if len(cmd_args) < 1:
539        raise ArgumentError("Insufficient arguments")
540
541    addr = ArgumentStringToInt(cmd_args[0])
542    kernel_uuid = str(kern.globals.kernel_uuid_string).lower()
543
544    match = (
545        (kinfo, seg_contains(kinfo.segments, addr))
546        for kinfo in GetAllKextSummaries()
547        if any(seg_contains(kinfo.segments, addr))
548    )
549
550    # Load all kexts which contain given address.
551    print(GetKextSummary.header)
552    for kinfo, segs in match:
553        for s in segs:
554            print(GetKextSummary(kinfo.kmod) + " segment: {} offset = {:#0x}".format(s.name, (addr - s.vmaddr)))
555            FetchDSYM(kinfo)
556
557
558class KextMemoryObject(kmemory.MemoryObject):
559    """ Describes an object landing in some kext """
560
561    MO_KIND = "kext mach-o"
562
563    def __init__(self, kmem, address, kinfo):
564        super(KextMemoryObject, self).__init__(kmem, address)
565        self.kinfo = kinfo
566        self.target = kmem.target
567
568    @property
569    def object_range(self):
570        seg = next(seg_contains(self.kinfo.segments, self.address))
571        sec = next(sec_contains(seg.sections, self.address), None)
572        if sec:
573            return kmemory.MemoryRange(sec.addr, sec.addr + sec.size)
574        return kmemory.MemoryRange(seg.vmaddr, seg.vmaddr + seg.vmsize)
575
576    def find_mod_seg_sect(self):
577        target  = self.target
578        address = self.address
579
580        return next((
581            (module, segment, next(sbsec_contains(target, segment, address), None))
582            for module in target.module_iter()
583            for segment in sbsec_contains(target, module.section_iter(), address)
584        ), (None, None, None))
585
586    def describe(self, verbose=False):
587        from lldb.utils.symbolication import Symbolicator
588
589        addr    = self.address
590        kinfo   = self.kinfo
591
592        sbmod, sbseg, sbsec = self.find_mod_seg_sect()
593        if sbmod is None:
594            FetchDSYM(kinfo)
595            print()
596            sbmod, sbseg, sbsec = self.find_mod_seg_sect()
597
598        syms   = Symbolicator.InitWithSBTarget(self.target).symbolicate(addr)
599        sym    = next(iter(syms)) if syms else None
600
601        if not sbseg:
602            # not really an SBSection but we only need to pretty print 'name'
603            # which both have, yay duck typing
604            sbseg = next(seg_contains(kinfo.segments, addr), None)
605
606        fmt  = "Kext Symbol Info\n"
607        fmt += " kext                 : {kinfo.name} ({kinfo.uuid})\n"
608        fmt += " module               : {sbmod.file.basename}\n" if sbmod else ""
609        fmt += " section              : {sbseg.name} {sbsec.name}\n" if sbsec else \
610               " segment              : {sbseg.name}\n" if sbseg else ""
611        fmt += " symbol               : {sym!s}\n" if sym else ""
612
613        print(fmt.format(kinfo=kinfo, sbmod=sbmod, sbseg=sbseg, sbsec=sbsec, sym=sym))
614
615
616class MainBinaryMemoryObject(kmemory.MemoryObject):
617    """ Describes an object landing in the main kernel binary """
618
619    MO_KIND = "kernel mach-o"
620
621    def __init__(self, kmem, address, section):
622        super(MainBinaryMemoryObject, self).__init__(kmem, address)
623        self.section = section
624        self.target = kmem.target
625
626    def _subsection(self):
627        return next(sbsec_contains(self.target, self.section, self.address), None)
628
629    @property
630    def object_range(self):
631        target  = self.target
632        section = self._subsection() or self.section
633        addr    = section.GetLoadAddress(target)
634        size    = section.GetByteSize()
635        return kmemory.MemoryRange(addr, addr + size)
636
637    @property
638    def module(self):
639        return self.target.GetModuleAtIndex(0).GetFileSpec().GetFilename()
640
641    @property
642    def uuid(self):
643        return self.target.GetModuleAtIndex(0).GetUUIDString()
644
645    def describe(self, verbose=False):
646        from lldb.utils.symbolication import Symbolicator
647
648        subsec  = self._subsection()
649        syms    = Symbolicator.InitWithSBTarget(self.target).symbolicate(self.address)
650        sym     = next(iter(syms)) if syms else None
651
652        fmt  = "Symbol Info\n"
653        fmt += " module               : {mo.module}\n"
654        fmt += " uuid                 : {mo.uuid}\n"
655        fmt += " section              : {mo.section.name} {subsec.name}\n" if subsec else ""
656        fmt += " segment              : {mo.section.name}\n" if not subsec else ""
657        fmt += " symbol               : {sym}\n" if sym else ""
658
659        print(fmt.format(mo=self, subsec=subsec, sym=sym))
660
661
662@kmemory.whatis_provider
663class KextWhatisProvider(kmemory.WhatisProvider):
664    """ Kext ranges whatis provider """
665
666    COST = 100
667
668    def claims(self, address):
669        target  = self.target
670        mainmod = target.GetModuleAtIndex(0)
671
672        #
673        # TODO: surely the kexts can provide a better range check
674        #
675
676        return any(
677            sbsec_contains(target, mainmod.section_iter(), address)
678        ) or any(
679            any(seg_contains(kinfo.segments, address))
680            for kinfo in GetAllKextSummaries()
681        )
682
683    def lookup(self, address):
684        target  = self.target
685        mainmod = target.GetModuleAtIndex(0)
686
687        section = next(sbsec_contains(target, mainmod.section_iter(), address), None)
688
689        if section:
690            return MainBinaryMemoryObject(self.kmem, address, section)
691
692        return KextMemoryObject(self.kmem, address, next(
693            kinfo
694            for kinfo in GetAllKextSummaries()
695            if any(seg_contains(kinfo.segments, address))
696        ))
697
698
699# Aliases for backward compatibility.
700
701lldb_alias('showkmod', 'showkmodaddr')
702lldb_alias('showkext', 'showkmodaddr')
703lldb_alias('showkextaddr', 'showkmodaddr')
704lldb_alias('showallkexts', 'showallkmods')
705