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