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