[ | Date | | | 2018-05-01 03:12 -0400 | ] |
[ | Current movie | | | Cube | ] |
The GNU Project Debugger can be scripted in at least three languages:
a simple built-in language, which has no support for compound data structures, which constrains how much one can easily do (as far as I can tell, this language has no specific name, and is referred to as "user-defined commands"; it supports only defining functions, simple conditionals, and loops);
Guile, but the build of GDB that comes with my distribution does not support that, which is a non-starter (GDB is apparently difficult to set up for some systems, so I would rather not start relying on a scripting language that is less often supported);
Python! which is a mainstream programming language, and that I can actually use today on my existing GDB install.
Let us say you have data structures WORD_DESC
and WORD_LIST
defined as follows (those come from the GNU Bash source code in command.h
):
typedef struct word_desc {
char *word; /* Zero terminated string. */
int flags; /* Flags associated with this word. */
} WORD_DESC;
typedef struct word_list {
struct word_list *next;
WORD_DESC *word;
} WORD_LIST;
Let us further assume that you have a pointer to a WORD_LIST
in variable wl
. Here is how GDB's built-in print
(or p
for short) displays it:
(gdb) p *wl
$1 = {next = 0x73cc48, word = 0x73ca68}
This is nice (the field names are listed, yay), but not as exciting as it could be; for example, the field values are printed as numeric addresses, whereas it would be more enlightening to print the WORD_DESC
structure (for word
) and the rest of the linked list (for next
).
Improved, custom results can be obtained using user-defined commands, such as:
define print_word_desc
set var $wd = $arg0
printf "(word-desc (word \"%s\") (flags %d))\n", \
$wd->word, $wd->flags
end
define print_word_list
set var $wl = $arg0
while $wl != 0
print_word_desc $wl->word
set var $wl = $wl->next
end
end
(Those definitions can be entered directly at the (gdb)
prompt.)
Now we can use print_word_list
to pretty-print a WORD_LIST
, and also print_word_desc
to print a WORD_DESC
:
(gdb) print_word_desc wd
(word-desc (word "date") (flags 0))
(gdb) print_word_list wl
(word-desc (word "date") (flags 0))
(word-desc (word "-u") (flags 0))
(word-desc (word "-d") (flags 0))
(word-desc (word "@0") (flags 0))
(word-desc (word "+'%Y-%m-%d'") (flags 2))
And, indeed, the command I had asked asked my bash
executable to run was date -u -d @0 +'%Y-%m-%d'
.
This is helpful, but:
Not the prettiest: it would be better to have flags
printed as a list of named flags (in this codebase, for example, 2
stands for W_QUOTED
, "some form of quoted character is present"). I believe it would be impractical to do this in the user-defined command language, expecially given the availability of more featureful alternatives.
Requires calling print_word_list
explicitly whenever we have a WORD_LIST
that we need to print: it would be helpful if p
could do this automatically. As far as I can tell, this cannot be done without using Python or Guile.
The code will be similar to that written in the "user-defined command" language, except a lot longer. We define two classes, one each for WORD_DESC
and WORD_LIST
; in addition to what the previous simple implementation did, this prints flags using their symbolic names rather than a single int
value, and the WORD_LIST
printer defines a children
method returning a list of name-value pairs, which GDB picks up and recursively applies the right formatter to (prettybash.py
):
class WordDescPrinter(object):
"""Print a WORD_DESC"""
def __init__(self, val):
self.val = val
def to_string(self):
return "{word = %s, flags = %s}" \
% (self.val['word'], self.pretty_flags(self.val['flags']))
def pretty_flags(self, flags):
flag_names = []
flags_dict = {
'W_HASDOLLAR': 0x000001,
'W_QUOTED': 0x000002,
# (etc.)
}
for k in flags_dict:
if flags & flags_dict[k]:
flag_names.append(k)
if flag_names == []:
flag_names.append('none')
return ' | '.join(flag_names)
class WordListPrinter(object):
"""Print a WORD_LIST"""
def __init__(self, val):
self.val = val
def to_string(self):
return "WORD_LIST" % self.val.address
def children(self):
c = []
o = self.val
i = 0
while o['next'].address != 0:
c.append(('[%d]' % i, o['word'].dereference()))
o = o['next']
i += 1
return c
Plus some code to attach printers to types (bash-gdb.py
; I don't know exactly why matching on word_desc
works when GDB normally displays the type of wd
as (WORD_DESC *)
, rather than the equivalent (struct word_desc *)
):
import gdb.printing
import prettybash
def build_pretty_printer():
pp = gdb.printing.RegexpCollectionPrettyPrinter("prettybash")
pp.add_printer('word-desc', 'word_desc', prettybash.WordDescPrinter)
pp.add_printer('word-list', 'word_list', prettybash.WordListPrinter)
return pp
gdb.printing.register_pretty_printer(
gdb.current_objfile(),
build_pretty_printer())
We can confirm that everything has been loaded by asking GDB what pretty-printers it knows about:
(gdb) info pretty-printer
global pretty-printers:
builtin
mpx_bound128
prettybash
word-desc
word-list
And then calling just print
within GDB results in our pretty-printers getting used when the parameter has type WORD_DESC
or WORD_LIST
(output line-wrapped by hand; GDB does not actually print this prettily):
(gdb) p *wd
$1 = {word = 0x73cb08 "date", flags = none}
(gdb) p *wl
$2 = WORD_LIST = {
[0] = {word = 0x73cb08 "date", flags = none},
[1] = {word = 0x739218 "-u", flags = none},
[2] = {word = 0x739238 "-d", flags = none},
[3] = {word = 0x739248 "@0", flags = none},
[4] = {word = 0x73ccc8 "+'%Y-%m-%d'", flags = W_QUOTED}
}
This makes our life much easier than the default printer did; we can see meaningful flag names, and a single print
will show the entire linked list that is a WORD_LIST
.
This is based on GDB version 7.11.1 (from May 2016 đ), and a checkout of Bash from March 6, 2018 (git commit 7de2745), at which time the latest stable release was Bash-4.4.
GDB user-defined commands can be entered directly at the (gdb)
prompt, or can be stored in files and then loaded using source FILENAME
.
Python scripts for GDB can be loaded using source
as well. The Python module search path must be set properly in order for import
to work; for simple testing with all files in the same directory, pi sys.path += '.'
is enough. Additionally, the name bash-gdb.py
used above is not random: GDB supports auto-loading relevant extensions when an objfile
is read; the extension code must reside in a file matching objfile-gdb.ext
. This feature is security-restricted, and therefore requires some safe path configuration.
Quick links: