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