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(f"{lldb.SBDebugger.GetVersionString()}\n") 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 ArgumentStringToAddress(arg_string) -> int: 86 """ converts an argument to an address 87 params: 88 arg_string: str - typically a string passed from the commandline. 89 Accepted inputs: 90 1. A base 2/8/10/16 literal representation, e.g. "0b101"/"0o5"/"5"/"0x5" 91 2. An LLDB expression, e.g. "((char*)foo_ptr + sizeof(bar_type))" 92 returns: 93 int - integer representation of the string 94 """ 95 try: 96 return int(arg_string, 0) 97 except ValueError: 98 val = LazyTarget.GetTarget().chkEvaluateExpression(arg_string) 99 return val.unsigned 100 101def ArgumentStringToInt(arg_string) -> int: 102 """ converts an argument to an int 103 params: 104 arg_string: str - typically a string passed from the commandline. 105 Accepted inputs: 106 1. A base 2/8/10/16 literal representation, e.g. "0b101"/"0o5"/"5"/"0x5" 107 2. An LLDB expression, e.g. "((char*)foo_ptr + sizeof(bar_type))" 108 returns: 109 int - integer representation of the string 110 """ 111 try: 112 return int(arg_string, 0) 113 except ValueError: 114 val = LazyTarget.GetTarget().chkEvaluateExpression(arg_string) 115 return val.signed 116 117def GetLongestMatchOption(searchstr, options=[], ignore_case=True): 118 """ Get longest matched string from set of options. 119 params: 120 searchstr : string of chars to be matched 121 options : array of strings that are to be matched 122 returns: 123 [] - array of matched options. The order of options is same as the arguments. 124 empty array is returned if searchstr does not match any option. 125 example: 126 subcommand = LongestMatch('Rel', ['decode', 'enable', 'reload'], ignore_case=True) 127 print subcommand # prints ['reload'] 128 """ 129 if ignore_case: 130 searchstr = searchstr.lower() 131 found_options = [] 132 for o in options: 133 so = o 134 if ignore_case: 135 so = o.lower() 136 if so == searchstr: 137 return [o] 138 if so.find(searchstr) >=0 : 139 found_options.append(o) 140 return found_options 141 142def GetType(target_type): 143 """ type cast an object to new type. 144 params: 145 target_type - str, ex. 'char', 'uint32_t' etc 146 returns: 147 lldb.SBType - a new Type that can be used as param to lldb.SBValue.Cast() 148 raises: 149 NameError - Incase the type is not identified 150 """ 151 return gettype(target_type) 152 153 154def Cast(obj, target_type): 155 """ Type cast an object to another C type. 156 params: 157 obj - core.value object representing some C construct in lldb 158 target_type - str : ex 'char *' 159 - lldb.SBType : 160 """ 161 return cast(obj, target_type) 162 163def ContainerOf(obj, target_type, field_name): 164 """ Type cast an object to another C type from a pointer to a field. 165 params: 166 obj - core.value object representing some C construct in lldb 167 target_type - str : ex 'struct thread' 168 - lldb.SBType : 169 field_name - the field name within the target_type obj is a pointer to 170 """ 171 return containerof(obj, target_type, field_name) 172 173def loadLLDB(): 174 """ Util function to load lldb python framework in case not available in common include paths. 175 """ 176 try: 177 import lldb 178 print('Found LLDB on path') 179 except: 180 platdir = subprocess.check_output('xcodebuild -version -sdk iphoneos PlatformPath'.split()) 181 offset = platdir.find("Contents/Developer") 182 if offset == -1: 183 lldb_py = os.path.join(os.path.dirname(os.path.dirname(platdir)), 'Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python') 184 else: 185 lldb_py = os.path.join(platdir[0:offset+8], 'SharedFrameworks/LLDB.framework/Versions/A/Resources/Python') 186 if os.path.isdir(lldb_py): 187 sys.path.append(lldb_py) 188 global lldb 189 lldb = __import__('lldb') 190 print('Found LLDB in SDK') 191 else: 192 print('Failed to locate lldb.py from', lldb_py) 193 sys.exit(-1) 194 return True 195 196class Logger(object): 197 """ A logging utility """ 198 def __init__(self, log_file_path="/tmp/xnu.log"): 199 self.log_file_handle = open(log_file_path, "w+") 200 self.redirect_to_stdout = False 201 202 def log_debug(self, *args): 203 current_timestamp = time.time() 204 debug_line_str = "DEBUG:" + str(current_timestamp) + ":" 205 for arg in args: 206 debug_line_str += " " + str(arg).replace("\n", " ") + ", " 207 208 self.log_file_handle.write(debug_line_str + "\n") 209 if self.redirect_to_stdout : 210 print(debug_line_str) 211 212 def write(self, line): 213 self.log_debug(line) 214 215 216def sizeof_fmt(num, unit_str='B'): 217 """ format large number into human readable values. 218 convert any number into Kilo, Mega, Giga, Tera format for human understanding. 219 params: 220 num - int : number to be converted 221 unit_str - str : a suffix for unit. defaults to 'B' for bytes. 222 returns: 223 str - formatted string for printing. 224 """ 225 for x in ['','K','M','G','T']: 226 if num < 1024.0: 227 return "%3.1f%s%s" % (num, x,unit_str) 228 num /= 1024.0 229 return "%3.1f%s%s" % (num, 'P', unit_str) 230 231def WriteStringToMemoryAddress(stringval, addr): 232 """ write a null terminated string to address. 233 params: 234 stringval: str- string to be written to memory. a '\0' will be added at the end 235 addr : int - address where data is to be written 236 returns: 237 bool - True if successfully written 238 """ 239 serr = lldb.SBError() 240 length = len(stringval) + 1 241 format_string = "%ds" % length 242 sdata = struct.pack(format_string,stringval.encode()) 243 numbytes = LazyTarget.GetProcess().WriteMemory(addr, sdata, serr) 244 if numbytes == length and serr.Success(): 245 return True 246 return False 247 248def WriteInt64ToMemoryAddress(intval, addr): 249 """ write a 64 bit integer at an address. 250 params: 251 intval - int - an integer value to be saved 252 addr - int - address where int is to be written 253 returns: 254 bool - True if successfully written. 255 """ 256 serr = lldb.SBError() 257 sdata = struct.pack('Q', intval) 258 addr = int(hex(addr).rstrip('L'), 16) 259 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 260 if numbytes == 8 and serr.Success(): 261 return True 262 return False 263 264def WritePtrDataToMemoryAddress(intval, addr): 265 """ Write data to pointer size memory. 266 This is equivalent of doing *(&((struct pmap *)addr)) = intval 267 It will identify 32/64 bit kernel and write memory accordingly. 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 if kern.ptrsize == 8: 275 return WriteInt64ToMemoryAddress(intval, addr) 276 else: 277 return WriteInt32ToMemoryAddress(intval, addr) 278 279def WriteInt32ToMemoryAddress(intval, addr): 280 """ write a 32 bit integer at an address. 281 params: 282 intval - int - an integer value to be saved 283 addr - int - address where int is to be written 284 returns: 285 bool - True if successfully written. 286 """ 287 serr = lldb.SBError() 288 sdata = struct.pack('I', intval) 289 addr = int(hex(addr).rstrip('L'), 16) 290 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 291 if numbytes == 4 and serr.Success(): 292 return True 293 return False 294 295def WriteInt16ToMemoryAddress(intval, addr): 296 """ write a 16 bit integer at an address. 297 params: 298 intval - int - an integer value to be saved 299 addr - int - address where int is to be written 300 returns: 301 bool - True if successfully written. 302 """ 303 serr = lldb.SBError() 304 sdata = struct.pack('H', intval) 305 addr = int(hex(addr).rstrip('L'), 16) 306 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 307 if numbytes == 2 and serr.Success(): 308 return True 309 return False 310 311def WriteInt8ToMemoryAddress(intval, addr): 312 """ write a 8 bit integer at an address. 313 params: 314 intval - int - an integer value to be saved 315 addr - int - address where int is to be written 316 returns: 317 bool - True if successfully written. 318 """ 319 serr = lldb.SBError() 320 sdata = struct.pack('B', intval) 321 addr = int(hex(addr).rstrip('L'), 16) 322 numbytes = LazyTarget.GetProcess().WriteMemory(addr,sdata, serr) 323 if numbytes == 1 and serr.Success(): 324 return True 325 return False 326 327_enum_cache = {} 328def GetEnumValue(enum_name_or_combined, member_name = None): 329 """ Finds the value of a particular enum define. Ex kdp_req_t::KDP_VERSION => 0x3 330 params: 331 enum_name_or_combined: str 332 name of an enum of the format type::name (legacy) 333 name of an enum type 334 member_name: None, or the name of an enum member 335 (then enum_name_or_combined is a type name). 336 returns: 337 int - value of the particular enum. 338 raises: 339 TypeError - if the enum is not found 340 """ 341 global _enum_cache 342 if member_name is None: 343 enum_name, member_name = enum_name_or_combined.strip().split("::") 344 else: 345 enum_name = enum_name_or_combined 346 347 if enum_name not in _enum_cache: 348 ty = GetType(enum_name) 349 d = {} 350 351 for e in ty.get_enum_members_array(): 352 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 353 d[e.GetName()] = e.GetValueAsSigned() 354 else: 355 d[e.GetName()] = e.GetValueAsUnsigned() 356 357 _enum_cache[enum_name] = d 358 359 return _enum_cache[enum_name][member_name] 360 361def GetEnumValues(enum_name, names): 362 """ Finds the values of a particular set of enum defines. 363 params: 364 enum_name: str 365 name of an enum type 366 member_name: str list 367 list of fields to resolve 368 returns: 369 int list - value of the particular enum. 370 raises: 371 TypeError - if the enum is not found 372 """ 373 return [GetEnumValue(enum_name, x) for x in names] 374 375_enum_name_cache = {} 376def GetEnumName(enum_name, value, prefix = ''): 377 """ Finds symbolic name for a particular enum integer value 378 params: 379 enum_name - str: name of an enum type 380 value - value: the value to decode 381 prefix - str: a prefix to strip from the tag 382 returns: 383 str - the symbolic name or UNKNOWN(value) 384 raises: 385 TypeError - if the enum is not found 386 """ 387 global _enum_name_cache 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 key = int(value) 406 else: 407 key = unsigned(value) 408 409 name = ty_dict.get(key, "UNKNOWN({:d})".format(key)) 410 if name.startswith(prefix): 411 return name[len(prefix):] 412 return name 413 414def GetOptionString(enum_name, value, prefix = ''): 415 """ Tries to format a given value as a combination of options 416 params: 417 enum_name - str: name of an enum type 418 value - value: the value to decode 419 prefix - str: a prefix to strip from the tag 420 raises: 421 TypeError - if the enum is not found 422 """ 423 ty = GetType(enum_name) 424 425 if enum_name not in _enum_name_cache: 426 ty_dict = {} 427 428 for e in ty.get_enum_members_array(): 429 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 430 ty_dict[e.GetValueAsSigned()] = e.GetName() 431 else: 432 ty_dict[e.GetValueAsUnsigned()] = e.GetName() 433 434 _enum_name_cache[enum_name] = ty_dict 435 else: 436 ty_dict = _enum_name_cache[enum_name] 437 438 if ty.GetTypeFlags() & lldb.eTypeIsSigned: 439 v = int(value) 440 else: 441 v = unsigned(value) 442 443 flags = [] 444 for bit in range(0, 64): 445 mask = 1 << bit 446 if not v & mask: continue 447 if mask not in ty_dict: continue 448 name = ty_dict[mask] 449 if name.startswith(prefix): 450 name = name[len(prefix):] 451 flags.append(name) 452 v &= ~mask 453 if v: 454 flags.append("UNKNOWN({:d})".format(v)) 455 return " ".join(flags) 456 457def ResolveFSPath(path): 458 """ expand ~user directories and return absolute path. 459 params: path - str - eg "~rc/Software" 460 returns: 461 str - abs path with user directories and symlinks expanded. 462 str - if path resolution fails then returns the same string back 463 """ 464 expanded_path = os.path.expanduser(path) 465 norm_path = os.path.normpath(expanded_path) 466 return norm_path 467 468_dsymlist = {} 469uuid_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) 470def addDSYM(uuid, info): 471 """ add a module by dsym into the target modules. 472 params: uuid - str - uuid string eg. 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E 473 info - dict - info dictionary passed from dsymForUUID 474 """ 475 global _dsymlist 476 if "DBGSymbolRichExecutable" not in info: 477 print("Error: Unable to find syms for %s" % uuid) 478 return False 479 if not uuid in _dsymlist: 480 # add the dsym itself 481 cmd_str = "target modules add --uuid %s" % uuid 482 debuglog(cmd_str) 483 lldb.debugger.HandleCommand(cmd_str) 484 # set up source path 485 #lldb.debugger.HandleCommand("settings append target.source-map %s %s" % (info["DBGBuildSourcePath"], info["DBGSourcePath"])) 486 # modify the list to show we loaded this 487 _dsymlist[uuid] = True 488 489def loadDSYM(uuid, load_address, sections=[]): 490 """ Load an already added symbols to a particular load address 491 params: uuid - str - uuid string 492 load_address - int - address where to load the symbols 493 returns bool: 494 True - if successful 495 False - if failed. possible because uuid is not presently loaded. 496 """ 497 if uuid not in _dsymlist: 498 return False 499 if not sections: 500 cmd_str = "target modules load --uuid %s --slide %d" % ( uuid, load_address) 501 debuglog(cmd_str) 502 else: 503 cmd_str = "target modules load --uuid {} ".format(uuid) 504 sections_str = "" 505 for s in sections: 506 sections_str += " {} {:#0x} ".format(s.name, s.vmaddr) 507 cmd_str += sections_str 508 debuglog(cmd_str) 509 510 lldb.debugger.HandleCommand(cmd_str) 511 return True 512 513 514def RunShellCommand(command): 515 """ Run a shell command in subprocess. 516 params: command with arguments to run (a list is preferred, but a string is also supported) 517 returns: (exit_code, stdout, stderr) 518 """ 519 import subprocess 520 521 if not isinstance(command, list): 522 import shlex 523 command = shlex.split(command) 524 525 result = subprocess.run(command, capture_output=True, encoding="utf-8") 526 returncode = result.returncode 527 stdout = result.stdout 528 stderr = result.stderr 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, start=0, desc="", marks={}, prefix=" ", extra=None): 583 """ print on stdout "hexdump -C < data" like output 584 params: 585 data - bytearray or array of int where each int < 255 586 start - 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 extra - a function returning some extra things to add on the line 590 """ 591 592 if desc: 593 print("{}:".format(desc)) 594 595 end = start + len(data) 596 597 for row in range(start & -16, end, 16): 598 line = "" 599 chars = "" 600 lend = "" 601 602 for col in range(16): 603 addr = row + col 604 605 if col == 8: 606 line += " " 607 if start <= addr < end: 608 b = data[addr - start] 609 line += "{}{:02x}".format(marks.get(addr, ' '), b) 610 chars += chr(b) if 0x20 <= b < 0x80 else '.' 611 else: 612 line += " " 613 chars += ' ' 614 615 if extra: 616 lend = extra(row) 617 if lend: lend = " " + lend 618 619 print("{}{:#016x} {} |{}|{}".format(prefix, row, line, chars, lend)) 620 621def Ones(x): 622 return (1 << x)-1 623 624def StripPAC(x, TySz): 625 sign_mask = 1 << 55 626 ptr_mask = Ones(64-TySz) 627 pac_mask = ~ptr_mask 628 sign = x & sign_mask 629 if sign: 630 return (x | pac_mask) + 2**64 631 else: 632 return x & ptr_mask 633 634@cache_statically 635def IsDebuggingCore(target: lldb.SBTarget=None): 636 return "mach-o-core" in target.process.GetPluginName().lower() 637