| """ | |
| This module implements a couple of utility classes to make writing | |
| lldb parsed commands more Pythonic. | |
| The way to use it is to make a class for your command that inherits from ParsedCommandBase. | |
| That will make an LLDBOptionValueParser which you will use for your | |
| option definition, and to fetch option values for the current invocation | |
| of your command. For concision, I'll call this the `OVParser`. | |
| Access to the `OVParser` is through: | |
| ParsedCommandBase.get_parser() | |
| Next, implement setup_command_definition() in your new command class, and call: | |
| self.get_parser().add_option() | |
| to add all your options. The order doesn't matter for options, lldb will sort them | |
| alphabetically for you when it prints help. | |
| Similarly you can define the arguments with: | |
| self.get_parser().add_argument() | |
| At present, lldb doesn't do as much work as it should verifying arguments, it | |
| only checks that commands that take no arguments don't get passed arguments. | |
| Then implement the execute function for your command as: | |
| def __call__(self, debugger, args_list, exe_ctx, result): | |
| The arguments will be a list of strings. | |
| You can access the option values using the 'dest' string you passed in when defining the option. | |
| And if you need to know whether a given option was set by the user or not, you can | |
| use the was_set API. | |
| So for instance, if you have an option whose "dest" is "my_option", then: | |
| self.get_parser().my_option | |
| will fetch the value, and: | |
| self.get_parser().was_set("my_option") | |
| will return True if the user set this option, and False if it was left at its default | |
| value. | |
| Custom Completions: | |
| You can also implement custom completers for your custom command, either for the | |
| arguments to your command or to the option values in your command. If you use enum | |
| values or if your option/argument uses is one of the types we have completers for, | |
| you should not need to do this. But if you have your own completeable types, or if | |
| you want completion of one option to be conditioned by other options on the command | |
| line, you can use this interface to take over the completion. | |
| You can choose to add a completion for the option values defined for your command, | |
| or for the arguments, separately. For the option values, define: | |
| def handle_option_argument_completion(self, long_option, cursor_pos): | |
| The line to be completed will be parsed up to the option containint the cursor position, | |
| and the values will be set in the OptionValue parser object. long_option will be | |
| the option name containing the cursor, and cursor_pos will be the position of the cursor | |
| in that option's value. You can call the `OVParser` method: `dest_for_option(long_option)` | |
| to get the value for that option. The other options that came before the cursor in the command | |
| line will also be set in the `OVParser` when the completion handler is called. | |
| For argument values, define: | |
| def handle_argument_completion(self, args, arg_pos, cursor_pos): | |
| Again, the command line will be parsed up to the cursor position, and all the options | |
| before the cursor pose will be set in the `OVParser`. args is a python list of the | |
| arguments, arg_pos is the index of the argument with the cursor, and cursor_pos is | |
| the position of the cursor in the argument. | |
| In both cases, the return value determines the completion. | |
| Return False to mean "Not Handled" - in which case lldb will fall back on the | |
| standard completion machinery. | |
| Return True to mean "Handled with no completions". | |
| If there is a single unique completion, return a Python dictionary with two elements: | |
| return {"completion" : "completed_value", "mode" : <"partial", "complete">} | |
| If the mode is "partial", then the completion is to a common base, if it is "complete" | |
| then the argument is considered done - mostly meaning lldb will put a space after the | |
| completion string. "complete" is the default if no "mode" is specified. | |
| If there are multiple completion options, then return: | |
| return {"values" : ["option1", "option2"]} | |
| Optionally, you can return a parallel array of "descriptions" which the completer will | |
| print alongside the options: | |
| return {"values" : ["option1", "option2"], "descriptions" : ["the first option", "the second option"]} | |
| The cmdtemplate example currently uses the parsed command infrastructure: | |
| llvm-project/lldb/examples/python/cmdtemplate.py | |
| There are also a few example commands in the lldb testsuite at: | |
| llvm-project/lldb/test/API/commands/command/script/add/test_commands.py | |
| """ | |
| import inspect | |
| import lldb | |
| import sys | |
| from abc import abstractmethod | |
| # Some methods to translate common value types. Should return a | |
| # tuple of the value and an error value (True => error) if the | |
| # type can't be converted. These are called internally when the | |
| # command line is parsed into the 'dest' properties, you should | |
| # not need to call them directly. | |
| # FIXME: Need a way to push the conversion error string back to lldb. | |
| def to_bool(in_value): | |
| error = True | |
| value = False | |
| if type(in_value) != str or len(in_value) == 0: | |
| return (value, error) | |
| low_in = in_value.lower() | |
| if low_in in ["y", "yes", "t", "true", "1"]: | |
| value = True | |
| error = False | |
| if not value and low_in in ["n", "no", "f", "false", "0"]: | |
| value = False | |
| error = False | |
| return (value, error) | |
| def to_int(in_value): | |
| #FIXME: Not doing errors yet... | |
| return (int(in_value), False) | |
| def to_unsigned(in_value): | |
| # FIXME: find an unsigned converter... | |
| # And handle errors. | |
| return (int(in_value), False) | |
| translators = { | |
| lldb.eArgTypeBoolean : to_bool, | |
| lldb.eArgTypeBreakpointID : to_unsigned, | |
| lldb.eArgTypeByteSize : to_unsigned, | |
| lldb.eArgTypeCount : to_unsigned, | |
| lldb.eArgTypeFrameIndex : to_unsigned, | |
| lldb.eArgTypeIndex : to_unsigned, | |
| lldb.eArgTypeLineNum : to_unsigned, | |
| lldb.eArgTypeNumLines : to_unsigned, | |
| lldb.eArgTypeNumberPerLine : to_unsigned, | |
| lldb.eArgTypeOffset : to_int, | |
| lldb.eArgTypeThreadIndex : to_unsigned, | |
| lldb.eArgTypeUnsignedInteger : to_unsigned, | |
| lldb.eArgTypeWatchpointID : to_unsigned, | |
| lldb.eArgTypeColumnNum : to_unsigned, | |
| lldb.eArgTypeRecognizerID : to_unsigned, | |
| lldb.eArgTypeTargetID : to_unsigned, | |
| lldb.eArgTypeStopHookID : to_unsigned | |
| } | |
| def translate_value(value_type, value): | |
| try: | |
| return translators[value_type](value) | |
| except KeyError: | |
| # If we don't have a translator, return the string value. | |
| return (value, False) | |
| class LLDBOptionValueParser: | |
| """ | |
| This class holds the option definitions for the command, and when | |
| the command is run, you can ask the parser for the current values. """ | |
| def __init__(self): | |
| # This is a dictionary of dictionaries. The key is the long option | |
| # name, and the value is the rest of the definition. | |
| self.options_dict = {} | |
| self.args_array = [] | |
| # FIXME: would this be better done on the C++ side? | |
| # The common completers are missing some useful ones. | |
| # For instance there really should be a common Type completer | |
| # And an "lldb command name" completer. | |
| completion_table = { | |
| lldb.eArgTypeAddressOrExpression : lldb.eVariablePathCompletion, | |
| lldb.eArgTypeArchitecture : lldb.eArchitectureCompletion, | |
| lldb.eArgTypeBreakpointID : lldb.eBreakpointCompletion, | |
| lldb.eArgTypeBreakpointIDRange : lldb.eBreakpointCompletion, | |
| lldb.eArgTypeBreakpointName : lldb.eBreakpointNameCompletion, | |
| lldb.eArgTypeClassName : lldb.eSymbolCompletion, | |
| lldb.eArgTypeDirectoryName : lldb.eDiskDirectoryCompletion, | |
| lldb.eArgTypeExpression : lldb.eVariablePathCompletion, | |
| lldb.eArgTypeExpressionPath : lldb.eVariablePathCompletion, | |
| lldb.eArgTypeFilename : lldb.eDiskFileCompletion, | |
| lldb.eArgTypeFrameIndex : lldb.eFrameIndexCompletion, | |
| lldb.eArgTypeFunctionName : lldb.eSymbolCompletion, | |
| lldb.eArgTypeFunctionOrSymbol : lldb.eSymbolCompletion, | |
| lldb.eArgTypeLanguage : lldb.eTypeLanguageCompletion, | |
| lldb.eArgTypePath : lldb.eDiskFileCompletion, | |
| lldb.eArgTypePid : lldb.eProcessIDCompletion, | |
| lldb.eArgTypeProcessName : lldb.eProcessNameCompletion, | |
| lldb.eArgTypeRegisterName : lldb.eRegisterCompletion, | |
| lldb.eArgTypeRunArgs : lldb.eDiskFileCompletion, | |
| lldb.eArgTypeShlibName : lldb.eModuleCompletion, | |
| lldb.eArgTypeSourceFile : lldb.eSourceFileCompletion, | |
| lldb.eArgTypeSymbol : lldb.eSymbolCompletion, | |
| lldb.eArgTypeThreadIndex : lldb.eThreadIndexCompletion, | |
| lldb.eArgTypeVarName : lldb.eVariablePathCompletion, | |
| lldb.eArgTypePlatform : lldb.ePlatformPluginCompletion, | |
| lldb.eArgTypeWatchpointID : lldb.eWatchpointIDCompletion, | |
| lldb.eArgTypeWatchpointIDRange : lldb.eWatchpointIDCompletion, | |
| lldb.eArgTypeModuleUUID : lldb.eModuleUUIDCompletion, | |
| lldb.eArgTypeStopHookID : lldb.eStopHookIDCompletion | |
| } | |
| @classmethod | |
| def determine_completion(cls, arg_type): | |
| return cls.completion_table.get(arg_type, lldb.eNoCompletion) | |
| def add_argument_set(self, arguments): | |
| self.args_array.append(arguments) | |
| def get_option_element(self, long_name): | |
| return self.options_dict.get(long_name, None) | |
| def is_enum_opt(self, opt_name): | |
| elem = self.get_option_element(opt_name) | |
| if not elem: | |
| return False | |
| return "enum_values" in elem | |
| def option_parsing_started(self): | |
| """ This makes the ivars for all the "dest" values in the array and gives them | |
| their default values. You should not have to call this by hand, though if | |
| you have some option that needs to do some work when a new command invocation | |
| starts, you can override this to handle your special option. """ | |
| for key, elem in self.options_dict.items(): | |
| elem['_value_set'] = False | |
| # If there's no value_type, then there can't be a dest. | |
| if not "value_type" in elem: | |
| continue | |
| try: | |
| object.__setattr__(self, elem["dest"], elem["default"]) | |
| except AttributeError: | |
| # It isn't an error not to have a "dest" variable name, you'll | |
| # just have to manage this option's value on your own. | |
| continue | |
| except KeyError: | |
| continue | |
| def set_enum_value(self, enum_values, input): | |
| """ This sets the value for an enum option, you should not have to call this | |
| by hand. """ | |
| candidates = [] | |
| for candidate in enum_values: | |
| # The enum_values are a two element list of value & help string. | |
| value = candidate[0] | |
| if value.startswith(input): | |
| candidates.append(value) | |
| if len(candidates) == 1: | |
| return (candidates[0], False) | |
| else: | |
| return (input, True) | |
| def set_option_value(self, exe_ctx, opt_name, opt_value): | |
| """ This sets a single option value. This will handle most option | |
| value types, but if you have an option that has some complex behavior, | |
| you can override this to implement that behavior, and then pass the | |
| rest of the options to the base class implementation. """ | |
| elem = self.get_option_element(opt_name) | |
| if not elem: | |
| return False | |
| # If there's no value_type in element, then it has no value, so just mark | |
| # it set and return: | |
| if not "value_type" in elem: | |
| elem["_value_set"] = True | |
| return True | |
| if "enum_values" in elem: | |
| (value, error) = self.set_enum_value(elem["enum_values"], opt_value) | |
| else: | |
| (value, error) = translate_value(elem["value_type"], opt_value) | |
| if error: | |
| return False | |
| object.__setattr__(self, elem["dest"], value) | |
| elem["_value_set"] = True | |
| return True | |
| def was_set(self, opt_name): | |
| """Call this in the __call__ method of your command to determine | |
| whether this option was set on the command line. It is sometimes | |
| useful to know whether an option has the default value because the | |
| user set it explicitly (was_set -> True) or not. | |
| You can also call this in a handle_completion method, but it will | |
| currently only report true values for the options mentioned | |
| BEFORE the cursor point in the command line. | |
| """ | |
| elem = self.get_option_element(opt_name) | |
| if not elem: | |
| return False | |
| try: | |
| return elem["_value_set"] | |
| except AttributeError: | |
| return False | |
| def dest_for_option(self, opt_name): | |
| """This will return the value of the dest variable you defined for opt_name. | |
| Mostly useful for handle_completion where you get passed the long option. | |
| """ | |
| elem = self.get_option_element(opt_name) | |
| if not elem: | |
| return None | |
| value = self.__dict__[elem["dest"]] | |
| return value | |
| def add_option( | |
| self, | |
| short_option, | |
| long_option, | |
| help, | |
| default=None, | |
| dest=None, | |
| required=False, | |
| groups=None, | |
| value_type=None, | |
| completion_type=None, | |
| enum_values=None, | |
| ): | |
| """ | |
| short_option: one character, must be unique, not required | |
| long_option: no spaces, must be unique, required | |
| help: a usage string for this option, will print in the command help | |
| default: the initial value for this option (if it has a value) | |
| dest: the name of the property that gives you access to the value for | |
| this value. Defaults to the long option if not provided. | |
| required: if true, this option must be provided or the command will error out | |
| groups: Which "option groups" does this option belong to. This can either be | |
| a simple list (e.g. [1, 3, 4, 5]) or you can specify ranges by sublists: | |
| so [1, [3,5]] is the same as [1, 3, 4, 5]. | |
| value_type: one of the lldb.eArgType enum values. Some of the common arg | |
| types also have default completers, which will be applied automatically. | |
| completion_type: currently these are values form the lldb.CompletionType enum. If | |
| you need custom completions, implement handle_option_argument_completion. | |
| enum_values: An array of duples: ["element_name", "element_help"]. If provided, | |
| only one of the enum elements is allowed. The value will be the | |
| element_name for the chosen enum element as a string. | |
| """ | |
| if not dest: | |
| dest = long_option | |
| if not completion_type: | |
| completion_type = self.determine_completion(value_type) | |
| dict = { | |
| "short_option": short_option, | |
| "required": required, | |
| "help": help, | |
| } | |
| if enum_values: | |
| if not value_type: | |
| print("I am setting value type for an enum value") | |
| value_type = lldb.eArgTypeNone | |
| else: | |
| print(f"An enum value had a type: {value_type}") | |
| dict["enum_values"] = enum_values | |
| if value_type: | |
| dict["value_type"] = value_type | |
| dict["completion_type"] = completion_type | |
| dict["dest"] = dest | |
| dict["default"] = default | |
| if groups: | |
| dict["groups"] = groups | |
| self.options_dict[long_option] = dict | |
| def make_argument_element(self, arg_type, repeat = "optional", groups = None): | |
| element = {"arg_type" : arg_type, "repeat" : repeat} | |
| if groups: | |
| element["groups"] = groups | |
| return element | |
| class ParsedCommand: | |
| def __init__(self, debugger, unused): | |
| self.debugger = debugger | |
| self.ov_parser = LLDBOptionValueParser() | |
| self.setup_command_definition() | |
| def get_options_definition(self): | |
| return self.get_parser().options_dict | |
| def get_flags(self): | |
| return 0 | |
| def get_args_definition(self): | |
| return self.get_parser().args_array | |
| # The base class will handle calling these methods | |
| # when appropriate. | |
| def option_parsing_started(self): | |
| self.get_parser().option_parsing_started() | |
| def set_option_value(self, exe_ctx, opt_name, opt_value): | |
| return self.get_parser().set_option_value(exe_ctx, opt_name, opt_value) | |
| def get_parser(self): | |
| """Returns the option value parser for this command. | |
| When defining the command, use the parser to add | |
| argument and option definitions to the command. | |
| When you are in the command callback, the parser | |
| gives you access to the options passes to this | |
| invocation""" | |
| return self.ov_parser | |
| # These are the two "pure virtual" methods: | |
| @abstractmethod | |
| def __call__(self, debugger, args_array, exe_ctx, result): | |
| """This is the command callback. The option values are | |
| provided by the 'dest' properties on the parser. | |
| args_array: This is the list of arguments provided. | |
| exe_ctx: Gives the SBExecutionContext on which the | |
| command should operate. | |
| result: Any results of the command should be | |
| written into this SBCommandReturnObject. | |
| """ | |
| raise NotImplementedError() | |
| @abstractmethod | |
| def setup_command_definition(self): | |
| """This will be called when your command is added to | |
| the command interpreter. Here is where you add your | |
| options and argument definitions for the command.""" | |
| raise NotImplementedError() | |
| @staticmethod | |
| def do_register_cmd(cls, debugger, module_name): | |
| """ Add any commands contained in this module to LLDB """ | |
| command = "command script add -o -p -c %s.%s %s" % ( | |
| module_name, | |
| cls.__name__, | |
| cls.program, | |
| ) | |
| debugger.HandleCommand(command) | |
| print( | |
| 'The "{0}" command has been installed, type "help {0}"' | |
| 'for detailed help.'.format(cls.program) | |
| ) |