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. 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.
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.
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)