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