Duel: gdb vs. linked lists, trees, and hash tables

My first encounter with the gdb commandduelwas on some old IRIX about 15 years ago. I immediately loved how convenient it was for displaying various data structures during MySQL debugging, and I wished Linux had something similar. Later I found out that Duel was not something IRIX specific, but a public domain patch for gdb 4.6 written in ’93 by Michael Golan. Unfortunately, it never got into gdb (for licensing reasons, so I’ve heard). Now the gdb 8 is out, and the patch, obviously doesn’t apply. Instead of fixing the patch, I’ve re-implemented Duel in Python, usinggdb Python APIand theArpeggioparser. Now it can be loaded run-time into your gdb, no patching or compilation is needed. And, hopefully, it will work over many gdb versions, as it no longer relies on undocumented gdb internals. So, allow me to introduce…Duel.pyfor gdb!

Examples

Let’s start with a few examples, to get the taste of it. Michael Golan managed to create a concise but very naturally-looking language. For example, whilea->bmeans the elementbof the structure pointera, in Duela-->bmeans to walk the linked list, producing the sequence of valuesa,a->b,a->b->b, and so on. In MariaDB, all tables of a query are stored in a linked list, and it can be printed with just one command:

(gdb) dl table_list--
>
next_local-
>
table_name
tables-
>
table_name =
0x7fffc40126b8
"t2"
tables-
>
next_local-
>
table_name =
0x7fffc4012d18
"t1"
tables--
>
next_local[[
2
]]-
>
table_name =
0x7fffc4013388
"t1"

Also, quite naturally, instead of a number, you can specify a range, great for printing arrays:

(gdb) dl longopts[
0.
.405
].name
longopts[
0
].name =
"help"
longopts[
1
].name =
"allow-suspicious-udfs"
<
...
cut
...
>
longopts[
404
].name =
"session_track_transaction_info"
longopts[
405
].name =
"session_track_state_change"

And instead of any expression, you can specify a set of expressions:

(gdb)
dl
my_long_options[
1
..
3
].(name,def_value)
my_long_options[
1
].(name) =
"allow-suspicious-udfs"
my_long_options[
1
].(def_value) =
0
my_long_options[
2
].(name) =
"ansi"
my_long_options[
2
].(def_value) =
0
my_long_options[
3
].(name) =
"autocommit"
my_long_options[
3
].(def_value) =
1

Concepts

Duel is built on the idea that every expression can return multiple values. You’ve seena..b,a-->b, anda,bas examples of such expressions. Any such expression can be used normally with other operators. Like in

(
gdb
) dl (
1
..
3
)+(
100
,
200
)
(
1
) + (
100
) =
101
(
1
) + (
200
) =
201
(
2
) + (
100
) =
102
(
2
) + (
200
) =
202
(
3
) + (
100
) =
103
(
3
) + (
200
) =
203

Besides operators that generate multiple values, there are operators that stop generation and grep-like operators that filter generated values. Let’s take a closer look.

Operators

Duel syntax is C-like, C operators work as expected, one can use C-style casts, refer to variables and invoke functions of the inferior (program, being debugged). But additionally one can use special Duel operators that deal with multiple values.

Ranges and lists,..and,

You’ve seen examples above. These operators look rather familiar, they exist in other languages too. A range can have both or only one end specified,..xmeans a range ofxelements, same as0..x-1. An open rangex..will generate values ad infinitum (or until the counter overflows, whatever comes first), one usually uses it with the@“until” operator.

Until,@

In the[email protected]expression,xwill keep generating values untilybecomes true. For example,

(gdb)
dl
arr[
0
..]@(
count
>
10
)

will print elements of thearrarray, untilarr[i].countbecomes larger than ten. As a convenient shortcut, the second argument can be a constant, in this case the generation stops as soon as the generated value becomes equal to this constant. For example, to print all command-line arguments:

(gdb)
dl
argv
[
0
..]@
0
argv
[
0
] =
"./mysqld"
argv
[
1
] =
"--log-output=file"
argv
[
2
] =
"--gdb"
argv
[
3
] =
"--core-file"

Although this can be also achieved with

(gdb)
dl
argv
[..
argc
]
argv
[
0
] =
"./mysqld"
argv
[
1
] =
"--log-output=file"
argv
[
2
] =
"--gdb"
argv
[
3
] =
"--core-file"

Walk the list,-->

The operatorx-->yproduces multiple values,x,x->y,x->y->y,x->y->y->y, and so on, until it hits NULL. This can be obviously used to print the whole linked list in one command. But it works for trees just the same:

(gdb)
dl
tree
-
>
(left,right)
-
>
data

Evaluating braces,{ }

Curly braces work just like parentheses, but they replace the expression with its value in the output. It’s easier to explain with the example, assume thati = 5:

(gdb)
dl
i+
6
i+
6
=
11
(gdb)
dl
{i}+
6
5
+
6
=
11
(gdb)
dl
{i+
6
}
11
=
11

This is useful in complex expressions around array indexes:

(gdb) dl
if
(my_long_options[i:=
1.
.20
].name[
0
] ==
'd'
) my_long_options[i].name
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[i].name =
"debug-abort-slave-event-count"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[i].name =
"debug-assert-on-error"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[i].name =
"debug-assert-if-crashed-table"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[i].name =
"debug-disconnect-slave-event-count"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[i].name =
"debug-exit-info"
(gdb) dl
if
(my_long_options[i:=
1.
.20
].name[
0
] ==
'd'
) my_long_options[{i}].name
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[
16
].name =
"debug-abort-slave-event-count"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[
17
].name =
"debug-assert-on-error"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[
18
].name =
"debug-assert-if-crashed-table"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[
19
].name =
"debug-disconnect-slave-event-count"
if
(my_long_options[i].name[
0
] ==
'd'
) my_long_options[
20
].name =
"debug-exit-info"

Note, how in the second example curly braces allow us to see what array elements satisfy the condition.

Filters,<?,>?,<=?,>=?,==?,!=?

These operators work kind of like grep, they select values of left argument that satisfy the condition. In other words, the result ofx ==? yisx, if it’s equal toy, otherwise there is no result at all. The previous, rather complex, example could be simplified using filters:

(gdb) dl my_long_options[
1.
.20
].(name[
0
] ==?
'd'
=
>
name)
my_long_options[
16
].(name) =
"debug-abort-slave-event-count"
my_long_options[
17
].(name) =
"debug-assert-on-error"
my_long_options[
18
].(name) =
"debug-assert-if-crashed-table"
my_long_options[
19
].(name) =
"debug-disconnect-slave-event-count"
my_long_options[
20
].(name) =
"debug-exit-info"

Other operators

There are also aliases (:=), grouping operators (#/, etc), conditionalifoperator, and other rarely needed things.

Advanced

Few more examples from the manual.

Print the cyclic list (start fromheadand follow thenextpointer until you seeheadagain):

dl head--
>
(
next
!=?head)

Find the third positive element in the array (uses[[ ]]operator that selects a value from a sequence):

(gdb) dl (x[
0.
.]
>
?
0
)
[[2]]

Find the last element in the linked list (uses the counter#/operator that returns the number of values in a sequence):

(gdb) head--
>
next
[[
#/head--
>
next - 1]]

See if the array is sorted:

(gdb)
dl
x
[i:=..100]
>
? x
[i+1]

Summary a.k.a. tl;dr

Duel in gdb works (again (after 24 years)). It’s ridiculously useful in debugging anything more complex than “Hello, world”. Practically you’ll only need to remember four constructs:..,,,-->, and@0.

You can get it from my repository:https://github.com/vuvova/gdb-tools

Disclaimer: while Duel itself is very stable and time-proven language, Duel.py is new, expect bugs.

Source: http://www.tuicool.com/articles/eauMNzr