Actions

EmSys

An Introduction to the GNU Debugger

From EdWiki

Introduction

The GNU Debugger, part of the GNU Compiler Tools software suite, is the sourcelevel debugger used in the Embedded Systems Laboratory. This powerful debugger allows you to run your programs under controlled conditions. Doing this lets you see exactly what is going on in your program, helping you to remove any problems (“bugs”) that might be present.

The GNU Debugger is extensively documented in the GNU Debugger Manual. This Introduction is a summary of that manual specifically for the Laboratory.

The examples directory and its sub directories contain many examples of assembly language and C programs that you can use to build up your debugging skills. And you are encouraged to do so, as part of your laboratory preparation!

Ideally, you should read this document in front of a computer with the GNU Debugger, so that you can try out the various commands. Please see the end of this document for details.

Invoking the Debugger

The GNU Debugger can be run in two different modes: with a graphical interface such as Insight or DDD, and with a traditional command-line interface. The graphical interface makes basic debugging tasks much easier, while the command-line interface gives you considerable power for more complicated tasks. You will most likely want to use the graphical interface for most of your debugging, using the command line only when necessary.

Using the Command-line Interface

There are two main commands that display the contents of memory: x and print. These two commands are very powerful and only basic examples are given in this document; you should read Chapter 10 of the GNU Debugger Reference if you want a more in-depth treatment.

The x command examines an area of memory at a particular address and displays it in a particular format. This command has the following form:

x/nfu address

The first three parameters, n, f and u, are all optional (if you omit all of them, you do not need to specify the “/”, either). These parameters specify the repeat count n, the format f and the unit size u.

The most useful formats are x for hexadecimal, d for signed decimal, u for unsigned decimal, t for binary (mnemonic: t for “two”) and i for an ARM instruction. The most useful unit sizes are b for bytes, h for half-words (16-bit quantities) and w for words (32-bit quantities). The parameter address has the same format as that used in the Memory window in the graphical interface. All this sounds very complicated, but some examples should help:

x/4ub 0x08

Display four bytes as unsigned integers, starting from address 0x08. Pressing ENTER on an empty line after this command will display the next 4 bytes.

x/16xw $r13

Display 16 hexadecimal words (32-bit quantities) starting from the address contained in register R13; in other words, display the top 16 words on the processor stack (since register R13 is the Stack Pointer register SP).

x/10i &_start

Display 10 ARM instructions starting from the label _start. This is also known as disassembling your program.

The other major command to display memory is print (or p for short). This command operates at a much higher level than the x command: it understands things like full-blown C expressions, the types of variables and can even call functions in your program. It is also the command used to change the contents of variables and registers.

One major drawback of using the print command is that at times it requires a good understanding of C pointers. Pointers are a notoriously difficult subject for the beginner—an understanding of the difference between a variable, the address of that variable and a pointer to that variable does not come easily!

Examples are probably the best way to illustrate some of the power of the print command. And do remember that help print (or h p for short) gives you a brief summary:

p 102 * (22 + 23) + 0x1234 – 9208

A simple calculator!

p (char) 0x4A

Print the hexadecimal number 0x4A as the character “J”. This is an example of a C type-cast, in this case to char.

p/x &_start

Print the address of the _start label, in hexadecimal.

p/t $cpsr

Print the value of the ARM processor register CPSR, in binary.

p/d (int) *0x10

Print the 32-bit integer (ie, a C type of int) stored at address 0x10 (since *0x10 essentially tells the debugger to “treat 0x10 as an address and look at what is stored there”).

The next four examples use the program wordcopy.elf; note that the src array is defined in the .data section, not in the .text section:

p/x &src

Print the address of the src array as a hexadecimal number.

p src

Print the contents of the word stored at src. The value “1” is printed.

p (int) src

The same as the above, except that a C-style type-cast to int is used.

p (int[10]) src

Treat src as a pointer to an array of 10 ints, and print out that array. This is another example of a type-cast.

The next seven examples use the program strcopy.elf. You need to run the program at least to line 31 (the first printf statement) to get correct results:

p srcstr

Print the string pointed to by the srcstr variable.

p dststr

Print the array of characters contained in the dststr variable.

p dststr[2]

Print the character at index 2 in the dststr array (ie, the character “c”). Remember that arrays in C start at index 0.

p (int *) srcstr

Treat srcstr as a pointer to int (a C type-cast) and print out its value (ie, the value stored in the variable srcstr, which is an address).

p/x *((int *) srcstr)

Print the value pointed to by srcstr (when it is treated as a pointer to int), in hexadecimal. You should see the value “0x73726946” (the first four bytes of what srcstr points to).

p/x (int *) (*((int *) srcstr) - 1936877878)

Print that value subtracted by 1936877878, and treat the result as a pointer to int. You should see the value “0x10”.

p/x (int) *((int *) (*((int *) srcstr) - 1936877878))

Finally, print the 32-bit integer that is stored at that address (ie, the address 0x10). All this is a rather esoteric way of printing the hexadecimal representation of the instruction at address 0x10!

The last four examples use the program jumptbl.elf:

p dispatch(1, 218, 34)

Call the dispatch function with three parameters and print the result. This can be done even if you are in the middle of debugging that function!

p dispatch(&f_mul, 1234, 4321)

Call the same function with different parameters. Note that &f_mul is used instead of f_mul: this is due to the fact that labels assigned via the .set, .equ or “=” assembler directives are treated as addresses, not integers.

p/t do_add(-1, 26, 16)

Print the result of the function call to do_add in binary. Can you figure out why the first parameter is –1? Hint: the first parameter is always passed in register R0.

p do_add(-1, $r0, $r0)

Call the function do_add with the current value of the register R0 and print the result. Note that, even though that function returns the result in R0, your real R0 (ie,the register R0 in the program being debugged) is not modified. Issue p $r0 to check this, if you like.

The best way to become proficient in using the x and print commands is by constant practice! And do remember the help x and help print commands…

Modifying the Registers

The GNU Debugger allows you not only to examine registers and memory, but to modify them as well. You do need to be careful, however: it is often very easy to crash your own programs when they are presented with unexpected values!

In the graphical interface, you change a register’s value by double-clicking on that register in the Register window. An edit cursor will appear; simply enter the new value (using BACKSPACE to delete the old one) and press ENTER.

The command-line interface uses the print (p) command to modify the processor’s registers. You simply specify the register name (with a leading “$”, of course), the C assignment operator “=” and the new value.

Some examples:

p $r0 = 0x4A4E5A00

Set register R0 to 0x4A4E5A00.

p $r3 = $r3 – 5

Set register R3 to its current value less 5.

p $cpsr = ($cpsr & 0xFF) | (0xE << 28)

Set the N, Z and C flags, and reset the V flag, in the Current Processor Status Register.

Modifying Memory

Modifying the contents of memory is as easy as modifying the processor’s registers. Under the graphical interface, find the appropriate address in the Memory window, then double-click on the value you wish to modify. Simply change the value to whatever you want (using the BACKSPACE key as necessary) and press ENTER. Voila!

Under the command-line interface, you use the print command with the C assignment operator “=”. Some examples (using the program wordcopy.elf):

p *((char *) 0x100)

Print the character (ie, a C type of char) stored at address 0x100. You should always print the current value before making modifications, just to check that everything is OK!

p *((char *) 0x100) = 'J'

Store the character “J” at address 0x100.

p *(((int *) &dst) + 2)

Print element number 2 of the array dst. Unfortunately, you cannot specify p dst[2] (as you would if you were debugging a program written in C) as your program does not contain sufficient type information.

p *((int *) ((int *) &dst) + 2) = 7

Set element number 2 of the array dst to 7.

Summary

The GNU Debugger is a powerful source-level debugger, and learning how to use it can be quite complicated. You can use the following table as a “quick reference” to the most useful commands:

h

Give you help with commands.

q

Quit the debugger.

r

Run your program from the beginning.

b

location Set a breakpoint at location, which can be a label, a line number in the source code or *address (eg, *0x8000).

i b

Show information about breakpoints in your program.

d num

Delete breakpoint num.

en num

Enable breakpoint num.

dis num

Disable breakpoint num.

c

Continue running your program until it reaches a breakpoint or the end

u location

Run until the program reaches location (location has the same syntax as the b command).

s

Step through the next source line of code.

n

Step through to the next line, treating function calls as if they were a single line.

si

Step through the next assembly language instruction.

ni

As with si, but do not trace through functions or backward jumps.

i r

Display all ARM processor registers.

x/nfu address

Examine memory at address. The most useful formats f are x for hexadecimal, d for signed decimal, u for unsigned decimal, t for binary and i for ARM instructions. The most useful unit sizes u are b for bytes, h for half-words and w for words. Examples of address: 0x1234, $r0, &dst.

p expression

Print the value of expression (which can be any complicated C expression that you like).

p/f expression

Print the value of expression using format f (see the x command for a list of these).

See Also

GDB Commands By Function - Simple Guide