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    return retval
31
32def EnableLLDBAPILogging():
33    """ Enable file based logging for lldb and also provide essential information about what information
34        to include when filing a bug with lldb or xnu.
35    """
36    logfile_name = "/tmp/lldb.%d.log" % int(time.time())
37    enable_log_base_cmd = "log enable --file %s " % logfile_name
38    cmd_str = enable_log_base_cmd + ' lldb api'
39    print cmd_str
40    print lldb_run_command(cmd_str)
41    cmd_str = enable_log_base_cmd + ' gdb-remote packets'
42    print cmd_str
43    print lldb_run_command(cmd_str)
44    cmd_str = enable_log_base_cmd + ' kdp-remote packets'
45    print cmd_str
46    print lldb_run_command(cmd_str)
47    print lldb_run_command("verison")
48    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
49    print "Please make sure to provide the output of 'verison', 'image list' and output of command that failed."
50    return
51
52def GetConnectionProtocol():
53    """ Returns a string representing what kind of connection is used for debugging the target.
54        params: None
55        returns:
56            str - connection type. One of ("core","kdp","gdb", "unknown")
57    """
58    retval = "unknown"
59    process_plugin_name = LazyTarget.GetProcess().GetPluginName().lower()
60    if "kdp" in process_plugin_name:
61        retval = "kdp"
62    elif "gdb" in process_plugin_name:
63        retval = "gdb"
64    elif "mach-o" in process_plugin_name and "core" in process_plugin_name:
65        retval = "core"
66    return retval
67
68def SBValueToPointer(sbval):
69    """ Helper function for getting pointer value from an object of pointer type.
70        ex. void *astring = 0x12345
71        use SBValueToPointer(astring_val) to get 0x12345
72        params: sbval - value object of type '<type> *'
73        returns: int - pointer value as an int.
74    """
75    if type(sbval) == core.value:
76        sbval = sbval.GetSBValue()
77    if sbval.IsPointerType():
78        return sbval.GetValueAsUnsigned()
79    else:
80        return int(sbval.GetAddress())
81
82def ArgumentStringToInt(arg_string):
83    """ convert '1234' or '0x123' to int
84        params:
85          arg_string: str - typically string passed from commandline. ex '1234' or '0xA12CD'
86        returns:
87          int - integer representation of the string
88    """
89    arg_string = arg_string.strip()
90    if arg_string.find('0x') >=0:
91        return int(arg_string, 16)
92    else:
93        return int(arg_string)
94
95def GetLongestMatchOption(searchstr, options=[], ignore_case=True):
96    """ Get longest matched string from set of options.
97        params:
98            searchstr : string of chars to be matched
99            options : array of strings that are to be matched
100        returns:
101            [] - array of matched options. The order of options is same as the arguments.
102                 empty array is returned if searchstr does not match any option.
103        example:
104            subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True)
105            print subcommand # prints ['reload']
106    """
107    if ignore_case:
108        searchstr = searchstr.lower()
109    found_options = []
110    for o in options:
111        so = o
112        if ignore_case:
113            so = o.lower()
114        if so.find(searchstr) >=0 :
115            found_options.append(o)
116    return found_options
117
118def GetType(target_type):
119    """ type cast an object to new type.
120        params:
121            target_type - str, ex. 'char', 'uint32_t' etc
122        returns:
123            lldb.SBType - a new Type that can be used as param to  lldb.SBValue.Cast()
124        raises:
125            NameError  - Incase the type is not identified
126    """
127    return gettype(target_type)
128
129
130def Cast(obj, target_type):
131    """ Type cast an object to another C type.
132        params:
133            obj - core.value  object representing some C construct in lldb
134            target_type - str : ex 'char *'
135                        - lldb.SBType :
136    """
137    return cast(obj, target_type)
138
139
140def loadLLDB():
141    """ Util function to load lldb python framework in case not available in common include paths.
142    """
143    try:
144        import lldb
145        print 'Found LLDB on path'
146    except:
147        platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split())
148        offset = platdir.find("Contents/Developer")
149        if offset == -1:
150            lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python')
151        else:
152            lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python')
153        if os.path.isdir(lldb_py):
154            sys.path.append(lldb_py)
155            global lldb
156            lldb = __import__('lldb')
157            print 'Found LLDB in SDK'
158        else:
159            print 'Failed to locate lldb.py from', lldb_py
160            sys.exit(-1)
161    return True
162
163class Logger():
164    """ A logging utility """
165    def __init__(self, log_file_path="/tmp/xnu.log"):
166        self.log_file_handle = open(log_file_path, "w+")
167        self.redirect_to_stdout = False
168
169    def log_debug(self, *args):
170        current_timestamp = time.time()
171        debug_line_str = "DEBUG:" + str(current_timestamp) + ":"
172        for arg in args:
173            debug_line_str += " " + str(arg).replace("\n", " ") + ", "
174
175        self.log_file_handle.write(debug_line_str + "\n")
176        if self.redirect_to_stdout :
177            print debug_line_str
178
179    def write(self, line):
180        self.log_debug(line)
181
182
183def sizeof_fmt(num, unit_str='B'):
184    """ format large number into human readable values.
185        convert any number into Kilo, Mega, Giga, Tera format for human understanding.
186        params:
187            num - int : number to be converted
188            unit_str - str : a suffix for unit. defaults to 'B' for bytes.
189        returns:
190            str - formatted string for printing.
191    """
192    for x in ['','K','M','G','T']:
193        if num < 1024.0:
194            return "%3.1f%s%s" % (num, x,unit_str)
195        num /= 1024.0
196    return "%3.1f%s%s" % (num, 'P', unit_str)
197
198def WriteStringToMemoryAddress(stringval, addr):
199    """ write a null terminated string to address.
200        params:
201            stringval: str- string to be written to memory. a '\0' will be added at the end
202            addr : int - address where data is to be written
203        returns:
204            bool - True if successfully written
205    """
206    serr = lldb.SBError()
207    length = len(stringval) + 1
208    format_string = "%ds" % length
209    sdata = struct.pack(format_string,stringval)
210    numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr)
211    if numbytes == length and serr.Success():
212        return True
213    return False
214
215def WriteInt64ToMemoryAddress(intval, addr):
216    """ write a 64 bit integer at an address.
217        params:
218          intval - int - an integer value to be saved
219          addr - int - address where int is to be written
220        returns:
221          bool - True if successfully written.
222    """
223    serr = lldb.SBError()
224    sdata = struct.pack('Q', intval)
225    addr = int(hex(addr).rstrip('L'), 16)
226    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
227    if numbytes == 8 and serr.Success():
228        return True
229    return False
230
231def WritePtrDataToMemoryAddress(intval, addr):
232    """ Write data to pointer size memory.
233        This is equivalent of doing *(&((struct pmap *)addr)) = intval
234        It will identify 32/64 bit kernel and write memory accordingly.
235        params:
236          intval - int - an integer value to be saved
237          addr - int - address where int is to be written
238        returns:
239          bool - True if successfully written.
240    """
241    if kern.ptrsize == 8:
242        return WriteInt64ToMemoryAddress(intval, addr)
243    else:
244        return WriteInt32ToMemoryAddress(intval, addr)
245
246def WriteInt32ToMemoryAddress(intval, addr):
247    """ write a 32 bit integer at an address.
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    serr = lldb.SBError()
255    sdata = struct.pack('I', intval)
256    addr = int(hex(addr).rstrip('L'), 16)
257    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
258    if numbytes == 4 and serr.Success():
259        return True
260    return False
261
262def WriteInt16ToMemoryAddress(intval, addr):
263    """ write a 16 bit integer at an address.
264        params:
265          intval - int - an integer value to be saved
266          addr - int - address where int is to be written
267        returns:
268          bool - True if successfully written.
269    """
270    serr = lldb.SBError()
271    sdata = struct.pack('H', intval)
272    addr = int(hex(addr).rstrip('L'), 16)
273    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
274    if numbytes == 2 and serr.Success():
275        return True
276    return False
277
278def WriteInt8ToMemoryAddress(intval, addr):
279    """ write a 8 bit integer at an address.
280        params:
281          intval - int - an integer value to be saved
282          addr - int - address where int is to be written
283        returns:
284          bool - True if successfully written.
285    """
286    serr = lldb.SBError()
287    sdata = struct.pack('B', intval)
288    addr = int(hex(addr).rstrip('L'), 16)
289    numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr)
290    if numbytes == 1 and serr.Success():
291        return True
292    return False
293
294_enum_cache = {}
295def GetEnumValue(name):
296    """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION  => 0x3
297        params:
298            name : str - name of enum in the format type::name
299        returns:
300            int - value of the particular enum.
301        raises:
302            TypeError - if the enum is not found
303    """
304    name = name.strip()
305    global _enum_cache
306    if name not in _enum_cache:
307        res = lldb.SBCommandReturnObject()
308        lldb.debugger.GetCommandInterpreter().HandleCommand("p/x (`%s`)" % name, res)
309        if not res.Succeeded():
310            raise TypeError("Enum not found with name: " + name)
311        # the result is of format '(int) $481 = 0x00000003\n'
312        _enum_cache[name] = int( res.GetOutput().split('=')[-1].strip(), 16)
313    return _enum_cache[name]
314
315def ResolveFSPath(path):
316    """ expand ~user directories and return absolute path.
317        params: path - str - eg "~rc/Software"
318        returns:
319                str - abs path with user directories and symlinks expanded.
320                str - if path resolution fails then returns the same string back
321    """
322    expanded_path = os.path.expanduser(path)
323    norm_path = os.path.normpath(expanded_path)
324    return norm_path
325
326_dsymlist = {}
327uuid_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)
328def addDSYM(uuid, info):
329    """ add a module by dsym into the target modules.
330        params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
331                info - dict - info dictionary passed from dsymForUUID
332    """
333    global _dsymlist
334    if "DBGSymbolRichExecutable" not in info:
335        print "Error: Unable to find syms for %s" % uuid
336        return False
337    if not uuid in _dsymlist:
338        # add the dsym itself
339        cmd_str = "target modules add --uuid %s" % uuid
340        debuglog(cmd_str)
341        lldb.debugger.HandleCommand(cmd_str)
342        # set up source path
343        #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"]))
344        # modify the list to show we loaded this
345        _dsymlist[uuid] = True
346
347def loadDSYM(uuid, load_address):
348    """ Load an already added symbols to a particular load address
349        params: uuid - str - uuid string
350                load_address - int - address where to load the symbols
351        returns bool:
352            True - if successful
353            False - if failed. possible because uuid is not presently loaded.
354    """
355    if uuid not in _dsymlist:
356        return False
357    cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address)
358    debuglog(cmd_str)
359    lldb.debugger.HandleCommand(cmd_str)
360
361def dsymForUUID(uuid):
362    """ Get dsym informaiton by calling dsymForUUID
363        params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E
364        returns:
365            {} - a dictionary holding dsym information printed by dsymForUUID.
366            None - if failed to find information
367    """
368    import subprocess
369    import plistlib
370    output = subprocess.check_output(["/usr/local/bin/dsymForUUID", uuid])
371    if output:
372        # because of <rdar://12713712>
373        #plist = plistlib.readPlistFromString(output)
374        #beginworkaround
375        keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL)
376        plist={}
377        plist[uuid] = {}
378        for item in keyvalue_extract_re.findall(output):
379            plist[uuid][item[0]] = item[1]
380        #endworkaround
381        if plist and plist[uuid]:
382            return plist[uuid]
383    return None
384
385def debuglog(s):
386    """ Print a object in the debug stream
387    """
388    global config
389    if config['debug']:
390      print "DEBUG:",s
391    return None
392