xref: /xnu-12377.1.9/tools/lldbmacros/utils.py (revision f6217f891ac0bb64f3d375211650a4c1ff8ca1ea)
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 ArgumentStringToAddress(arg_string) -> int:
86    """ converts an argument to an address
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.unsigned
100
101def ArgumentStringToInt(arg_string) -> int:
102    """ converts an argument to an int
103        params:
104            arg_string: str - typically a string passed from the commandline.
105                        Accepted inputs:
106                        1. A base 2/8/10/16 literal representation, e.g. "0b101"/"0o5"/"5"/"0x5"
107                        2. An LLDB expression, e.g. "((char*)foo_ptr + sizeof(bar_type))"
108        returns:
109            int - integer representation of the string
110    """
111    try:
112        return int(arg_string, 0)
113    except ValueError:
114        val = LazyTarget.GetTarget().chkEvaluateExpression(arg_string)
115        return val.signed
116
117def GetLongestMatchOption(searchstr, options=[], ignore_case=True):
118    """ Get longest matched string from set of options.
119        params:
120            searchstr : string of chars to be matched
121            options : array of strings that are to be matched
122        returns:
123            [] - array of matched options. The order of options is same as the arguments.
124                 empty array is returned if searchstr does not match any option.
125        example:
126            subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True)
127            print subcommand # prints ['reload']
128    """
129    if ignore_case:
130        searchstr = searchstr.lower()
131    found_options = []
132    for o in options:
133        so = o
134        if ignore_case:
135            so = o.lower()
136        if so == searchstr:
137            return [o]
138        if so.find(searchstr) >=0 :
139            found_options.append(o)
140    return found_options
141
142def GetType(target_type):
143    """ type cast an object to new type.
144        params:
145            target_type - str, ex. 'char', 'uint32_t' etc
146        returns:
147            lldb.SBType - a new Type that can be used as param to  lldb.SBValue.Cast()
148        raises:
149            NameError  - Incase the type is not identified
150    """
151    return gettype(target_type)
152
153
154def Cast(obj, target_type):
155    """ Type cast an object to another C type.
156        params:
157            obj - core.value  object representing some C construct in lldb
158            target_type - str : ex 'char *'
159                        - lldb.SBType :
160    """
161    return cast(obj, target_type)
162
163def ContainerOf(obj, target_type, field_name):
164    """ Type cast an object to another C type from a pointer to a field.
165        params:
166            obj - core.value  object representing some C construct in lldb
167            target_type - str : ex 'struct thread'
168                        - lldb.SBType :
169            field_name - the field name within the target_type obj is a pointer to
170    """
171    return containerof(obj, target_type, field_name)
172
173def loadLLDB():
174    """ Util function to load lldb python framework in case not available in common include paths.
175    """
176    try:
177        import lldb
178        print('Found LLDB on path')
179    except:
180        platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split())
181        offset = platdir.find("Contents/Developer")
182        if offset == -1:
183            lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python')
184        else:
185            lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python')
186        if os.path.isdir(lldb_py):
187            sys.path.append(lldb_py)
188            global lldb
189            lldb = __import__('lldb')
190            print('Found LLDB in SDK')
191        else:
192            print('Failed to locate lldb.py from', lldb_py)
193            sys.exit(-1)
194    return True
195
196class Logger(object):
197    """ A logging utility """
198    def __init__(self, log_file_path="/tmp/xnu.log"):
199        self.log_file_handle = open(log_file_path, "w+")
200        self.redirect_to_stdout = False
201
202    def log_debug(self, *args):
203        current_timestamp = time.time()
204        debug_line_str = "DEBUG:" + str(current_timestamp) + ":"
205        for arg in args:
206            debug_line_str += " " + str(arg).replace("\n", " ") + ", "
207
208        self.log_file_handle.write(debug_line_str + "\n")
209        if self.redirect_to_stdout :
210            print(debug_line_str)
211
212    def write(self, line):
213        self.log_debug(line)
214
215
216def sizeof_fmt(num, unit_str='B'):
217    """ format large number into human readable values.
218        convert any number into Kilo, Mega, Giga, Tera format for human understanding.
219        params:
220            num - int : number to be converted
221            unit_str - str : a suffix for unit. defaults to 'B' for bytes.
222        returns:
223            str - formatted string for printing.
224    """
225    for x in ['','K','M','G','T']:
226        if num < 1024.0:
227            return "%3.1f%s%s" % (num, x,unit_str)
228        num /= 1024.0
229    return "%3.1f%s%s" % (num, 'P', unit_str)
230
231def WriteStringToMemoryAddress(stringval, addr):
232    """ write a null terminated string to address.
233        params:
234            stringval: str- string to be written to memory. a '\0' will be added at the end
235            addr : int - address where data is to be written
236        returns:
237            bool - True if successfully written
238    """
239    serr = lldb.SBError()
240    length = len(stringval) + 1
241    format_string = "%ds" % length
242    sdata = struct.pack(format_string,stringval.encode())
243    numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr)
244    if numbytes == length and serr.Success():
245        return True
246    return False
247
248def WriteInt64ToMemoryAddress(intval, addr):
249    """ write a 64 bit integer at an address.
250        params:
251          intval - int - an integer value to be saved
252          addr - int - address where int is to be written
253        returns:
254          bool - True if successfully written.
255    """
256    serr = lldb.SBError()
257    sdata = struct.pack('Q', intval)
258    addr = int(hex(addr).rstrip('L'), 16)
259    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
260    if numbytes == 8 and serr.Success():
261        return True
262    return False
263
264def WritePtrDataToMemoryAddress(intval, addr):
265    """ Write data to pointer size memory.
266        This is equivalent of doing *(&((struct pmap *)addr)) = intval
267        It will identify 32/64 bit kernel and write memory accordingly.
268        params:
269          intval - int - an integer value to be saved
270          addr - int - address where int is to be written
271        returns:
272          bool - True if successfully written.
273    """
274    if kern.ptrsize == 8:
275        return WriteInt64ToMemoryAddress(intval, addr)
276    else:
277        return WriteInt32ToMemoryAddress(intval, addr)
278
279def WriteInt32ToMemoryAddress(intval, addr):
280    """ write a 32 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('I', intval)
289    addr = int(hex(addr).rstrip('L'), 16)
290    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
291    if numbytes == 4 and serr.Success():
292        return True
293    return False
294
295def WriteInt16ToMemoryAddress(intval, addr):
296    """ write a 16 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('H', intval)
305    addr = int(hex(addr).rstrip('L'), 16)
306    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
307    if numbytes == 2 and serr.Success():
308        return True
309    return False
310
311def WriteInt8ToMemoryAddress(intval, addr):
312    """ write a 8 bit integer at an address.
313        params:
314          intval - int - an integer value to be saved
315          addr - int - address where int is to be written
316        returns:
317          bool - True if successfully written.
318    """
319    serr = lldb.SBError()
320    sdata = struct.pack('B', intval)
321    addr = int(hex(addr).rstrip('L'), 16)
322    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
323    if numbytes == 1 and serr.Success():
324        return True
325    return False
326
327_enum_cache = {}
328def GetEnumValue(enum_name_or_combined, member_name = None):
329    """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION  => 0x3
330        params:
331            enum_name_or_combined: str
332                name of an enum of the format type::name (legacy)
333                name of an enum type
334            member_name: None, or the name of an enum member
335                   (then enum_name_or_combined is a type name).
336        returns:
337            int - value of the particular enum.
338        raises:
339            TypeError - if the enum is not found
340    """
341    global _enum_cache
342    if member_name is None:
343        enum_name, member_name = enum_name_or_combined.strip().split("::")
344    else:
345        enum_name = enum_name_or_combined
346
347    if enum_name not in _enum_cache:
348        ty = GetType(enum_name)
349        d  = {}
350
351        for e in ty.get_enum_members_array():
352            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
353                d[e.GetName()] = e.GetValueAsSigned()
354            else:
355                d[e.GetName()] = e.GetValueAsUnsigned()
356
357        _enum_cache[enum_name] = d
358
359    return _enum_cache[enum_name][member_name]
360
361def GetEnumValues(enum_name, names):
362    """ Finds the values of a particular set of enum defines.
363        params:
364            enum_name: str
365                name of an enum type
366            member_name: str list
367                list of fields to resolve
368        returns:
369            int list - value of the particular enum.
370        raises:
371            TypeError - if the enum is not found
372    """
373    return [GetEnumValue(enum_name, x) for x in names]
374
375_enum_name_cache = {}
376def GetEnumName(enum_name, value, prefix = ''):
377    """ Finds symbolic name for a particular enum integer value
378        params:
379            enum_name - str:   name of an enum type
380            value     - value: the value to decode
381            prefix    - str:   a prefix to strip from the tag
382        returns:
383            str - the symbolic name or UNKNOWN(value)
384        raises:
385            TypeError - if the enum is not found
386    """
387    global _enum_name_cache
388
389    ty = GetType(enum_name)
390
391    if enum_name not in _enum_name_cache:
392        ty_dict  = {}
393
394        for e in ty.get_enum_members_array():
395            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
396                ty_dict[e.GetValueAsSigned()] = e.GetName()
397            else:
398                ty_dict[e.GetValueAsUnsigned()] = e.GetName()
399
400        _enum_name_cache[enum_name] = ty_dict
401    else:
402        ty_dict = _enum_name_cache[enum_name]
403
404    if ty.GetTypeFlags() & lldb.eTypeIsSigned:
405        key = int(value)
406    else:
407        key = unsigned(value)
408
409    name = ty_dict.get(key, "UNKNOWN({:d})".format(key))
410    if name.startswith(prefix):
411        return name[len(prefix):]
412    return name
413
414def GetOptionString(enum_name, value, prefix = ''):
415    """ Tries to format a given value as a combination of options
416        params:
417            enum_name - str:   name of an enum type
418            value     - value: the value to decode
419            prefix    - str:   a prefix to strip from the tag
420        raises:
421            TypeError - if the enum is not found
422    """
423    ty = GetType(enum_name)
424
425    if enum_name not in _enum_name_cache:
426        ty_dict  = {}
427
428        for e in ty.get_enum_members_array():
429            if ty.GetTypeFlags() & lldb.eTypeIsSigned:
430                ty_dict[e.GetValueAsSigned()] = e.GetName()
431            else:
432                ty_dict[e.GetValueAsUnsigned()] = e.GetName()
433
434        _enum_name_cache[enum_name] = ty_dict
435    else:
436        ty_dict = _enum_name_cache[enum_name]
437
438    if ty.GetTypeFlags() & lldb.eTypeIsSigned:
439        v = int(value)
440    else:
441        v = unsigned(value)
442
443    flags = []
444    for bit in range(0, 64):
445        mask = 1 << bit
446        if not v & mask: continue
447        if mask not in ty_dict: continue
448        name = ty_dict[mask]
449        if name.startswith(prefix):
450            name = name[len(prefix):]
451        flags.append(name)
452        v &= ~mask
453    if v:
454        flags.append("UNKNOWN({:d})".format(v))
455    return " ".join(flags)
456
457def ResolveFSPath(path):
458    """ expand ~user directories and return absolute path.
459        params: path - str - eg "~rc/Software"
460        returns:
461                str - abs path with user directories and symlinks expanded.
462                str - if path resolution fails then returns the same string back
463    """
464    expanded_path = os.path.expanduser(path)
465    norm_path = os.path.normpath(expanded_path)
466    return norm_path
467
468_dsymlist = {}
469uuid_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)
470def addDSYM(uuid, info):
471    """ add a module by dsym into the target modules.
472        params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
473                info - dict - info dictionary passed from dsymForUUID
474    """
475    global _dsymlist
476    if "DBGSymbolRichExecutable" not in info:
477        print("Error: Unable to find syms for %s" % uuid)
478        return False
479    if not uuid in _dsymlist:
480        # add the dsym itself
481        cmd_str = "target modules add --uuid %s" % uuid
482        debuglog(cmd_str)
483        lldb.debugger.HandleCommand(cmd_str)
484        # set up source path
485        #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"]))
486        # modify the list to show we loaded this
487        _dsymlist[uuid] = True
488
489def loadDSYM(uuid, load_address, sections=[]):
490    """ Load an already added symbols to a particular load address
491        params: uuid - str - uuid string
492                load_address - int - address where to load the symbols
493        returns bool:
494            True - if successful
495            False - if failed. possible because uuid is not presently loaded.
496    """
497    if uuid not in _dsymlist:
498        return False
499    if not sections:
500        cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address)
501        debuglog(cmd_str)
502    else:
503        cmd_str = "target modules load --uuid {}   ".format(uuid)
504        sections_str = ""
505        for s in sections:
506            sections_str += " {} {:#0x} ".format(s.name, s.vmaddr)
507        cmd_str += sections_str
508        debuglog(cmd_str)
509
510    lldb.debugger.HandleCommand(cmd_str)
511    return True
512
513
514def RunShellCommand(command):
515    """ Run a shell command in subprocess.
516        params: command with arguments to run (a list is preferred, but a string is also supported)
517        returns: (exit_code, stdout, stderr)
518    """
519    import subprocess
520
521    if not isinstance(command, list):
522        import shlex
523        command = shlex.split(command)
524
525    result = subprocess.run(command, capture_output=True, encoding="utf-8")
526    returncode =  result.returncode
527    stdout = result.stdout
528    stderr = result.stderr
529
530    if returncode != 0:
531        print("Failed to run command. Command: {}, "
532              "exit code: {}, stdout: '{}', stderr: '{}'".format(command, returncode, stdout, stderr))
533
534    return (returncode, stdout, stderr)
535
536def dsymForUUID(uuid):
537    """ Get dsym informaiton by calling dsymForUUID
538        params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
539        returns:
540            {} - a dictionary holding dsym information printed by dsymForUUID.
541            None - if failed to find information
542    """
543    import plistlib
544    rc, output, _ = RunShellCommand(["/usr/local/bin/dsymForUUID", "--copyExecutable", uuid])
545    if rc != 0:
546        return None
547
548    if output:
549        # because of <rdar://12713712>
550        #plist = plistlib.readPlistFromString(output)
551        #beginworkaround
552        keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL)
553        plist={}
554        plist[uuid] = {}
555        for item in keyvalue_extract_re.findall(output):
556            plist[uuid][item[0]] = item[1]
557        #endworkaround
558        if plist and plist[uuid]:
559            return plist[uuid]
560    return None
561
562def debuglog(s):
563    """ Print a object in the debug stream
564    """
565    global config
566    if config['debug']:
567      print("DEBUG:",s)
568    return None
569
570def IsAppleInternal():
571    """ check if apple_internal modules are available
572        returns: True if apple_internal module is present
573    """
574    import imp
575    try:
576        imp.find_module("apple_internal")
577        retval = True
578    except ImportError:
579        retval = False
580    return retval
581
582def print_hex_data(data, start=0, desc="", marks={}, prefix=" ", extra=None):
583    """ print on stdout "hexdump -C < data" like output
584        params:
585            data - bytearray or array of int where each int < 255
586            start - int offset that should be printed in left column
587            desc - str optional description to print on the first line to describe data
588            mark - dictionary of markers
589            extra - a function returning some extra things to add on the line
590    """
591
592    if desc:
593        print("{}:".format(desc))
594
595    end = start + len(data)
596
597    for row in range(start & -16, end, 16):
598        line  = ""
599        chars = ""
600        lend  = ""
601
602        for col in range(16):
603            addr = row + col
604
605            if col == 8:
606                line += " "
607            if start <= addr < end:
608                b      = data[addr - start]
609                line  += "{}{:02x}".format(marks.get(addr, ' '), b)
610                chars += chr(b) if 0x20 <= b < 0x80 else '.'
611            else:
612                line  += "   "
613                chars += ' '
614
615        if extra:
616            lend = extra(row)
617            if lend: lend = " " + lend
618
619        print("{}{:#016x} {}  |{}|{}".format(prefix, row, line, chars, lend))
620
621def Ones(x):
622    return (1 << x)-1
623
624def CanonicalAddress(x, TySz):
625    """ Canonicalize an address. That is to say, sign-extend the upper 64-TySz
626        address bits with either 0 or 1 depending on bit 55.
627
628        params:
629            x: The address to modify.
630            TySz: Size of the corresponding VA region.
631    """
632    sign_mask = 1 << 55
633    ptr_mask = Ones(64-TySz)
634    msb_mask = ~ptr_mask
635    sign = x & sign_mask
636    if sign:
637        # Sign-extend
638        return (x | msb_mask) + 2**64
639    else:
640        # Zero-extend
641        return x & ptr_mask
642
643@cache_statically
644def IsDebuggingCore(target: lldb.SBTarget=None):
645    return "mach-o-core" in target.process.GetPluginName().lower()
646