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