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