CS240 Lab 6

GDB and Pointer Manipulation

Objective

This lab is an exercise on writing robust program and using GDB to debug programs. At the end of this lab, you will be familiar with the core dump file and how to fix the Segmentation Fault problem.

Preparation

Download the following files, you will need them in the lab.

lab6.c - This file contains code to evaluate postfix expression. The code is similar to what you implemented in Project 1.

stack.c - This file contains implementation of a stack. This implementation of stack takes the pointer to an array stack, the stack pointer top, and the size of the array size as arguments. This allow the same code to operate on different stacks instead of implementing the stack operations for different stacks. For example, you have two stacks given by (stack1, top1, size1) and (stack2, top2, size2). To push an item onto the first stack, you call stack_push(stack1, &top1, size1, [item to push]). This way, you do not need to write two push functions for two different stack.

stack.h - This file contains the prototypes of stack operations that can be used in the program. Take a look at how #define is used to prevent including the file multiple times as discussed in lecture.

Makefile - This is the make file. Take a look at this file. The program is compiled with -g -O0 in addition to what you usually have. -g tells the compiler to emit verbose debugging information into the executable, and -O0 tells the compiler to not perform any kind of optimization to the program. Optimized programs are harder to debug.

Now, compile the program with make and run the program. Suprisingly, the program crashes!

lore 14 % ./lab6.out
Enter an expression in postfix order. E.g. 1 234 5 * 6 / + 7 - 89 -
1 234 5 * 6 / + 7 - 89 -
Read a number '1'
Segmentation fault (core dumped)
lore 15 %

Lab Exercise

There are three parts to this lab.

Turnin

When you are satisfied that your program works correctly (or you run out of time), please do the following.

  1. Create a directory named lab06
  2. Copy all program files (*.c and *.h) and your Makefile to the directory created in step 1.
  3. Goto the parent directory of lab06 and type the following command into the terminal:

    turnin -c cs240=XXXX -p lab06 lab06

    where XXXX represents your lab section number.

    The turnin section is as follows:

    Section Time TA
    0201 Thursday 15:30-17:20 Dan Zhang
    0301 Friday 09:30-11:20 Suli Xi
    0401 Friday 13:30-15:20 Youhan Fang
    0501 Thursday 09:30-11:20 J. C. Chin

    Make sure turnin reports that your project was submitted for grading. You can check the files you have submitted by running the following command:

    turnin -c cs240=XXXX -v -p lab06
  4. Submit often to make sure that you did not try to submit after turnin has been turned off.

Grading Breakdown

10 points Errors in stack.c are fixed. (About 2.5 points for each error)
6 points TODOs in stack.c were implemented correctly.
4 points Buffer overrun problem in getToken() is fixed.

Additional Information about GDB

GDB is a powerful interactive debugger for many compiled languages. We will of course be focusing on C.

GDB can be used in one of two major fashions -- at runtime (gdb is started before there is a problem) or post-mortem (gdb is run to determine why a program that has already died did so). Runtime debugging is desirable whenever possible, but post-mortem debugging is useful when you have a process that fails in some unexpected way and leaves a core dump.

These two major modes of operation are invoked in different fashions. To debug a program at run time, you can either start the program from within gdb or attach to an already running program. To start a program within gdb, you would do the following:

%gdb [progname]
(gdb) run

The second common form of runtime debugging is attaching GDB to a process that is already running. This requires you to know the PID of the running process you are interested in:

%gdb [progname] [PID]

After connecting in this fashion the program can be debugged normally, as if you had started gdb and run the program from there.

To invoke gdb for post-mortem debugging, you must have both the binary you wish to debug and the coredump file from where it failed. (FYI, you can often force a core dump with Ctrl-\ [control backslash]. However, it is often more useful to attach gdb to the running process than force a core.)

%gdb [progname] [coredump file]

Coredump files can also be loaded at runtime in GDB with the core-file command, and executables can be loaded with the exec-file command.

Here is a brief list of GDB commands you will find useful.

Command syntax Description
break
break linenumber
break filename:linenumber
break function
These commands set a "breakpoint", or a place where the debugger should suspend operation of the debugger and ask for further input. Play with their arguments a little to see how flexible break is.
bt
where
The bt and where commands are functionally identical. They cause gdb to print a "backtrace", or listing of the function call stack of the current program. This is very useful for determining where and why a program died. You should play with this command on both programs that have crashed and programs that are running normally to get a feel for how it works.
cont This command continues execution where it was last stopped, either by break, watch, Ctrl-C, a signal, or some other action.
help
help topic
help command
GDB includes an extraordinarily comprehensive help system. The bare command help will generate a list of high-level topics which can be researched in more depth by giving arguments to the help command. If you think GDB should probably be able to do some particular thing, look here -- it probably can.
info
category
This prints information about some part of your program or GDB's configuration; you may find the categories locals, variables, functions, and display useful.
next
Execute the current line of code and proceed to the next, treating subroutine calls as one line.
step
Like next, except step enters subroutines, treating the first line of a called subroutine as the next line of code.
print expression print is one of the most powerful commands in GDB; it is used to print the value of an arbitrary expression, such as a simple variable, a calculation, or a function call. The power of print stems from the fact that expression can be a quite complex C or C++ expression, and it will be executed just as if your program had done it; for instance, if the variable x is equal to 5 and you know that it should be six, the command print x = 6 will print the number "6", but will more importantly change the actual value of the variable x. This can be used to both determine why a section of code is failing and to prevent it from doing so, as well as to test corner cases and bounds in your code that may not be easily reached through test cases.
watch expression watch is like break, except that it will stop the program's execution whenever the value of the given expression changes, rather than at a specific point in program flow. Watchpoints can make your program run significantly slower if they have to be constantly checked, so set them carefully.

Remember that the source code line printed to the screen when the program stops is the em>next line to be executed, it has not yet run.