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