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