xref: /xnu-11215/tools/lldbmacros/utils.py (revision 8d741a5d)
1#General Utility functions for debugging or introspection
2
3""" Please make sure you read the README file COMPLETELY BEFORE reading anything below.
4    It is very critical that you read coding guidelines in Section E in README file.
5"""
6import sys, re, time, os, time
7import lldb
8import struct
9
10from core.cvalue import *
11from core.configuration import *
12from core.lazytarget import *
13
14#DONOTTOUCHME: exclusive use for lldb_run_command only.
15lldb_run_command_state = {'active':False}
16
17def lldb_run_command(cmdstring):
18    """ Run a lldb command and get the string output.
19        params: cmdstring - str : lldb command string which could be executed at (lldb) prompt. (eg. "register read")
20        returns: str - output of command. it may be "" in case if command did not return any output.
21    """
22    global lldb_run_command_state
23    retval =""
24    res = lldb.SBCommandReturnObject()
25    # set special attribute to notify xnu framework to not print on stdout
26    lldb_run_command_state['active'] = True
27    lldb.debugger.GetCommandInterpreter().HandleCommand(cmdstring, res)
28    lldb_run_command_state['active'] = False
29    if res.Succeeded():
30        retval = res.GetOutput()
31    else:
32        retval = "ERROR:" + res.GetError()
33    return retval
34
35def EnableLLDBAPILogging():
36    """ Enable file based logging for lldb and also provide essential information about what information
37        to include when filing a bug with lldb or xnu.
38    """
39    logfile_name = "/tmp/lldb.%d.log" % int(time.time())
40    enable_log_base_cmd = "log enable --file %s " % logfile_name
41    cmd_str = enable_log_base_cmd + ' lldb api'
42    print(cmd_str)
43    print(lldb_run_command(cmd_str))
44    cmd_str = enable_log_base_cmd + ' gdb-remote packets'
45    print(cmd_str)
46    print(lldb_run_command(cmd_str))
47    cmd_str = enable_log_base_cmd + ' kdp-remote packets'
48    print(cmd_str)
49    print(lldb_run_command(cmd_str))
50    print(f"{lldb.SBDebugger.GetVersionString()}\n")
51    print("Please collect the logs from %s for filing a radar. If you had encountered an exception in a lldbmacro command please re-run it." % logfile_name)
52    print("Please make sure to provide the output of 'version', 'image list' and output of command that failed.")
53    return
54
55def GetConnectionProtocol():
56    """ Returns a string representing what kind of connection is used for debugging the target.
57        params: None
58        returns:
59            str - connection type. One of ("core","kdp","gdb", "unknown")
60    """
61    retval = "unknown"
62    process_plugin_name = LazyTarget.GetProcess().GetPluginName().lower()
63    if "kdp" in process_plugin_name:
64        retval = "kdp"
65    elif "gdb" in process_plugin_name:
66        retval = "gdb"
67    elif "mach-o" in process_plugin_name and "core" in process_plugin_name:
68        retval = "core"
69    return retval
70
71def SBValueToPointer(sbval):
72    """ Helper function for getting pointer value from an object of pointer type.
73        ex. void *astring = 0x12345
74        use SBValueToPointer(astring_val) to get 0x12345
75        params: sbval - value object of type '<type> *'
76        returns: int - pointer value as an int.
77    """
78    if type(sbval) == core.value:
79        sbval = sbval.GetSBValue()
80    if sbval.IsPointerType():
81        return sbval.GetValueAsUnsigned()
82    else:
83        return int(sbval.GetAddress())
84
85def ArgumentStringToInt(arg_string) -> int:
86    """ converts an argument to an int
87        params:
88            arg_string: str - typically a string passed from the commandline.
89                        Accepted inputs:
90                        1. A base 2/8/10/16 literal representation, e.g. "0b101"/"0o5"/"5"/"0x5"
91                        2. An LLDB expression, e.g. "((char*)foo_ptr + sizeof(bar_type))"
92        returns:
93            int - integer representation of the string
94    """
95    try:
96        return int(arg_string, 0)
97    except ValueError:
98        val = LazyTarget.GetTarget().chkEvaluateExpression(arg_string)
99        return val.signed
100
101def GetLongestMatchOption(searchstr, options=[], ignore_case=True):
102    """ Get longest matched string from set of options.
103        params:
104            searchstr : string of chars to be matched
105            options : array of strings that are to be matched
106        returns:
107            [] - array of matched options. The order of options is same as the arguments.
108                 empty array is returned if searchstr does not match any option.
109        example:
110            subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True)
111            print subcommand # prints ['reload']
112    """
113    if ignore_case:
114        searchstr = searchstr.lower()
115    found_options = []
116    for o in options:
117        so = o
118        if ignore_case:
119            so = o.lower()
120        if so == searchstr:
121            return [o]
122        if so.find(searchstr) >=0 :
123            found_options.append(o)
124    return found_options
125
126def GetType(target_type):
127    """ type cast an object to new type.
128        params:
129            target_type - str, ex. 'char', 'uint32_t' etc
130        returns:
131            lldb.SBType - a new Type that can be used as param to  lldb.SBValue.Cast()
132        raises:
133            NameError  - Incase the type is not identified
134    """
135    return gettype(target_type)
136
137
138def Cast(obj, target_type):
139    """ Type cast an object to another C type.
140        params:
141            obj - core.value  object representing some C construct in lldb
142            target_type - str : ex 'char *'
143                        - lldb.SBType :
144    """
145    return cast(obj, target_type)
146
147def ContainerOf(obj, target_type, field_name):
148    """ Type cast an object to another C type from a pointer to a field.
149        params:
150            obj - core.value  object representing some C construct in lldb
151            target_type - str : ex 'struct thread'
152                        - lldb.SBType :
153            field_name - the field name within the target_type obj is a pointer to
154    """
155    return containerof(obj, target_type, field_name)
156
157def loadLLDB():
158    """ Util function to load lldb python framework in case not available in common include paths.
159    """
160    try:
161        import lldb
162        print('Found LLDB on path')
163    except:
164        platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split())
165        offset = platdir.find("Contents/Developer")
166        if offset == -1:
167            lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python')
168        else:
169            lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python')
170        if os.path.isdir(lldb_py):
171            sys.path.append(lldb_py)
172            global lldb
173            lldb = __import__('lldb')
174            print('Found LLDB in SDK')
175        else:
176            print('Failed to locate lldb.py from', lldb_py)
177            sys.exit(-1)
178    return True
179
180class Logger(object):
181    """ A logging utility """
182    def __init__(self, log_file_path="/tmp/xnu.log"):
183        self.log_file_handle = open(log_file_path, "w+")
184        self.redirect_to_stdout = False
185
186    def log_debug(self, *args):
187        current_timestamp = time.time()
188        debug_line_str = "DEBUG:" + str(current_timestamp) + ":"
189        for arg in args:
190            debug_line_str += " " + str(arg).replace("\n", " ") + ", "
191
192        self.log_file_handle.write(debug_line_str + "\n")
193        if self.redirect_to_stdout :
194            print(debug_line_str)
195
196    def write(self, line):
197        self.log_debug(line)
198
199
200def sizeof_fmt(num, unit_str='B'):
201    """ format large number into human readable values.
202        convert any number into Kilo, Mega, Giga, Tera format for human understanding.
203        params:
204            num - int : number to be converted
205            unit_str - str : a suffix for unit. defaults to 'B' for bytes.
206        returns:
207            str - formatted string for printing.
208    """
209    for x in ['','K','M','G','T']:
210        if num < 1024.0:
211            return "%3.1f%s%s" % (num, x,unit_str)
212        num /= 1024.0
213    return "%3.1f%s%s" % (num, 'P', unit_str)
214
215def WriteStringToMemoryAddress(stringval, addr):
216    """ write a null terminated string to address.
217        params:
218            stringval: str- string to be written to memory. a '\0' will be added at the end
219            addr : int - address where data is to be written
220        returns:
221            bool - True if successfully written
222    """
223    serr = lldb.SBError()
224    length = len(stringval) + 1
225    format_string = "%ds" % length
226    sdata = struct.pack(format_string,stringval.encode())
227    numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr)
228    if numbytes == length and serr.Success():
229        return True
230    return False
231
232def WriteInt64ToMemoryAddress(intval, addr):
233    """ write a 64 bit integer at an address.
234        params:
235          intval - int - an integer value to be saved
236          addr - int - address where int is to be written
237        returns:
238          bool - True if successfully written.
239    """
240    serr = lldb.SBError()
241    sdata = struct.pack('Q', intval)
242    addr = int(hex(addr).rstrip('L'), 16)
243    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
244    if numbytes == 8 and serr.Success():
245        return True
246    return False
247
248def WritePtrDataToMemoryAddress(intval, addr):
249    """ Write data to pointer size memory.
250        This is equivalent of doing *(&((struct pmap *)addr)) = intval
251        It will identify 32/64 bit kernel and write memory accordingly.
252        params:
253          intval - int - an integer value to be saved
254          addr - int - address where int is to be written
255        returns:
256          bool - True if successfully written.
257    """
258    if kern.ptrsize == 8:
259        return WriteInt64ToMemoryAddress(intval, addr)
260    else:
261        return WriteInt32ToMemoryAddress(intval, addr)
262
263def WriteInt32ToMemoryAddress(intval, addr):
264    """ write a 32 bit integer at an address.
265        params:
266          intval - int - an integer value to be saved
267          addr - int - address where int is to be written
268        returns:
269          bool - True if successfully written.
270    """
271    serr = lldb.SBError()
272    sdata = struct.pack('I', intval)
273    addr = int(hex(addr).rstrip('L'), 16)
274    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
275    if numbytes == 4 and serr.Success():
276        return True
277    return False
278
279def WriteInt16ToMemoryAddress(intval, addr):
280    """ write a 16 bit integer at an address.
281        params:
282          intval - int - an integer value to be saved
283          addr - int - address where int is to be written
284        returns:
285          bool - True if successfully written.
286    """
287    serr = lldb.SBError()
288    sdata = struct.pack('H', intval)
289    addr = int(hex(addr).rstrip('L'), 16)
290    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
291    if numbytes == 2 and serr.Success():
292        return True
293    return False
294
295def WriteInt8ToMemoryAddress(intval, addr):
296    """ write a 8 bit integer at an address.
297        params:
298          intval - int - an integer value to be saved
299          addr - int - address where int is to be written
300        returns:
301          bool - True if successfully written.
302    """
303    serr = lldb.SBError()
304    sdata = struct.pack('B', intval)
305    addr = int(hex(addr).rstrip('L'), 16)
306    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
307    if numbytes == 1 and serr.Success():
308        return True
309    return False
310
311_enum_cache = {}
312def GetEnumValue(enum_name_or_combined, member_name = None):
313    """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION  => 0x3
314        params:
315            enum_name_or_combined: str
316                name of an enum of the format type::name (legacy)
317                name of an enum type
318            member_name: None, or the name of an enum member
319                   (then enum_name_or_combined is a type name).
320        returns:
321            int - value of the particular enum.
322        raises:
323            TypeError - if the enum is not found
324    """
325    global _enum_cache
326    if member_name is None:
327        enum_name, member_name = enum_name_or_combined.strip().split("::")
328    else:
329        enum_name = enum_name_or_combined
330
331    if enum_name not in _enum_cache:
332        ty = GetType(enum_name)
333        d  = {}
334
335        for e in ty.get_enum_members_array():
336            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
337                d[e.GetName()] = e.GetValueAsSigned()
338            else:
339                d[e.GetName()] = e.GetValueAsUnsigned()
340
341        _enum_cache[enum_name] = d
342
343    return _enum_cache[enum_name][member_name]
344
345def GetEnumValues(enum_name, names):
346    """ Finds the values of a particular set of enum defines.
347        params:
348            enum_name: str
349                name of an enum type
350            member_name: str list
351                list of fields to resolve
352        returns:
353            int list - value of the particular enum.
354        raises:
355            TypeError - if the enum is not found
356    """
357    return [GetEnumValue(enum_name, x) for x in names]
358
359_enum_name_cache = {}
360def GetEnumName(enum_name, value, prefix = ''):
361    """ Finds symbolic name for a particular enum integer value
362        params:
363            enum_name - str:   name of an enum type
364            value     - value: the value to decode
365            prefix    - str:   a prefix to strip from the tag
366        returns:
367            str - the symbolic name or UNKNOWN(value)
368        raises:
369            TypeError - if the enum is not found
370    """
371    global _enum_name_cache
372
373    ty = GetType(enum_name)
374
375    if enum_name not in _enum_name_cache:
376        ty_dict  = {}
377
378        for e in ty.get_enum_members_array():
379            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
380                ty_dict[e.GetValueAsSigned()] = e.GetName()
381            else:
382                ty_dict[e.GetValueAsUnsigned()] = e.GetName()
383
384        _enum_name_cache[enum_name] = ty_dict
385    else:
386        ty_dict = _enum_name_cache[enum_name]
387
388    if ty.GetTypeFlags() & lldb.eTypeIsSigned:
389        key = int(value)
390    else:
391        key = unsigned(value)
392
393    name = ty_dict.get(key, "UNKNOWN({:d})".format(key))
394    if name.startswith(prefix):
395        return name[len(prefix):]
396    return name
397
398def GetOptionString(enum_name, value, prefix = ''):
399    """ Tries to format a given value as a combination of options
400        params:
401            enum_name - str:   name of an enum type
402            value     - value: the value to decode
403            prefix    - str:   a prefix to strip from the tag
404        raises:
405            TypeError - if the enum is not found
406    """
407    ty = GetType(enum_name)
408
409    if enum_name not in _enum_name_cache:
410        ty_dict  = {}
411
412        for e in ty.get_enum_members_array():
413            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
414                ty_dict[e.GetValueAsSigned()] = e.GetName()
415            else:
416                ty_dict[e.GetValueAsUnsigned()] = e.GetName()
417
418        _enum_name_cache[enum_name] = ty_dict
419    else:
420        ty_dict = _enum_name_cache[enum_name]
421
422    if ty.GetTypeFlags() & lldb.eTypeIsSigned:
423        v = int(value)
424    else:
425        v = unsigned(value)
426
427    flags = []
428    for bit in range(0, 64):
429        mask = 1 << bit
430        if not v & mask: continue
431        if mask not in ty_dict: continue
432        name = ty_dict[mask]
433        if name.startswith(prefix):
434            name = name[len(prefix):]
435        flags.append(name)
436        v &= ~mask
437    if v:
438        flags.append("UNKNOWN({:d})".format(v))
439    return " ".join(flags)
440
441def ResolveFSPath(path):
442    """ expand ~user directories and return absolute path.
443        params: path - str - eg "~rc/Software"
444        returns:
445                str - abs path with user directories and symlinks expanded.
446                str - if path resolution fails then returns the same string back
447    """
448    expanded_path = os.path.expanduser(path)
449    norm_path = os.path.normpath(expanded_path)
450    return norm_path
451
452_dsymlist = {}
453uuid_regex = re.compile("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",re.IGNORECASE|re.DOTALL)
454def addDSYM(uuid, info):
455    """ add a module by dsym into the target modules.
456        params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
457                info - dict - info dictionary passed from dsymForUUID
458    """
459    global _dsymlist
460    if "DBGSymbolRichExecutable" not in info:
461        print("Error: Unable to find syms for %s" % uuid)
462        return False
463    if not uuid in _dsymlist:
464        # add the dsym itself
465        cmd_str = "target modules add --uuid %s" % uuid
466        debuglog(cmd_str)
467        lldb.debugger.HandleCommand(cmd_str)
468        # set up source path
469        #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"]))
470        # modify the list to show we loaded this
471        _dsymlist[uuid] = True
472
473def loadDSYM(uuid, load_address, sections=[]):
474    """ Load an already added symbols to a particular load address
475        params: uuid - str - uuid string
476                load_address - int - address where to load the symbols
477        returns bool:
478            True - if successful
479            False - if failed. possible because uuid is not presently loaded.
480    """
481    if uuid not in _dsymlist:
482        return False
483    if not sections:
484        cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address)
485        debuglog(cmd_str)
486    else:
487        cmd_str = "target modules load --uuid {}   ".format(uuid)
488        sections_str = ""
489        for s in sections:
490            sections_str += " {} {:#0x} ".format(s.name, s.vmaddr)
491        cmd_str += sections_str
492        debuglog(cmd_str)
493
494    lldb.debugger.HandleCommand(cmd_str)
495    return True
496
497
498def RunShellCommand(command):
499    """ Run a shell command in subprocess.
500        params: command with arguments to run (a list is preferred, but a string is also supported)
501        returns: (exit_code, stdout, stderr)
502    """
503    import subprocess
504
505    if not isinstance(command, list):
506        import shlex
507        command = shlex.split(command)
508
509    result = subprocess.run(command, capture_output=True, encoding="utf-8")
510    returncode =  result.returncode
511    stdout = result.stdout
512    stderr = result.stderr
513
514    if returncode != 0:
515        print("Failed to run command. Command: {}, "
516              "exit code: {}, stdout: '{}', stderr: '{}'".format(command, returncode, stdout, stderr))
517
518    return (returncode, stdout, stderr)
519
520def dsymForUUID(uuid):
521    """ Get dsym informaiton by calling dsymForUUID
522        params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
523        returns:
524            {} - a dictionary holding dsym information printed by dsymForUUID.
525            None - if failed to find information
526    """
527    import plistlib
528    rc, output, _ = RunShellCommand(["/usr/local/bin/dsymForUUID", "--copyExecutable", uuid])
529    if rc != 0:
530        return None
531
532    if output:
533        # because of <rdar://12713712>
534        #plist = plistlib.readPlistFromString(output)
535        #beginworkaround
536        keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL)
537        plist={}
538        plist[uuid] = {}
539        for item in keyvalue_extract_re.findall(output):
540            plist[uuid][item[0]] = item[1]
541        #endworkaround
542        if plist and plist[uuid]:
543            return plist[uuid]
544    return None
545
546def debuglog(s):
547    """ Print a object in the debug stream
548    """
549    global config
550    if config['debug']:
551      print("DEBUG:",s)
552    return None
553
554def IsAppleInternal():
555    """ check if apple_internal modules are available
556        returns: True if apple_internal module is present
557    """
558    import imp
559    try:
560        imp.find_module("apple_internal")
561        retval = True
562    except ImportError:
563        retval = False
564    return retval
565
566def print_hex_data(data, start=0, desc="", marks={}, prefix=" "):
567    """ print on stdout "hexdump -C < data" like output
568        params:
569            data - bytearray or array of int where each int < 255
570            start - int offset that should be printed in left column
571            desc - str optional description to print on the first line to describe data
572            mark - dictionary of markers
573    """
574
575    if desc:
576        print("{}:".format(desc))
577
578    end = start + len(data)
579
580    for row in range(start & -16, end, 16):
581        line  = ""
582        chars = ""
583
584        for col in range(16):
585            addr = row + col
586
587            if col == 8:
588                line += " "
589            if start <= addr < end:
590                b      = data[addr - start]
591                line  += "{}{:02x}".format(marks.get(addr, ' '), b)
592                chars += chr(b) if 0x20 <= b < 0x80 else '.'
593            else:
594                line  += "   "
595                chars += ' '
596
597        print("{}{:#016x} {}  |{}|".format(prefix, row, line, chars))
598
599def Ones(x):
600    return (1 << x)-1
601
602def StripPAC(x, TySz):
603    sign_mask = 1 << 55
604    ptr_mask = Ones(64-TySz)
605    pac_mask = ~ptr_mask
606    sign = x & sign_mask
607    if sign:
608        return (x | pac_mask) + 2**64
609    else:
610        return x & ptr_mask
611