Skip to main content
\(\newcommand{\doubler}[1]{2#1} \newcommand{\binary}{\texttt} \newcommand{\hex}{\texttt} \newcommand{\octal}{\texttt} \newcommand{\prog}{\texttt} \newcommand{\lt}{ < } \newcommand{\gt}{ > } \newcommand{\amp}{ & } \)

Section8.5Using 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.
 * Bob Plantz - 26 July 2016
 */

#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;
}
Listing8.5.1Simple 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:~/book-progs/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:~/book-progs/chap08 $ gdb ./gdbExample1
GNU gdb (Raspbian 7.7.1+dfsg-5) 7.7.1
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
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 command lists ten lines of source code. The display is centered around the current line. Since I have not started execution of this program, the display is centered around the beginning of main. The display ends with 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 0x10490: file gdbExample1.c, line 18.
(gdb)run
Starting program: /home/bob/book-progs/chap08/gdbExample1 

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

I set a breakpoint at line 19 then run the program. When line 19 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 *) 0x7efff5e8

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 0x7efff5e8
0x7efff5e8:	305441741

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

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

I examine the same integer in hexadecimal format.

(gdb)x/4xb 0x7efff5e8
0x7efff5e8:	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{0x7efff5e8}\text{,}\)

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

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

  • \(\hex{0x12}\) is stored in the byte at address \(\hex{0x7efff5eb}\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.6.

(gdb)x/2xh 0x7efff5e8
0x7efff5e8:	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{0x7efff5e8}\text{,}\)

  • \(\hex{0x1234}\) is stored in the byte at address \(\hex{0x7efff5ea}\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 *) 0x7efff5e8
(gdb)print &ptr
$4 = (int **) 0x7efff5ec

Look carefully at the ptr variable. It is located at address \(\hex{0x7efff5ec}\) and it contains another address, \(\hex{0x7efff5e8}\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             0x7efff754	2130704212
r2             0x7efff75c	2130704220
r3             0x1234abcd	305441741
r4             0xffffffff	4294967295
r5             0x0	0
r6             0x10348	66376
r7             0x0	0
r8             0x0	0
r9             0x0	0
r10            0x76fff000	1996484608
r11            0x7efff5fc	2130703868
r12            0x76fa3000	1996107776
sp             0x7efff5e8	0x7efff5e8
lr             0x76e7e294	1994908308
pc             0x10490	0x10490 <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.3.) 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{0x10490}\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 0x104a4: file gdbExample1.c, line 20.
(gdb)br 2a
Breakpoint 3 at 0x104b0: 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             0x104b0	0x104b0 <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{0x10490}\) 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:~/book-progs/chap08 $

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

Subsection8.5.1Exercises

1

Enter the program in Listing 8.5.1 and trace through the program one line at a time using gdb. Use the n command, not s or si. Keep a written record of the pc register at the beginning of each line. How many bytes of machine code are in each of the C statements in this program? Note that the addresses you see in the pc register may differ from the example given in this chapter.

Hint Answer
2

As you trace through the program in Exercise 8.5.1.1 stop on line 21:

wye += *ptr;

We determined in the example above that the r4 register is used for the variable wye. Determine which register is being used for wye in your instance of the program. Inspect the registers.

  1. When your program stops at this C statement, what is the address of the first instruction that will be executed when you enter the n command?

  2. How will the register that holds the wye value change when this statement is executed?

  3. How many bytes of the program does this statement is use?

Hint Answer
3

Modify the program in Listing 8.5.1 so that a register is also requested for the ex variable. Were you able to convince the compiler to do this for you? Did the compiler produce any error or warning messages? Why do you think the compiler would not use a register for this variable.

Answer
4

Use the gdb debugger to observe the contents of memory in the program from Exercise 2.8.1.2. Verify that your algorithm creates a null-terminated string without the newline character.

5

Write a program in C that allows you to determine the endianess of your computer.

Hint Solution
6

Modify the program in Exercise 8.5.1.5 so that you can demonstrate, using gdb, that endianess is a property of the CPU. That is, even though a 32-bit int is stored little endian in memory, it will be read into a register in the “proper” order.

Hint Solution