X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/bd504ef0e0b883cdd7917b73b3574eb9ce669905..39236c6e673c41db228275375ab7fdb0f837b292:/tools/lldbmacros/xnu.py diff --git a/tools/lldbmacros/xnu.py b/tools/lldbmacros/xnu.py new file mode 100644 index 000000000..d72c3aeef --- /dev/null +++ b/tools/lldbmacros/xnu.py @@ -0,0 +1,626 @@ +import sys, subprocess, os, re, time, getopt, shlex +import lldb +from functools import wraps +from ctypes import c_ulonglong as uint64_t +from ctypes import c_void_p as voidptr_t +import code +import core +from core import caching +from core.standard import * +from core.configuration import * +from core.kernelcore import * +from utils import * +from core.lazytarget import * + +MODULE_NAME=__name__ + +""" Kernel Debugging macros for lldb. + Please make sure you read the README COMPLETELY BEFORE reading anything below. + It is very critical that you read coding guidelines in Section E in README file. +""" + +# End Utility functions +# Debugging specific utility functions + +#decorators. Not to be called directly. + +def static_var(var_name, initial_value): + def _set_var(obj): + setattr(obj, var_name, initial_value) + return obj + return _set_var + +def header(initial_value): + def _set_header(obj): + setattr(obj, 'header', initial_value) + return obj + return _set_header + +# holds type declarations done by xnu. +#DONOTTOUCHME: Exclusive use of lldb_type_summary only. +lldb_summary_definitions = {} +def lldb_type_summary(types_list): + """ A function decorator to register a summary for a type in lldb. + params: types_list - [] an array of types that you wish to register a summary callback function. (ex. ['task *', 'task_t']) + returns: Nothing. This is a decorator. + """ + def _get_summary(obj): + def _internal_summary_function(lldbval, internal_dict): + out_string= "" + if internal_dict != None and len(obj.header) > 0 : + out_string += "\n" + obj.header +"\n" + out_string += obj( core.value(lldbval) ) + return out_string + + myglobals = globals() + summary_function_name = "LLDBSummary" + obj.__name__ + myglobals[summary_function_name] = _internal_summary_function + summary_function = myglobals[summary_function_name] + summary_function.__doc__ = obj.__doc__ + + global lldb_summary_definitions + for single_type in types_list: + if config['showTypeSummary']: + if single_type in lldb_summary_definitions.keys(): + lldb.debugger.HandleCommand("type summary delete --category kernel \""+ single_type + "\"") + lldb.debugger.HandleCommand("type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + "." + summary_function_name) + lldb_summary_definitions[single_type] = obj + + return obj + return _get_summary + +#global cache of documentation for lldb commands exported by this module +#DONOTTOUCHME: Exclusive use of lldb_command only. +lldb_command_documentation = {} + +def lldb_command(cmd_name, option_string = ''): + """ A function decorator to define a command with namd 'cmd_name' in the lldb scope to call python function. + params: cmd_name - str : name of command to be set in lldb prompt. + option_string - str: getopt like option string. Only CAPITAL LETTER options allowed. + see README on Customizing command options. + """ + if option_string != option_string.upper(): + raise RuntimeError("Cannot setup command with lowercase option args. %s" % option_string) + + def _cmd(obj): + def _internal_command_function(debugger, command, result, internal_dict): + global config, lldb_run_command_state + stream = CommandOutput(result) + # need to avoid printing on stdout if called from lldb_run_command. + if 'active' in lldb_run_command_state and lldb_run_command_state['active']: + debuglog('Running %s from lldb_run_command' % command) + else: + result.SetImmediateOutputFile(sys.__stdout__) + + command_args = shlex.split(command) + lldb.debugger.HandleCommand('type category disable kernel' ) + def_verbose_level = config['verbosity'] + + try: + stream.setOptions(command_args, option_string) + if stream.verbose_level != 0: + config['verbosity'] = stream.verbose_level + with RedirectStdStreams(stdout=stream) : + if option_string: + obj(cmd_args=stream.target_cmd_args, cmd_options=stream.target_cmd_options) + else: + obj(cmd_args=stream.target_cmd_args) + except KeyboardInterrupt: + print "Execution interrupted by user" + except ArgumentError as arg_error: + if str(arg_error) != "HELP": + print "Argument Error: " + str(arg_error) + print "{0:s}:\n {1:s}".format(cmd_name, obj.__doc__.strip()) + return False + except Exception as exc: + if not config['debug']: + print """ +************ LLDB found an exception ************ +There has been an uncaught exception. A possible cause could be that remote connection has been disconnected. +However, it is recommended that you report the exception to lldb/kernel debugging team about it. +************ Please run 'xnudebug debug enable' to start collecting logs. ************ + """ + raise + + if config['showTypeSummary']: + lldb.debugger.HandleCommand('type category enable kernel' ) + + if stream.pluginRequired : + plugin = LoadXNUPlugin(stream.pluginName) + if plugin == None : + print "Could not load plugins."+stream.pluginName + return + plugin.plugin_init(kern, config, lldb, kern.IsDebuggerConnected()) + return_data = plugin.plugin_execute(cmd_name, result.GetOutput()) + ProcessXNUPluginResult(return_data) + plugin.plugin_cleanup() + + #restore the verbose level after command is complete + config['verbosity'] = def_verbose_level + + return + + myglobals = globals() + command_function_name = obj.__name__+"Command" + myglobals[command_function_name] = _internal_command_function + command_function = myglobals[command_function_name] + if not obj.__doc__ : + print "ERROR: Cannot register command({:s}) without documentation".format(cmd_name) + return obj + command_function.__doc__ = obj.__doc__ + global lldb_command_documentation + if cmd_name in lldb_command_documentation: + lldb.debugger.HandleCommand("command script delete "+cmd_name) + lldb_command_documentation[cmd_name] = (obj.__name__, obj.__doc__.lstrip(), option_string) + lldb.debugger.HandleCommand("command script add -f " + MODULE_NAME + "." + command_function_name + " " + cmd_name) + return obj + return _cmd + +def lldb_alias(alias_name, cmd_line): + """ define an alias in the lldb command line. + A programatic way of registering an alias. This basically does + (lldb)command alias alias_name "cmd_line" + ex. + lldb_alias('readphys16', 'readphys 16') + """ + alias_name = alias_name.strip() + cmd_line = cmd_line.strip() + lldb.debugger.HandleCommand("command alias " + alias_name + " "+ cmd_line) + +def SetupLLDBTypeSummaries(reset=False): + global lldb_summary_definitions, MODULE_NAME + if reset == True: + lldb.debugger.HandleCommand("type category delete kernel ") + for single_type in lldb_summary_definitions.keys(): + summary_function = lldb_summary_definitions[single_type] + lldb_cmd = "type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + ".LLDBSummary" + summary_function.__name__ + debuglog(lldb_cmd) + lldb.debugger.HandleCommand(lldb_cmd) + if config['showTypeSummary']: + lldb.debugger.HandleCommand("type category enable kernel") + else: + lldb.debugger.HandleCommand("type category disable kernel") + + return + +def LoadXNUPlugin(name): + """ Try to load a plugin from the plugins directory. + """ + retval = None + name=name.strip() + try: + module_obj = __import__('plugins.'+name, globals(), locals(), [], -1) + module_obj = module_obj.__dict__[name] + defs = dir(module_obj) + if 'plugin_init' in defs and 'plugin_execute' in defs and 'plugin_cleanup' in defs: + retval = module_obj + else: + print "Plugin is not correctly implemented. Please read documentation on implementing plugins" + except: + print "plugin not found :"+name + + return retval + +def ProcessXNUPluginResult(result_data): + """ Look at the returned data from plugin and see if anymore actions are required or not + params: result_data - list of format (status, out_string, more_commands) + """ + ret_status = result_data[0] + ret_string = result_data[1] + ret_commands = result_data[2] + + if ret_status == False: + print "Plugin failed: " + ret_string + return + print ret_string + if len(ret_commands) >= 0: + for cmd in ret_commands: + print "Running command on behalf of plugin:" + cmd + lldb.debugger.HandleCommand(cmd) + return + +# holds tests registered with xnu. +#DONOTTOUCHME: Exclusive use of xnudebug_test only +lldb_command_tests = {} +def xnudebug_test(test_name): + """ A function decoratore to register a test with the framework. Each test is supposed to be of format + def Test(kernel_target, config, lldb_obj, isConnected ) + + NOTE: The testname should start with "Test" else exception will be raised. + """ + def _test(obj): + global lldb_command_tests + if obj.__name__.find("Test") != 0 : + print "Test name ", obj.__name__ , " should start with Test" + raise ValueError + lldb_command_tests[test_name] = (test_name, obj.__name__, obj, obj.__doc__) + return obj + return _test + + +# End Debugging specific utility functions +# Kernel Debugging specific classes and accessor methods + +# global access object for target kernel + +def GetObjectAtIndexFromArray(array_base, index): + """ Subscript indexing for arrays that are represented in C as pointers. + for ex. int *arr = malloc(20*sizeof(int)); + now to get 3rd int from 'arr' you'd do + arr[2] in C + GetObjectAtIndexFromArray(arr_val,2) + params: + array_base : core.value - representing a pointer type (ex. base of type 'ipc_entry *') + index : int - 0 based index into the array + returns: + core.value : core.value of the same type as array_base_val but pointing to index'th element + """ + array_base_val = array_base.GetSBValue() + base_address = array_base_val.GetValueAsUnsigned() + size = array_base_val.GetType().GetPointeeType().GetByteSize() + obj_address = base_address + (index * size) + obj = kern.GetValueFromAddress(obj_address, array_base_val.GetType().GetName()) + return Cast(obj, array_base_val.GetType()) + + +kern = None + +def GetLLDBThreadForKernelThread(thread_obj): + """ Get a reference to lldb.SBThread representation for kernel thread. + params: + thread_obj : core.cvalue - thread object of type thread_t + returns + lldb.SBThread - lldb thread object for getting backtrace/registers etc. + """ + tid = unsigned(thread_obj.thread_id) + lldb_process = LazyTarget.GetProcess() + sbthread = lldb_process.GetThreadByID(tid) + if not sbthread.IsValid(): + # in case lldb doesnt know about this thread, create one + if hasattr(lldb_process, "CreateOSPluginThread"): + debuglog("creating os plugin thread on the fly for {0:d} 0x{1:x}".format(tid, thread_obj)) + lldb_process.CreateOSPluginThread(tid, unsigned(thread_obj)) + else: + raise RuntimeError("LLDB process does not support CreateOSPluginThread.") + sbthread = lldb_process.GetThreadByID(tid) + + if not sbthread.IsValid(): + raise RuntimeError("Unable to find lldb thread for tid={0:d} thread = {1:#018x}".format(tid, thread_obj)) + + return sbthread + +def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""): + """ Get a string to display back trace for a thread. + params: + thread_obj - core.cvalue : a thread object of type thread_t. + verbosity - int : either of vHUMAN, vSCRIPT or vDETAIL to describe the verbosity of output + prefix - str : a string prefix added before the line for each frame. + isContinuation - bool : is thread a continuation? + returns: + str - a multi line string showing each frame in backtrace. + """ + is_continuation = not bool(unsigned(thread_obj.kernel_stack)) + thread_val = GetLLDBThreadForKernelThread(thread_obj) + out_string = "" + kernel_stack = unsigned(thread_obj.kernel_stack) + reserved_stack = unsigned(thread_obj.reserved_stack) + if not is_continuation: + if kernel_stack and reserved_stack: + out_string += prefix + "reserved_stack = {:#018x}\n".format(reserved_stack) + out_string += prefix + "kernel_stack = {:#018x}\n".format(kernel_stack) + else: + out_string += prefix + "continuation =" + iteration = 0 + last_frame_p = 0 + for frame in thread_val.frames: + addr = frame.GetPCAddress() + load_addr = addr.GetLoadAddress(LazyTarget.GetTarget()) + function = frame.GetFunction() + frame_p = frame.GetFP() + mod_name = frame.GetModule().GetFileSpec().GetFilename() + + if iteration == 0 and not is_continuation: + out_string += prefix +"stacktop = {:#018x}\n".format(frame_p) + + if not function: + # No debug info for 'function'. + symbol = frame.GetSymbol() + file_addr = addr.GetFileAddress() + start_addr = symbol.GetStartAddress().GetFileAddress() + symbol_name = symbol.GetName() + symbol_offset = file_addr - start_addr + out_string += prefix + if not is_continuation: + out_string += "{fp:#018x} ".format(fp = frame_p) + out_string += "{addr:#018x} {mod}`{symbol} + {offset} \n".format(addr=load_addr, mod=mod_name, symbol=symbol_name, offset=symbol_offset) + else: + # Debug info is available for 'function'. + func_name = frame.GetFunctionName() + file_name = frame.GetLineEntry().GetFileSpec().GetFilename() + line_num = frame.GetLineEntry().GetLine() + func_name = '%s [inlined]' % func_name if frame.IsInlined() else func_name + if is_continuation and frame.IsInlined(): + debuglog("Skipping frame for thread {:#018x} since its inlined".format(thread_obj)) + continue + out_string += prefix + if not is_continuation: + out_string += "{fp:#018x} ".format(fp=frame_p) + out_string += "{addr:#018x} {func}{args} \n".format(addr=load_addr, + func=func_name, + file=file_name, line=line_num, + args="(" + (str(frame.arguments).replace("\n", ", ") if len(frame.arguments) > 0 else "void") + ")") + iteration += 1 + if frame_p: + last_frame_p = frame_p + + if not is_continuation and last_frame_p: + out_string += prefix + "stackbottom = {:#018x}".format(last_frame_p) + out_string = out_string.replace("variable not available","") + return out_string + +def GetSourceInformationForAddress(addr): + """ convert and address to function +offset information. + params: addr - int address in the binary to be symbolicated + returns: string of format "0xaddress: function + offset" + """ + symbols = kern.SymbolicateFromAddress(addr) + format_string = "{0:#018x} <{1:s} + {2:#0x}>" + offset = 0 + function_name = "" + if len(symbols) > 0: + s = symbols[0] + function_name = str(s.name) + offset = addr - s.GetStartAddress().GetLoadAddress(LazyTarget.GetTarget()) + if function_name == "": + function_name = "???" + return format_string.format(addr, function_name, offset) + +def GetFrameLocalVariable(variable_name, frame_no=0): + """ Find a local variable by name + params: + variable_name: str - name of variable to search for + returns: + core.value - if the variable is found. + None - if not found or not Valid + """ + retval = None + sbval = None + lldb_SBThread = LazyTarget.GetProcess().GetSelectedThread() + frame = lldb_SBThread.GetSelectedFrame() + if frame_no : + frame = lldb_SBThread.GetFrameAtIndex(frame_no) + if frame : + sbval = frame.FindVariable(variable_name) + if sbval and sbval.IsValid(): + retval = core.cvalue.value(sbval) + return retval + +# Begin Macros for kernel debugging + +@lldb_command('kgmhelp') +def KernelDebugCommandsHelp(cmd_args=None): + """ Show a list of registered commands for kenel debugging. + """ + global lldb_command_documentation + print "List of commands provided by " + MODULE_NAME + " for kernel debugging." + cmds = lldb_command_documentation.keys() + cmds.sort() + for cmd in cmds: + if type(lldb_command_documentation[cmd][-1]) == type(""): + print " {0: <20s} - {1}".format(cmd , lldb_command_documentation[cmd][1].split("\n")[0].strip()) + else: + print " {0: <20s} - {1}".format(cmd , "No help string found.") + print """ + Each of the functions listed here accept the following common options. + -h Show the help string for the command. + -o The output of this command execution will be saved to file. Parser information or errors will + not be sent to file though. eg /tmp/output.txt + -s The "filter_string" param is parsed to python regex expression and each line of output + will be printed/saved only if it matches the expression. + -v [-v...] Each additional -v will increase the verbosity of the command. + -p Send the output of the command to plugin. Please see README for usage of plugins. + + Additionally, each command implementation may have more options. "(lldb) help " will show these options. + """ + return None + + +@lldb_command('showraw') +def ShowRawCommand(cmd_args=None): + """ A command to disable the kernel summaries and show data as seen by the system. + This is useful when trying to read every field of a struct as compared to brief summary + """ + command = " ".join(cmd_args) + lldb.debugger.HandleCommand('type category disable kernel' ) + lldb.debugger.HandleCommand( command ) + lldb.debugger.HandleCommand('type category enable kernel' ) + + +@lldb_command('xnudebug') +def XnuDebugCommand(cmd_args=None): + """ command interface for operating on the xnu macros. Allowed commands are as follows + reload: + Reload a submodule from the xnu/tools/lldb directory. Do not include the ".py" suffix in modulename. + usage: xnudebug reload (eg. memory, process, stats etc) + test: + Start running registered test with from various modules. + usage: xnudebug test (eg. test_memstats) + testall: + Go through all registered tests and run them + debug: + Toggle state of debug configuration flag. + """ + global config + command_args = cmd_args + if len(command_args) == 0: + raise ArgumentError("No command specified.") + supported_subcommands = ['debug', 'reload', 'test', 'testall'] + subcommand = GetLongestMatchOption(command_args[0], supported_subcommands, True) + + if len(subcommand) == 0: + raise ArgumentError("Subcommand (%s) is not a valid command. " % str(command_args[0])) + + subcommand = subcommand[0].lower() + if subcommand == 'debug': + if command_args[-1].lower().find('dis') >=0 and config['debug']: + config['debug'] = False + print "Disabled debug logging." + elif command_args[-1].lower().find('dis') < 0 and not config['debug']: + config['debug'] = True + EnableLLDBAPILogging() # provided by utils.py + print "Enabled debug logging. \nPlease run 'xnudebug debug disable' to disable it again. " + + if subcommand == 'reload': + module_name = command_args[-1] + if module_name in sys.modules: + reload(sys.modules[module_name]) + print module_name + " is reloaded from " + sys.modules[module_name].__file__ + else: + print "Unable to locate module named ", module_name + if subcommand == 'testall': + for test_name in lldb_command_tests.keys(): + print "[BEGIN]", test_name + res = lldb_command_tests[test_name][2](kern, config, lldb, True) + if res: + print "[PASSED] {:s}".format(test_name) + else: + print "[FAILED] {:s}".format(test_name) + if subcommand == 'test': + test_name = command_args[-1] + if test_name in lldb_command_tests: + test = lldb_command_tests[test_name] + print "Running test {:s}".format(test[0]) + if test[2](kern, config, lldb, True) : + print "[PASSED] {:s}".format(test[0]) + else: + print "[FAILED] {:s}".format(test[0]) + return "" + else: + print "No such test registered with name: {:s}".format(test_name) + print "XNUDEBUG Available tests are:" + for i in lldb_command_tests.keys(): + print i + return None + + return False + +@lldb_command('showversion') +def ShowVersion(cmd_args=None): + """ Read the kernel version string from a fixed address in low + memory. Useful if you don't know which kernel is on the other end, + and need to find the appropriate symbols. Beware that if you've + loaded a symbol file, but aren't connected to a remote target, + the version string from the symbol file will be displayed instead. + This macro expects to be connected to the remote kernel to function + correctly. + + """ + print kern.version + + +@lldb_command('paniclog') +def ShowPanicLog(cmd_args=None): + """ Display the paniclog information + """ + panic_buf = kern.globals.debug_buf + panic_buf_start = addressof(panic_buf) + panic_buf_end = unsigned(kern.globals.debug_buf_ptr) + num_bytes = panic_buf_end - panic_buf_start + if num_bytes == 0 : + return + panic_data = panic_buf.GetSBValue().GetData() + err = lldb.SBError() + line = '' + for i in range(0, num_bytes): + c = panic_data.GetUnsignedInt8(err, i) + if chr(c) == '\n': + if line =='': + line = " " + print line + line = '' + else: + line += chr(c) + + if len(line) > 0: + print line + + return + +@lldb_command('showbootargs') +def ShowBootArgs(cmd_args=None): + """ Display boot arguments passed to the target kernel + """ + bootargs = Cast(kern.GetGlobalVariable('PE_state').bootArgs, 'boot_args *') + bootargs_cmd = bootargs.CommandLine + print str(bootargs_cmd) + +@static_var("last_process_uniq_id", 1) +def GetDebuggerStopIDValue(): + """ Create a unique session identifier. + returns: + int - a unique number identified by processid and stopid. + """ + stop_id = 0 + process_obj = LazyTarget.GetProcess() + if hasattr(process_obj, "GetStopID"): + stop_id = process_obj.GetStopID() + proc_uniq_id = 0 + if hasattr(process_obj, 'GetUniqueID'): + proc_uniq_id = process_obj.GetUniqueID() + #FIXME forces us to do this twice + proc_uniq_id = process_obj.GetUniqueID() + else: + GetDebuggerStopIDValue.last_process_uniq_id +=1 + proc_uniq_id = GetDebuggerStopIDValue.last_process_uniq_id + 1 + + stop_id_str = "{:d}:{:d}".format(proc_uniq_id, stop_id) + return hash(stop_id_str) + +# The initialization code to add your commands +_xnu_framework_init = False +def __lldb_init_module(debugger, internal_dict): + global kern, lldb_command_documentation, config, _xnu_framework_init + if _xnu_framework_init: + return + _xnu_framework_init = True + caching._GetDebuggerSessionID = GetDebuggerStopIDValue + debugger.HandleCommand('type summary add --regex --summary-string "${var%s}" -C yes -p -v "char \[[0-9]*\]"') + debugger.HandleCommand('type format add --format hex -C yes uintptr_t') + kern = KernelTarget(debugger) + print "xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries." + +__lldb_init_module(lldb.debugger, None) + +@lldb_command("showlldbtypesummaries") +def ShowLLDBTypeSummaries(cmd_args=[]): + """ Enable/Disable kernel type summaries. Default is disabled. + Usage: showlldbtypesummaries [enable|disable] + default is enable + """ + global config + action = "enable" + trailer_msg = '' + if len(cmd_args) > 0 and cmd_args[0].lower().find('disable') >=0: + action = "disable" + config['showTypeSummary'] = False + trailer_msg = "Please run 'showlldbtypesummaries enable' to enable the summary feature." + else: + config['showTypeSummary'] = True + SetupLLDBTypeSummaries(True) + trailer_msg = "Please run 'showlldbtypesummaries disable' to disable the summary feature." + lldb_run_command("type category "+ action +" kernel") + print "Successfully "+action+"d the kernel type summaries. %s" % trailer_msg + +from memory import * +from process import * +from ipc import * +from pmap import * +from ioreg import * +from mbufs import * +from net import * +from kdp import * +from userspace import * +from pci import * +from misc import * +from apic import * +from scheduler import *