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. Introduction to Lab 4.

2. Starting Point Code

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

$ cd ~/cs31/weeklylabs
$ mkdir week05
$ cd week05
$ pwd
$ cp ~newhall/public/cs31/week05/* ./
$ ls
Makefile   functions.c	memparts.c    valtester.c
badprog.c  loops.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:


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 Weekly 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 Weekly 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:

vim 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:

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 2nd example in 3.1.2 is this same example with more explanation.

4.4. conditional breakpoints

We will not go over this example, but loops.c can be used to practice setting conditional breakpoints (a breakpoint that is only hit when a certain expression is true). Conditional breakpoints are useful if the buggy behavior only happens on certain conditions. For example, it may only happen after the 1,000th iteration of a loop. In this case, a conditional breakpoint can be set on the loop counter variable to only break when the loop counter’s value is greater than or equal to 1000. See the comment at the top of the file for how to do this, or follow along with the example in the gdb guide: setting conditional breakpoints example

For more information, see Commands for Setting and Manipulating Breakpoints in section 3.2 of the textbook.

5. 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.

6. Lab 4 Intro

Lets talk through the next Lab 4 Assignment, where you will implement a C program that uses pointers and dynamic memory allocation and includes a second part that has you implement a function in IA32 assembly code (we will discuss this second part next week).

7. Handy Resources