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