1import sys, os, re, time, getopt, shlex, inspect, xnudefines 2import lldb 3import uuid 4import base64 5import json 6from importlib import reload 7from functools import wraps 8from ctypes import c_ulonglong as uint64_t 9from ctypes import c_void_p as voidptr_t 10import core 11from core import caching 12from core.standard import * 13from core.configuration import * 14from core.kernelcore import * 15from utils import * 16from core.lazytarget import * 17 18MODULE_NAME=__name__ 19 20""" Kernel Debugging macros for lldb. 21 Please make sure you read the README COMPLETELY BEFORE reading anything below. 22 It is very critical that you read coding guidelines in Section E in README file. 23""" 24 25COMMON_HELP_STRING = """ 26 -h Show the help string for the command. 27 -c [always|auto|never|0|1] 28 Control the colorized output of certain commands 29 -o <path/to/filename> The output of this command execution will be saved to file. Parser information or errors will 30 not be sent to file though. eg /tmp/output.txt 31 -s <filter_string> The "filter_string" param is parsed to python regex expression and each line of output 32 will be printed/saved only if it matches the expression. 33 -v [-v...] Each additional -v will increase the verbosity of the command. 34 -p <plugin_name> Send the output of the command to plugin. Please see README for usage of plugins. 35""" 36# End Utility functions 37# Debugging specific utility functions 38 39#decorators. Not to be called directly. 40 41def static_var(var_name, initial_value): 42 def _set_var(obj): 43 setattr(obj, var_name, initial_value) 44 return obj 45 return _set_var 46 47def header(initial_value): 48 def _set_header(obj): 49 setattr(obj, 'header', initial_value) 50 return obj 51 return _set_header 52 53def md_header(fmt, args): 54 def _set_md_header(obj): 55 header = "|" + "|".join(fmt.split(" ")).format(*args) + "|" 56 57 colhead = map(lambda fmt, col: "-"*len(fmt.format(col)), fmt.split(" "), args) 58 sub_header = "|" + "|".join(colhead) + "|" 59 setattr(obj, 'markdown', "\n".join([header, sub_header])) 60 return obj 61 return _set_md_header 62 63# holds type declarations done by xnu. 64#DONOTTOUCHME: Exclusive use of lldb_type_summary only. 65lldb_summary_definitions = {} 66def lldb_type_summary(types_list): 67 """ A function decorator to register a summary for a type in lldb. 68 params: types_list - [] an array of types that you wish to register a summary callback function. (ex. ['task *', 'task_t']) 69 returns: Nothing. This is a decorator. 70 """ 71 def _get_summary(obj): 72 summary_function_name = "LLDBSummary" + obj.__name__ 73 74 def _internal_summary_function(lldbval, internal_dict): 75 args, _, _, _ = inspect.getargspec(obj) 76 if 'O' in args: 77 stream = CommandOutput(summary_function_name, fhandle=sys.stdout) 78 with RedirectStdStreams(stdout=stream), caching.ImplicitContext(lldbval): 79 return '\n' + obj.header + '\n' + obj(core.value(lldbval), O=stream) 80 81 out_string = "" 82 if internal_dict != None and len(obj.header) > 0 : 83 out_string += "\n" + obj.header +"\n" 84 with caching.ImplicitContext(lldbval): 85 out_string += obj(core.value(lldbval)) 86 return out_string 87 88 myglobals = globals() 89 myglobals[summary_function_name] = _internal_summary_function 90 summary_function = myglobals[summary_function_name] 91 summary_function.__doc__ = obj.__doc__ 92 93 global lldb_summary_definitions 94 for single_type in types_list: 95 if config['showTypeSummary']: 96 if single_type in lldb_summary_definitions: 97 lldb.debugger.HandleCommand("type summary delete --category kernel \""+ single_type + "\"") 98 lldb.debugger.HandleCommand("type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + "." + summary_function_name) 99 lldb_summary_definitions[single_type] = obj 100 101 return obj 102 return _get_summary 103 104# 105# Exception handling from commands 106# 107 108_LLDB_WARNING = ( 109 "********************* LLDB found an exception *********************\n" 110 " There has been an uncaught exception.\n" 111 " It could be because the debugger was disconnected.\n" 112 "\n" 113 " In order to debug the macro being run, run the macro again\n" 114 " with the `--debug` flag to get richer information, for example:\n" 115 "\n" 116 " (lldb) showtask --debug 0x1234\n" 117 "\n" 118 " In order to file a bug report instead, run the macro again\n" 119 " with the `--radar` flag which will produce a tarball to\n" 120 " attach to your report, for example:\n" 121 "\n" 122 " (lldb) showtask --radar 0x1234\n" 123 "********************************************************************\n" 124) 125 126def _format_exc(exc, vt): 127 import traceback, textwrap 128 129 out_str = "" 130 131 w = textwrap.TextWrapper(width=100, placeholder="...", max_lines=3) 132 tb = traceback.TracebackException.from_exception(exc, limit=None, lookup_lines=True, capture_locals=True) 133 134 for frame in tb.stack: 135 out_str += ( 136 f"File \"{vt.DarkBlue}{frame.filename}\"{vt.Magenta}@{frame.lineno}{vt.Reset} " 137 f"in {vt.Bold}{vt.DarkCyan}{frame.name}{vt.Reset}\n" 138 ) 139 out_str += " Locals:\n" 140 for name, value in frame.locals.items(): 141 variable = f" {vt.Bold}{vt.DarkGreen}{name}{vt.Reset} = " 142 first = True 143 for wline in w.wrap(str(value)): 144 if first: 145 out_str += variable + f"{vt.Oblique}{wline}\n" 146 first = False 147 else: 148 out_str += " " * (len(name) + 7) + wline + "\n" 149 out_str += vt.EndOblique 150 151 out_str += " " + "-" * 100 + "\n" 152 try: 153 src = open(frame.filename, "r") 154 except IOError: 155 out_str += " < Sources not available >\n" 156 else: 157 with src: 158 lines = src.readlines() 159 160 startline = frame.lineno - 3 if frame.lineno > 2 else 0 161 endline = min(frame.lineno + 2, len(lines)) 162 for lineno in range(startline, endline): 163 164 if lineno + 1 == frame.lineno: 165 fmt = vt.Bold + vt.Default 166 marker = '>' 167 else: 168 fmt = vt.Default 169 marker = ' ' 170 171 out_str += f"{fmt} {marker} {lineno + 1:5} {lines[lineno].rstrip()}{vt.Reset}\n" 172 173 out_str += " " + "-" * 100 + "\n" 174 out_str += "\n" 175 176 return out_str 177 178_RADAR_URL = "rdar://new/problem?title=LLDB%20macro%20failed%3A%20{}&attachments={}" 179 180def diagnostic_report(exc, stream, cmd_name, debug_opts, lldb_log_fname=None): 181 """ Collect diagnostic report for radar submission. 182 183 @param exc (Exception type) 184 Exception being reported. 185 186 @param stream (OutputObject) 187 Command's output stream to support formattting. 188 189 @param cmd_name (str) 190 Name of command being executed. 191 192 @param debug_opts ([str]) 193 List of active debugging options (--debug, --radar, --pdb) 194 195 @param lldb_log_fname (str) 196 LLDB log file name to collect (optional) 197 """ 198 199 # Print prologue common to all exceptions handling modes. 200 print(stream.VT.DarkRed + _LLDB_WARNING) 201 print(stream.VT.Bold + stream.VT.DarkGreen + type(exc).__name__ + 202 stream.VT.Default + ": {}".format(str(exc)) + stream.VT.Reset) 203 print() 204 205 if not debug_opts: 206 raise exc 207 208 # 209 # Display enhanced diagnostics when requested. 210 # 211 if "--debug" in debug_opts: 212 # Format exception for terminal 213 print(_format_exc(exc, stream.VT)) 214 215 print("version:") 216 print(lldb_run_command("version")) 217 print() 218 219 # 220 # Construct tar.gz bundle for radar attachement 221 # 222 if "--radar" in debug_opts: 223 import tarfile, urllib.parse 224 print("Creating radar bundle ...") 225 226 itime = int(time.time()) 227 tar_fname = "/tmp/debug.{:d}.tar.gz".format(itime) 228 229 with tarfile.open(tar_fname, "w") as tar: 230 # Collect LLDB log. It can't be unlinked here because it is still used 231 # for the whole duration of xnudebug debug enable. 232 if lldb_log_fname is not None: 233 print(" Adding {}".format(lldb_log_fname)) 234 tar.add(lldb_log_fname, "radar/lldb.log") 235 os.unlink(lldb_log_fname) 236 237 # Collect traceback 238 tb_fname = "/tmp/tb.{:d}.log".format(itime) 239 print(" Adding {}".format(tb_fname)) 240 with open(tb_fname,"w") as f: 241 f.write("{}: {}\n\n".format(type(exc).__name__, str(exc))) 242 f.write(_format_exc(exc, NOVT())) 243 f.write("version:\n") 244 f.write(lldb_run_command("version")) 245 tar.add(tb_fname, "radar/traceback.log") 246 os.unlink(tb_fname) 247 248 # Radar submission 249 print() 250 print(stream.VT.DarkRed + "Please attach {} to your radar or open the URL below:".format(tar_fname) + stream.VT.Reset) 251 print() 252 print(" " + _RADAR_URL.format(urllib.parse.quote(cmd_name),urllib.parse.quote(tar_fname))) 253 print() 254 255 # Enter pdb when requested. 256 if "--pdb" in debug_opts: 257 print("Starting debugger ...") 258 import pdb 259 pdb.post_mortem(exc.__traceback__) 260 261 return False 262 263#global cache of documentation for lldb commands exported by this module 264#DONOTTOUCHME: Exclusive use of lldb_command only. 265lldb_command_documentation = {} 266 267_DEBUG_OPTS = { "--debug", "--radar", "--pdb" } 268 269def lldb_command(cmd_name, option_string = '', fancy=False): 270 """ A function decorator to define a command with name 'cmd_name' in the lldb scope to call python function. 271 params: cmd_name - str : name of command to be set in lldb prompt. 272 option_string - str: getopt like option string. Only CAPITAL LETTER options allowed. 273 see README on Customizing command options. 274 fancy - bool : whether the command will receive an 'O' object to do fancy output (tables, indent, color) 275 """ 276 if option_string != option_string.upper(): 277 raise RuntimeError("Cannot setup command with lowercase option args. %s" % option_string) 278 279 def _cmd(obj): 280 def _internal_command_function(debugger, command, exe_ctx, result, internal_dict): 281 global config, lldb_run_command_state 282 stream = CommandOutput(cmd_name, result) 283 # need to avoid printing on stdout if called from lldb_run_command. 284 if 'active' in lldb_run_command_state and lldb_run_command_state['active']: 285 debuglog('Running %s from lldb_run_command' % command) 286 else: 287 result.SetImmediateOutputFile(sys.__stdout__) 288 289 command_args = shlex.split(command) 290 lldb.debugger.HandleCommand('type category disable kernel') 291 def_verbose_level = config['verbosity'] 292 293 # Filter out debugging arguments and enable logging 294 debug_opts = [opt for opt in command_args if opt in _DEBUG_OPTS] 295 command_args = [opt for opt in command_args if opt not in _DEBUG_OPTS] 296 lldb_log_filename = None 297 298 if "--radar" in debug_opts: 299 lldb_log_filename = "/tmp/lldb.{:d}.log".format(int(time.time())) 300 lldb_run_command("log enable --file {:s} lldb api".format(lldb_log_filename)) 301 lldb_run_command("log enable --file {:s} gdb-remote packets".format(lldb_log_filename)) 302 lldb_run_command("log enable --file {:s} kdp-remote packets".format(lldb_log_filename)) 303 304 try: 305 stream.setOptions(command_args, option_string) 306 if stream.verbose_level != 0: 307 config['verbosity'] += stream.verbose_level 308 with RedirectStdStreams(stdout=stream), caching.ImplicitContext(exe_ctx): 309 args = { 'cmd_args': stream.target_cmd_args } 310 if option_string: 311 args['cmd_options'] = stream.target_cmd_options 312 if fancy: 313 args['O'] = stream 314 obj(**args) 315 except KeyboardInterrupt: 316 print("Execution interrupted by user") 317 except ArgumentError as arg_error: 318 if str(arg_error) != "HELP": 319 print("Argument Error: " + str(arg_error)) 320 print("{0:s}:\n {1:s}".format(cmd_name, obj.__doc__.strip())) 321 return False 322 except Exception as exc: 323 if "--radar" in debug_opts: lldb_run_command("log disable") 324 return diagnostic_report(exc, stream, cmd_name, debug_opts, lldb_log_filename) 325 326 if config['showTypeSummary']: 327 lldb.debugger.HandleCommand('type category enable kernel' ) 328 329 if stream.pluginRequired : 330 plugin = LoadXNUPlugin(stream.pluginName) 331 if plugin == None : 332 print("Could not load plugins."+stream.pluginName) 333 return 334 plugin.plugin_init(kern, config, lldb, kern.IsDebuggerConnected()) 335 return_data = plugin.plugin_execute(cmd_name, result.GetOutput()) 336 ProcessXNUPluginResult(return_data) 337 plugin.plugin_cleanup() 338 339 #restore the verbose level after command is complete 340 config['verbosity'] = def_verbose_level 341 342 return 343 344 myglobals = globals() 345 command_function_name = obj.__name__+"Command" 346 myglobals[command_function_name] = _internal_command_function 347 command_function = myglobals[command_function_name] 348 if not obj.__doc__ : 349 print("ERROR: Cannot register command({:s}) without documentation".format(cmd_name)) 350 return obj 351 obj.__doc__ += "\n" + COMMON_HELP_STRING 352 command_function.__doc__ = obj.__doc__ 353 global lldb_command_documentation 354 if cmd_name in lldb_command_documentation: 355 lldb.debugger.HandleCommand("command script delete "+cmd_name) 356 lldb_command_documentation[cmd_name] = (obj.__name__, obj.__doc__.lstrip(), option_string) 357 lldb.debugger.HandleCommand("command script add -f " + MODULE_NAME + "." + command_function_name + " " + cmd_name) 358 359 setattr(obj, 'fancy', fancy) 360 if fancy: 361 def wrapped_fun(cmd_args=None, cmd_options={}, O=None): 362 if O is None: 363 stream = CommandOutput(cmd_name, fhandle=sys.stdout) 364 with RedirectStdStreams(stdout=stream): 365 return obj(cmd_args, cmd_options, O=stream) 366 else: 367 return obj(cmd_args, cmd_options, O) 368 return wrapped_fun 369 return obj 370 return _cmd 371 372def lldb_alias(alias_name, cmd_line): 373 """ define an alias in the lldb command line. 374 A programatic way of registering an alias. This basically does 375 (lldb)command alias alias_name "cmd_line" 376 ex. 377 lldb_alias('readphys16', 'readphys 16') 378 """ 379 alias_name = alias_name.strip() 380 cmd_line = cmd_line.strip() 381 lldb.debugger.HandleCommand("command alias " + alias_name + " "+ cmd_line) 382 383def SetupLLDBTypeSummaries(reset=False): 384 global lldb_summary_definitions, MODULE_NAME 385 if reset: 386 lldb.debugger.HandleCommand("type category delete kernel ") 387 for single_type in list(lldb_summary_definitions.keys()): 388 summary_function = lldb_summary_definitions[single_type] 389 lldb_cmd = "type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + ".LLDBSummary" + summary_function.__name__ 390 debuglog(lldb_cmd) 391 lldb.debugger.HandleCommand(lldb_cmd) 392 if config['showTypeSummary']: 393 lldb.debugger.HandleCommand("type category enable kernel") 394 else: 395 lldb.debugger.HandleCommand("type category disable kernel") 396 397 return 398 399def LoadXNUPlugin(name): 400 """ Try to load a plugin from the plugins directory. 401 """ 402 retval = None 403 name=name.strip() 404 try: 405 module_obj = __import__('plugins.'+name, globals(), locals(), [], -1) 406 module_obj = module_obj.__dict__[name] 407 defs = dir(module_obj) 408 if 'plugin_init' in defs and 'plugin_execute' in defs and 'plugin_cleanup' in defs: 409 retval = module_obj 410 else: 411 print("Plugin is not correctly implemented. Please read documentation on implementing plugins") 412 except: 413 print("plugin not found :"+name) 414 415 return retval 416 417def ProcessXNUPluginResult(result_data): 418 """ Look at the returned data from plugin and see if anymore actions are required or not 419 params: result_data - list of format (status, out_string, more_commands) 420 """ 421 ret_status = result_data[0] 422 ret_string = result_data[1] 423 ret_commands = result_data[2] 424 425 if not ret_status: 426 print("Plugin failed: " + ret_string) 427 return 428 print(ret_string) 429 if len(ret_commands) >= 0: 430 for cmd in ret_commands: 431 print("Running command on behalf of plugin:" + cmd) 432 lldb.debugger.HandleCommand(cmd) 433 return 434 435# holds tests registered with xnu. 436#DONOTTOUCHME: Exclusive use of xnudebug_test only 437lldb_command_tests = {} 438def xnudebug_test(test_name): 439 """ A function decoratore to register a test with the framework. Each test is supposed to be of format 440 def Test<name>(kernel_target, config, lldb_obj, isConnected ) 441 442 NOTE: The testname should start with "Test" else exception will be raised. 443 """ 444 def _test(obj): 445 global lldb_command_tests 446 if obj.__name__.find("Test") != 0 : 447 print("Test name ", obj.__name__ , " should start with Test") 448 raise ValueError 449 lldb_command_tests[test_name] = (test_name, obj.__name__, obj, obj.__doc__) 450 return obj 451 return _test 452 453 454# End Debugging specific utility functions 455# Kernel Debugging specific classes and accessor methods 456 457# global access object for target kernel 458 459def GetObjectAtIndexFromArray(array_base, index): 460 """ Subscript indexing for arrays that are represented in C as pointers. 461 for ex. int *arr = malloc(20*sizeof(int)); 462 now to get 3rd int from 'arr' you'd do 463 arr[2] in C 464 GetObjectAtIndexFromArray(arr_val,2) 465 params: 466 array_base : core.value - representing a pointer type (ex. base of type 'ipc_entry *') 467 index : int - 0 based index into the array 468 returns: 469 core.value : core.value of the same type as array_base_val but pointing to index'th element 470 """ 471 array_base_val = array_base.GetSBValue() 472 base_address = array_base_val.GetValueAsUnsigned() 473 size = array_base_val.GetType().GetPointeeType().GetByteSize() 474 obj_address = base_address + (index * size) 475 obj = kern.GetValueFromAddress(obj_address, array_base_val.GetType()) 476 return Cast(obj, array_base_val.GetType()) 477 478 479kern = None 480 481def GetLLDBThreadForKernelThread(thread_obj): 482 """ Get a reference to lldb.SBThread representation for kernel thread. 483 params: 484 thread_obj : core.cvalue - thread object of type thread_t 485 returns 486 lldb.SBThread - lldb thread object for getting backtrace/registers etc. 487 """ 488 tid = unsigned(thread_obj.thread_id) 489 lldb_process = LazyTarget.GetProcess() 490 sbthread = lldb_process.GetThreadByID(tid) 491 if not sbthread.IsValid(): 492 # in case lldb doesnt know about this thread, create one 493 if hasattr(lldb_process, "CreateOSPluginThread"): 494 debuglog("creating os plugin thread on the fly for {0:d} 0x{1:x}".format(tid, thread_obj)) 495 lldb_process.CreateOSPluginThread(tid, unsigned(thread_obj)) 496 else: 497 raise RuntimeError("LLDB process does not support CreateOSPluginThread.") 498 sbthread = lldb_process.GetThreadByID(tid) 499 500 if not sbthread.IsValid(): 501 raise RuntimeError("Unable to find lldb thread for tid={0:d} thread = {1:#018x} (#16049947: have you put 'settings set target.load-script-from-symbol-file true' in your .lldbinit?)".format(tid, thread_obj)) 502 503 return sbthread 504 505def GetKextSymbolInfo(load_addr): 506 """ Get a string descriptiong load_addr <kextname> + offset 507 params: 508 load_addr - int address value of pc in backtrace. 509 returns: str - kext name + offset string. If no cached data available, warning message is returned. 510 """ 511 symbol_name = "None" 512 symbol_offset = load_addr 513 kmod_val = kern.globals.kmod 514 if not kern.arch.startswith('arm64'): 515 for kval in IterateLinkedList(kmod_val, 'next'): 516 if load_addr >= unsigned(kval.address) and \ 517 load_addr <= (unsigned(kval.address) + unsigned(kval.size)): 518 symbol_name = kval.name 519 symbol_offset = load_addr - unsigned(kval.address) 520 break 521 return "{:#018x} {:s} + {:#x} \n".format(load_addr, symbol_name, symbol_offset) 522 523 # only for arm64 we do lookup for split kexts. 524 if not GetAllKextSummaries.cached(): 525 if str(GetConnectionProtocol()) != "core": 526 return "{:#018x} ~ kext info not available. please run 'showallkexts' once ~ \n".format(load_addr) 527 528 for kval in GetAllKextSummaries(): 529 text_seg = text_segment(kval.segments) 530 if load_addr >= text_seg.vmaddr and \ 531 load_addr <= (text_seg.vmaddr + text_seg.vmsize): 532 symbol_name = kval.name 533 symbol_offset = load_addr - text_seg.vmaddr 534 break 535 return "{:#018x} {:s} + {:#x} \n".format(load_addr, symbol_name, symbol_offset) 536 537def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""): 538 """ Get a string to display back trace for a thread. 539 params: 540 thread_obj - core.cvalue : a thread object of type thread_t. 541 verbosity - int : either of vHUMAN, vSCRIPT or vDETAIL to describe the verbosity of output 542 prefix - str : a string prefix added before the line for each frame. 543 isContinuation - bool : is thread a continuation? 544 returns: 545 str - a multi line string showing each frame in backtrace. 546 """ 547 is_continuation = not bool(unsigned(thread_obj.kernel_stack)) 548 thread_val = GetLLDBThreadForKernelThread(thread_obj) 549 out_string = "" 550 kernel_stack = unsigned(thread_obj.kernel_stack) 551 reserved_stack = unsigned(thread_obj.reserved_stack) 552 if not is_continuation: 553 if kernel_stack and reserved_stack: 554 out_string += prefix + "reserved_stack = {:#018x}\n".format(reserved_stack) 555 out_string += prefix + "kernel_stack = {:#018x}\n".format(kernel_stack) 556 else: 557 out_string += prefix + "continuation =" 558 iteration = 0 559 last_frame_p = 0 560 for frame in thread_val.frames: 561 addr = frame.GetPCAddress() 562 load_addr = addr.GetLoadAddress(LazyTarget.GetTarget()) 563 function = frame.GetFunction() 564 frame_p = frame.GetFP() 565 mod_name = frame.GetModule().GetFileSpec().GetFilename() 566 567 if iteration == 0 and not is_continuation: 568 out_string += prefix +"stacktop = {:#018x}\n".format(frame_p) 569 570 if not function: 571 # No debug info for 'function'. 572 out_string += prefix 573 if not is_continuation: 574 out_string += "{fp:#018x} ".format(fp = frame_p) 575 576 symbol = frame.GetSymbol() 577 if not symbol: 578 out_string += GetKextSymbolInfo(load_addr) 579 else: 580 file_addr = addr.GetFileAddress() 581 start_addr = symbol.GetStartAddress().GetFileAddress() 582 symbol_name = symbol.GetName() 583 symbol_offset = file_addr - start_addr 584 out_string += "{addr:#018x} {mod}`{symbol} + {offset:#x} \n".format(addr=load_addr, 585 mod=mod_name, symbol=symbol_name, offset=symbol_offset) 586 else: 587 # Debug info is available for 'function'. 588 func_name = frame.GetFunctionName() 589 # file_name = frame.GetLineEntry().GetFileSpec().GetFilename() 590 # line_num = frame.GetLineEntry().GetLine() 591 func_name = '%s [inlined]' % func_name if frame.IsInlined() else func_name 592 if is_continuation and frame.IsInlined(): 593 debuglog("Skipping frame for thread {:#018x} since its inlined".format(thread_obj)) 594 continue 595 out_string += prefix 596 if not is_continuation: 597 out_string += "{fp:#018x} ".format(fp=frame_p) 598 599 if len(frame.arguments) > 0: 600 strargs = "(" + str(frame.arguments).replace('\n', ', ') + ")" 601 out_string += "{addr:#018x} {func}{args} \n".format( 602 addr=load_addr, func=func_name, args=strargs) 603 else: 604 out_string += "{addr:#018x} {func}(void) \n".format( 605 addr=load_addr, func=func_name) 606 607 iteration += 1 608 if frame_p: 609 last_frame_p = frame_p 610 611 if not is_continuation and last_frame_p: 612 out_string += prefix + "stackbottom = {:#018x}".format(last_frame_p) 613 out_string = out_string.replace("variable not available","") 614 return out_string 615 616def GetSourceInformationForAddress(addr): 617 """ convert and address to function +offset information. 618 params: addr - int address in the binary to be symbolicated 619 returns: string of format "0xaddress: function + offset" 620 """ 621 try: 622 return str(kern.SymbolicateFromAddress(addr, fullSymbol=True)[0]) 623 except: 624 return '{0:<#x} <unknown: use `addkextaddr {0:#x}` to resolve>'.format(addr) 625 626def GetFrameLocalVariable(variable_name, frame_no=0): 627 """ Find a local variable by name 628 params: 629 variable_name: str - name of variable to search for 630 returns: 631 core.value - if the variable is found. 632 None - if not found or not Valid 633 """ 634 retval = None 635 sbval = None 636 lldb_SBThread = LazyTarget.GetProcess().GetSelectedThread() 637 frame = lldb_SBThread.GetSelectedFrame() 638 if frame_no : 639 frame = lldb_SBThread.GetFrameAtIndex(frame_no) 640 if frame : 641 sbval = frame.FindVariable(variable_name) 642 if sbval and sbval.IsValid(): 643 retval = core.cvalue.value(sbval) 644 return retval 645 646# Begin Macros for kernel debugging 647 648@lldb_command('kgmhelp') 649def KernelDebugCommandsHelp(cmd_args=None): 650 """ Show a list of registered commands for kenel debugging. 651 """ 652 global lldb_command_documentation 653 print("List of commands provided by " + MODULE_NAME + " for kernel debugging.") 654 cmds = list(lldb_command_documentation.keys()) 655 cmds.sort() 656 for cmd in cmds: 657 if isinstance(lldb_command_documentation[cmd][-1], str): 658 print(" {0: <20s} - {1}".format(cmd , lldb_command_documentation[cmd][1].split("\n")[0].strip())) 659 else: 660 print(" {0: <20s} - {1}".format(cmd , "No help string found.")) 661 print('Each of the functions listed here accept the following common options. ') 662 print(COMMON_HELP_STRING) 663 print('Additionally, each command implementation may have more options. "(lldb) help <command> " will show these options.') 664 return None 665 666 667@lldb_command('showraw') 668def ShowRawCommand(cmd_args=None): 669 """ A command to disable the kernel summaries and show data as seen by the system. 670 This is useful when trying to read every field of a struct as compared to brief summary 671 """ 672 command = " ".join(cmd_args) 673 lldb.debugger.HandleCommand('type category disable kernel' ) 674 lldb.debugger.HandleCommand(command) 675 lldb.debugger.HandleCommand('type category enable kernel' ) 676 677 678@lldb_command('xnudebug') 679def XnuDebugCommand(cmd_args=None): 680 """ command interface for operating on the xnu macros. Allowed commands are as follows 681 reload: 682 Reload a submodule from the xnu/tools/lldb directory. Do not include the ".py" suffix in modulename. 683 usage: xnudebug reload <modulename> (eg. memory, process, stats etc) 684 flushcache: 685 remove any cached data held in static or dynamic data cache. 686 usage: xnudebug flushcache 687 test: 688 Start running registered test with <name> from various modules. 689 usage: xnudebug test <name> (eg. test_memstats) 690 testall: 691 Go through all registered tests and run them 692 debug: 693 Toggle state of debug configuration flag. 694 profile <path_to_profile> <cmd...>: 695 Profile an lldb command and write its profile info to a file. 696 """ 697 global config 698 command_args = cmd_args 699 if len(command_args) == 0: 700 raise ArgumentError("No command specified.") 701 supported_subcommands = ['debug', 'reload', 'test', 'testall', 'flushcache', 'profile'] 702 subcommand = GetLongestMatchOption(command_args[0], supported_subcommands, True) 703 704 if len(subcommand) == 0: 705 raise ArgumentError("Subcommand (%s) is not a valid command. " % str(command_args[0])) 706 707 subcommand = subcommand[0].lower() 708 if subcommand == 'debug': 709 if command_args[-1].lower().find('dis') >=0 and config['debug']: 710 config['debug'] = False 711 print("Disabled debug logging.") 712 elif command_args[-1].lower().find('dis') < 0 and not config['debug']: 713 config['debug'] = True 714 EnableLLDBAPILogging() # provided by utils.py 715 print("Enabled debug logging. \nPlease run 'xnudebug debug disable' to disable it again. ") 716 717 if subcommand == 'flushcache': 718 print("Current size of cache: {}".format(caching.GetSizeOfCache())) 719 caching.ClearAllCache() 720 721 if subcommand == 'reload': 722 module_name = command_args[-1] 723 if module_name in sys.modules: 724 reload(sys.modules[module_name]) 725 print(module_name + " is reloaded from " + sys.modules[module_name].__file__) 726 else: 727 print("Unable to locate module named ", module_name) 728 729 if subcommand == 'testall': 730 for test_name in list(lldb_command_tests.keys()): 731 print("[BEGIN]", test_name) 732 res = lldb_command_tests[test_name][2](kern, config, lldb, True) 733 if res: 734 print("[PASSED] {:s}".format(test_name)) 735 else: 736 print("[FAILED] {:s}".format(test_name)) 737 738 if subcommand == 'test': 739 test_name = command_args[-1] 740 if test_name in lldb_command_tests: 741 test = lldb_command_tests[test_name] 742 print("Running test {:s}".format(test[0])) 743 if test[2](kern, config, lldb, True) : 744 print("[PASSED] {:s}".format(test[0])) 745 else: 746 print("[FAILED] {:s}".format(test[0])) 747 return "" 748 else: 749 print("No such test registered with name: {:s}".format(test_name)) 750 print("XNUDEBUG Available tests are:") 751 for i in list(lldb_command_tests.keys()): 752 print(i) 753 return None 754 755 if subcommand == 'profile': 756 import cProfile, pstats, io 757 758 pr = cProfile.Profile() 759 pr.enable() 760 761 lldb.debugger.HandleCommand(" ".join(command_args[2:])) 762 763 pr.disable() 764 pr.dump_stats(command_args[1]) 765 766 print("") 767 print("=" * 80) 768 print("") 769 770 s = io.StringIO() 771 ps = pstats.Stats(pr, stream=s) 772 ps.strip_dirs() 773 ps.sort_stats('cumulative') 774 ps.print_stats(30) 775 print(s.getvalue().rstrip()) 776 print("") 777 778 print('Profile info saved to "{}"'.format(command_args[1])) 779 780 return False 781 782@lldb_command('showversion') 783def ShowVersion(cmd_args=None): 784 """ Read the kernel version string from a fixed address in low 785 memory. Useful if you don't know which kernel is on the other end, 786 and need to find the appropriate symbols. Beware that if you've 787 loaded a symbol file, but aren't connected to a remote target, 788 the version string from the symbol file will be displayed instead. 789 This macro expects to be connected to the remote kernel to function 790 correctly. 791 792 """ 793 print(kern.version) 794 795def ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len, cmd_options): 796 """ Process the panic stackshot from the panic header, saving it to a file if it is valid 797 params: panic_stackshot_addr : start address of the panic stackshot binary data 798 panic_stackshot_len : length of the stackshot binary data 799 returns: nothing 800 """ 801 if not panic_stackshot_addr: 802 print("No panic stackshot available (invalid addr)") 803 return 804 805 if not panic_stackshot_len: 806 print("No panic stackshot available (zero length)") 807 return; 808 809 if "-D" in cmd_options: 810 dir_ = cmd_options["-D"] 811 if os.path.exists(dir_): 812 if not os.access(dir_, os.W_OK): 813 print("Write access to {} denied".format(dir_)) 814 return 815 else: 816 try: 817 os.makedirs(dir_) 818 except OSError as e: 819 print("An error occurred {} while creating a folder : {}".format(e, dir_)) 820 return 821 else: 822 dir_ = "/tmp" 823 824 id = str(uuid.uuid4())[:8] 825 ss_binfile = os.path.join(dir_, "panic_%s.bin" % id) 826 ss_ipsfile = os.path.join(dir_, "panic_%s.ips" % id) 827 828 if not SaveDataToFile(panic_stackshot_addr, panic_stackshot_len, ss_binfile, None): 829 print("Failed to save stackshot binary data to file") 830 return 831 832 from kcdata import decode_kcdata_file 833 try: 834 with open(ss_binfile, "rb") as binfile: 835 decode_kcdata_file(binfile, ss_ipsfile) 836 print("Saved ips stackshot file as %s" % ss_ipsfile) 837 except Exception as e: 838 print("Failed to decode the stackshot: %s" % str(e)) 839 840def ParseEmbeddedPanicLog(panic_header, cmd_options={}): 841 panic_buf = Cast(panic_header, 'char *') 842 panic_log_magic = unsigned(panic_header.eph_magic) 843 panic_log_begin_offset = unsigned(panic_header.eph_panic_log_offset) 844 panic_log_len = unsigned(panic_header.eph_panic_log_len) 845 other_log_begin_offset = unsigned(panic_header.eph_other_log_offset) 846 other_log_len = unsigned(panic_header.eph_other_log_len) 847 expected_panic_magic = xnudefines.EMBEDDED_PANIC_MAGIC 848 panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.eph_stackshot_offset) 849 panic_stackshot_len = unsigned(panic_header.eph_stackshot_len) 850 panic_header_flags = unsigned(panic_header.eph_panic_flags) 851 852 warn_str = "" 853 out_str = "" 854 855 if panic_log_magic != 0 and panic_log_magic != expected_panic_magic: 856 warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic, 857 expected_panic_magic) 858 859 if warn_str: 860 print("\n %s" % warn_str) 861 if panic_log_begin_offset == 0: 862 return 863 864 if "-E" in cmd_options: 865 ProcessExtensiblePaniclog(panic_header, cmd_options) 866 867 if "-S" in cmd_options: 868 if panic_header_flags & xnudefines.EMBEDDED_PANIC_STACKSHOT_SUCCEEDED_FLAG: 869 ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len, cmd_options) 870 else: 871 print("No panic stackshot available") 872 elif "-D" in cmd_options: 873 print("-D option must be specified along with the -S option") 874 return 875 876 panic_log_curindex = 0 877 while panic_log_curindex < panic_log_len: 878 p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)]) 879 out_str += p_char 880 panic_log_curindex += 1 881 882 if other_log_begin_offset != 0: 883 other_log_curindex = 0 884 while other_log_curindex < other_log_len: 885 p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)]) 886 out_str += p_char 887 other_log_curindex += 1 888 889 print(out_str) 890 return 891 892def ParseMacOSPanicLog(panic_header, cmd_options={}): 893 panic_buf = Cast(panic_header, 'char *') 894 panic_log_magic = unsigned(panic_header.mph_magic) 895 panic_log_begin_offset = unsigned(panic_header.mph_panic_log_offset) 896 panic_log_len = unsigned(panic_header.mph_panic_log_len) 897 other_log_begin_offset = unsigned(panic_header.mph_other_log_offset) 898 other_log_len = unsigned(panic_header.mph_other_log_len) 899 cur_debug_buf_ptr_offset = (unsigned(kern.globals.debug_buf_ptr) - unsigned(panic_header)) 900 if other_log_begin_offset != 0 and (other_log_len == 0 or other_log_len < (cur_debug_buf_ptr_offset - other_log_begin_offset)): 901 other_log_len = cur_debug_buf_ptr_offset - other_log_begin_offset 902 expected_panic_magic = xnudefines.MACOS_PANIC_MAGIC 903 904 # use the global if it's available (on an x86 corefile), otherwise refer to the header 905 if hasattr(kern.globals, "panic_stackshot_buf"): 906 panic_stackshot_addr = unsigned(kern.globals.panic_stackshot_buf) 907 panic_stackshot_len = unsigned(kern.globals.panic_stackshot_len) 908 else: 909 panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.mph_stackshot_offset) 910 panic_stackshot_len = unsigned(panic_header.mph_stackshot_len) 911 912 panic_header_flags = unsigned(panic_header.mph_panic_flags) 913 914 warn_str = "" 915 out_str = "" 916 917 if panic_log_magic != 0 and panic_log_magic != expected_panic_magic: 918 warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic, 919 expected_panic_magic) 920 921 if warn_str: 922 print("\n %s" % warn_str) 923 if panic_log_begin_offset == 0: 924 return 925 926 if "-S" in cmd_options: 927 if panic_header_flags & xnudefines.MACOS_PANIC_STACKSHOT_SUCCEEDED_FLAG: 928 ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len, cmd_options) 929 else: 930 print("No panic stackshot available") 931 elif "-D" in cmd_options: 932 print("-D option must be specified along with the -S option") 933 return 934 935 panic_log_curindex = 0 936 while panic_log_curindex < panic_log_len: 937 p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)]) 938 out_str += p_char 939 panic_log_curindex += 1 940 941 if other_log_begin_offset != 0: 942 other_log_curindex = 0 943 while other_log_curindex < other_log_len: 944 p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)]) 945 out_str += p_char 946 other_log_curindex += 1 947 948 print(out_str) 949 return 950 951def ParseAURRPanicLog(panic_header, cmd_options={}): 952 reset_cause = { 953 0x0: "OTHER", 954 0x1: "CATERR", 955 0x2: "SWD_TIMEOUT", 956 0x3: "GLOBAL RESET", 957 0x4: "STRAIGHT TO S5", 958 } 959 960 expected_panic_magic = xnudefines.AURR_PANIC_MAGIC 961 962 panic_buf = Cast(panic_header, 'char *') 963 964 try: 965 # This line will blow up if there's not type info for this struct (older kernel) 966 # We fall back to manual parsing below 967 aurr_panic_header = Cast(panic_header, 'struct efi_aurr_panic_header *') 968 panic_log_magic = unsigned(aurr_panic_header.efi_aurr_magic) 969 panic_log_version = unsigned(aurr_panic_header.efi_aurr_version) 970 panic_log_reset_cause = unsigned(aurr_panic_header.efi_aurr_reset_cause) 971 panic_log_reset_log_offset = unsigned(aurr_panic_header.efi_aurr_reset_log_offset) 972 panic_log_reset_log_len = unsigned(aurr_panic_header.efi_aurr_reset_log_len) 973 except Exception as e: 974 print("*** Warning: kernel symbol file has no type information for 'struct efi_aurr_panic_header'...") 975 print("*** Warning: trying to manually parse...") 976 aurr_panic_header = Cast(panic_header, "uint32_t *") 977 panic_log_magic = unsigned(aurr_panic_header[0]) 978 # panic_log_crc = unsigned(aurr_panic_header[1]) 979 panic_log_version = unsigned(aurr_panic_header[2]) 980 panic_log_reset_cause = unsigned(aurr_panic_header[3]) 981 panic_log_reset_log_offset = unsigned(aurr_panic_header[4]) 982 panic_log_reset_log_len = unsigned(aurr_panic_header[5]) 983 984 if panic_log_magic != 0 and panic_log_magic != expected_panic_magic: 985 print("BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic, 986 expected_panic_magic)) 987 return 988 989 print("AURR Panic Version: %d" % (panic_log_version)) 990 991 # When it comes time to extend this in the future, please follow the 992 # construct used below in ShowPanicLog() 993 if panic_log_version in (xnudefines.AURR_PANIC_VERSION, xnudefines.AURR_CRASHLOG_PANIC_VERSION): 994 # AURR Report Version 1 (AURR/MacEFI) or 2 (Crashlog) 995 # see macefifirmware/Vendor/Apple/EfiPkg/AppleDebugSupport/Library/Debugger.h 996 print("Reset Cause: 0x%x (%s)" % (panic_log_reset_cause, reset_cause.get(panic_log_reset_cause, "UNKNOWN"))) 997 998 # Adjust panic log string length (cap to maximum supported values) 999 if panic_log_version == xnudefines.AURR_PANIC_VERSION: 1000 max_string_len = panic_log_reset_log_len 1001 elif panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION: 1002 max_string_len = xnudefines.CRASHLOG_PANIC_STRING_LEN 1003 1004 panic_str_offset = 0 1005 out_str = "" 1006 1007 while panic_str_offset < max_string_len: 1008 p_char = str(panic_buf[panic_log_reset_log_offset + panic_str_offset]) 1009 out_str += p_char 1010 panic_str_offset += 1 1011 1012 print(out_str) 1013 1014 # Save Crashlog Binary Data (if available) 1015 if "-S" in cmd_options and panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION: 1016 crashlog_binary_offset = panic_log_reset_log_offset + xnudefines.CRASHLOG_PANIC_STRING_LEN 1017 crashlog_binary_size = (panic_log_reset_log_len > xnudefines.CRASHLOG_PANIC_STRING_LEN) and (panic_log_reset_log_len - xnudefines.CRASHLOG_PANIC_STRING_LEN) or 0 1018 1019 if 0 == crashlog_binary_size: 1020 print("No crashlog data found...") 1021 return 1022 1023 # Save to file 1024 ts = int(time.time()) 1025 ss_binfile = "/tmp/crashlog_%d.bin" % ts 1026 1027 if not SaveDataToFile(panic_buf + crashlog_binary_offset, crashlog_binary_size, ss_binfile, None): 1028 print("Failed to save crashlog binary data to file") 1029 return 1030 else: 1031 return ParseUnknownPanicLog(panic_header, cmd_options) 1032 1033 return 1034 1035def ParseUnknownPanicLog(panic_header, cmd_options={}): 1036 magic_ptr = Cast(panic_header, 'uint32_t *') 1037 panic_log_magic = dereference(magic_ptr) 1038 print("Unrecognized panic header format. Magic: 0x%x..." % unsigned(panic_log_magic)) 1039 print("Panic region starts at 0x%08x" % int(panic_header)) 1040 print("Hint: To dump this panic header in order to try manually parsing it, use this command:") 1041 print(" (lldb) memory read -fx -s4 -c64 0x%08x" % int(panic_header)) 1042 print(" ^ that will dump the first 256 bytes of the panic region") 1043 ## TBD: Hexdump some bits here to allow folks to poke at the region manually? 1044 return 1045 1046 1047@lldb_command('paniclog', 'SME:D:') 1048def ShowPanicLog(cmd_args=None, cmd_options={}): 1049 """ Display the paniclog information 1050 usage: (lldb) paniclog 1051 options: 1052 -v : increase verbosity 1053 -S : parse stackshot data (if panic stackshot available) 1054 -D : Takes a folder name for stackshot. This must be specified along with the -S option. 1055 -M : parse macOS panic area (print panic string (if available), and/or capture crashlog info) 1056 -E : Takes a file name and redirects the ext paniclog output to the file 1057 """ 1058 1059 if "-M" in cmd_options: 1060 if not hasattr(kern.globals, "mac_panic_header"): 1061 print("macOS panic data requested but unavailable on this device") 1062 return 1063 panic_header = kern.globals.mac_panic_header 1064 # DEBUG HACK FOR TESTING 1065 #panic_header = kern.GetValueFromAddress(0xfffffff054098000, "uint32_t *") 1066 else: 1067 panic_header = kern.globals.panic_info 1068 1069 if hasattr(panic_header, "eph_magic"): 1070 panic_log_magic = unsigned(panic_header.eph_magic) 1071 elif hasattr(panic_header, "mph_magic"): 1072 panic_log_magic = unsigned(panic_header.mph_magic) 1073 else: 1074 print("*** Warning: unsure of panic header format, trying anyway") 1075 magic_ptr = Cast(panic_header, 'uint32_t *') 1076 panic_log_magic = int(dereference(magic_ptr)) 1077 1078 if panic_log_magic == 0: 1079 # No panic here.. 1080 return 1081 1082 panic_parsers = { 1083 int(xnudefines.AURR_PANIC_MAGIC) : ParseAURRPanicLog, 1084 int(xnudefines.MACOS_PANIC_MAGIC) : ParseMacOSPanicLog, 1085 int(xnudefines.EMBEDDED_PANIC_MAGIC) : ParseEmbeddedPanicLog, 1086 } 1087 1088 # Find the right parser (fall back to unknown parser above) 1089 parser = panic_parsers.get(panic_log_magic, ParseUnknownPanicLog) 1090 1091 # execute it 1092 return parser(panic_header, cmd_options) 1093 1094def ProcessExtensiblePaniclog(panic_header, cmd_options): 1095 1096 process = LazyTarget().GetProcess() 1097 error = lldb.SBError() 1098 EXT_PANICLOG_MAX_SIZE = 32 # 32 is the max size of the string in Data ID 1099 1100 ext_paniclog_len = unsigned(panic_header.eph_ext_paniclog_len) 1101 if ext_paniclog_len == 0: 1102 print("Cannot find extensible paniclog") 1103 return 1104 1105 ext_paniclog_addr = unsigned(panic_header) + unsigned(panic_header.eph_ext_paniclog_offset) 1106 1107 ext_paniclog_bytes = process.chkReadMemory(ext_paniclog_addr, ext_paniclog_len); 1108 1109 idx = 0; 1110 ext_paniclog_ver_bytes = ext_paniclog_bytes[idx:idx+sizeof('uint32_t')] 1111 ext_paniclog_ver = int.from_bytes(ext_paniclog_ver_bytes, 'little') 1112 1113 idx += sizeof('uint32_t') 1114 no_of_logs_bytes = ext_paniclog_bytes[idx:idx+sizeof('uint32_t')] 1115 no_of_logs = int.from_bytes(no_of_logs_bytes, 'little') 1116 1117 idx += sizeof('uint32_t') 1118 1119 ext_paniclog = dict() 1120 1121 logs_processed = 0 1122 for _ in range(no_of_logs): 1123 uuid_bytes = ext_paniclog_bytes[idx:idx+sizeof('uuid_t')] 1124 ext_uuid = str(uuid.UUID(bytes=uuid_bytes)) 1125 1126 idx += sizeof('uuid_t') 1127 data_id_bytes = ext_paniclog_bytes[idx:idx + EXT_PANICLOG_MAX_SIZE].split(b'\0')[0] 1128 data_id = data_id_bytes.decode('utf-8') 1129 data_id_len = len(data_id_bytes) 1130 1131 idx += data_id_len + 1 1132 data_len_bytes = ext_paniclog_bytes[idx:idx+sizeof('uint32_t')] 1133 data_len = int.from_bytes(data_len_bytes, 'little') 1134 1135 idx += sizeof('uint32_t') 1136 data_bytes = ext_paniclog_bytes[idx:idx+data_len] 1137 data = base64.b64encode(data_bytes).decode('ascii') 1138 1139 idx += data_len 1140 1141 temp_dict = dict(Data_Id=data_id, Data=data) 1142 1143 ext_paniclog.setdefault(ext_uuid, []).append(temp_dict) 1144 1145 logs_processed += 1 1146 1147 if logs_processed < no_of_logs: 1148 print("** Warning: Extensible paniclog might be corrupted **") 1149 1150 with open(cmd_options['-E'], 'w') as out_file: 1151 out_file.write(json.dumps(ext_paniclog)) 1152 print("Wrote extensible paniclog to %s" % cmd_options['-E']) 1153 1154 return 1155 1156@lldb_command('showbootargs') 1157def ShowBootArgs(cmd_args=None): 1158 """ Display boot arguments passed to the target kernel 1159 """ 1160 bootargs = Cast(kern.GetGlobalVariable('PE_state').bootArgs, 'boot_args *') 1161 bootargs_cmd = bootargs.CommandLine 1162 print(str(bootargs_cmd)) 1163 1164# The initialization code to add your commands 1165_xnu_framework_init = False 1166def __lldb_init_module(debugger, internal_dict): 1167 global kern, lldb_command_documentation, config, _xnu_framework_init 1168 if _xnu_framework_init: 1169 return 1170 _xnu_framework_init = True 1171 debugger.HandleCommand('type summary add --regex --summary-string "${var%s}" -C yes -p -v "char *\[[0-9]*\]"') 1172 debugger.HandleCommand('type format add --format hex -C yes uintptr_t') 1173 kern = KernelTarget(debugger) 1174 if not hasattr(lldb.SBValue, 'GetValueAsAddress'): 1175 warn_str = "WARNING: lldb version is too old. Some commands may break. Please update to latest lldb." 1176 if os.isatty(sys.__stdout__.fileno()): 1177 warn_str = VT.DarkRed + warn_str + VT.Default 1178 print(warn_str) 1179 print("xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries.") 1180 1181__lldb_init_module(lldb.debugger, None) 1182 1183@lldb_command("showlldbtypesummaries") 1184def ShowLLDBTypeSummaries(cmd_args=[]): 1185 """ Enable/Disable kernel type summaries. Default is disabled. 1186 Usage: showlldbtypesummaries [enable|disable] 1187 default is enable 1188 """ 1189 global config 1190 action = "enable" 1191 trailer_msg = '' 1192 if len(cmd_args) > 0 and cmd_args[0].lower().find('disable') >=0: 1193 action = "disable" 1194 config['showTypeSummary'] = False 1195 trailer_msg = "Please run 'showlldbtypesummaries enable' to enable the summary feature." 1196 else: 1197 config['showTypeSummary'] = True 1198 SetupLLDBTypeSummaries(True) 1199 trailer_msg = "Please run 'showlldbtypesummaries disable' to disable the summary feature." 1200 lldb_run_command("type category "+ action +" kernel") 1201 print("Successfully "+action+"d the kernel type summaries. %s" % trailer_msg) 1202 1203@lldb_command('walkqueue_head', 'S') 1204def WalkQueueHead(cmd_args=[], cmd_options={}): 1205 """ walk a queue_head_t and list all members in it. Note this is for queue_head_t. refer to osfmk/kern/queue.h 1206 Option: -S - suppress summary output. 1207 Usage: (lldb) walkqueue_head <queue_entry *> <struct type> <fieldname> 1208 ex: (lldb) walkqueue_head 0x7fffff80 "thread *" "task_threads" 1209 1210 """ 1211 global lldb_summary_definitions 1212 if not cmd_args: 1213 raise ArgumentError("invalid arguments") 1214 if len(cmd_args) != 3: 1215 raise ArgumentError("insufficient arguments") 1216 queue_head = kern.GetValueFromAddress(cmd_args[0], 'struct queue_entry *') 1217 el_type = cmd_args[1] 1218 field_name = cmd_args[2] 1219 showsummary = False 1220 if el_type in lldb_summary_definitions: 1221 showsummary = True 1222 if '-S' in cmd_options: 1223 showsummary = False 1224 1225 for i in IterateQueue(queue_head, el_type, field_name): 1226 if showsummary: 1227 print(lldb_summary_definitions[el_type](i)) 1228 else: 1229 print("{0: <#020x}".format(i)) 1230 1231 1232 1233@lldb_command('walklist_entry', 'SE') 1234def WalkList(cmd_args=[], cmd_options={}): 1235 """ iterate over a list as defined with LIST_ENTRY in bsd/sys/queue.h 1236 params: 1237 object addr - value : address of object 1238 element_type - str : Type of the next element 1239 field_name - str : Name of the field in next element's structure 1240 1241 Options: -S - suppress summary output. 1242 -E - Iterate using SLIST_ENTRYs 1243 1244 Usage: (lldb) walklist_entry <obj with list_entry *> <struct type> <fieldname> 1245 ex: (lldb) walklist_entry 0x7fffff80 "struct proc *" "p_sibling" 1246 1247 """ 1248 global lldb_summary_definitions 1249 if not cmd_args: 1250 raise ArgumentError("invalid arguments") 1251 if len(cmd_args) != 3: 1252 raise ArgumentError("insufficient arguments") 1253 el_type = cmd_args[1] 1254 queue_head = kern.GetValueFromAddress(cmd_args[0], el_type) 1255 field_name = cmd_args[2] 1256 showsummary = False 1257 if el_type in lldb_summary_definitions: 1258 showsummary = True 1259 if '-S' in cmd_options: 1260 showsummary = False 1261 if '-E' in cmd_options: 1262 prefix = 's' 1263 else: 1264 prefix = '' 1265 elt = queue_head 1266 while unsigned(elt) != 0: 1267 i = elt 1268 elt = elt.__getattr__(field_name).__getattr__(prefix + 'le_next') 1269 if showsummary: 1270 print(lldb_summary_definitions[el_type](i)) 1271 else: 1272 print("{0: <#020x}".format(i)) 1273 1274def trace_parse_Copt(Copt): 1275 """Parses the -C option argument and returns a list of CPUs 1276 """ 1277 cpusOpt = Copt 1278 cpuList = cpusOpt.split(",") 1279 chosen_cpus = [] 1280 for cpu_num_string in cpuList: 1281 try: 1282 if '-' in cpu_num_string: 1283 parts = cpu_num_string.split('-') 1284 if len(parts) != 2 or not (parts[0].isdigit() and parts[1].isdigit()): 1285 raise ArgumentError("Invalid cpu specification: %s" % cpu_num_string) 1286 firstRange = int(parts[0]) 1287 lastRange = int(parts[1]) 1288 if firstRange >= kern.globals.real_ncpus or lastRange >= kern.globals.real_ncpus: 1289 raise ValueError() 1290 if lastRange < firstRange: 1291 raise ArgumentError("Invalid CPU range specified: `%s'" % cpu_num_string) 1292 for cpu_num in range(firstRange, lastRange + 1): 1293 if cpu_num not in chosen_cpus: 1294 chosen_cpus.append(cpu_num) 1295 else: 1296 chosen_cpu = int(cpu_num_string) 1297 if chosen_cpu < 0 or chosen_cpu >= kern.globals.real_ncpus: 1298 raise ValueError() 1299 if chosen_cpu not in chosen_cpus: 1300 chosen_cpus.append(chosen_cpu) 1301 except ValueError: 1302 raise ArgumentError("Invalid CPU number specified. Valid range is 0..%d" % (kern.globals.real_ncpus - 1)) 1303 1304 return chosen_cpus 1305 1306 1307IDX_CPU = 0 1308IDX_RINGPOS = 1 1309IDX_RINGENTRY = 2 1310def Trace_cmd(cmd_args=[], cmd_options={}, headerString=lambda:"", entryString=lambda x:"", ring='', entries_per_cpu=0, max_backtraces=0): 1311 """Generic trace dumper helper function 1312 """ 1313 1314 if '-S' in cmd_options: 1315 field_arg = cmd_options['-S'] 1316 try: 1317 getattr(kern.PERCPU_GET(ring, 0)[0], field_arg) 1318 sort_key_field_name = field_arg 1319 except AttributeError: 1320 raise ArgumentError("Invalid sort key field name `%s'" % field_arg) 1321 else: 1322 sort_key_field_name = 'start_time_abs' 1323 1324 if '-C' in cmd_options: 1325 chosen_cpus = trace_parse_Copt(cmd_options['-C']) 1326 else: 1327 chosen_cpus = [x for x in range(kern.globals.real_ncpus)] 1328 1329 try: 1330 limit_output_count = int(cmd_options['-N']) 1331 except ValueError: 1332 raise ArgumentError("Invalid output count `%s'" % cmd_options['-N']); 1333 except KeyError: 1334 limit_output_count = None 1335 1336 reverse_sort = '-R' in cmd_options 1337 backtraces = '-B' in cmd_options 1338 1339 # entries will be a list of 3-tuples, each holding the CPU on which the iotrace entry was collected, 1340 # the original ring index, and the iotrace entry. 1341 entries = [] 1342 for x in chosen_cpus: 1343 ring_slice = [(x, y, kern.PERCPU_GET(ring, x)[y]) for y in range(entries_per_cpu)] 1344 entries.extend(ring_slice) 1345 1346 total_entries = len(entries) 1347 1348 entries.sort(key=lambda x: getattr(x[IDX_RINGENTRY], sort_key_field_name), reverse=reverse_sort) 1349 1350 if limit_output_count is not None and limit_output_count > total_entries: 1351 print ("NOTE: Output count `%d' is too large; showing all %d entries" % (limit_output_count, total_entries)); 1352 limit_output_count = total_entries 1353 1354 if len(chosen_cpus) < kern.globals.real_ncpus: 1355 print("NOTE: Limiting to entries from cpu%s %s" % ("s" if len(chosen_cpus) > 1 else "", str(chosen_cpus))) 1356 1357 if limit_output_count is not None and limit_output_count < total_entries: 1358 entries_to_display = limit_output_count 1359 print("NOTE: Limiting to the %s" % ("first entry" if entries_to_display == 1 else ("first %d entries" % entries_to_display))) 1360 else: 1361 entries_to_display = total_entries 1362 1363 print(headerString()) 1364 1365 for x in range(entries_to_display): 1366 print(entryString(entries[x])) 1367 1368 if backtraces: 1369 for btidx in range(max_backtraces): 1370 nextbt = entries[x][IDX_RINGENTRY].backtrace[btidx] 1371 if nextbt == 0: 1372 break 1373 print("\t" + GetSourceInformationForAddress(nextbt)) 1374 1375 1376@lldb_command('iotrace', 'C:N:S:RB') 1377def IOTrace_cmd(cmd_args=[], cmd_options={}): 1378 """ Prints the iotrace ring buffers for all CPUs by default. 1379 Arguments: 1380 -B : Print backtraces for each ring entry 1381 -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a 1382 single CPU number or a range separated by a dash (e.g. "0-3")) 1383 -N <count> : Limit output to the first <count> entries (across all chosen CPUs) 1384 -R : Display results in reverse-sorted order (oldest first; default is newest-first) 1385 -S <sort_key_field_name> : Sort output by specified iotrace_entry_t field name (instead of by timestamp) 1386 """ 1387 MAX_IOTRACE_BACKTRACES = 16 1388 1389 if not hasattr(kern.globals, 'iotrace_entries_per_cpu'): 1390 print("Sorry, iotrace is not supported.") 1391 return 1392 1393 if kern.globals.iotrace_entries_per_cpu == 0: 1394 print("Sorry, iotrace is disabled.") 1395 return 1396 1397 hdrString = lambda : "%-19s %-8s %-10s %-20s SZ %-18s %-17s DATA" % ( 1398 "START TIME", 1399 "DURATION", 1400 "CPU#[RIDX]", 1401 " TYPE", 1402 " VIRT ADDR", 1403 " PHYS ADDR") 1404 1405 entryString = lambda x : "%-20u(%6u) %6s[%02d] %-20s %-2d 0x%016x 0x%016x 0x%x" % ( 1406 x[IDX_RINGENTRY].start_time_abs, 1407 x[IDX_RINGENTRY].duration, 1408 "CPU%d" % x[IDX_CPU], 1409 x[IDX_RINGPOS], 1410 str(x[IDX_RINGENTRY].iotype).split("=")[1].strip(), 1411 x[IDX_RINGENTRY].size, 1412 x[IDX_RINGENTRY].vaddr, 1413 x[IDX_RINGENTRY].paddr, 1414 x[IDX_RINGENTRY].val) 1415 1416 Trace_cmd(cmd_args, cmd_options, hdrString, entryString, 'iotrace_ring', 1417 kern.globals.iotrace_entries_per_cpu, MAX_IOTRACE_BACKTRACES) 1418 1419 1420@lldb_command('ttrace', 'C:N:S:RB') 1421def TrapTrace_cmd(cmd_args=[], cmd_options={}): 1422 """ Prints the iotrace ring buffers for all CPUs by default. 1423 Arguments: 1424 -B : Print backtraces for each ring entry 1425 -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a 1426 single CPU number or a range separated by a dash (e.g. "0-3")) 1427 -N <count> : Limit output to the first <count> entries (across all chosen CPUs) 1428 -R : Display results in reverse-sorted order (oldest first; default is newest-first) 1429 -S <sort_key_field_name> : Sort output by specified traptrace_entry_t field name (instead of by timestamp) 1430 """ 1431 MAX_TRAPTRACE_BACKTRACES = 8 1432 1433 if kern.arch != "x86_64": 1434 print("Sorry, ttrace is an x86-only command.") 1435 return 1436 1437 hdrString = lambda : "%-30s CPU#[RIDX] VECT INTERRUPTED_THREAD PREMLV INTRLV INTERRUPTED_PC" % ( 1438 "START TIME (DURATION [ns])") 1439 entryString = lambda x : "%-20u(%6s) %8s[%02d] 0x%02x 0x%016x %6d %6d %s" % ( 1440 x[IDX_RINGENTRY].start_time_abs, 1441 str(x[IDX_RINGENTRY].duration) if hex(x[IDX_RINGENTRY].duration) != "0xffffffffffffffff" else 'inprog', 1442 "CPU%d" % x[IDX_CPU], 1443 x[IDX_RINGPOS], 1444 int(x[IDX_RINGENTRY].vector), 1445 x[IDX_RINGENTRY].curthread, 1446 x[IDX_RINGENTRY].curpl, 1447 x[IDX_RINGENTRY].curil, 1448 GetSourceInformationForAddress(x[IDX_RINGENTRY].interrupted_pc)) 1449 1450 Trace_cmd(cmd_args, cmd_options, hdrString, entryString, 'traptrace_ring', 1451 kern.globals.traptrace_entries_per_cpu, MAX_TRAPTRACE_BACKTRACES) 1452 1453# Yields an iterator over all the sysctls from the provided root. 1454# Can optionally filter by the given prefix 1455def IterateSysctls(root_oid, prefix="", depth = 0, parent = ""): 1456 headp = root_oid 1457 for pp in IterateListEntry(headp, 'oid_link', 's'): 1458 node_str = "" 1459 if prefix != "": 1460 node_str = str(pp.oid_name) 1461 if parent != "": 1462 node_str = parent + "." + node_str 1463 if node_str.startswith(prefix): 1464 yield pp, depth, parent 1465 else: 1466 yield pp, depth, parent 1467 type = pp.oid_kind & 0xf 1468 if type == 1 and pp.oid_arg1 != 0: 1469 if node_str == "": 1470 next_parent = str(pp.oid_name) 1471 if parent != "": 1472 next_parent = parent + "." + next_parent 1473 else: 1474 next_parent = node_str 1475 # Only recurse if the next parent starts with our allowed prefix. 1476 # Note that it's OK if the parent string is too short (because the prefix might be for a deeper node). 1477 prefix_len = min(len(prefix), len(next_parent)) 1478 if next_parent[:prefix_len] == prefix[:prefix_len]: 1479 for x in IterateSysctls(Cast(pp.oid_arg1, "struct sysctl_oid_list *"), prefix, depth + 1, next_parent): 1480 yield x 1481 1482@lldb_command('showsysctls', 'P:') 1483def ShowSysctls(cmd_args=[], cmd_options={}): 1484 """ Walks the list of sysctl data structures, printing out each during traversal. 1485 Arguments: 1486 -P <string> : Limit output to sysctls starting with the specified prefix. 1487 """ 1488 if '-P' in cmd_options: 1489 _ShowSysctl_prefix = cmd_options['-P'] 1490 allowed_prefixes = _ShowSysctl_prefix.split('.') 1491 if allowed_prefixes: 1492 for x in range(1, len(allowed_prefixes)): 1493 allowed_prefixes[x] = allowed_prefixes[x - 1] + "." + allowed_prefixes[x] 1494 else: 1495 _ShowSysctl_prefix = '' 1496 allowed_prefixes = [] 1497 1498 for sysctl, depth, parentstr in IterateSysctls(kern.globals.sysctl__children, _ShowSysctl_prefix): 1499 if parentstr == "": 1500 parentstr = "<none>" 1501 headp = sysctl 1502 st = (" " * depth * 2) + str(sysctl.GetSBValue().Dereference()).replace("\n", "\n" + (" " * depth * 2)) 1503 print('parent = "%s"' % parentstr, st[st.find("{"):]) 1504 1505@lldb_command('showexperiments', 'F') 1506def ShowExperiments(cmd_args=[], cmd_options={}): 1507 """ Shows any active kernel experiments being run on the device via trial. 1508 Arguments: 1509 -F: Scan for changed experiment values even if no trial identifiers have been set. 1510 """ 1511 1512 treatment_id = str(kern.globals.trial_treatment_id) 1513 experiment_id = str(kern.globals.trial_experiment_id) 1514 deployment_id = kern.globals.trial_deployment_id._GetValueAsSigned() 1515 if treatment_id == "" and experiment_id == "" and deployment_id == -1: 1516 print("Device is not enrolled in any kernel experiments.") 1517 if not '-F' in cmd_options: 1518 return 1519 else: 1520 print("""Device is enrolled in a kernel experiment: 1521 treatment_id: %s 1522 experiment_id: %s 1523 deployment_id: %d""" % (treatment_id, experiment_id, deployment_id)) 1524 1525 print("Scanning sysctl tree for modified factors...") 1526 1527 kExperimentFactorFlag = 0x00100000 1528 1529 formats = { 1530 "IU": gettype("unsigned int *"), 1531 "I": gettype("int *"), 1532 "LU": gettype("unsigned long *"), 1533 "L": gettype("long *"), 1534 "QU": gettype("uint64_t *"), 1535 "Q": gettype("int64_t *") 1536 } 1537 1538 for sysctl, depth, parentstr in IterateSysctls(kern.globals.sysctl__children): 1539 if sysctl.oid_kind & kExperimentFactorFlag: 1540 spec = cast(sysctl.oid_arg1, "struct experiment_spec *") 1541 # Skip if arg2 isn't set to 1 (indicates an experiment factor created without an experiment_spec). 1542 if sysctl.oid_arg2 == 1: 1543 if spec.modified == 1: 1544 fmt = str(sysctl.oid_fmt) 1545 ptr = spec.ptr 1546 t = formats.get(fmt, None) 1547 if t: 1548 value = cast(ptr, t) 1549 else: 1550 # Unknown type 1551 continue 1552 name = str(parentstr) + "." + str(sysctl.oid_name) 1553 print("%s = %d (Default value is %d)" % (name, dereference(value), spec.original_value)) 1554 1555from memory import * 1556from process import * 1557from ipc import * 1558from pmap import * 1559from ioreg import * 1560from mbufs import * 1561from net import * 1562from skywalk import * 1563from kext import * 1564from kdp import * 1565from userspace import * 1566from pci import * 1567from misc import * 1568from apic import * 1569from scheduler import * 1570from structanalyze import * 1571from ipcimportancedetail import * 1572from bank import * 1573from turnstile import * 1574from kasan import * 1575from kauth import * 1576from waitq import * 1577from usertaskgdbserver import * 1578from ktrace import * 1579from xnutriage import * 1580from kmtriage import * 1581from kevent import * 1582from workqueue import * 1583from ulock import * 1584from ntstat import * 1585from zonetriage import * 1586from sysreg import * 1587from counter import * 1588from refgrp import * 1589from workload import * 1590from recount import * 1591from log import showLogStream, show_log_stream_info 1592from nvram import * 1593from exclaves import * 1594