Gem #120 : GDB Scripting — Part 2
by Jean-Charles Delay —AdaCore
Let's get started...
GDB provides you with a number of predefined variables. We already saw some of them in the previous gem ($argc, $arg0, ...), but much more is available. Among them are variables to access the values of machine registers. See the list below for a full enumeration of them in the case of x86:
$eax $ebx $ecx $edx $eflags $esi $edi $esp $ebp $eip $cs $ds $es $fs $gs $ss
Armed with those, and using the macro-coding language we saw in the previous Gem, we can easily write some functions to nicely display the variables. For example, let's write a function that displays the flag values in $eflags:
define eflags printf " OF <%d> DF <%d> IF <%d> TF <%d>",\ (($eflags >> 0xB) & 1), (($eflags >> 0xA) & 1), \ (($eflags >> 9) & 1), (($eflags >> 8) & 1) printf " SF <%d> ZF <%d> AF <%d> PF <%d> CF <%d> ",\ (($eflags >> 7) & 1), (($eflags >> 6) & 1),\ (($eflags >> 4) & 1), (($eflags >> 2) & 1), ($eflags & 1) printf " ID <%d> VIP <%d> VIF <%d> AC <%d>",\ (($eflags >> 0x15) & 1), (($eflags >> 0x14) & 1), \ (($eflags >> 0x13) & 1), (($eflags >> 0x12) & 1) printf " VM <%d> RF <%d> NT <%d> IOPL <%d> ",\ (($eflags >> 0x11) & 1), (($eflags >> 0x10) & 1),\ (($eflags >> 0xE) & 1), (($eflags >> 0xC) & 3) end document eflags Print eflags register. end
With this macro, issuing an eflags command gives the following output:
gdb$ eflags OF <0> DF <0> IF <1> TF <0> SF <1> ZF <0> AF <0> PF <0> CF <0> ID <1> VIP <0> VIF <0> AC <0> VM <0> RF <0> NT <0> IOPL <0>
This outputs the value of each flag, but in a quite verbose way. Let's write a function to print the flag values by representing each of them by a letter, and print it in upper case if the flag is set, or in lower case if it is not.
define flags if (($eflags >> 0xB) & 1) printf "O " else printf "o " end if (($eflags >> 0xA) & 1) printf "D " else printf "d " end if (($eflags >> 9) & 1) printf "I " else printf "i " end if (($eflags >> 8) & 1) printf "T " else printf "t " end ... if ($eflags & 1) printf "C " else printf "c " end printf " " end document flags Print flags register. end
The output of this function is, for example:
o d I t S z a p c
You can of course create as many functions as you need, and customize them however you like.
When you're debugging a running process, it's often desirable to get an overall view of the process context, and do this for each step in the program. The useful process-context commands you have created so far can be called automatically each time you break when debugging your program. For this, you can use a GDB-specific macro called hook-stop.
The hook function hook-stop is a special definition that GDB calls at every breakpoint event. This means that you can use it to call your user-defined functions each time GDB stops (after a breakpoint, after each next/nexti, etc.).
The only thing you have to do is define it, and make it do whatever you want.
define hook-stop eflags end document hook-stop /!\ For internal use only - do not call manually /!\ end
Once this hook is defined, the function eflags -- which print the value of each register flag -- will be called each time GDB hits a breakpoint.
Ada-related hints when macro-coding
Beware that the GDB macro scripting syntax varies somewhat depending on the current language set. The language is usually set to "c" when debugging C programs, and is set to "ada" when debugging Ada programs. But the "c" syntax and the "ada" syntax differ. For example, an assignment in "c" mode is done with:
set var = 1
whereas in "ada" mode it looks like:
set var := 1
As you can see, GDB adapts its syntax to the current language, so that the user writes with the same convention whether he is developing or debugging. This feature has pros and cons, but this is not the topic of this Gem. The only thing you need to know is that, because of this, most of the macros we have defined so far won't work when debugging Ada programs.
There is, however, a workaround. Inside each macro definition you can manually set the current language. Therefore, the following macro:
define hexdump if $argc != 1 help hexdump else Do the work ... end end
can be rewritten as follows:
define hexdump set language c if $argc != 1 help hexdump else Do the work ... end set language auto end
This avoids any issues related to the current language in use, and since we set the language back to "auto", there won't be any side effects after a call to this macro.
GDB scripting is different from most programming languages, particularly because there is no notion of scoping: every command has a global effect on the GDB environment. Let's say, for example, that you have two macros macroA and macroB, and that the first one calls the second:
define macroA set language c <= (1) ... do some stuff ... macroB ... do some stuff ... set language auto end
define macroB set language c <= (2) ... do some stuff ... set language auto <= (3) end
The above example will work like a charm in "c" mode, but look what happens in "ada" mode:
(1) You enter macroA, set the language to "c"
(2) You enter macroB, set the language to "c"
(3) You leave macroB, set the language back to "auto" (which is "ada").
Once back from macroB, the language is set to "ada". If the second part of macroA uses C-specific features, such as the comparison operator != (instead of /= for ada), the macro execution will fail.
For this reason, be sure to use the "c" language mode when executing macros, and always restore it to "auto" just before exiting a "scope".
Scripting is a powerful feature of the GNU debugger, and offers a significant aid when debugging programs using the command line. It can also be very useful when using GPS, since GPS sources the .gdbinit file, so that all predefined macros are available from the command-line interface of GPS. This allows one to combine the power of both textual and graphical debugging.