Rizin
unix-like reverse engineering framework and cli tools
Command parsing and command handling

Rizin has moved away from the default way of parsing radare2 commands and the way commands were handled there. It enables by default what is/was called in r2 cfg.newshell, which enables a generated parser that parses rizin commands and a new way of registering and developing commands.

A bit of history

Rizin is a fork of radare2. Radare2 did not have, until recently, a generic parser for user inputs, but each command had to parse its arguments by itself. Moreover, there was no global register of commands available in the radare2 shell, instead the input was chopped by looking for specific characters and then it was analyzed char by char, using big switch-cases to recognize the right command.

As an example, you can see cmd_flag.c:1163, which identifies the fsr command and then parses its input to check if an argument was available or not.

This approach, although simple at the beginning, has some drawbacks like the inconsistency coming from having many different places in the code doing mostly the same thing (e.g. checking if an argument is available or not), the inability to easily register/unregister new commands at runtime (e.g. a new Core plugin that wants to provide a new command) or the inconsistency between commands actually available and commands shown to users in help messages.

Rizin shell

Not long ago, radare2 introduced the variable cfg.newshell that, when enabled, allows you to use new features in the code. Rizin has chosen to enable this by default and it is going to transition most commands to the new way of writing commands, which will make it easier/faster to write commands and make the overall CLI experience more consistent and reliable.

Rizin uses a parser generated with tree-sitter, which allows you to write grammars in JavaScript. You can see our grammar here. The parser recognizes the entire syntax of the rizin/radare2 shell language, like:

These patterns deal with the structure of the rizin/radare2 shell language, but they don't parse the input of each specific command available in the rizin shell (e.g. af, pd, etc.). The parser just splits the input statement into a "command name" and a list of "arguments".

Commands registry

The parser alone already provides better consistency with regards to how the shell behaves, as all commands are split in the same way and it has a more rigid behavior. However it was also essential to have a global commands registry, where a command could be registered together with all the information associated with it, like help messages, description, etc..

The module RzCmd is the one in charge of dealing with commands. It provides API to register a new "command descriptor" (called RzCmdDesc), deregister it, call the right command descriptor handler based on a list of command name + arguments, get the help of a command and potentially do many other things.

As rizin/radare2 commands mainly form a tree, RzCmdDesc are organized in a tree, with each descriptor having references to its parent and its children. Moreover, a descriptor has its help messages and its handler.

To make the retrieval of the right command easier, they are also stored in a hashtable, using their names as keys.

How to write a new command?

Let's make an example and suppose we want to add the sky command, which would find all occurrences of the word "sky" in a binary. The first thing to do is to see where sky command could be added by reading librz/core/cmd_descs/cmd_descs.yaml. sky is s command's subcommand and they are splitted and placed inside the .YAML file specified by the descriptor subcommands of the respective command. Since sky starts with an s, its subcommands would be in librz/core/cmd_descs/cmd_seek.yaml. That file respects the same tree structure used when executing rizin and seeing its help, so it should be simple to see where to place it. If we want to place it under the s sub-tree, we just need to define the descriptors for the command with at least name, cname, summary and a list of args accepted by the command.

Now we need to choose what kind of command (type field in YAML) we want to have. We can see the various types in the RzCmdDescType enum, however let's assume we want a regular command, which is the default one, so no action is required in this regard.

If our new sky command accepts a numeric argument, we can specify it in the args list, by using the type RZ_CMD_ARG_TYPE_NUM.

Then we only need to write the actual code that performs the command's job. You have to call it according to the cname field you previously set for the sky command, appending _handler to that string.

Below you can see how the code for adding the sky command would look like:

- name: s
cname: cmd_seek
summary: Seek to address
type: RZ_CMD_DESC_TYPE_OLDINPUT
subcommands:
- name: sky
cname: sky
summary: Find all occurrences of the work "sky" in the opened file
args:
- name: n
type: RZ_CMD_ARG_TYPE_NUM
optional: true
// cmd_seek.c (example, real place depends on the parent command)
RZ_IPI RzCmdStatus rz_sky_handler(RzCore *core, int argc, const char **argv) {
// argc/argv is like in main(), i.e. argv[0] always contains the command name
int limit = -1;
if (argc > 1) {
limit = rz_num_math (core->num, argv[1]);
}
// ... add the logic of your command
}
#define RZ_IPI
Definition: analysis_wasm.c:11
static static fork const void static count static fd const char const char static newpath char char argv
Definition: sflib.h:40
static uint32_t const uint8_t uint32_t uint32_t limit
Definition: memcmplen.h:45
enum rz_cmd_status_t RzCmdStatus
@ RZ_CMD_STATUS_OK
command handler exited in the right way
Definition: rz_cmd.h:24
RZ_API ut64 rz_num_math(RzNum *num, const char *str)
Definition: unum.c:456
RzNum * num
Definition: rz_core.h:316

The YAML file is used at built-time (by meson only) to autogenerate two files: cmd_descs.c and cmd_descs.h.

Where is the handler of command x?

You can use the script sys/rzshell_which.py to get the name of the function handling the specified command.

If that doesn't work, please report the problem to us! However, you can still find the handler yourself by looking at the file librz/core/cmd_descs/cmd_descs.yaml. By looking at the cname field of the command descriptor, you can see what is the name of the handler of the x command. If the cname is hex and the type is RZ_CMD_DESC_TYPE_OLDINPUT, then the handler will be named rz_hex. In all other cases, the handler will be named rz_hex_handler.

Some examples:

  • command: wv, type: unspecified (default to RZ_CMD_DESC_TYPE_ARGV), handler: rz_write_value_handler
  • command: w6d, type: unspecified (default to RZ_CMD_DESC_TYPE_ARGV), handler: rz_write_base64_decode_handler
  • command: s, type: RZ_CMD_DESC_TYPE_OLDINPUT, handler: rz_cmd_seek

How to improve the help messages of a command

Find the command in librz/core/cmd_descs/cmd_descs.yaml, then fix/improve the summary and/or description fields. If the command cannot be directly found in cmd_descs.yaml, look for the other files in librz/core/cmd_descs.

How to show examples of a command or additional details

You may notice some commands like env, %, * and others have additional sections when you show the extensive help with e.g. env?? (or %??, etc.). Those additional secions are called details and they can be specified, again, in the file librz/core/cmd_descs/cmd_descs.yaml. The structure is explained at the beginning of the file and it can be seen in existing commands (e.g. https://github.com/rizinorg/rizin/blob/6f40dfe493f0caf9e0541e1ee83e3d8012b5750f/librz/core/cmd_descs/cmd_shell.yaml#L18 ). The result, looks something like:

[0x00000000]> %??
Usage: %[varname[=varvalue]] # get/set environment variables
Examples:
| % # list all environment variables
| %SHELL # print value of SHELL variable
| %TMPDIR=/tmp # set TMPDIR to "/tmp"
| envSHELL # same as `%SHELL`
Environment:
| RZ_FILE # currently opened file name
| RZ_OFFSET # 10base offset 64bit value
| RZ_XOFFSET # same as above, but in 16 base
| RZ_BSIZE # block size
| RZ_ENDIAN # 'big' or 'little'
| RZ_IOVA # is io.va true? virtual addressing (1,0)
| RZ_DEBUG # debug mode enabled? (1,0)
| RZ_SIZE # file size
| RZ_ARCH # value of asm.arch
| RZ_BITS # arch reg size (8, 16, 32, 64)
| RZ_BIN_LANG # assume this lang to demangle
| RZ_BIN_DEMANGLE # demangle or not
| RZ_BIN_PDBSERVER # e pdb.server