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