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
143
144def loadLLDB():
145    """ Util function to load lldb python framework in case not available in common include paths.
146    """
147    try:
148        import lldb
149        print 'Found LLDB on path'
150    except:
151        platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split())
152        offset = platdir.find("Contents/Developer")
153        if offset == -1:
154            lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python')
155        else:
156            lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python')
157        if os.path.isdir(lldb_py):
158            sys.path.append(lldb_py)
159            global lldb
160            lldb = __import__('lldb')
161            print 'Found LLDB in SDK'
162        else:
163            print 'Failed to locate lldb.py from', lldb_py
164            sys.exit(-1)
165    return True
166
167class Logger():
168    """ A logging utility """
169    def __init__(self, log_file_path="/tmp/xnu.log"):
170        self.log_file_handle = open(log_file_path, "w+")
171        self.redirect_to_stdout = False
172
173    def log_debug(self, *args):
174        current_timestamp = time.time()
175        debug_line_str = "DEBUG:" + str(current_timestamp) + ":"
176        for arg in args:
177            debug_line_str += " " + str(arg).replace("\n", " ") + ", "
178
179        self.log_file_handle.write(debug_line_str + "\n")
180        if self.redirect_to_stdout :
181            print debug_line_str
182
183    def write(self, line):
184        self.log_debug(line)
185
186
187def sizeof_fmt(num, unit_str='B'):
188    """ format large number into human readable values.
189        convert any number into Kilo, Mega, Giga, Tera format for human understanding.
190        params:
191            num - int : number to be converted
192            unit_str - str : a suffix for unit. defaults to 'B' for bytes.
193        returns:
194            str - formatted string for printing.
195    """
196    for x in ['','K','M','G','T']:
197        if num < 1024.0:
198            return "%3.1f%s%s" % (num, x,unit_str)
199        num /= 1024.0
200    return "%3.1f%s%s" % (num, 'P', unit_str)
201
202def WriteStringToMemoryAddress(stringval, addr):
203    """ write a null terminated string to address.
204        params:
205            stringval: str- string to be written to memory. a '\0' will be added at the end
206            addr : int - address where data is to be written
207        returns:
208            bool - True if successfully written
209    """
210    serr = lldb.SBError()
211    length = len(stringval) + 1
212    format_string = "%ds" % length
213    sdata = struct.pack(format_string,stringval)
214    numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr)
215    if numbytes == length and serr.Success():
216        return True
217    return False
218
219def WriteInt64ToMemoryAddress(intval, addr):
220    """ write a 64 bit integer at an address.
221        params:
222          intval - int - an integer value to be saved
223          addr - int - address where int is to be written
224        returns:
225          bool - True if successfully written.
226    """
227    serr = lldb.SBError()
228    sdata = struct.pack('Q', intval)
229    addr = int(hex(addr).rstrip('L'), 16)
230    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
231    if numbytes == 8 and serr.Success():
232        return True
233    return False
234
235def WritePtrDataToMemoryAddress(intval, addr):
236    """ Write data to pointer size memory.
237        This is equivalent of doing *(&((struct pmap *)addr)) = intval
238        It will identify 32/64 bit kernel and write memory accordingly.
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    if kern.ptrsize == 8:
246        return WriteInt64ToMemoryAddress(intval, addr)
247    else:
248        return WriteInt32ToMemoryAddress(intval, addr)
249
250def WriteInt32ToMemoryAddress(intval, addr):
251    """ write a 32 bit integer at an address.
252        params:
253          intval - int - an integer value to be saved
254          addr - int - address where int is to be written
255        returns:
256          bool - True if successfully written.
257    """
258    serr = lldb.SBError()
259    sdata = struct.pack('I', intval)
260    addr = int(hex(addr).rstrip('L'), 16)
261    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
262    if numbytes == 4 and serr.Success():
263        return True
264    return False
265
266def WriteInt16ToMemoryAddress(intval, addr):
267    """ write a 16 bit integer at an address.
268        params:
269          intval - int - an integer value to be saved
270          addr - int - address where int is to be written
271        returns:
272          bool - True if successfully written.
273    """
274    serr = lldb.SBError()
275    sdata = struct.pack('H', intval)
276    addr = int(hex(addr).rstrip('L'), 16)
277    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
278    if numbytes == 2 and serr.Success():
279        return True
280    return False
281
282def WriteInt8ToMemoryAddress(intval, addr):
283    """ write a 8 bit integer at an address.
284        params:
285          intval - int - an integer value to be saved
286          addr - int - address where int is to be written
287        returns:
288          bool - True if successfully written.
289    """
290    serr = lldb.SBError()
291    sdata = struct.pack('B', intval)
292    addr = int(hex(addr).rstrip('L'), 16)
293    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
294    if numbytes == 1 and serr.Success():
295        return True
296    return False
297
298_enum_cache = {}
299def GetEnumValue(name):
300    """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION  => 0x3
301        params:
302            name : str - name of enum in the format type::name
303        returns:
304            int - value of the particular enum.
305        raises:
306            TypeError - if the enum is not found
307    """
308    name = name.strip()
309    global _enum_cache
310    if name not in _enum_cache:
311        res = lldb.SBCommandReturnObject()
312        lldb.debugger.GetCommandInterpreter().HandleCommand("p/x (`%s`)" % name, res)
313        if not res.Succeeded():
314            raise TypeError("Enum not found with name: " + name)
315        # the result is of format '(int) $481 = 0x00000003\n'
316        _enum_cache[name] = int( res.GetOutput().split('=')[-1].strip(), 16)
317    return _enum_cache[name]
318
319def ResolveFSPath(path):
320    """ expand ~user directories and return absolute path.
321        params: path - str - eg "~rc/Software"
322        returns:
323                str - abs path with user directories and symlinks expanded.
324                str - if path resolution fails then returns the same string back
325    """
326    expanded_path = os.path.expanduser(path)
327    norm_path = os.path.normpath(expanded_path)
328    return norm_path
329
330_dsymlist = {}
331uuid_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)
332def addDSYM(uuid, info):
333    """ add a module by dsym into the target modules.
334        params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
335                info - dict - info dictionary passed from dsymForUUID
336    """
337    global _dsymlist
338    if "DBGSymbolRichExecutable" not in info:
339        print "Error: Unable to find syms for %s" % uuid
340        return False
341    if not uuid in _dsymlist:
342        # add the dsym itself
343        cmd_str = "target modules add --uuid %s" % uuid
344        debuglog(cmd_str)
345        lldb.debugger.HandleCommand(cmd_str)
346        # set up source path
347        #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"]))
348        # modify the list to show we loaded this
349        _dsymlist[uuid] = True
350
351def loadDSYM(uuid, load_address):
352    """ Load an already added symbols to a particular load address
353        params: uuid - str - uuid string
354                load_address - int - address where to load the symbols
355        returns bool:
356            True - if successful
357            False - if failed. possible because uuid is not presently loaded.
358    """
359    if uuid not in _dsymlist:
360        return False
361    cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address)
362    debuglog(cmd_str)
363    lldb.debugger.HandleCommand(cmd_str)
364
365def dsymForUUID(uuid):
366    """ Get dsym informaiton by calling dsymForUUID
367        params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
368        returns:
369            {} - a dictionary holding dsym information printed by dsymForUUID.
370            None - if failed to find information
371    """
372    import subprocess
373    import plistlib
374    output = subprocess.check_output(["/usr/local/bin/dsymForUUID", uuid])
375    if output:
376        # because of <rdar://12713712>
377        #plist = plistlib.readPlistFromString(output)
378        #beginworkaround
379        keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL)
380        plist={}
381        plist[uuid] = {}
382        for item in keyvalue_extract_re.findall(output):
383            plist[uuid][item[0]] = item[1]
384        #endworkaround
385        if plist and plist[uuid]:
386            return plist[uuid]
387    return None
388
389def debuglog(s):
390    """ Print a object in the debug stream
391    """
392    global config
393    if config['debug']:
394      print "DEBUG:",s
395    return None
396