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""" 6from __future__ import absolute_import, print_function 7 8from builtins import hex 9from builtins import range 10from builtins import object 11 12import sys, re, time, os, time 13import lldb 14import struct 15import six 16 17from core import PY3 18from core.cvalue import * 19from core.configuration import * 20from core.lazytarget import * 21 22#DONOTTOUCHME: exclusive use for lldb_run_command only. 23lldb_run_command_state = {'active':False} 24 25def lldb_run_command(cmdstring): 26 """ Run a lldb command and get the string output. 27 params: cmdstring - str : lldb command string which could be executed at (lldb) prompt. (eg. "register read") 28 returns: str - output of command. it may be "" in case if command did not return any output. 29 """ 30 global lldb_run_command_state 31 retval ="" 32 res = lldb.SBCommandReturnObject() 33 # set special attribute to notify xnu framework to not print on stdout 34 lldb_run_command_state['active'] = True 35 lldb.debugger.GetCommandInterpreter().HandleCommand(cmdstring, res) 36 lldb_run_command_state['active'] = False 37 if res.Succeeded(): 38 retval = res.GetOutput() 39 else: 40 retval = "ERROR:" + res.GetError() 41 return retval 42 43def EnableLLDBAPILogging(): 44 """ Enable file based logging for lldb and also provide essential information about what information 45 to include when filing a bug with lldb or xnu. 46 """ 47 logfile_name = "/tmp/lldb.%d.log" % int(time.time()) 48 enable_log_base_cmd = "log enable --file %s " % logfile_name 49 cmd_str = enable_log_base_cmd + ' lldb api' 50 print(cmd_str) 51 print(lldb_run_command(cmd_str)) 52 cmd_str = enable_log_base_cmd + ' gdb-remote packets' 53 print(cmd_str) 54 print(lldb_run_command(cmd_str)) 55 cmd_str = enable_log_base_cmd + ' kdp-remote packets' 56 print(cmd_str) 57 print(lldb_run_command(cmd_str)) 58 print(lldb_run_command("version")) 59 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) 60 print("Please make sure to provide the output of 'version', 'image list' and output of command that failed.") 61 return 62 63def GetConnectionProtocol(): 64 """ Returns a string representing what kind of connection is used for debugging the target. 65 params: None 66 returns: 67 str - connection type. One of ("core","kdp","gdb", "unknown") 68 """ 69 retval = "unknown" 70 process_plugin_name = LazyTarget.GetProcess().GetPluginName().lower() 71 if "kdp" in process_plugin_name: 72 retval = "kdp" 73 elif "gdb" in process_plugin_name: 74 retval = "gdb" 75 elif "mach-o" in process_plugin_name and "core" in process_plugin_name: 76 retval = "core" 77 return retval 78 79def SBValueToPointer(sbval): 80 """ Helper function for getting pointer value from an object of pointer type. 81 ex. void *astring = 0x12345 82 use SBValueToPointer(astring_val) to get 0x12345 83 params: sbval - value object of type '<type> *' 84 returns: int - pointer value as an int. 85 """ 86 if type(sbval) == core.value: 87 sbval = sbval.GetSBValue() 88 if sbval.IsPointerType(): 89 return sbval.GetValueAsUnsigned() 90 else: 91 return int(sbval.GetAddress()) 92 93def ArgumentStringToInt(arg_string): 94 """ convert '1234' or '0x123' to int 95 params: 96 arg_string: str - typically string passed from commandline. ex '1234' or '0xA12CD' 97 returns: 98 int - integer representation of the string 99 """ 100 arg_string = arg_string.strip() 101 if arg_string.find('0x') >=0: 102 return int(arg_string, 16) 103 else: 104 return int(arg_string) 105 106def GetLongestMatchOption(searchstr, options=[], ignore_case=True): 107 """ Get longest matched string from set of options. 108 params: 109 searchstr : string of chars to be matched 110 options : array of strings that are to be matched 111 returns: 112 [] - array of matched options. The order of options is same as the arguments. 113 empty array is returned if searchstr does not match any option. 114 example: 115 subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True) 116 print subcommand # prints ['reload'] 117 """ 118 if ignore_case: 119 searchstr = searchstr.lower() 120 found_options = [] 121 for o in options: 122 so = o 123 if ignore_case: 124 so = o.lower() 125 if so == searchstr: 126 return [o] 127 if so.find(searchstr) >=0 : 128 found_options.append(o) 129 return found_options 130 131def GetType(target_type): 132 """ type cast an object to new type. 133 params: 134 target_type - str, ex. 'char', 'uint32_t' etc 135 returns: 136 lldb.SBType - a new Type that can be used as param to lldb.SBValue.Cast() 137 raises: 138 NameError - Incase the type is not identified 139 """ 140 return gettype(target_type) 141 142 143def Cast(obj, target_type): 144 """ Type cast an object to another C type. 145 params: 146 obj - core.value object representing some C construct in lldb 147 target_type - str : ex 'char *' 148 - lldb.SBType : 149 """ 150 return cast(obj, target_type) 151 152def ContainerOf(obj, target_type, field_name): 153 """ Type cast an object to another C type from a pointer to a field. 154 params: 155 obj - core.value object representing some C construct in lldb 156 target_type - str : ex 'struct thread' 157 - lldb.SBType : 158 field_name - the field name within the target_type obj is a pointer to 159 """ 160 return containerof(obj, target_type, field_name) 161 162def loadLLDB(): 163 """ Util function to load lldb python framework in case not available in common include paths. 164 """ 165 try: 166 import lldb 167 print('Found LLDB on path') 168 except: 169 platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split()) 170 offset = platdir.find("Contents/Developer") 171 if offset == -1: 172 lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python') 173 else: 174 lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python') 175 if os.path.isdir(lldb_py): 176 sys.path.append(lldb_py) 177 global lldb 178 lldb = __import__('lldb') 179 print('Found LLDB in SDK') 180 else: 181 print('Failed to locate lldb.py from', lldb_py) 182 sys.exit(-1) 183 return True 184 185class Logger(object): 186 """ A logging utility """ 187 def __init__(self, log_file_path="/tmp/xnu.log"): 188 self.log_file_handle = open(log_file_path, "w+") 189 self.redirect_to_stdout = False 190 191 def log_debug(self, *args): 192 current_timestamp = time.time() 193 debug_line_str = "DEBUG:" + str(current_timestamp) + ":" 194 for arg in args: 195 debug_line_str += " " + str(arg).replace("\n", " ") + ", " 196 197 self.log_file_handle.write(debug_line_str + "\n") 198 if self.redirect_to_stdout : 199 print(debug_line_str) 200 201 def write(self, line): 202 self.log_debug(line) 203 204 205def sizeof_fmt(num, unit_str='B'): 206 """ format large number into human readable values. 207 convert any number into Kilo, Mega, Giga, Tera format for human understanding. 208 params: 209 num - int : number to be converted 210 unit_str - str : a suffix for unit. defaults to 'B' for bytes. 211 returns: 212 str - formatted string for printing. 213 """ 214 for x in ['','K','M','G','T']: 215 if num < 1024.0: 216 return "%3.1f%s%s" % (num, x,unit_str) 217 num /= 1024.0 218 return "%3.1f%s%s" % (num, 'P', unit_str) 219 220def WriteStringToMemoryAddress(stringval, addr): 221 """ write a null terminated string to address. 222 params: 223 stringval: str- string to be written to memory. a '\0' will be added at the end 224 addr : int - address where data is to be written 225 returns: 226 bool - True if successfully written 227 """ 228 serr = lldb.SBError() 229 length = len(stringval) + 1 230 format_string = "%ds" % length 231 sdata = struct.pack(format_string,stringval) 232 numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr) 233 if numbytes == length and serr.Success(): 234 return True 235 return False 236 237def WriteInt64ToMemoryAddress(intval, addr): 238 """ write a 64 bit integer at an address. 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 serr = lldb.SBError() 246 sdata = struct.pack('Q', intval) 247 addr = int(hex(addr).rstrip('L'), 16) 248 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 249 if numbytes == 8 and serr.Success(): 250 return True 251 return False 252 253def WritePtrDataToMemoryAddress(intval, addr): 254 """ Write data to pointer size memory. 255 This is equivalent of doing *(&((struct pmap *)addr)) = intval 256 It will identify 32/64 bit kernel and write memory accordingly. 257 params: 258 intval - int - an integer value to be saved 259 addr - int - address where int is to be written 260 returns: 261 bool - True if successfully written. 262 """ 263 if kern.ptrsize == 8: 264 return WriteInt64ToMemoryAddress(intval, addr) 265 else: 266 return WriteInt32ToMemoryAddress(intval, addr) 267 268def WriteInt32ToMemoryAddress(intval, addr): 269 """ write a 32 bit integer at an address. 270 params: 271 intval - int - an integer value to be saved 272 addr - int - address where int is to be written 273 returns: 274 bool - True if successfully written. 275 """ 276 serr = lldb.SBError() 277 sdata = struct.pack('I', intval) 278 addr = int(hex(addr).rstrip('L'), 16) 279 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 280 if numbytes == 4 and serr.Success(): 281 return True 282 return False 283 284def WriteInt16ToMemoryAddress(intval, addr): 285 """ write a 16 bit integer at an address. 286 params: 287 intval - int - an integer value to be saved 288 addr - int - address where int is to be written 289 returns: 290 bool - True if successfully written. 291 """ 292 serr = lldb.SBError() 293 sdata = struct.pack('H', intval) 294 addr = int(hex(addr).rstrip('L'), 16) 295 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 296 if numbytes == 2 and serr.Success(): 297 return True 298 return False 299 300def WriteInt8ToMemoryAddress(intval, addr): 301 """ write a 8 bit integer at an address. 302 params: 303 intval - int - an integer value to be saved 304 addr - int - address where int is to be written 305 returns: 306 bool - True if successfully written. 307 """ 308 serr = lldb.SBError() 309 sdata = struct.pack('B', intval) 310 addr = int(hex(addr).rstrip('L'), 16) 311 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 312 if numbytes == 1 and serr.Success(): 313 return True 314 return False 315 316_enum_cache = {} 317def GetEnumValue(enum_name_or_combined, member_name = None): 318 """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION => 0x3 319 params: 320 enum_name_or_combined: str 321 name of an enum of the format type::name (legacy) 322 name of an enum type 323 member_name: None, or the name of an enum member 324 (then enum_name_or_combined is a type name). 325 returns: 326 int - value of the particular enum. 327 raises: 328 TypeError - if the enum is not found 329 """ 330 global _enum_cache 331 if member_name is None: 332 enum_name, member_name = enum_name_or_combined.strip().split("::") 333 else: 334 enum_name = enum_name_or_combined 335 336 if enum_name not in _enum_cache: 337 ty = GetType(enum_name) 338 d = {} 339 340 for e in ty.get_enum_members_array(): 341 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 342 d[e.GetName()] = e.GetValueAsSigned() 343 else: 344 d[e.GetName()] = e.GetValueAsUnsigned() 345 346 _enum_cache[enum_name] = d 347 348 return _enum_cache[enum_name][member_name] 349 350def GetEnumValues(enum_name, names): 351 """ Finds the values of a particular set of enum defines. 352 params: 353 enum_name: str 354 name of an enum type 355 member_name: str list 356 list of fields to resolve 357 returns: 358 int list - value of the particular enum. 359 raises: 360 TypeError - if the enum is not found 361 """ 362 return [GetEnumValue(enum_name, x) for x in names] 363 364_enum_name_cache = {} 365def GetEnumName(enum_name, value, prefix = ''): 366 """ Finds symbolic name for a particular enum integer value 367 params: 368 enum_name - str: name of an enum type 369 value - value: the value to decode 370 prefix - str: a prefix to strip from the tag 371 returns: 372 str - the symbolic name or UNKNOWN(value) 373 raises: 374 TypeError - if the enum is not found 375 """ 376 global _enum_name_cache 377 378 ty = GetType(enum_name) 379 380 if enum_name not in _enum_name_cache: 381 ty_dict = {} 382 383 for e in ty.get_enum_members_array(): 384 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 385 ty_dict[e.GetValueAsSigned()] = e.GetName() 386 else: 387 ty_dict[e.GetValueAsUnsigned()] = e.GetName() 388 389 _enum_name_cache[enum_name] = ty_dict 390 else: 391 ty_dict = _enum_name_cache[enum_name] 392 393 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 394 key = int(value) 395 else: 396 key = unsigned(value) 397 398 name = ty_dict.get(key, "UNKNOWN({:d})".format(key)) 399 if name.startswith(prefix): 400 return name[len(prefix):] 401 return name 402 403def GetOptionString(enum_name, value, prefix = ''): 404 """ Tries to format a given value as a combination of options 405 params: 406 enum_name - str: name of an enum type 407 value - value: the value to decode 408 prefix - str: a prefix to strip from the tag 409 raises: 410 TypeError - if the enum is not found 411 """ 412 ty = GetType(enum_name) 413 414 if enum_name not in _enum_name_cache: 415 ty_dict = {} 416 417 for e in ty.get_enum_members_array(): 418 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 419 ty_dict[e.GetValueAsSigned()] = e.GetName() 420 else: 421 ty_dict[e.GetValueAsUnsigned()] = e.GetName() 422 423 _enum_name_cache[enum_name] = ty_dict 424 else: 425 ty_dict = _enum_name_cache[enum_name] 426 427 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 428 v = int(value) 429 else: 430 v = unsigned(value) 431 432 flags = [] 433 for bit in range(0, 64): 434 mask = 1 << bit 435 if not v & mask: continue 436 if mask not in ty_dict: continue 437 name = ty_dict[mask] 438 if name.startswith(prefix): 439 name = name[len(prefix):] 440 flags.append(name) 441 v &= ~mask 442 if v: 443 flags.append("UNKNOWN({:d})".format(v)) 444 return " ".join(flags) 445 446def ResolveFSPath(path): 447 """ expand ~user directories and return absolute path. 448 params: path - str - eg "~rc/Software" 449 returns: 450 str - abs path with user directories and symlinks expanded. 451 str - if path resolution fails then returns the same string back 452 """ 453 expanded_path = os.path.expanduser(path) 454 norm_path = os.path.normpath(expanded_path) 455 return norm_path 456 457_dsymlist = {} 458uuid_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) 459def addDSYM(uuid, info): 460 """ add a module by dsym into the target modules. 461 params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E 462 info - dict - info dictionary passed from dsymForUUID 463 """ 464 global _dsymlist 465 if "DBGSymbolRichExecutable" not in info: 466 print("Error: Unable to find syms for %s" % uuid) 467 return False 468 if not uuid in _dsymlist: 469 # add the dsym itself 470 cmd_str = "target modules add --uuid %s" % uuid 471 debuglog(cmd_str) 472 lldb.debugger.HandleCommand(cmd_str) 473 # set up source path 474 #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"])) 475 # modify the list to show we loaded this 476 _dsymlist[uuid] = True 477 478def loadDSYM(uuid, load_address, sections=[]): 479 """ Load an already added symbols to a particular load address 480 params: uuid - str - uuid string 481 load_address - int - address where to load the symbols 482 returns bool: 483 True - if successful 484 False - if failed. possible because uuid is not presently loaded. 485 """ 486 if uuid not in _dsymlist: 487 return False 488 if not sections: 489 cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address) 490 debuglog(cmd_str) 491 else: 492 cmd_str = "target modules load --uuid {} ".format(uuid) 493 sections_str = "" 494 for s in sections: 495 sections_str += " {} {:#0x} ".format(s.name, s.vmaddr) 496 cmd_str += sections_str 497 debuglog(cmd_str) 498 499 lldb.debugger.HandleCommand(cmd_str) 500 return True 501 502 503def RunShellCommand(command): 504 """ Run a shell command in subprocess. 505 params: command with arguments to run (a list is preferred, but a string is also supported) 506 returns: (exit_code, stdout, stderr) 507 """ 508 import subprocess 509 510 if not isinstance(command, list): 511 import shlex 512 command = shlex.split(command) 513 514 if PY3: 515 result = subprocess.run(command, capture_output=True, encoding="utf-8") 516 returncode = result.returncode 517 stdout = result.stdout 518 stderr = result.stderr 519 else: 520 try: 521 process = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 522 stdout, stderr = process.communicate() 523 returncode = process.returncode 524 except OSError as e: 525 # This usually happens if the command cannot be found (e.g. ["derp"]), NOT because the command failed 526 returncode = 255 527 stdout = "" 528 stderr = str(e) 529 530 if returncode != 0: 531 print("Failed to run command. Command: {}, " 532 "exit code: {}, stdout: '{}', stderr: '{}'".format(command, returncode, stdout, stderr)) 533 534 return (returncode, stdout, stderr) 535 536def dsymForUUID(uuid): 537 """ Get dsym informaiton by calling dsymForUUID 538 params: uuid - str - uuid string from executable. eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E 539 returns: 540 {} - a dictionary holding dsym information printed by dsymForUUID. 541 None - if failed to find information 542 """ 543 import plistlib 544 rc, output, _ = RunShellCommand(["/usr/local/bin/dsymForUUID", "--copyExecutable", uuid]) 545 if rc != 0: 546 return None 547 548 if output: 549 # because of <rdar://12713712> 550 #plist = plistlib.readPlistFromString(output) 551 #beginworkaround 552 keyvalue_extract_re = re.compile("<key>(.*?)</key>\s*<string>(.*?)</string>",re.IGNORECASE|re.MULTILINE|re.DOTALL) 553 plist={} 554 plist[uuid] = {} 555 for item in keyvalue_extract_re.findall(output): 556 plist[uuid][item[0]] = item[1] 557 #endworkaround 558 if plist and plist[uuid]: 559 return plist[uuid] 560 return None 561 562def debuglog(s): 563 """ Print a object in the debug stream 564 """ 565 global config 566 if config['debug']: 567 print("DEBUG:",s) 568 return None 569 570def IsAppleInternal(): 571 """ check if apple_internal modules are available 572 returns: True if apple_internal module is present 573 """ 574 import imp 575 try: 576 imp.find_module("apple_internal") 577 retval = True 578 except ImportError: 579 retval = False 580 return retval 581 582def print_hex_data(data, begin_offset=0, desc="", marks={}): 583 """ print on stdout "hexdump -C < data" like output 584 params: 585 data - bytearray or array of int where each int < 255 586 begin_offset - int offset that should be printed in left column 587 desc - str optional description to print on the first line to describe data 588 mark - dictionary of markers 589 """ 590 if desc: 591 print("{}:".format(desc)) 592 index = 0 593 total_len = len(data) 594 hex_buf = "" 595 char_buf = "" 596 while index < total_len: 597 if begin_offset + index in marks: 598 hex_buf += marks[begin_offset + index] 599 hex_buf += "{:02x}".format(data[index]) 600 else: 601 hex_buf += " {:02x}".format(data[index]) 602 if data[index] < 0x20 or data[index] > 0x7e: 603 char_buf += "." 604 else: 605 char_buf += "{:c}".format(data[index]) 606 index += 1 607 if index and index < total_len and index % 8 == 0: 608 hex_buf += " " 609 if index > 1 and index < total_len and (index % 16) == 0: 610 print("{:08x} {: <50s} |{: <16s}|".format(begin_offset + index - 16, hex_buf, char_buf)) 611 hex_buf = "" 612 char_buf = "" 613 print("{:08x} {: <50s} |{: <16s}|".format(begin_offset + index - 16, hex_buf, char_buf)) 614 return 615 616def Ones(x): 617 return (1 << x)-1 618 619def StripPAC(x, TySz): 620 sign_mask = 1 << 55 621 ptr_mask = Ones(64-TySz) 622 pac_mask = ~ptr_mask 623 sign = x & sign_mask 624 if sign: 625 return (x | pac_mask) + 2**64 626 else: 627 return x & ptr_mask 628