1. Goals for this week:

  1. See the different parts of memory in an example program.

  2. Debugging C programs using gdb.

  3. Debugging memory errors using valgrind.

  4. Practice Writing and Compiling Assembly (ASM) Code

  5. Use the ASM visualizer to trace through ASM code

  6. Introduction to Lab 4.

2. Starting Point Code

Start by creating a week05 in your cs31/weeklylab subdirectory and copying over some files:

$ cd ~/cs31/weeklylab
$ mkdir week05
$ cd week05
$ pwd
/home/you/cs31/weeklylab/week05
$ cp ~kwebb/public/cs31/week05/* ./
$ ls
badprog.c      dosomething.s  loops.c   memparts.c  README        valtester.c
dosomething.c  functions.c    Makefile  prog.c      segfaulter.c

3. Parts of Memory

Let’s start by looking 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:

$ ./memparts

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.

4. Debugging C programs using gdb

GDB is the GNU debugger. Its primary use is to debug C programs. In an earlier weekly lab, we introduced gdb in In-Lab 2 (intro gdb). This week, we will revisit some of the basics of using gdb, and take a closer look at using gdb to examine the stack and to examine function calls with pass-by-pointer parameters.

4.1. common gdb commands

We will not go through this together, but as a good reminder of some of the commonly used gdb commands that we covered in In-Lab 2 (intro gdb), you can try running gdb on the badprog program, and follow along with a debugging session of it from the gdb guide: badprog example

The course textbook Section 3.1 contains a similar example, and Section 3.2 discuss gdb commands in more detail.

4.2. examining stack contents

We will start by opening up functions.c and looking at the code:

$ code functions.c

This program contains a lot of functions, and we will use it to see gdb’s support for examining the state of the program stack. Let’s run in gdb, and set breakpoints in some of the functions, and run until the breakpoint in function g is reached:

$ make
$ gdb ./functions
(gdb) break main     # break at main
(gdb) break g
(gdb) run
(gdb) where          # list stack at break point in main
(gdb) cont
(gdb) where          # list stack at break point in g

At this point we can print out local variables and parameters in the stack from of function g (the function on the top of the stack). We can also move into the context of a different frame on the stack and examine its local variables and parameters.

(gdb) where             # list stack at break point in g
#0  g (x=41) at functions.c:15
#1  0x00005555555546a2 in f (y=40) at functions.c:23
#2  0x00005555555546ff in blah (y=0x7fffffffe2bc) at functions.c:33
#3  0x0000555555554748 in foo (x=40) at functions.c:40
#4  0x00005555555547a4 in main (argc=1, argv=0x7fffffffe3d8) at functions.c:53
(gdb) list
(gdb) print x           # prints out function g's x
(gdb) frame 3           # move into foo's stack frame
(gdb) list
(gdb) print x           # print out foo's x variable value
(gdb) print &x          # print out the address of foo's x
(gdb) frame 2           # move into stack frame 2's context (blah)
(gdb) list
(gdb) print y           # print value of blah's y parameter
(gdb) print *y          # print value of what blah's y parameter points to
(gdb) where             # we are still at the same point in execution
(gdb) cont

4.3. finding where program segfaults

Next, let’s run segfaulter. We are going to follow along the gdb guide to see how to find where a program segfaults in order to help determine the cause of the segfault and fix it: gdb guide: segfaulter example

The course textbook has an example in 3.1.2 that is the same as this example with more explanation.

5. Arrays and Functions

The allocate.c program demonstrates how to pass a pointer to a function, how to return a pointer from a function, and how to use malloc and free to allocate heap memory for an array.

Let’s trace through this program, drawing the stack and the heap as the program executes.

Here are the important takeaways:

  1. In main, we pass the memory address of array_size to the allocate_array function. This allows the allocate_array function to modify the array_size variable that is in the stack frame of main.

  2. The allocate_array function uses malloc to allocate enough space on the heap to hold val integers and then returns the memory address of the start of this block of memory. We save this memory address in the variable array.

  3. It’s good practice to check to make sure that the call to malloc succeeded. If the malloc function returns NULL, it means that heap memory was not allocated because, e.g. there was no memory left to be allocated.

  4. The return type of the allocate_array function is int *. This allows us to return the memory address of the allocated heap memory back to main.

  5. In main, we can treat array as if it were a statically allocated array. A statically allocated array is one that was declared like this, e.g.: int array[10];

  6. When we are done using the heap-allocated memory, we need to free it.

6. Debugging C programs using Valgrind

Next, we will use the valtester.c program to demo valgrind, following along with the example from the valgrind guide

Chapt. 3.3 of the textbook also covers valgrind.

The valtester.c program has comments associated with every bad memory access error, which is designed to help explain valgrind output:

vim valtester.c

Valgrind is a tool for finding Heap memory access errors in programs. Memory errors are the most difficult bugs to find in programs. When debugging programs that use pointer variables to access dynamically allocated heap memory space (malloc and free memory), using valgrind can save you hours of debugging time.

7. Writing x86_64 Assembly

This semester, we’re going to be beta-testing a new assembly visualization tool that was developed here at Swarthmore. You can use this tool whenever you would like to trace small snippets of x86_64 assembly language. For this in-lab exercise, we’d like you to test out two examples in the visualizer.

As you go, you may find it helpful to refer to the x86_64 instruction reference sheet.

If you run into problems with the visualizer tool, or if you have suggestions for ways to improve it, please send an email with details to Kevin.

7.1. Task 1: Code Tracing

For the first task, we would like you trace through the following assembly code and figure out what it is doing. Work with a neighbor or lab partner to step through the code and describe to each other what’s happening. Roughly speaking, what would equivalent C code look like?

You can and paste this code into Assembly Visualizer window (check that you have the "Arithmetic" view and not the "Function" view window to copy code into), then press the submit button:

  subq $16, %rsp
  movq $10, -8(%rbp)
  movq -8(%rbp), %rax
  movq $5, %rdx
  addq %rax, %rdx
  cmp %rax, %rdx

  jg .L1
  subq $3, %rax
  movq %rax, -16(%rbp)

  jmp .L2

.L1:
  movq $0xff, -16(%rbp)

.L2:
  movq -16(%rbp), %rax
  addq $16, %rsp

7.2. Task 2: Writing Assembly

For the second task we would like you to write assembly code to compute the sum of the values 1 to 5 using a loop. For example, you could try converting the following while loop to x86_64 (you can use stack locations %rbp-8 and %rbp-16 or any of the general purpose registers for the variables i and sum):

i = 1;

sum = 0;

while (i <= 5) {

    sum = sum + i;

    i++;

}

Work with a neighbor or lab partner to write the x86_64 assembly equivalent of this while loop in the "Arithmetic" view.

For reasons we haven’t explained in class yet, you should include the following two lines as the first and last lines of your solution:

subq $16, %rsp

  ... # your solution goes here

addq $16, %rsp

Because this is a beta version of the assembly tool, it sometimes forgets the code you’ve entered, particularly when it has errors (sorry, we’re working on it!). For now, we strongly suggest that you write your code in your favorite text editor and then copy it into the visualization tool when you’re ready to run it.

7.3. Compiling and Running Assembly Code on Lab Machines

As part of Lab 4, you’ll be writing a small assembly program. While you may (hopefully) find it helpful to use the assembly visualization tool when working on it, ultimately you’ll need to submit a file for grading. You can compile and run assembly programs (whose file names end in .s) like this:

$ gcc -o prog prog.c dosomething.s
$ make    # or just type make to compile
$ ./prog

The dosomething.s file in today’s starter code demonstrates an example of compiling an assembly program like this.

8. Lab 4 Intro

Let’s look at Lab 4, and then you can use the remaining time to get started. 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. Part 2 is x86_64 Assembly programming. Refer to the assembly writing we did in lab today as you work on this part.

9. Handy Resources