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