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