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