Skip to content

Commit a0c72c0

Browse files
committed
Add the ability to define a Python based command that uses the
CommandObjectParsed way to specify options and arguments so that these commands can be "first class citizens" with build-in commands.
1 parent 997ffce commit a0c72c0

File tree

15 files changed

+1714
-15
lines changed

15 files changed

+1714
-15
lines changed

lldb/bindings/python/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar
9696
${lldb_python_target_dir}
9797
"utils"
9898
FILES "${LLDB_SOURCE_DIR}/examples/python/in_call_stack.py"
99-
"${LLDB_SOURCE_DIR}/examples/python/symbolication.py")
99+
"${LLDB_SOURCE_DIR}/examples/python/symbolication.py"
100+
"${LLDB_SOURCE_DIR}/examples/python/parsed_cmd.py")
100101

101102
create_python_package(
102103
${swig_target}

lldb/bindings/python/python-wrapper.swig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,37 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommandObject(
831831
return true;
832832
}
833833

834+
bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
835+
PyObject *implementor, lldb::DebuggerSP debugger, lldb_private::StructuredDataImpl &args_impl,
836+
lldb_private::CommandReturnObject &cmd_retobj,
837+
lldb::ExecutionContextRefSP exe_ctx_ref_sp) {
838+
839+
PyErr_Cleaner py_err_cleaner(true);
840+
841+
PythonObject self(PyRefType::Borrowed, implementor);
842+
auto pfunc = self.ResolveName<PythonCallable>("__call__");
843+
844+
if (!pfunc.IsAllocated())
845+
return false;
846+
847+
auto cmd_retobj_arg = SWIGBridge::ToSWIGWrapper(cmd_retobj);
848+
849+
// FIXME:
850+
// I wanted to do something like:
851+
// size_t num_elem = args.size();
852+
// PythonList my_list(num_elem);
853+
// for (const char *elem : args)
854+
// my_list.append(PythonString(elem);
855+
//
856+
// and then pass my_list to the pfunc, but that crashes somewhere
857+
// deep in Python for reasons that aren't clear to me.
858+
859+
pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), SWIGBridge::ToSWIGWrapper(args_impl),
860+
SWIGBridge::ToSWIGWrapper(exe_ctx_ref_sp), cmd_retobj_arg.obj());
861+
862+
return true;
863+
}
864+
834865
PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin(
835866
const char *python_class_name, const char *session_dictionary_name,
836867
const lldb::ProcessSP &process_sp) {

lldb/examples/python/parsed_cmd.py

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
"""
2+
This module implements a couple of utility classes to make writing
3+
lldb parsed commands more Pythonic.
4+
The way to use it is to make a class for you command that inherits from ParsedCommandBase.
5+
That will make an LLDBOVParser which you will use for your
6+
option definition, and to fetch option values for the current invocation
7+
of your command. Access to the OV parser is through:
8+
9+
ParsedCommandBase.get_parser()
10+
11+
Next, implement setup_command_definition in your new command class, and call:
12+
13+
self.get_parser().add_option
14+
15+
to add all your options. The order doesn't matter for options, lldb will sort them
16+
alphabetically for you when it prints help.
17+
18+
Similarly you can define the arguments with:
19+
20+
self.get_parser.add_argument
21+
22+
at present, lldb doesn't do as much work as it should verifying arguments, it pretty
23+
much only checks that commands that take no arguments don't get passed arguments.
24+
25+
Then implement the execute function for your command as:
26+
27+
def __call__(self, debugger, args_array, exe_ctx, result):
28+
29+
The arguments will be in a python array as strings.
30+
31+
You can access the option values using varname you passed in when defining the option.
32+
If you need to know whether a given option was set by the user or not, you can retrieve
33+
the option definition array with:
34+
35+
self.get_options_definition()
36+
37+
look up your element by varname and check the "_value_set" element.
38+
39+
There are example commands in the lldb testsuite at:
40+
41+
llvm-project/lldb/test/API/commands/command/script/add/test_commands.py
42+
43+
FIXME: I should make a convenient wrapper for that.
44+
"""
45+
import inspect
46+
import lldb
47+
import sys
48+
49+
class LLDBOVParser:
50+
def __init__(self):
51+
self.options_array = []
52+
self.args_array = []
53+
54+
# Some methods to translate common value types. Should return a
55+
# tuple of the value and an error value (True => error) if the
56+
# type can't be converted.
57+
# FIXME: Need a way to push the conversion error string back to lldb.
58+
@staticmethod
59+
def to_bool(in_value):
60+
error = True
61+
value = False
62+
low_in = in_value.lower()
63+
if low_in == "yes" or low_in == "true" or low_in == "1":
64+
value = True
65+
error = False
66+
67+
if not value and low_in == "no" or low_in == "false" or low_in == "0":
68+
value = False
69+
error = False
70+
71+
return (value, error)
72+
73+
@staticmethod
74+
def to_int(in_value):
75+
#FIXME: Not doing errors yet...
76+
return (int(in_value), False)
77+
78+
def to_unsigned(in_value):
79+
# FIXME: find an unsigned converter...
80+
# And handle errors.
81+
return (int(in_value), False)
82+
83+
translators = {
84+
lldb.eArgTypeBoolean : to_bool,
85+
lldb.eArgTypeBreakpointID : to_unsigned,
86+
lldb.eArgTypeByteSize : to_unsigned,
87+
lldb.eArgTypeCount : to_unsigned,
88+
lldb.eArgTypeFrameIndex : to_unsigned,
89+
lldb.eArgTypeIndex : to_unsigned,
90+
lldb.eArgTypeLineNum : to_unsigned,
91+
lldb.eArgTypeNumLines : to_unsigned,
92+
lldb.eArgTypeNumberPerLine : to_unsigned,
93+
lldb.eArgTypeOffset : to_int,
94+
lldb.eArgTypeThreadIndex : to_unsigned,
95+
lldb.eArgTypeUnsignedInteger : to_unsigned,
96+
lldb.eArgTypeWatchpointID : to_unsigned,
97+
lldb.eArgTypeColumnNum : to_unsigned,
98+
lldb.eArgTypeRecognizerID : to_unsigned,
99+
lldb.eArgTypeTargetID : to_unsigned,
100+
lldb.eArgTypeStopHookID : to_unsigned
101+
}
102+
103+
@classmethod
104+
def translate_value(cls, value_type, value):
105+
error = False
106+
try:
107+
return cls.translators[value_type](value)
108+
except KeyError:
109+
# If we don't have a translator, return the string value.
110+
return (value, False)
111+
112+
# FIXME: would this be better done on the C++ side?
113+
# The common completers are missing some useful ones.
114+
# For instance there really should be a common Type completer
115+
# And an "lldb command name" completer.
116+
completion_table = {
117+
lldb.eArgTypeAddressOrExpression : lldb.eVariablePathCompletion,
118+
lldb.eArgTypeArchitecture : lldb.eArchitectureCompletion,
119+
lldb.eArgTypeBreakpointID : lldb.eBreakpointCompletion,
120+
lldb.eArgTypeBreakpointIDRange : lldb.eBreakpointCompletion,
121+
lldb.eArgTypeBreakpointName : lldb.eBreakpointNameCompletion,
122+
lldb.eArgTypeClassName : lldb.eSymbolCompletion,
123+
lldb.eArgTypeDirectoryName : lldb.eDiskDirectoryCompletion,
124+
lldb.eArgTypeExpression : lldb.eVariablePathCompletion,
125+
lldb.eArgTypeExpressionPath : lldb.eVariablePathCompletion,
126+
lldb.eArgTypeFilename : lldb.eDiskFileCompletion,
127+
lldb.eArgTypeFrameIndex : lldb.eFrameIndexCompletion,
128+
lldb.eArgTypeFunctionName : lldb.eSymbolCompletion,
129+
lldb.eArgTypeFunctionOrSymbol : lldb.eSymbolCompletion,
130+
lldb.eArgTypeLanguage : lldb.eTypeLanguageCompletion,
131+
lldb.eArgTypePath : lldb.eDiskFileCompletion,
132+
lldb.eArgTypePid : lldb.eProcessIDCompletion,
133+
lldb.eArgTypeProcessName : lldb.eProcessNameCompletion,
134+
lldb.eArgTypeRegisterName : lldb.eRegisterCompletion,
135+
lldb.eArgTypeRunArgs : lldb.eDiskFileCompletion,
136+
lldb.eArgTypeShlibName : lldb.eModuleCompletion,
137+
lldb.eArgTypeSourceFile : lldb.eSourceFileCompletion,
138+
lldb.eArgTypeSymbol : lldb.eSymbolCompletion,
139+
lldb.eArgTypeThreadIndex : lldb.eThreadIndexCompletion,
140+
lldb.eArgTypeVarName : lldb.eVariablePathCompletion,
141+
lldb.eArgTypePlatform : lldb.ePlatformPluginCompletion,
142+
lldb.eArgTypeWatchpointID : lldb.eWatchpointIDCompletion,
143+
lldb.eArgTypeWatchpointIDRange : lldb.eWatchpointIDCompletion,
144+
lldb.eArgTypeModuleUUID : lldb.eModuleUUIDCompletion,
145+
lldb.eArgTypeStopHookID : lldb.eStopHookIDCompletion
146+
}
147+
148+
@classmethod
149+
def determine_completion(cls, arg_type):
150+
try:
151+
return cls.completion_table[arg_type]
152+
except KeyError:
153+
return lldb.eNoCompletion
154+
155+
def get_option_element(self, long_name):
156+
# Fixme: Is it worth making a long_option dict holding the rest of
157+
# the options dict so this lookup is faster?
158+
for item in self.options_array:
159+
if item["long_option"] == long_name:
160+
return item
161+
162+
return None
163+
164+
def option_parsing_started(self):
165+
# This makes the ivars for all the varnames in the array and gives them
166+
# their default values.
167+
for elem in self.options_array:
168+
elem['_value_set'] = False
169+
try:
170+
object.__setattr__(self, elem["varname"], elem["default"])
171+
except AttributeError:
172+
# It isn't an error not to have a target, you'll just have to set and
173+
# get this option value on your own.
174+
continue
175+
176+
def set_enum_value(self, enum_values, input):
177+
candidates = []
178+
for candidate in enum_values:
179+
# The enum_values are a duple of value & help string.
180+
value = candidate[0]
181+
if value.startswith(input):
182+
candidates.append(value)
183+
184+
if len(candidates) == 1:
185+
return (candidates[0], False)
186+
else:
187+
return (input, True)
188+
189+
def set_option_value(self, exe_ctx, opt_name, opt_value):
190+
elem = self.get_option_element(opt_name)
191+
if not elem:
192+
return False
193+
194+
if "enum_values" in elem:
195+
(value, error) = self.set_enum_value(elem["enum_values"], opt_value)
196+
else:
197+
(value, error) = __class__.translate_value(elem["value_type"], opt_value)
198+
199+
if not error:
200+
object.__setattr__(self, elem["varname"], value)
201+
elem["_value_set"] = True
202+
return True
203+
return False
204+
205+
def was_set(self, opt_name):
206+
elem = self.get_option_element(opt_name)
207+
if not elem:
208+
return False
209+
try:
210+
return elem["_value_set"]
211+
except AttributeError:
212+
return False
213+
214+
def is_enum_opt(self, opt_name):
215+
elem = self.get_option_element(opt_name)
216+
if not elem:
217+
return False
218+
return "enum_values" in elem
219+
220+
def add_option(self, short_option, long_option, usage, default,
221+
varname = None, required=False, groups = None,
222+
value_type=lldb.eArgTypeNone, completion_type=None,
223+
enum_values=None):
224+
"""
225+
short_option: one character, must be unique, not required
226+
long_option: no spaces, must be unique, required
227+
usage: a usage string for this option, will print in the command help
228+
default: the initial value for this option (if it has a value)
229+
varname: the name of the property that gives you access to the value for
230+
this value. Defaults to the long option if not provided.
231+
required: if true, this option must be provided or the command will error out
232+
groups: Which "option groups" does this option belong to
233+
value_type: one of the lldb.eArgType enum values. Some of the common arg
234+
types also have default completers, which will be applied automatically.
235+
completion_type: currently these are values form the lldb.CompletionType enum, I
236+
haven't done custom completions yet.
237+
enum_values: An array of duples: ["element_name", "element_help"]. If provided,
238+
only one of the enum elements is allowed. The value will be the
239+
element_name for the chosen enum element as a string.
240+
"""
241+
if not varname:
242+
varname = long_option
243+
244+
if not completion_type:
245+
completion_type = self.determine_completion(value_type)
246+
247+
dict = {"short_option" : short_option,
248+
"long_option" : long_option,
249+
"required" : required,
250+
"usage" : usage,
251+
"value_type" : value_type,
252+
"completion_type" : completion_type,
253+
"varname" : varname,
254+
"default" : default}
255+
256+
if enum_values:
257+
dict["enum_values"] = enum_values
258+
if groups:
259+
dict["groups"] = groups
260+
261+
self.options_array.append(dict)
262+
263+
def make_argument_element(self, arg_type, repeat = "optional", groups = None):
264+
element = {"arg_type" : arg_type, "repeat" : repeat}
265+
if groups:
266+
element["groups"] = groups
267+
return element
268+
269+
def add_argument_set(self, arguments):
270+
self.args_array.append(arguments)
271+
272+
class ParsedCommandBase:
273+
def __init__(self, debugger, unused):
274+
self.debugger = debugger
275+
self.ov_parser = LLDBOVParser()
276+
self.setup_command_definition()
277+
278+
def get_parser(self):
279+
return self.ov_parser
280+
281+
def get_options_definition(self):
282+
return self.get_parser().options_array
283+
284+
def get_flags(self):
285+
return 0
286+
287+
def get_args_definition(self):
288+
return self.get_parser().args_array
289+
290+
def option_parsing_started(self):
291+
self.get_parser().option_parsing_started()
292+
293+
def set_option_value(self, exe_ctx, opt_name, opt_value):
294+
return self.get_parser().set_option_value(exe_ctx, opt_name, opt_value)
295+
296+
# These are the two "pure virtual" methods:
297+
def __call__(self, debugger, args_array, exe_ctx, result):
298+
raise NotImplementedError()
299+
300+
def setup_command_definition(self):
301+
raise NotImplementedError()
302+
303+
@staticmethod
304+
def do_register_cmd(cls, debugger, module_name):
305+
# Add any commands contained in this module to LLDB
306+
command = "command script add -o -p -c %s.%s %s" % (
307+
module_name,
308+
cls.__name__,
309+
cls.program,
310+
)
311+
debugger.HandleCommand(command)
312+
print(
313+
'The "{0}" command has been installed, type "help {0}"'
314+
'for detailed help.'.format(cls.program)
315+
)

lldb/include/lldb/Interpreter/CommandObject.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
224224
void GetFormattedCommandArguments(Stream &str,
225225
uint32_t opt_set_mask = LLDB_OPT_SET_ALL);
226226

227-
bool IsPairType(ArgumentRepetitionType arg_repeat_type);
227+
static bool IsPairType(ArgumentRepetitionType arg_repeat_type);
228+
229+
static std::optional<ArgumentRepetitionType>
230+
ArgRepetitionFromString(llvm::StringRef string);
228231

229232
bool ParseOptions(Args &args, CommandReturnObject &result);
230233

0 commit comments

Comments
 (0)