TITLE(« All software sucks, be it open-source of proprietary. The only question is what can be done with particular instance of suckage, and that's where having the source matters. -- Al Viro (2004) », __file__) SECTION(«Introduction»)

It's safe to bet that every non-trivial program contains bugs. Bugs in the operating system kernel are often fatal in that they lead to a system crash which requires a reboot. However, thanks to the concept of virtual memory, bugs in applications usually affect neither the operating system nor independent processes which happen to run at the same time. This makes user space programs easier to debug than kernel code because the debugging tools will usually run as a separate process.

We then look at valgrind and gdb, two popular tools which help to locate bugs in application software. valgrind is easy to use but is also limited because it does not alter the target process. On the other hand, gdb is much more powerful but also infamous for being hard to learn. The exercises aim to get the reader started with both tools.

A couple of exercises on gcc, the GNU C compiler, ask the reader to incorporate debugging information into an executable and to see the effect of various diagnostic messages which show up when valid but dubious code is being encountered. Warning messages can be classified into one of two categories. First, there are warnings which are based on static code analysis. These so-called compile-time warnings are printed by the compiler when the executable is being created from its source code. The second approach, called code instrumentation, instructs the compiler to add sanity checks to the executable, along with additional code that prints a warning when one of these checks fails. In contrast to the compile-time warnings, the messages of the second category show up at run-time, and are generated by the compiled program rather than by the compiler.

EXERCISES() SUPPLEMENTS() SUBSECTION(«deref.c»)
	#include 
	#include 
	int main(int argc, char **argv)
	{
		printf("arg has %zu chars\n", strlen(argv[1]));
	}
SUBSECTION(«deref.sh»)
	#!/bin/sh
	./deref
SUBSECTION(«strerror.c»)
	#include "stdio.h"
	#include "stdlib.h"
	#include "string.h"
	#include "assert.h"

	/* print "system error: ", and the error string of a system call number */
	int main(int argc, char **argv)
	{
		unsigned errno, i;
		char *result = malloc(25); /* 5 * 5 */
		/* fail early on errors or if no option is given */
		if (errno && argc == 0)
			exit(0);
		errno = atoi(argv[1]);
		sprintf(result, strerror(errno));
		printf("system error %d: %s\n", errno, result, argc);
	}
SUBSECTION(«print_arg1.c»)
	#include 
	#include 

	static int print_it(char *arg)
	{
		return printf("arg is %d\n", atoi(arg));
	}

	int main(int argc, char **argv)
	{
		return print_it(argv[1]);
	}
SUBSECTION(«ubsan.c»)
	#include 
	#include 
	int main(int argc, char **argv)
	{
		int factor1 = atoi(argv[1]), factor2 = atoi(argv[2]),
			product = factor1 * factor2;
		return printf("%d * %d = %d\n", factor1, factor2, product);
	}