| [ | 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 cPlus 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: