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