Day 1: Introduction

Lecture slides

Using GDB

The GNU Debugger, or GDB, is a freely available tool that you will find almost everywhere. Using a debugger, you will be able to stop execution of your program, step through execution, and observe the state of your program. You can also do post-mortem analysis on core dump files, which are left by the operating system when your program suffers a catastrophic crash.

We will use this program for learning gdb:

#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>

#define EXP 6
#define SIZE (1 << EXP)
#define SWAP(a, b) { tmp = a; a = b; b = tmp; }
int comparisons = 0;

/* TUTORIAL: The fill() and init() functions can be ignored. */
/** Fill an array of size n with values between 0 and n. */
void fill(int * array, size_t n) {
    int i;

    for (i = 0; i < n; ++i) {
	array[i] = random() % n;
    }
}

/** Initialize the random number generator using the current time and fill the array. */
void init(int * array, size_t n) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    srandom(tv.tv_sec);
    fill(array, SIZE);
}

/** Partition the array in to values less than and greater to the pivot, and return the middle index. */
int partition(int * array, size_t n, int pivot) {
    int i, tmp;

    SWAP(array[n - 1], array[pivot]);

    int index = 0;
    for (i = 0; i < n - 1; ++i) {
	++comparisons;
	if (array[i] < array[n - 1]) {
	    SWAP(array[i], array[index]);
	    ++index;
	}
    }

    SWAP(array[n - 1], array[index]);

    return index;
}

/** Sort the given array in ascending order. */
void quicksort(int * array, size_t n) {
    /* TUTORIAL: You can see the entire array here by typing "print n" to get the value of n,
       TUTORIAL: followed by "print (int[XXX])*array", where XXX equals the current value of n. */
    int pivot;

    if (n < 2) return;
    pivot = random() % n;
    int middle = partition(array, n, pivot);

    /* TUTORIAL: What happens when you use the step command here? How about the next command? */
    /* NOTE: Incidentally, this step can be parallelized with the SECTIONS and SECTION directives of OpenMP */
    quicksort(array, middle);
    quicksort(array + middle + 1, n - middle - 1);
}

int main(int argc, char ** argv) {
    int i;
    int array[SIZE];

    init(array, SIZE);

    /* TUTORIAL: Consider setting your first breakpoint here to skip past the initialization routine. */
    quicksort(array, SIZE);

    printf("%d comparisons to sort array\n", comparisons);
    printf("average case %d comparisons\n", SIZE * EXP);
    printf("worst case %d comparisons\n", SIZE * SIZE);

    return 0;
}

To invoke gdb, type:

$ gcc -g -o quicksort quicksort.c
$ gdb quicksort

You should investigate the following commands in gdb, and understand how and when they should be applied. Commands generally lie in two categories: controlling execution and inspecting the state of the program. First though, is the list command:

  • list: List source code. You can list a particular function by using “list function-name”. This is useful when using the GDB interface from the command line.

Controlling execution

Breakpoints

A breakpoint is a place in the code where GDB will stop execution. The most simple type of breakpoint unconditionally stops the program every time that point is reached during execution. There are other kinds of breakpoints, such as conditional breakpoints and temporary breakpoints, that we will not cover here.

Running

  • run: Run your program from the beginning. This will run the program until completion, the next breakpoint, or until some error occurs.
  • continue: Continue execution until completion, the next breakpoint, or until some error occurs.
  • step: Execute the next line of code, including entering functions.
  • next: Execute the next line of code, but skip over function calls.

Inspecting state

When a function is called in a program, it pushes down the current program address, function parameters, and local variables for the function on a function call stack. Each entry on this stack is called a stack frame. The stack serves as a space to allocate temporary variables and let the program know how to return from the function. Among other things, the stack can tell us how the program got to its current state when it crosses a breakpoint or crashes.

Stack

  • backtrace: Print the stack trace, from the current function down to the main() function (in C).
  • up and down: Go up and down in the function call stack to examine function parameters and local variables.

Memory

  • print: Print the current state of a given variable or expression. The print command is smart enough to print arrays and structs properly. You can even make function calls as part of the expression.
  • display: Like print, but this produces output every time the program stops. This prints nothing if the expression is not valid.

To see the contents of the array, you will have to cast the pointer as an array so that GDB can display it correctly:

display (int[SIZE])*array

Using Gprof

Compile the program with -pg:

$ gcc -pg -O3 -o pgm pgm.c

When you run the program, a gmon.out file will be created. Then, we run gprof to obtain text output:

$ gprof pgm gmon.out

We will use this program to trying gprof:

#include <stdio.h>
#define MAX 500000

/* This program is from "More Programming Pearls", Jon Bentley, 1988 */

int prime(int n);

int prime(int n) {
  int i;

  for (i= 2; i < n; i++)
    if (n % i == 0)
      return 0;

  return 1;
}

int main() {
  int i;

  for (i= 2; i <= MAX; i++)
    if (prime(i))
      printf("%d is prime\n", i);

  return 0;
}

You can download all the variations using these links: primes.zip

Makefiles

make makes it easy to compile large code bases. A Makefile lets us specify what needs to be compiled and what are the dependencies.

An example of a simple Makefile is below:

CC     = gcc
CFLAGS = -g

all: quicksort 

quicksort: quicksort.c
	$(CC) $(CFLAGS) -o quicksort quicksort.c

clean:
	- rm quicksort *.o *~

In the make directory for today’s lab, there are eight files comprising a hypothetical program. Don’t try to figure out what the program does, as it doesn’t do anything interesting. Instead, write a Makefile for the whole program that separately compiles each .c file and generates a single executable file. Then add a target clean to the Makefile that removes executable and object files, and then add another target main.tgz that creates a tarfile with that name containing all header and source files. The command:

$ tar -czvf <tarfilename> <listoffiles>

will create a compressed tar file containing those files.