Skip to main content

Section 8.5 Using gdb to View the CPU Registers

We will use the program in Listing 8.5.1 to illustrate the use of gdb to view the contents of the CPU registers. I have used the register storage class modifier to request that the compiler use a CPU register for the int* ptr variable. The register modifier is “advisory” only. There are situations where the compiler may not be able to honor our request. You will get a chance to see this in the exercises at the end of this section.

/* gdbExample1.c
 * Subtracts one from user integer.
 * Demonstrate use of gdb to examine registers, etc.
 * 2017-09-29: Bob Plantz
 */

#include <stdio.h>

int main(void)
{
  register int wye;
  int *ptr;
  int ex;

  ptr = &ex;
  ex = 305441741;
  wye = -1;
  printf("Enter an integer: ");
  scanf("%i", ptr);
  wye += *ptr;
  printf("The result is %i\n", wye);

  return 0;
}
Listing 8.5.1. Simple program to illustrate the use of gdb to view CPU registers.

We introduced some gdb commands in Chapter 2. Here are some additional commands that will be useful in this section:

  • n — Execute current source code statement of a program that has been running; if it's a call to a function, the entire function is executed.

  • s — Execute current source code statement of a program that has been running; if it's a call to a function, step into the function.

  • si — Execute current (machine) instruction of a program that has been running; if it's a call to a function, step into the function.

Here is a screen shot (with some explanatory comments) of how I compiled the program then used gdb to control the execution of the program and observe the register contents. You will probably see different addresses if you replicate this example on your own Raspberry Pi.

pi@rpi3:~/chap08 $ gcc -g -O0 -Wall -o gdbExample1 gdbExample1.c

The “-g” option is required. It tells the compiler to include debugger information in the executable program. The “-Wall” option causes the compiler to warn you about many constructions that might be a programming error.

pi@rpi3:~/chap08 $ gdb ./gdbExample1
 GNU gdb (Raspbian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
     Reading symbols from ./gdbExample1...done.
(gdb)li 11
6	
7	#include <stdio.h>
8	
9	int main(void)
10	{
11	  register int wye;
12	  int *ptr;
13	  int ex;
14	
15	  ptr = &ex;
(gdb) 
16	  ex = 305441741;
17	  wye = -1;
18	  printf("Enter an integer: ");
19	  scanf("%i", ptr);
20	  wye += *ptr;
21	  printf("The result is %i\n", wye);
22	
23	  return 0;
24	}
25

The li 11 command lists ten lines of source code centered around the specified line number. The display ends with a return to the (gdb) prompt. Pushing the return key repeats the previous command, and li is smart enough to display the next ten lines.

(gdb)br 18
Breakpoint 1 at 0x104b0: file gdbExample1.c, line 18.
(gdb)run
Starting program: /home/pi/chap08/gdbExample1 

Breakpoint 1, main () at gdbExample1.c:18
19	    printf("Enter an integer: ");

I set a breakpoint at line 18 then run the program. When line 18 is reached, the program is paused before the statement is executed, and control returns to gdb.

(gdb)print ex
$1 = 305441741
(gdb)print &ex
$2 = (int *) 0x7efff178

I use the print command to view the value assigned to the ex variable and learn its memory address.

(gdb)help x
Examine memory: x/FMT ADDRESS.
ADDRESS is an expression for the memory address to examine.
FMT is a repeat count followed by a format letter and a size letter.
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char), s(string)
and z(hex, zero padded on the left).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
The specified number of objects of the specified size are printed
according to the format.

Defaults for format and size letters are those previously used.
Default count is 1.  Default address is following last thing printed
with this command or "print".

The help command will provide very brief instructions on using a command. We want to display values stored in specific memory locations in various formats, and the help command provides a reminder of how to use the commands.

(gdb)x/1dw 0x7efff178
0x7efff178:	305441741

I verify that the value assigned to the ex variable is stored at location of the variable, \(\hex{0x7efff178}\text{.}\)

(gdb)x/1xw 0x7efff178
0x7efff178:	0x1234abcd

I examine the same integer in hexadecimal format.

(gdb)x/4xb 0x7efff178
0x7efff178:	0xcd	0xab	0x34	0x12

Next, I examine all four bytes of the word, one byte at a time. In this display,

  • \(\hex{0xcd}\) is stored in the byte at address \(\hex{0x7efff178}\text{,}\)

  • \(\hex{0xab}\) is stored in the byte at address \(\hex{0x7efff179}\text{,}\)

  • \(\hex{0x34}\) is stored in the byte at address \(\hex{0x7efff17a}\text{,}\) and

  • \(\hex{0x12}\) is stored in the byte at address \(\hex{0x7efff17b}\text{.}\)

In other words, the byte-wise display appears to be backwards. This is due to the values being stored in the little endian storage scheme as explained in Section 2.11.

(gdb)x/2xh 0x7efff178
0x7efff178:	0xabcd	0x1234

I also examine all four bytes of the word, two bytes at a time. In this display,

  • \(\hex{0xabcd}\) is stored in the byte at address \(\hex{0x7efff178}\text{,}\)

  • \(\hex{0x1234}\) is stored in the byte at address \(\hex{0x7efff17a}\text{,}\)

This shows how gdb displays these four bytes as though they represent two 16-bit ints stored in little endian format. (You can now see why I entered such a strange integer in this demonstration.)

(gdb)print ptr
$3 = (int *) 0x7efff178
(gdb)print &ptr
$4 = (int **) 0x7efff17c

Look carefully at the ptr variable. It is located at address \(\hex{0x7efff17c}\) and it contains another address, \(\hex{0x7efff178}\text{,}\) that is, the address of the variable ex. It is important that you learn to distinguish between a memory address and the value that is stored there, which can be another memory address. Perhaps a good way to think about this is a group of numbered mailboxes, each containing a single piece of paper that you can write a single number on. You could write a number that represents a “data” value on the paper. Or you can write the address of a mailbox on the paper. One of the jobs of a programmer is to write the program such that it interprets the number appropriately—either a data value or an address.

(gdb)print wye
$5 = -1
(gdb)print &wye
Address requested for identifier "wye" which is in register $r4

The compiler has honored our request and allocated a register for the wye variable. Registers are located in the CPU and do not have memory addresses, so gdb cannot print the address. We will need to use the i r command to view the register contents.

(gdb)i r
r0             0x1	1
r1             0x7efff2e4	2130703076
r2             0x7efff2ec	2130703084
r3             0x1234abcd	305441741
r4             0xffffffff	4294967295
r5             0x0	0
r6             0x10368	66408
r7             0x0	0
r8             0x0	0
r9             0x0	0
r10            0x76fff000	1996484608
r11            0x7efff18c	2130702732
r12            0x7efff210	2130702864
sp             0x7efff178	0x7efff178
lr             0x76e7b678	1994897016
pc             0x104b0	0x104b0 <main+32>
cpsr           0x60000010	1610612752

The i r command displays the current contents of the CPU registers. The first column is the name of the register. The second shows the current bit pattern in the register, in hexadecimal. Notice that leading zeros are not displayed. The third column shows some the register contents in 32-bit unsigned decimal. The registers that always hold addresses are also shown in hexadecimal in the third column. The columns are often not aligned due to the tabbing of the display.

We see that the value in the r4 general purpose register is the same as that stored in the wye variable, \(\hex{0xffffffff}\text{.}\) (If this is not clear, you need to review Section 3.4.) This is consistent with the message from gdb that the compiler chose to allocate r4 as the wye variable.

Notice the value in the pc register, \(\hex{0x104b0}\text{.}\) Refer back to where I set the breakpoint on source line 19. This shows that the program stopped at the correct memory location.

It is only coincidental that the value stored in the ex variable is currently stored in the r3 register. If a general purpose register is not allocated as a variable within a function, it is often used to store results of intermediate computations. You will learn how to use registers this way in subsequent chapters of this book.

(gdb)br 20
Breakpoint 2 at 0x104c4: file gdbExample1.c, line 20.
(gdb)br 21
Breakpoint 3 at 0x104d0: file gdbExample1.c, line 21.

These two breakpoints will allow us to examine the value stored in the wye variable just before and just after it is modified.

(gdb)cont
Continuing.
Enter an integer: 123
Breakpoint 2, main () at gdbExample1.c:20
20	    wye += *ptr;
(gdb)print ex
$2 = 123
(gdb)print wye
$3 = -1

This verifies that the user's input value is stored correctly and that the wye variable has not yet been changed.

(gdb)cont
Continuing.

Breakpoint 3, main () at gdbExample1.c:21
21	    printf("The result is %i\n", wye);
(gdb)print ex
$2 = 123
(gdb)print wye
$5 = 122

And this verifies that our (rather simple) algorithm works correctly.

(gdb)i r r4 pc
r4             0x7a	122
pc             0x104d0	0x104d0 <main+64>

We can specify which registers to display with the i r command. This further verifies that the r4 register is being used as the wye variable.

And we see that the pc has incremented from \(\hex{0x104b0}\) to \(\hex{0x104b0}\text{.}\) Don't forget that the pc register always points to the next instruction to be executed.

(gdb)cont
Continuing.
The result is 122
[Inferior 1 (process 767) exited normally]
(gdb)q
pi@rpi3:~/chap08 $

Finally, I continue to the end of the program. Notice that gdb is still running and I have to quit the gdb program.