Gem #120 : GDB Scripting — Part 2

by Jean-Charles Delay —AdaCore

Let's get started...

Registers

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.

Enhanced debugging

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.

For example:

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.

Careful

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

Going further

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.