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