Review: Pointers

Consider the following program pointer.c.

#include <stdio.h>

int main() 
{
    int int_var = 5;
    int *int_ptr;

    int_ptr = &int_var;     // put the address of int_var into int_ptr

    printf(" int_var = %d\n &int_var = %p\n\n", int_var, &int_var);
    printf(" int_ptr = %p\n &int_ptr = %p\n *int_ptr = %d\n\n", int_ptr, &int_ptr, *int_ptr);

    *int_ptr = 10;

    printf(" int_var = %d\n &int_var = %p\n\n", int_var, &int_var);
    printf(" int_ptr = %p\n &int_ptr = %p\n *int_ptr = %d\n\n", int_ptr, &int_ptr, *int_ptr);
}
Fill out the empty space (if unsure, checkout the answers by compiling and running the code).
$ gcc pointer.c -o pointer
$ ./pointer
 int_var = 5
 &int_var = 0x7ffff7dbb72c

 int_ptr = 0x7ffff7dbb72c
 &int_ptr = 0x7ffff7dbb730
 *int_ptr = 5 

 int_var = 10
 &int_var = 0x7ffff7dbb72c

 int_ptr = 0x7ffff7dbb72c
 &int_ptr = 0x7ffff7dbb730
 *int_ptr = 10

Review: Memory Layout

The general memory layout of a program is as follows:

  • Text: program code as well as constant variables (i.e. "const int x = 5;").
  • Initialized data: global variables with initial values (i.e. "int x = 3;").
  • Uninitialized data: global variables without initial values (i.e. "int x;").
  • Heap: dynmically allocated memory. Only allocated via malloc or calloc.
  • Stack: local variables inside functions.

Note: Constant strings like "hello" and "cat", declared as in char * x = "hello"; reside in text section.

Important registers

Quick check


#include <stdio.h>
#include <stdlib.h>

int global = 3;

int main() 
{
    char hello[] = "hello";
    char* hello2 = "hello2";

    int* p = malloc(10); 

    printf( " hello=    %p\n\n", hello);
    printf( " hello2=    %p\n\n", hello2);
    printf( " &hello2=    %p\n\n", &hello2);

    printf( " main=     %p\n\n", main);
    printf( " &global=  %p\n\n", &global);
    printf( " p=    %p\n", p);

    free(p);

    return 0;
}
Fill out the blanks in the output of this program given below, by selecting one of the following options (note the addresses are already sorted):
A. 0x400626
B. 0x400794
C. 0x602010
D. 0x1417010
E. 0x7ffee66a3310
F. 0x7ffee66a3320
 hello=    F (E is allowable)

 hello2=    B

 &hello2=    E (F is allowable)

 main=     A

 &global=  C

 p=    D
(Drag your mouse for the answer)

GDB

GDB stands for GNU Debugger, which is a powerful text debugger that will let you do many things. For example, you can stop program at specified location and see what has happened when program stopped. We can look at the values of the variables and change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.

GDB Plug-in

To look at the binary data more conveniently, you can install the following GDB plug-in.
  1. Download gdb.tar
  2. Uncompress the archive and place the uncompressed files to your home directory.
    tar -xvf gdb.tar
    cp .gdbinit ~
    cp gdbx.py ~
    
  3. Start a new terminal to run gdb with the plug-in.

Compile Option for GDB Debugging

How to Start GDB

First, You should run gdb.
$ gdb -q pointer
The option -q specifies "quiet" mode. In this mode, the gdb does not print the introductory and copyright messages.

Looking at the Source Code: list

The command list will show you the source code (press enter many times if necessary).
(gdb) list
1	#include <stdio.h>
2	
3	int main() 
4	{
5	    int int_var = 5;
6	    int *int_ptr;
7	
8	    int_ptr = &int_var;     // put the address of int_var into int_ptr
9	
10	    printf(" int_var = %d\n &int_var = %p\n\n", int_var, &int_var);
(gdb) 
11	    printf(" int_ptr = %p\n &int_ptr = %p\n *int_ptr = %d\n\n", int_ptr, &int_ptr, *int_ptr);
12	
13	    *int_ptr = 10;
14	
15	    printf(" int_var = %d\n &int_var = %p\n\n", int_var, &int_var);
16	    printf(" int_ptr = %p\n &int_ptr = %p\n *int_ptr = %d\n\n", int_ptr, &int_ptr, *int_ptr);
17	}

Setting Breakpoints: break

We set breakpoints right before line 10, and right before line 15. The tasks can be accomplished using break command.
(gdb) break 10
Breakpoint 1 at 0x400544: file pointer.c, line 10.
(gdb) b 15
Breakpoint 2 at 0x400586: file pointer.c, line 15.

Run the program: run

Now, we can run the program with run command.
(gdb) run
Starting program: /home/choi/it432/lec/l10/pointer 

Breakpoint 1, main () at pointer.c:10
10	    printf(" int_var = %d\n &int_var = %p\n\n", int_var, &int_var);

Note that due the breakpoint we set, the control is paused right before executing line 10.

Showing Assembly code: disassemble

We can also see the assembly code using disassemble command.
(gdb) disassemble main
(gdb) disass main
Dump of assembler code for function main:
   0x000000000040052d <+0>:	push   %rbp
   0x000000000040052e <+1>:	mov    %rsp,%rbp
   0x0000000000400531 <+4>:	sub    $0x10, %rsp
   0x0000000000400535 <+8>:	movl   $0x5,-0xc(%rbp)
   0x000000000040053c <+15>:	lea    -0xc(%rbp),%rax
   0x0000000000400540 <+19>:	mov    %rax,-0x8(%rbp)
=> 0x0000000000400544 <+23>:	mov    -0xc(%rbp),%eax
   0x0000000000400547 <+26>:	lea    -0xc(%rbp),%rdx,
...
   0x00000000004005be <+145>:	leaveq  
   0x00000000004005bf <+146>:	ret    
End of assembler dump.

Look at variables and registers: print

We can look at the contents of the variable using print command.
(gdb) print int_var
$1 = 5
(gdb) p int_ptr
$2 = (int *) 0x7fffffffe394
We can also look at registers using print command.
(gdb) p $rip
$3 = (void (*)()) 0x400544 
(gdb) p $rsp
$4 = (void *) 0x7fffffffe390
(gdb) p $rbp
$5 = (void *) 0x7fffffffe3a0

Look at memory: hd

The gdb has x command to show the memory contents. To better show the memory contents, we will define a command hd (which stands for hexdump) and use it from now on. The format of hd commands as follows:
hd address how-much-to-dump-from-the-adddress
For example, starting from $rsp address you can see 40 bytes of memory (i.e., the first 40 bytes of stack from the top) as follows.
(gdb) hd $rsp 40
0x7fffffffe390: 80 E4 FF FF       . . . .
0x7fffffffe394: 05 00 00 00       . . . .   // int_var is shown in this line 
0x7fffffffe398: 94 E3 FF FF       . . . .   // int_ptr resides here
0x7fffffffe39c: FF 7F 00 00       . . . .   // int_ptr (8 bytes in total)
0x7fffffffe3a0: 00 00 00 00       . . . .
0x7fffffffe3a4: 00 00 00 00       . . . .
0x7fffffffe3a8: 45 6F A3 F7       E o . .
0x7fffffffe3ac: FF 7F 00 00       . . . .
0x7fffffffe3b0: 00 00 00 00       . . . .
0x7fffffffe3b4: 00 00 00 00       . . . .
Recall that the stack contains all the local variables of the current function. In particular, int_var and int_ptr are seen in this dump.

Continue the execution: continue

We can continue the exeuction by using the command continue (or just c) as follows:
Breakpoint 3, main () at pointer.c:10
10	    printf(" int_var = %d\n &int_var = %p\n\n", int_var, &int_var);
(gdb) c
Continuing.
 int_var = 5
 &int_var = 0x7fffffffe394

 int_ptr = 0x7fffffffe394
 &int_ptr = 0x7fffffffe398
 *int_ptr = 5


Breakpoint 4, main () at pointer.c:15
15	    printf(" int_var = %d\n &int_var = %p\n\n", int_var, &int_var);

So...

Note after continuation, the program stopped at the second breakpoint (line 15). Things to note about the code:
13	    *int_ptr = 10;
The aobve statement (line 13) will change the integer object int_ptr points to so as to have value 10. You can check this by inspecting the memory dump below:
(gdb) hd $rsp 40
0x7fffffffe390: 80 E4 FF FF       . . . .
0x7fffffffe394: 0A 00 00 00       . . . .
0x7fffffffe398: 94 E3 FF FF       . . . .
0x7fffffffe39c: FF 7F 00 00       . . . .
0x7fffffffe3a0: 00 00 00 00       . . . .
0x7fffffffe3a4: 00 00 00 00       . . . .
0x7fffffffe3a8: 45 6F A3 F7       E o . .
0x7fffffffe3ac: FF 7F 00 00       . . . .
0x7fffffffe3b0: 00 00 00 00       . . . .
0x7fffffffe3b4: 00 00 00 00       . . . .

Continue again

(gdb) c
Continuing.
 int_var = 10
 &int_var = 0x7fffffffe394

 int_ptr = 0x7fffffffe394
 &int_ptr = 0x7fffffffe398
 *int_ptr = 10

[Inferior 1 (process 88980) exited with code 0105]

Summary

Usage of *, &

GDB commands

Important registers