Debugging with GDB: How to create GDB Commands in Python
From GDB version 7.0, (I strongly suggest that you use version 7.1 or higher.) GDB embeds a Python language interpreter in it. This means that users can creates powerful user-defined commands in Python. Originally, GDB supports to make an user-defined command by creating canned sequences of commands. Or just, command sequence. Unfortunately, it lacks flexible flow-control commands, nor any OS specific interface. Thanks to the Python interpreter, we can create powerful GDB commands script for virtually anything.
This is the English version of my Korean article, Debugging with GDB: User Defined Commands in Python.
This article explains how to create GDB command in Python. Note that GDB Python interface contains more than just creating new command. See 23.2 Scripting GDB using Python of the GDB info manual for more.
To use the Python interpreter, you launch GDB 'python' command. Any arguments(i.e. expression) of 'python' will go to the interpreter's session. Unlike the native Python interpreter, the 'python' command will now print the result of the evaluation. To see the result, you need to call Python print statement explicitly:
(gdb) python print 1 + 2 3 (gdb) python msg = "Greeting, %s!" % "cinsk" (gdb) python print msg Greeting, cinsk!
Since GDB synchronizeds the interpreter's stdout/stderr to GDB's, you can use Python's sys.stdout and sys.stderr directly:
(gdb) python import sys (gdb) python sys.stdout.write("hello, world\n") hello, world (gdb) python sys.stderr.write("error output\n") error output
To insert more than one line of code, run 'python' command without arguments, insert Python code, then finish the code with 'end'. This is the same behavior when you use the GDB 'define ... end' command.
(gdb) python >if True: > print "it is true." >else: > print "it is false." >end it is true. (gdb) _
It is possible to load external Python script file into the interpreter by GDB 'source' command:
(gdb) source my-gdb-python-source.py
Normally, you wrote GDB commands in separate Python script file, and add following line to your .gdbinit file. Using this way, you no longer need to execute GDB 'source' command on every GDB session:
The Python interpreter in GDB automatically import gdb module before showing you "(GDB)" prompt. The gdb module contains several features such as:
- gdb.Command class -- The parent class for any user-defined GDB command. If you want to new one, sub-class this, and instantiate it.
- pretty printer -- You can create a pretty printer interface. (It is used when you execute GDB 'set print pretty'.) To use this feature, you defined a class contains pre-defined methods (such as 'to_string', 'children', 'display_hint', etc.), and install it. This is especially handy if you want to 'print' C++ template classes or very complex data structure.
- gdb.Function class -- The parent class for any user-define GDB function.
- gdb.Frame -- Classes for representing current call stack frame.
This article deals only gdb.Command class in above.
Creating New Commands
Let's create a "hello world"-like program in GDB command. If you give your name to the command, it will use your name to greet you:
(gdb) hello cinsk Hello, cinsk!
To create a new GDB command, you need to define your own class that has gdb.Command as its parent. Then you may need to define(override) pre-defined method such as 'invoke' method. The full source code for GDB 'hello' command is:
class Hello(gdb.Command): """Typical hello world: hello NAME Print "hello NAME" where NAME is the argument. This command is for demonstrating of creating new command in Python.""" def __init__(self): gdb.Command.__init__(self, "hello", gdb.COMMAND_OBSCURE) def invoke(self, arg, from_tty): print "hello, %s!" % arg Hello()
The name of the class, "Hello" is nothing to do with the GDB command's name, The new command name is passed to the second argument of gdb.Command.__init__(). The third argument of gdb.Command.__init__() represents the type of the command. For example, if the type is gdb.COMMAND_DATA, you will see the help message of the command, if the user executed GDB 'help data' command. GDB extract the help message from the documentation-string of the class definition. For example:
(gdb) help obscure ... 기타 다른 명령에 대한 설명 ... hello -- Typical hello world ... (gdb) help hello Typical hello world: hello NAME Print "hello NAME" where NAME is the argument. This command is for demonstrating of creating new command in Python. (gdb) _
As you might notice, when the GDB 'help' command is used for the type of commands such as 'help obscure', GDB will collect the first line from the documentation-string for every command in that type. If GDB 'help' command is used for the specific command, GDB will print the whole documentation-string.
You will see the various command type is pre-defined in gdb module in form of COMMAND_*. (I'll explain some of them later.)
When the GDB user-defined command is executed, GDB calls the 'invoke' method of the instance. The arguments to the GDB command is passed to the second argument to the 'invoke' method. Note that the type of the argument is a string. It is up-to 'invoke' method's responsibility to parse it.
The last line of the Python script above seems peculiar. It is useless in the Python context, but it is required since the gdb.Command will register the user-defined command if and only if an instance is created.
Inside the Python script, you can launch GDB command using gdb.execute() function. gdb.execute() accept a string value containing the GDB command (with arguments if any). For example:
(gdb) print 1 + 2 $1 = 3 (gdb) p 1 + 2 $2 = 3 (gdb) python gdb.execute("p 1 + 2") $3 = 3 (gdb) p asdf No symbol "asdf" in current context. (gdb) python gdb.execute("p asdf") Traceback (most recent call last): File "<string>", line 1, in <module> RuntimeError: No symbol "asdf" in current context. Error while executing Python code. (gdb) _
Note that when gdb.execute() fails, it raises RuntimeError exception. Thus, you may create a simple function to wrap gdb.execute():
try: gdb.execute("p asdf") except RuntimeError as e: print "error: exception occurred: %s" % e
gdb.Command.__init__() and sub-Commands
Let's look at gdb.Command.__init__() little bit more. In Python context, the __init__() might be defined in this way:
class Command(...): def __init__(self, name, command_class, completer_class = -1, prefix = False): ...
I already explained that 'command_class' represent the category of the command. Some of the valid type will be one of:
- gdb.COMMAND_DATA -- when your command's purpose is to examine data (such as GDB 'print' or 'x'), your command
falls into this category. (Most of commands in this article will be in this category)
- gdb.COMMAND_NONE -- Your command is not belong to any pre-defined category. Note that you cannot see any help message
using GDB 'help' if your command is in this category.
- gdb.COMMAND_OBSCURE -- Commands that is hardly ever used. For example, GDB 'stop' and 'fork' falls into this. Since GDB 'hello' that I introduced at the first place, I made it as this category.
- There are more COMMAND_*, which I don't explain here. See Commands In Python for more.
'completer_class' determines the auto-completion policy. If you omit it, or -1 is given, The class's 'complete()' method will be called. Otherwise you can use one of pre-defined value such as:
- gdb.COMPLETE_NONE -- No auto-completion is provided.
- gdb.COMPLETE_LOCATION -- auto-completion for location of the program. (e.g. filename, line number, function name, etc.)
- gdb.COMPLETE_SYMBOL -- auto-completion for symbols (e.g. name of variable/constant/function)
See the manual for other constants.
The last argument, 'prefix' determine if the command defined by this class is a prefix command. A prefix command is the GDB command that the purpose of the it is strongly depends on its arguments. For example, let's look at the GDB 'dump' command's syntax (in short version):
dump value FILENAME EXPR dump memory FILENAME START_ADDR END_ADDR
We can guess 'dump' is the prefix command, and the actual command is 'dump value' and 'dump memory'. If you re-define 'dump' command in Python, the skeleton code will be:
class MyDump(gdb.Command): def __init__(self): gdb.Command.__init__(self, "dump", gdb.COMMAND_DATA, gdb.COMPLETE_NONE, True) class MyDumpValue(gdb.Command): def __init__(self): gdb.Command.__init__(self, "dump value", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL) def invoke(self, args, from_tty): # Parse 'args' for FILENAME and EXPR, and do dumping. ... class MyDumpMemory(gdb.Command): def __init__(self): gdb.Command.__init__(self, "dump value", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL) def invoke(self, args, from_tty): # Parse 'args' for FILENAME, START_ADDR, and END_ADDR. And do dumping. ...
Of course, you can write big one class for GDB 'dump' without 'prefix' set to True. For example:
class MyDumpValue(gdb.Command): def __init__(self): gdb.Command.__init__(self, "dump", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL) def invoke(self, args, from_tty): (subcmd, dummy, rest) = args.partition(" ") if subcmd == "value": # parse 'rest' for FILENAME and EXPR. Do dumping ... else: # parse 'rest' for FILENAME, START_ADDR, and END_ADDR. Do dumping. ...
However, using 'prefix' then creating separate classes for its sub-commands is much better than that. The reason for this:
- Creating one big class may result your code buggy and hard-to-read.
- Since one big class will have only one, big documentation-string, it is not possible to see them separately via GDB 'help' command.
- In one big class method, you need to implement 'complete()' method, if you want auto-completion for the sub command name.
If you know GDB well, you might suspect that real GDB 'dump' command has actually different syntax:
dump [FORMAT] memory FILENAME START_ADDR END_ADDR dump [FORMAT] value FILENAME EXPR
I didn't explain the full version since introducing "[FORMAT]" makes me difficult to explain gdb.Command.__init__().
Luckily, If you understood my poor English for what I wanted to explain, you already know enough to make your own GDB command.
In this section, I'll introduce some utility classes for you to make your job easy. All of GDB commands that I will define uses GDB 'dump' command internally. In fact, they normally follows these steps:
- Create a temporary file.
- Using GDB 'dump', save the data into the temporary file.
- Process the data in the file and print it
- Delete the temporary file.
I found Python's tempfile module provides NamedTemporyFile is suitable for my purpose. Since most of commands that I'll create will have the same code for all steps except the step 3. I'll create GdbDumpParent class for the job.
GdbDumpParent is a sub-class of gdb.Command, do the common job such as step 1, 2, and 4. In other words, it's instance will create a temporary file, execute GDB 'dump' command to save the data into that file, launch the shell command on that file (the script writer will provide the exact shell commandline), print the output, and finally, delete the temporary.
Since all the arguments of a GDB command goes to the 'args' argument of 'invoke()' method. Some of the arguments in 'args' will be used for GDB 'dump' (In step 2), and the rest will be used for constructing the shell commandline. To postpone this job to the script writer, GdbDumpParent has 'parse_argument()' method. The script writer may override the method in his or her own sub-class. The layout of GdbDumpParent will be:
class GdbDumpParent(gdb.Command): def __init__(self): # Call gdb.Command.__init__() ... def invoke(self, args, from_tty): # args - contains all arguments in a string # We need to parse 'args' into two categories: arguments for GDB 'dump' # and arguments for shell commandline. (dump_args, exec_args) = self.parse_arguments(args) # TODO-1: Using 'dump_args', execute GDB 'dump' to save the data ... # Using 'exec_args', create the command-line, then execute it. self.execute(temporary_file_name, exec_args) def parse_arguments(self, args): # Overridable method to parse the arguments. return (args, "") def execute(self, filename, args): # TODO-2: Using the temporary 'filename' and 'args', # create the command line, and execute the # shell command. ...
def invoke(self, args, from_tty): with tempfile.NamedTemporaryFile(prefix="gdb-") as tmp: try: (dump_args, exec_args) = self.parse_arguments(args) # 실제 GDB 'dump' 명령 수행은 dump() method에 의해 처리 self.dump(tmp.name, dump_args) # shell 명령 실행은 execute() method에 의해 처리 self.execute(tmp.name, exec_args) except RuntimeError as e: print e except: sys.stderr.write("error: an exception occurred.")