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