Week 5: Memory in C & IA32

C pointers, gdb, valgrind
Create a week05 subdirectory in your weeklylab subdirectory and copy over some files (note: the cp -r):
    cd cs31/weeklylab		
    mkdir week05
    cd week05
    cp -r ~lammert/public/cs31/week05/* .
    Makefile  gdb_examples/  memparts.c  pointers.c  valgrind_examples/

Week 5 lab goals:

  1. Learn general memory addressing in IA32
  2. Learn about the leal instruction
  3. Example of accessing C pointer variables in IA32
  4. See an example of where different parts of program memory live
  5. See an introduction to valgrind
  6. Gain experience with gdb and valgrind
General memory addressing in IA32

So far we have looked at special forms of Memory addressing in IA32. We have seen examples such as these:

  (%eax) # get val @ address in %eax
-4(%eax) # get value @ offset -4 from address in %eax

The General Memory Addressing form for IA32 is the following:
D(Rb,Ri,S)   # get value @ Rb + S*Ri + D

   Rb: Base register
   Ri: Index register
   S: Scale factor
   D: Constant "displacement"

For example, an instruction could specify a memory operand as:

addl 8(%ecx, %eax, 4), %edx  # R[%edx] <-- M[R[%ecx] + 4*R[%eax] + 8]
There are a lot of Special Cases of this general form (not all 4 values need to be present):
 D(Rb,Ri,S)  # Mem[Reg[Rb]+S*Reg[Ri]+ D]   The General Form
  (Rb,Ri,S)  # Mem[Reg[Rb]+S*Reg[Ri]]
   (Rb,Ri)   # Mem[Reg[Rb]+Reg[Ri]]
  D(Rb,Ri)   # Mem[Reg[Rb]+Reg[Ri]+D]
   (,Ri,S)   # Mem[S*Reg[Ri]]
  D(,Ri,S)   # Mem[S*Reg[Ri] + D]
  (Rb)       # Mem[Reg[Rb]]        we have already seen this form: (%eax)
  D(Rb)      # Mem[Reg[Rb]+D]      we have already seen this form: -8(%ebp)
  D          # Mem[D]              we have already seen this form: $10
The leal instruction

Load effective address: leal S,D # D<--&S, where D must be a register, and S is a Memory operand. It's often used to implement C's address of (&) operator.

leal looks like a mov instr, but does not access Memory. Instead, it takes advantage of the addressing circuitry and uses it to do arithmetic (as opposed to generating multiple arithmetic instructions to do arithmetic).

(ex) if edx holds the value of x:
leal (%eax),%ecx # R[%ecx]<--&(M[R[%eax]])
# this moves the value stored in %eax to %ecx

The key is that the address of (M[ at address x ]) is x, so this is moving the value stored in %eax to %ecx; there is no memory access in this instruction's execution.


Assume:   %eax: x    %edx: y

leal (%eax), %ecx               # R[%ecx] <-- x
leal 6(%eax), %ecx # R[%ecx] <-- x+6 Assume y is a variable on the stack, at address %ebp - 4. leal -4(%ebp), %ecx # R[%ecx] <--- &(M[R[%ebp]-4]]): R[%ecx] <-- &y
leal appears a lot in compiler generated code. The compiler sometimes abuses leal to perform basic arithmetic.
C pointer variables in IA32
In pointers.c is a simple program that uses a pointer variable.
$ cat pointers.c
 int pointers() {
   int x, y, *ptr;
   x = 8;
   ptr = &y;
   *ptr = 30;
   x = *ptr + 20;
Lets compile a simple program using pointers and see what its assembly code looks like (or just type make):
$ gcc-4.4 -m32 -S pointers.c
Let's cat out the .s file an look at some of the instructions. The thing to note is that when the *ptr is used (ptr is dereferenced), first the value of the ptr variable is obtained (its value is the address of y) and then the value at that address is accessed: a level of indirection.

Here is the code with some annotations around what it is doing (note the use of leal instruction):

$ cat pointers.s
        pushl   %ebp
        movl    %esp, %ebp
        subl    $16, %esp
        movl    $8, -4(%ebp)     # x = 8
        leal    -8(%ebp), %eax   # R[%eax] <--- &(M[R[%ebp]-8]]): R[%eax] <-- &y
        movl    %eax, -12(%ebp)  # ptr = &y;
        movl    -12(%ebp), %eax  # R[%eax] <-- ptr: R[%eax] <-- &y 
        movl    $30, (%eax)      # what ptr points to gets 30
        movl    -12(%ebp), %eax
        movl    (%eax), %eax
        addl    $20, %eax
        movl    %eax, -4(%ebp)

Parts of Memory
Let's look at memparts.c. This program prints out the memory address of different parts of the program: global variables, local variables on the stack, instructions, and heap memory locations for malloc'ed space.

Let's just run this and see where some things are:

The thing to note now is that heap memory locations (malloc'ed space) and local variable locations (on the stack) are at very different addresses. We will revisit this program later in the semester when we talk about other parts of program memory.

gdb and valgrind to debug C programs
In the code you copied over are two subdirectories with test files for gdb and valgrind. We are going to go over just a couple of these, but see my gdb and valgrind documentation (both linked to below) for more information about using gdb and valgrind and try out some more examples.

GDB for C program debugging
Today, we are going to look at some features of gdb for debugging C programs. In particular, looking at a stack trace, moving between frames to examine parameter and argument values, and examining runtime state of a segfaulting program. Again, you can use ddd, but I'm going to show you the gdb commands running gdb.

cd into the gdb_examples subdirectory.

First, run make to build the executables (note they are all compiled with -g).

Let's look through a couple of the example programs in gdb, following along in my GDB Guide.

We are going to look at badprog and segfaulter in gdb. These are listed in the "Sample gdb sessions" part of my gdb guide under run 1: debugging badprog and run 2: debugging segfaulter.

Up the page on this guide are lists of common gdb commands and some examples of how to use them.

cd into the valgrind_examples subdirectory.

Valgrind is a tool for finding heap memory access errors and memory leaks in C and C++ programs. Memory access errors are often very difficult bugs to find, and valgrind helps you easily find errors like reads or writes beyond the bounds of a malloc'ed array, accessing free'ed memory, reading uninitialized memory, and memory leaks (not freeing malloc'ed space before all variables referring to it go out of scope).

To use valgrind, just compile with -g, and run valgrind on your program:

valgrind ./badprog
The output at first seems a bit cryptic, but once you see the basics of how to interpret it, it is extremely helpful for finding and fixing memory access errors. Let's look at my Valgrind Guide to see how to interpret some of this valgrind output. This guide contains links to other valgrind resources, and the README file in the code you copied over lists some command line options for running valgrind.
Some more information on debugging tools for C: C programming tools: gdb and valgrind

Lab 4
Spend the rest of the lab time starting on lab 4 assignment with your partner (see the partner list off the class schedule if you are unsure who your partner is). Start with Part 1, which is a C programming assignment using pointers, and remember this page with information on using gdb and valgrind to debug your C programs.