DDD tips and tricks

Back to homepage

DDD, which stands for "Data Display Debugger", is an extremely powerful frontend for gdb. It lets you to see the values of variables, structures, memory, and GDB expressions as the program runs. It provides an extremely easy way to manage breakpoints and watchpoints. You can also edit the memory contents on the fly.

This post isn't meant as an exhaustive manual for DDD. I'll show you some basic usage and some tips. What I wanted to describe here are some of the quirks of DDD, that should not discourage people from using this extremely powerful debugger in all of it's Motif glory.

Example program

Before we start using DDD, we need a program to debug with it. Below is an example that I will be using throughout this post:

#include <stdio.h>


int main(int argc, char** argv){
	for(int i = 0; i < 10; i++){
		int j = i*i;
		printf("%d ", j);
	}
	printf("\n");
}
  

Make sure to compile with -g to include the debug data:

gcc -g demo.c

Now, run the debugger with

ddd a.out

Note: you can't add args on the command line. If you need to set args, use the GDB console in DDD: set args foo bar baz

You should see something like this:

The DDD window is divided into 4 panes:

You can show/hide each of them in the View menu

I like to have the output appear in a separate xterm, so that it doesn't clog up the gdb prompt. To do this, tick Program > Run in Execution Window from the menu. I also have line numbers enabled (Edit > Preferences > Source > Display Source Line Numbers)

Setting breakpoints

To set a breakpoint, double click to the left of the source line in the source window. A red STOP icon will appear next to it. Click Run to start execution. The green arrow that shows the current position will appear as soon as you hit the breakpoint. The buttons above the Data window behave just like the aproppriate GDB commands.

You can disable or edit the breakpoint properties by double clicking on the STOP symbol

Peeking at variables

While the program is running, you can click on a variable name to bring it up a display in the data window (as seen above). These displays will update every time the program hits a breakpoint.

Note: If you try to bring a variable up while the program is not running, or a variable from a different context, you will get a No symbol xyz in curent context error. First you set the breakpoints, then you bring the displays up. The displays show up only if the corresponding variables are in the current scope!

Dumping memory

DDD can be used to peek at memory contents at any address. To do so, click Data > Memory. The following window will pop up:

You can enter any valid C expression into the From text field. Select appropriate values and click Display to bring it up in the Data window, or Print to dump it in the GDB console

In the above example you can see the value of i, 0x00000008 at 0x7fffffffddc8 (top left of the dump window) and the value of j, 0x00000040 at 0x7fffffffddcc (mid left 0x40 and the following 0x00-s)

But what if I want to look at the memory before a variable? Very simple with some pointer arithmetics:

This expression takes the address of i and takes away 4*sizeof(int) = 16 bytes. I want to display 32 bytes, so that the value of i will be in the middle of the dump. It's useful sometimes. You can see that I have typed

print &i

to peek at what address i is at.

Note: gdb respects C pointer arithmetics, so if you add, or substract X, you need to keep in mind that you are adding and substracting X*sizeof bytes.

Also note that there is a bug in the Memory window, and if you bring it up for the second time, the value of from field will be incorrect. You have to corect it manually

This is one of the DDD quirks... but hey! Copyright 2009, so...

Inspecting the stack

You can look up several words of memory beginning with the value of the stack pointer:

Let's stop here for a second since there is a lot going on in that image. First of all I have added a small function, since... well it's hard to show how stack works if your program is only a single function.

Then you can see the Examine Memory window which says the hexdump displayed is 32 quadwords (giants) after the address ..dda0.

Then, I have brought up the backtrace and the register window from the Status menu.

You can see that the address ..dda0 is the value of the stack pointer register in the register window

You can see the return address in memory on the stack (marked green in the hexdump, just after some address). It points to the mov just after the square() call (see the disassembly, marked)

Note: you can bring up any function source and/or disassembly if you type it into the (): text field (next to the buttons)

Editing values

Once you have a variable in the data window, you can right click it and select Set Value.

A window will pop up where you can enter the new value.

Click OK. Then double click on the STOP symbol to open the breakpoint properties window and disable it

Then click continue. You can see that the program's behavior has changed as we modified the value of i

Unfortuantely this doesn't work neither with hexdumps nor registers. You have to use the gdb prompt for that.

(gdb) set variable *(0xffffd040) = 0x1badc0de
(gdb) x/8w 0xffffd040
0xffffd040:   0x1badc0de   0x00000001   0x56559000   0xffffd114
0xffffd050:   0x00000001   0xffffd114   0xffffd11c   0x5655629d

(gdb) set variable $eip = 0x00000000
(gdb) print $eip
$3 = (void (*)()) 0x0
  

Note that changing the above eip value like that might not be the best idea...

Dealing with optimized out variables

Quite often you have to deal with optimized code, whether that's MCU development or OS programming.

Let's recompile the program with -O3 to force maximum optimization. This will cause less unnecesary writes to and reads from the memory, as well as unnecesary register shuffling. Open it in DDD, set a breakpoint, click Run, and try to peek at a variable...

...but what's that? No symbol "i" in current context? It's clearly there!

Let's take a closer look.

-O3 made the code heavily optimized, but we can make out the loop body (marked green), and the multiplication operation (orange).

We can also see that ebx is the loop counter, as first it's incremented (at +26), then compared with 10 (0xa, at +34) and the code jumps back to +16 if it's not equal (jne at +37)

There is also another register at play - esi. First, the mov (not marked, at +16) copies the value of ebx to esi, then the imul (orange) multiplies ebx with esi and puts the result in esi [1] (esi now contains the square of ebx).

Incidentally (our compiler put some hard work into make it all work together), esi carries the second argument for the next printf call (see SysV calling convention for x86_64). The optimizer removed all unnecesary memory writes by cleverly putting the variables in registers.

This means that the compiler put the variable i in ebx, and j in esi

They aren't stored in memory, but we can still examine them.

Let's put some breakpoints in the assembly. One at the very beginning should suffice. Double click left of the line in the listing. Note that the STOP icon will appear somehwere in the source window

We can now peek at the reigster values. Theres another quirk of DDD - double clicking the register name doesn't work in machine code window. Instead, you can do two things:

The first one is easy. I have shown it previously.

However, DDD has another powerful feature: You can display the output of any gdb command!

You just have to enclose the expression in backticks (`)

You can bring up a new display from the GDB command line with the graph display `expr`...

...or the GUI: right click in the data window and select New display

Below another example. Those windows will dynamically update

Footnotes

1 - ATT syntax uses different operand order than intel. If you don't like this syntax, you can change it the usual GDB way (set disassembly-flavor intel ) or select Edit > GDB Settings > Disassembly flavor (you have to scroll down past the tickbox options)

Back to homepage