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