clang 20.0.0 (based on r547379) from build 12806354. Bug: http://b/379133546 Test: N/A Change-Id: I2eb8938af55d809de674be63cb30cf27e801862b Upstream-Commit: ad834e67b1105d15ef907f6255d4c96e8e733f57
358 lines
14 KiB
Python
358 lines
14 KiB
Python
"""
|
|
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. Access to the OV parser 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.
|
|
|
|
There are 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
|
|
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
|
|
|
|
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 "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. """
|
|
|
|
elem = self.get_option_element(opt_name)
|
|
if not elem:
|
|
return False
|
|
try:
|
|
return elem["_value_set"]
|
|
except AttributeError:
|
|
return False
|
|
|
|
def add_option(self, short_option, long_option, help, default,
|
|
dest = None, required=False, groups = None,
|
|
value_type=lldb.eArgTypeNone, 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
|
|
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, I
|
|
haven't done custom completions yet.
|
|
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,
|
|
"value_type" : value_type,
|
|
"completion_type" : completion_type,
|
|
"dest" : dest,
|
|
"default" : default}
|
|
|
|
if enum_values:
|
|
dict["enum_values"] = enum_values
|
|
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)
|
|
)
|