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