1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
|
"""
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)
)
|