📜 ⬆️ ⬇️

gdb-duel - lists, trees and hash tables against the command line


The first time I saw the duel command in gdb on some ancient IRIX, about fifteen years ago. It was an incredibly cool thing to view various related lists, arrays of structures, and other similar constructs. Dreamed, they say, if I had such a Linux, and forgot. About ten years ago I remembered, googled - it turned out that DUEL, this is actually a 93rd patch for gdb 4.6, and not at all something unique in IRIX. Only the author for ideological reasons released it as the public domain, and gdb-schnick were also ideological and wanted the GPL, so it did not threaten to get into the upstream patch. I ported it to the then gdb 6.6.2, gave it to gentoo and before the release of the 7th gdb enjoyed life. Then the duel from gentoo was thrown out, it was difficult to port to the new gdb, nobody took it. And recently I tried to revive him. Only instead of a patch (it is necessary to assemble together with gdb from sources, it uses all sorts of internal gdb functions) I wrote it from scratch on python. Now Duel.py (this is the name of the new implementation of Duel) is loaded into gdb on the fly, and I hope the Python API will not change from version to version as undocumented gdb shny giblets. So, meet: DUEL is a high-level data analysis language for gdb.

Examples


Immediately, to show what he can do:

 (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" 

This is from the MariaDB debug. The command passes a TABLE_LIST connected list of TABLE_LIST structures and displays TABLE_LIST::table_name for each list TABLE_LIST::table_name .

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

From the same place (I cut out the addresses so as not to litter the text). There is an array of structures that specifies the command line options. The command displays only the names of the options, passing the entire array to name == 0 . And you can simply count how many of them:
')
 (gdb) dl #/longopts[0..].name @0 #/longopts[0..].name@0 = 406 

main idea


Duel is built on the fact that an expression can return many values. For example,

 (gdb) dl 1..4 1 = 1 2 = 2 3 = 3 4 = 4 

or here

 (gdb) dl my_long_options[1..4].(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 my_long_options[4].(name) = "bind-address" my_long_options[4].(def_value) = 0 

At the same time, such generator expressions are allowed to be used wherever one value is expected. This can be viewed as an automatic loop over all possible values. And in the second example, even two cycles, by array indices and by structure fields. Such a thing works very well when you have to go through an array, or a list, or, for example, a tree. Special operators allow you to conveniently record the conditions for stopping the generator.

Operators


The syntax is similar to C, and C-shny operators work as usual. Much more interesting are the new DUEL-specific operators. Consider the most useful ones:

Range and enumeration, .. and,


Above, I gave an example of both operators. These are familiar constructions, they are in other languages. In this case, in the range, you can omit one of the ends. If you specify only the end of the range, for example, ..20 , then the range will start from zero and there will be 20 values ​​in it, just as if 0..19 was written. If you specify only the beginning, you get an open range! In order for duel not to continue generating numbers until the thermal death of the universe (or until the counter overflow, whichever happens earlier), the stop operator is usually used along with the open range by the condition, @ .

Stop by condition @


In the expression x@y , the expression x will generate values ​​as long as y false. For example,

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

And duel will output the elements of the arr[] array until arr[i].count is not more than ten.

As a convenient abbreviation, you can use a constant as the second operand. Then the generation will stop when the displayed value equals this constant. I.e,

 (gdb) dl str[0..]@0 

returns all characters of the string, up to '\0' . A more practical example is to output all command line options from argv :

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

Although the same effect is achieved and

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

Follow the sign, ->


The generator a-->b generates a set of values a , a->b , a->b->b , and so on, until it hits a null. I have already given an example of how in this way one can go through a single-linked list. But it works the same way for trees, for example:

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

Calculating brackets {}


The curly brackets work like ordinary round braces, but they additionally replace the expression with its value in the output. Simply show an example:

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

This is mainly needed for arrays:

 (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" 

Here braces immediately show which elements of the array satisfy the condition.

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


These variations on the topic of comparison operators actually work as filters. That is, in x !=? y x !=? y , only those that are not equal to y are selected from the set of x values. Above was an example with a conditional if . With the filter, the same result is easier:

 (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" 

More operators


In addition, there are aliases, group operators, a conditional operator ( if ) and other rarely necessary things.

Advanced


Here are some more examples from the documentation.

Go through the cyclic list (start with the head and follow the signs until we reach the head again):

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

Find the second positive element in the array x[] . The [[ ]] operator selects elements from the sequence:

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

Find the last item in a single linked list. It uses the unary operator #/ , which returns the number of elements in the sequence, and the selector operator [[ ]] :

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

Returns only those elements of the array that are larger than the next element, in fact, checks whether the array is sorted in ascending order:

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

Once again, the most important thing


And the most important thing is that the duel in gdb works (again). It is wildly convenient when debugging something, harder than hello world. For normal use, four constructions are practically enough - two points, a comma, a long arrow --> and @0 .

You can take it from my repository: github.com/vuvova/gdb-tools
Disclaimer (excuse): although DUEL itself is a very respectable and time-tested interpreter, Duel.py is completely new, for sure there are bugs.

Source: https://habr.com/ru/post/328180/


All Articles