1. Goals for this week:
-
See the different parts of memory in an example program.
-
Debugging C programs using gdb.
-
Debugging Heap memory errors using valgrind.
-
Practice Writing and Compiling Assembly Code
-
Introduction to Lab 4: lab assignment.
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 ~newhall/public/cs31/week05/* .
ls
Makefile badprog.c dosomething.s loops.c prog.c valtester.c
README dosomething.c functions.c memparts.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 Week 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 Week 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
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:
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
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
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
The valtester.c
program has comments associated with every bad memory
access error, which is designed to help explain vagrind 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. Writing IA32 Assembly
Together, we are going to write some IA32 assembly code, and then compile and test it out. As we go, let’s refer to the IA32 instruction reference sheet.
First, open the prog.c
file. You will see that it reads in an int value from
the user and then makes a call to the int dosomething(int n)
function that
returns the result of some arithmetic operation on its parameter value. We are
going to implement this function in IA32 assembly code in the dosomething.s
file.
Lets look at the start of the dosomething
function written in IA32
assembly:
vim dosomething.s
The assembly code in this file doesn’t really do much yet; the
function just returns the value 3 (movl $3 %eax
). Let’s try
compiling and running it (remember the -m32
flag to tell gcc to compile
to IA32 code (or note that make includes this flag)):
gcc -m32 -o prog prog.c dosomething.s
./prog
make # or just type make to compile
We are going to implement the following function in IA32:
int dosomething(int n) {
int x, res;
x = n + 20;
res = x*3;
return res;
}
Let’s start by loading the value of the parameter n
into a register.
Next, see if you can get the function to return n
, compile and test
it out.
Next, see if you can set res
to 1 and get the function to
return the value of res
. Look at the comments in the .s file to
determine where there is space on the stack for res
.
Next, implement the rest of this function and try it out to see if it works.
7. Lab 4 Overview
Let’s look at Lab 4 lab assignment, 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 IA32 Assembly programming. Refer to the assembly writing we did in lab today as you work on this part. In Thursday’s lecture we will talk about assembly for loops and conditionals.
8. gdb for debugging assembly code
Next week we will talk in detail about using gdb at the assembly code level, but if you want to try using gdb to debug at the assembly code level, you can checkout these references:
-
GDB for Assembly (from the GDB guide page).
-
gdb for IA32 assembly debugging a more detailed IA32 gdb debugging guide
9. Handy References
-
Week 2 Wednesday lab gdb intro.
-
Chapter 3 of the textbook C debugging tools (valgrind, gdb for C, gdb for assembly)
-
Tools for examining phases of compiling and running C programs