This chapter describes how to add new Simics CLI commands. By adding such commands you can extend what a user can do from the Simics command line. For example, you can add new ways for a user to inspect and control device models you have written.
Commands in Simics are written in Python. They are normally implemented by accessing the interface functions and attributes of configuration objects, and by calling Simics API functions. Commands are easy to write, and let the user conveniently perform tasks that otherwise would require detailed knowledge of the attributes in your device.
Most commands are related to a specific class or interface, and are declared
when the corresponding module is loaded. For example, the sample-device-dml
module registers the commands <sample_device_dml>.info and
<sample_device_dml>.status when the module is loaded.
Commands can also be registered globally. For example, the sample-components
module defines the new-sample-pci-card command. For device models, global
commands are usually defined when the module loads. However, commands can also
be defined when Simics is started. Doing so makes the command available without
loading the module, but it also makes Simics startup a bit slower.
For a simple device, it is sufficient to declare all commands in a single Python
file, called module_load.py. In some cases it can make sense to split up this
file into multiple files; either in order to improve readability, to simplify
unit testing, or to enable code sharing between Simics modules. Chapter
13 discusses in detail how Python files are added to a
module.
This is an example on how to add a new Simics command using Python, as it would
appear in the module_load.py file of a module’s source code subdirectory:
# this line imports definitions needed below
import cli
# this is my Python function that will be called when the
# command is invoked from the Simics prompt.
def my_command_fun(int_arg, str_arg, flag_arg):
result = f"My integer: {int_arg}\n"
result += f"My string: {str_arg}\n"
result += f"Flag is {'' if flag_arg else 'not '}given"
return command_return(message = result, value = int_arg)
# register our new command
cli.new_command("my-command", my_command_fun,
args = [cli.arg(cli.int_t, "arg", "?", 10),
cli.arg(cli.str_t, "name"),
cli.arg(cli.flag_t, "-f")],
alias = "mc",
short = "my command does it",
doc_items = [("NOTE", "This command is best")],
see_also = ["my-other-command"],
doc = """
<b>my-command</b> is best.
This is its documentation. <arg>arg</arg>
is the first argument...""")
The code above will define a command called my-command, with mc as its
alias. When the command is invoked from the command line interface, the function
my_command_fun() will be called. The function must take exactly as many
arguments as the command; in this case, three: an integer, a string, and a flag.
The first command argument is called “arg” and is optional, indicated by
"?". If omitted by the user, the command function will be called with the
default value, 10. The second argument is called “name” and is required (since
there is no "?", there is no default value). The last argument is a flag and
will have a value of true if the flag is given, otherwise false. Flags are
thus always optional.
If the command function should print a message or return a value or object, use
the command_return class that is described in section
14.2.
Writing help my-command at the command line prompt will display:
Command my-command
Alias
mc
Synopsis
my-command [arg] "name" [-f]
Description
my-command is best. This is its documentation. arg is the first argument...
Provided By
Simics Core
Note
This command is best
See Also
my_other_command
The command can be invoked in different ways, here are some examples:
simics> my-command 2 foo -f
My integer: 2
My string: foo
Flag is given
simics> my-command bar
My integer: 10
My string: bar
Flag is not given
simics> my-command 20 -f
Argument error: argument number 2 is missing in 'my-command';
string expected
SYNOPSIS: my-command [arg] name [-f]
simics> print -x (mc -f name="hello there!" arg = 23)
0x17
In the last case the alias is used and the command is passed to the print command that outputs the value in hexadecimal notation, and only the value since the command inside the parenthesis is run as an expression, not interactively as such.
The new_command function defines a new command that can be used on the command
line and in scripts. The definition includes descriptions of the command
parameters, its documentation, and the Python function to call when the command
is run.
The parameters to the function are described below.
name — string (required) First argument (no need
to write name =) and the name of the command. It may include hyphens,
digits, and underscores and must begin with a letter.
fun — function (required) The command handler
function that will be called when the command is executed. The number of
arguments must match the list args, described below. Since new_command is
executed when the module_load.py file is loaded into Python, the function
must be defined before the new_command call, as in the example.
args — list of argument specifiers (required) This
is a list of the arguments given to the command, and must match the arguments
of the function described above. An argument specifier is created by calling
the function arg(). See below for its documentation.
doc — string (required if not doc_with is used)
This is the documentation of the command. The Simics standard is to use
<arg> for arguments and <tt> for flags. Other simple, HTML markup can be
used, such as <i>, <b> and <br/> for italic, bold and line-break. A
blank line separates paragraphs. Italic does not usually work in terminals so
underlining will be used instead.
short — string (recommended) A short description
of the command used when listing commands (e.g., list-commands). The short
description should start with a lower-case letter and should not end with a
period.
repeat — function (optional) If such a function
is supplied, it will be called when the user enters an empty line (i.e., just
presses enter) after this command has been run. The arguments passed to the
repeat function will be the same as those of fun (see above). This is
used to implement the behavior of commands like disassemble, where pressing
enter after having run one disassemble command disassembles the instructions
following the last one of the previous command.
cls — string (optional) Makes the command apply
to objects of the named configuration class, which must be defined by the time
new_command is called.
Command will take the form object.command. For example, if cls is
recorder and board.rec0 is a recorder object, then the command is invoked as
board.rec0.playback-start.
The command handler function will get the configuration object as first argument, followed by the rest of the arguments.
Other parts of CLI will refer to the command with the name <cls>.name, e.g.,
<recorder>.playback-start.
breakpoint,
then the command will be applied both to all memory-space objects and to all
CPU objects.It is not allowed to pass both the iface and cls parameters together in
the same new_command invocation.
doc_with — string (optional) This argument can
be specified if a command should be documented together with another one. For
example the disable command is documented with the enable command since
they are tightly coupled together. So the doc argument is missing for the
disable command and all documentation is written in the enable command. Note:
doc_with together with a namespace command must be given as
“<class_or_interface>.command”
alias — string or list of strings (optional) Specify aliases for this command. Does not work with namespace commands.
type — list of strings (optional) This is the command categories the command may belong to. Running help category:[name] will list all commands in that category.
Invoke the function cli.register_command_category without any argument
to print the standard categories, assuming no modules or targets have
been loaded.
A non-standard category may be useful when defining many commands that
serve a particular purpose which is of general interest. For such a case
it is required to run the cli.register_command_category function before
new_command is called. The category name must be at least 3 characters
long, and preferably being just one capitalized word, a descriptive noun.
A non-standard category should usually not be added for a class or interface, because its commands are listed by running help on the class or interface itself.
For backwards compatibility a single string is supported, instead of a list containing one string.
The arg function is used to define parameters to commands. It is used to
populate the list in the args in the new_command function.
The parameters to the function are described below.
handler The “type” of the parameter. Examples of
available types are int_t, str_t, addr_t, filename_t(), obj_t(), and
flag_t. These types are in fact functions that are called to handle the
command line input. See the API reference manual for more information about
types.name The name of the command parameter. This way
the documentation of the argument list (help command) will use these names
and also makes it possible to enter the argument in any order at the command
line, e.g.,
command file = "/tmp/bar" value = 17
The flag_t type requires the name to be specified and the name must begin with
a hyphen, e.g., –all.
spec This is a string that defines if this
parameter is required or optional, or if it accepts more than one argument on
the command line. Possible values are
"1" This is the default, and means that the
parameter is required, and that there is a single value."?" This is the most common alternative. It
means that the parameter is optional, and that the default value is used
if no argument is given on the command line."*" This means that no, one or more such
arguments can be provided on the command line. The provided arguments are
passed to the command function in a list. When no such arguments are
provided on the command line, the command handler function gets an empty
list."+" This means that it will accept one or more
arguments on the command line and pass them to the command function in a
list. This argument also accepts a CLI list of at least one element as
input.default The default value to use when spec is
"?" and the parameter was not provided on the command line. For other spec
values the value is ignored.
doc The command documentation uses the parameter
name in the command synopsis. When it is defined as a polyvalue (see
below), the synopsis will be a combination of the names. If the parameter has
no name, it will be shown as arg1, arg2 etc. The doc parameter to arg
is a string that will be used instead of the default.
expander This parameter specifies an argument
completion (expander) function. This function will be called when the user
presses the TAB key while entering the argument value. The expander function
takes an argument representing the text the user has written for the argument
so far. For example, if the user presses TAB after typing command cpu = ultr, the expander function will be passed "ultr" and should return a list
of strings completing "ultr". Here is an example of an expander function:
def exp_fun(comp):
return get_completions(comp, ["ultraI", "ultraII",
"ultraIII", "pentium"])
When called with "ultr", it will return ["ultraI", "ultraII", "ultraIII"].
The get_completions function is a utility function that filters the list and
keeps elements with prefix comp. Expander functions currently only works for
arguments of type string, object, boolean, or file.
Expander functions may take one, two or three arguments. The first argument is
the completion string, as previously described. The second argument is used for
namespace commands and contains a reference to the object corresponding to the
namespace. This argument is None for non-namespace commands. The third
argument is a list of all command argument values, as strings, that the user has
entered on the command line so far. The length of the list is the same as the
number of arguments for the command. A value of None is used in the list for
command arguments that have not been entered on the command line.
A command argument can be of multiple types as well (polyvalues). For example,
new_command(…,
args = [ arg((str_t, int_t, flag_t), ("cpu","value","-all"), "?",
(int_t, 0, "value"), expander = (exp1, exp2, None)) ],
…)
will create an argument that is either a string, an integer, or a flag. The
argument passed to the command handler function is a tuple specifying the
argument type, the value, and the name of the argument. E.g., command foo will
pass (str_t, "foo", "cpu") to the command handler function. This is why the
default value looks the way it does. If given the corresponding expander
function will also be used. command cpu = abc<tab> will use the exp1
expander.
Command functions signal errors by raising the CliError exception, with an
error message string as argument. For example:
if not valid(argument):
raise CliError("invalid argument")
Signalling errors with CliError ensures that the error will be reported in a
correct way to the user.
Commands frequently read or write attributes in configuration objects. When
doing so it is especially important to catch exceptions generated by the
attribute access and handle it appropriately, perhaps by raising a CliError.
Any uncaught attribute exception will cause a Python stacktrace to be shown in
the Simics user interface. The stacktrace is not likely to be helpful to the
user and should be thought of as a programming error by the command implementer.
def my_command(obj):
try:
val = obj.value
except simics.SimExc_General as e:
raise cli.CliError('Could not retrieve attribute "value" in object'
' %s: %s' % (obj.name, str(e)))
return cli.command_return(
message = "Value of %s is %s." % (obj.name, str(val)),
value = val)
The exceptions that an attribute access may trigger is highly dependent on the accessed attribute and the context of the command. For instance, some commands only operate on objects whose type is known, and on attributes that cannot under any circumstances cause an exception. On the other hand, some commands operate on objects that may not even have the attribute the command is trying to access. The level of information available about the object and its attributes needs to be taken into consideration when deciding what exceptional conditions the command needs be able to handle.
For information on what exceptions an attribute access can trigger, refer to
SIM_get_attribute and SIM_set_attribute in the reference manual.
For information on how to implement an attribute to raise exceptions in a correct way, refer to section 5.3.3 and 15.4.3.
When run interactively at the Simics prompt, a command will get its return value
printed to the user. Sometimes, however, a more human-friendly message is
desirable. To achieve this, the command’s function should return an object of
the special type command_return:
import cli, platform
def platform_fun():
sysname = platform.system()
return cli.command_return(
message = "This machine is running %s." % (sysname,),
value = sysname)
cli.new_command("get-os-type", platform_fun)
The get-os-type command will now return the name of the operating system when
used in an expression, but print a human-readable message when used as a
top-level interactive command.
simics> get-os-type
This machine is running Linux.
simics> $os_type = (get-os-type)
simics> echo $os_type
"Linux"
In the same way, you can also use cli.command_quiet_return to completely
suppress the interactive message.
For the non-interactive case it is good coding style to return a Python object or plain value, so that the receiving command can consume the value programmatically.
For more information, see the documentation on cli.command_return,
cli.command_quiet_return and cli.command_verbose_return in the Simics
Reference Manual.
Every model should have an info command, giving static information about the
device, and a status command, that gives dynamic information.
To simplify the creation of info and status commands, there are a couple of
helper functions that make it easy to add these commands and have the output
formatted in a standard fashion.
Instead of calling new_command directly, you call new_info_command and
new_status_command. The functions you provide to these functions should not
print anything directly, instead they should return the information to be
printed.
The data returned from new_info_command and new_status_command should be a
list of sections, where each section is a tuple of a section title and a list
of entries. The section title should be a string or None. An entry is a tuple
of a name and a value. The name is a string, and the value can be just about
anything.
import cli
def get_info(obj):
return [("Connections",
[("Up", obj.up),
("Down", obj.down)]),
("Sizes",
[("Width", obj.width),
("Height", obj.height),
("Area", obj.width * obj.height)])]
cli.new_info_command('sample-device', get_info)
def get_status(obj):
return [(None,
[("Attribute 'value'", obj.value)])]
cli.new_status_command('sample-device', get_status)