In this blog post we are going to illustrate how to use the command line interpreter (CLI) interface from Python and how to write a basic command completion functionality for the Python CLI.
IDA Pro SDK allows programmers to implement command line interpreters with the use of the cli_t structure:
struct cli_t
{
size_t size; // Size of this structure
int32 flags; // Feature bits
const char *sname; // Short name (displayed on the button)
const char *lname; // Long name (displayed in the menu)
const char *hint; // Hint for the input line
// callback: the user pressed Enter
bool (idaapi *execute_line)(const char *line);
// callback: the user pressed Tab (optional)
bool (idaapi *complete_line)(
qstring *completion,
const char *prefix,
int n,
const char *line,
int prefix_start);
// callback: a keyboard key has been pressed (optional)
bool (idaapi *keydown)(
qstring *line,
int *p_x,
int *p_sellen,
int *vk_key,
int shift);
};
For example, the IDAPython plugin defines its CLI like this:
static const cli_t cli_python =
{
sizeof(cli_t),
0,
"Python",
"Python - IDAPython plugin",
"Enter any Python expression",
IDAPython_cli_execute_line,
NULL, // No completion support
NULL // No keydown support
};
And registers/unregisters it with:
void install_command_interpreter(const cli_t *cp); void remove_command_interpreter(const cli_t *cp);
The CLI functionality has been added in IDAPython 1.4.1. Let us suppose that we want to reimplement the Python CLI in Python (rather than in C++), we would do it like this:
class pycli_t(idaapi.cli_t):
flags = 0
sname = "PyPython"
lname = "Python - PyCLI"
hint = "Enter any Python statement"
def OnExecuteLine(self, line):
"""
The user pressed Enter.
@param line: typed line(s)
@return Boolean: True-executed line, False-ask for more lines
"""
try:
exec(line, globals(), globals())
except Exception, e:
print str(e) + "\n" + traceback.format_exc()
return True
Summary:
To try this code, simply instantiate a CLI and register it:
pycli = pycli_t() pycli.register()
And later to unregister the CLI:
pycli.unregister()
In order to add command completion, we need to implement the OnCompleteLine() callback:
class cli_t:
def OnCompleteLine(self, prefix, n, line, prefix_start):
"""
The user pressed Tab. Find a completion number N for prefix PREFIX
This callback is optional.
@param prefix: Line prefix at prefix_start (string)
@param n: completion number (int)
@param line: the current line (string)
@param prefix_start: the index where PREFIX starts in LINE (int)
@return: None or a String with the completion value
"""
print "OnCompleteLine: pfx=%s n=%d line=%s pfx_st=%d" % (prefix, n, line, prefix_start)
return None
This callback is invoked everytime the user presses TAB in the CLI. The callback receives:
For example typing “os.path.ex” and pressing TAB would call:
OnCompleteLine(prefix="ex", n=0, line="print os.path.ex", prefix_start=14)
For demonstration purposes, here is a very simple algorithm to guess what could complete the “os.path.ex” expression:
def parse_identifier(line, prefix, prefix_start):
id_start = prefix_start
while id_start > 0:
ch = line[id_start]
if not ch.isalpha() and ch != '.' and ch != '_':
id_start += 1
break
id_start -= 1
return line[id_start:prefix_start + len(prefix)] def get_completion(id, prefix):
try:
parts = id.split('.')
m = sys.modules['__main__']
for i in xrange(0, len(parts)-1):
m = getattr(m, parts[i])
except Exception, e:
return None
else:
completion = [x for x in dir(m) if x.startswith(prefix)]
return completion if len(completion) else None def OnCompleteLine(self, prefix, n, line, prefix_start):
# new search?
if n == 0:
self.n = n
id = self.parse_identifier(line, prefix, prefix_start)
self.completion = self.get_completion(id, prefix)
return None if (self.completion is None) or (n >= len(self.completion)) else \
self.completion[n] With this approach we could complete something like this:
f = open('somefile.txt', 'r')
f.re^TAB => f.read, f.readinto, f.readline or f.readlines
Or:
print idau^TAB.GetRe^TAB => print idautils.GetRegisterList
The sample script can be downloaded from here and a win32 build of the latest IDAPython plugin (with completion integrated in the plugin) can be downloaded from here.