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