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.
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 int
s 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.