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 12 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):
print(f"My integer: {int_arg}")
print(f"My string: {str_arg}")
print(f"Flag is {'' if flag_arg else 'not '}given")
return 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",
type = "[my-module-commands]",
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
. When the command is invoked from the Simics 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 1 if the flag is given, otherwise 0. Flags are thus always optional.
If the command function returns a value (a string or an integer) Simics will print this value on the terminal or pass it on to other commands as input arguments, e.g., print -x (my-command 15 foo)
.
Writing help my-command
at the Simics prompt will display:
NAME
my-command - my command does it
SYNOPSIS
my-command [arg] name [-f]
ALIAS
mc
DESCRIPTION
my-command is best. This is its documentation. arg is the first argument.
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
2
simics> my-command bar
My integer: 10
My string: bar
Flag is not given
10
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)
My integer: 23
My string: hello there!
Flag is given
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.
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. May include digits and underscores as well as dashes. 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. 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.
type — list of strings (optional) This is the command categories that the command belongs to. All categories will be listed when the help command is used. help category will list all commands in that category.
For backwards compatibility a single string is supported instead of a list containing one string.
short — string (recommended)
A short description of the command used when listing commands (e.g., command-list
). 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
.
iface — string (optional)
Similar to the cls parameter above, but makes the command apply to objects of all classes that implement the specified interface. For example, if iface is 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.
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 it will accept zero or more arguments on the command line and pass them to the command function in a list. This argument also accepts a CLI list as input."+"
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 used when spec
is "?"
.
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, 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 14.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 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)