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