Debugging
(Part 1)
Aarti Gupta
The material for this lecture is drawn, in part, from
The Practice of Programming (Kernighan & Pike) Chapter 5
1
For Your Amusement
“When debugging, novices insert corrective code;; experts
remove defective code.”
-- Richard Pattis
“If debugging is the act of removing errors from code, what's
programming?”
-- Tom Gilb
“Debugging is twice as hard as writing the code in the first
place. Therefore, if you write the code as cleverly as
possible, you are, by definition, not smart enough to debug
it.”
-- Brian Kernighan
2
For Your Amusement
The first computer bug
(found in the Harvard Mark II computer) 3
“Programming in the Large” Steps
Design & Implement
• Program & programming style (done)
• Common data structures and algorithms
• Modularity
• Building techniques & tools (done)
Test
• Testing techniques (done)
Debug
• Debugging techniques & tools <-- we are here
Maintain
• Performance improvement techniques & tools
4
Goals of this Lecture
Help you learn about:
• Strategies and tools for debugging your code
Why?
• Debugging large programs can be difficult
• A power programmer knows a wide variety of debugging strategies
• A power programmer knows about tools that facilitate debugging
• Debuggers
• Version control systems
5
Testing vs. Debugging
Testing
• What should I do to try to break my program?
Debugging
• What should I do to try to fix my program?
6
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for familiar bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
7
Understand Error Messages
Debugging at build-time is easier than debugging at run-
time, if and only if you…
Understand the error messages!
#include <stdioo.h>
/* Print "hello, world" to stdout and What are the
return 0.
int main(void)
errors? (No
{ printf("hello, world\n"); fair looking at
return 0; the next slide!)
}
8
Understand Error Messages
#include <stdioo.h>
/* Print "hello, world" to stdout and
return 0. Which tool
int main(void) (preprocessor,
{ printf("hello, world\n"); compiler, or
return 0;
linker) reports
}
the error(s)?
$ gcc217 hello.c -o hello
hello.c:1:20: error: stdioo.h: No such file or
directory
hello.c:2:1: error: unterminated comment
hello.c:7: warning: ISO C forbids an empty
translation unit
9
Understand Error Messages
#include <stdio.h>
/* Print "hello, world" to stdout and
return 0. */ What are the
int main(void) errors? (No
{ printf("hello, world\n") fair looking at
return 0;
} the next slide!)
10
Understand Error Messages
#include <stdio.h>
/* Print "hello, world" to stdout and
return 0. */
Which tool
int main(void) (preprocessor,
{ printf("hello, world\n") compiler, or
return 0; linker) reports
}
the error?
$ gcc217 hello.c -o hello
hello.c: In function 'main':
hello.c:6: error: expected ';' before 'return'
11
Understand Error Messages
#include <stdio.h>
/* Print "hello, world" to stdout and
return 0. */ What are the
int main(void) errors? (No
{ prinf("hello, world\n"); fair looking at
return 0;
} the next slide!)
12
Understand Error Messages
#include <stdio.h>
/* Print "hello, world" to stdout and Which tool
return 0. */ (preprocessor,
int main(void)
{ prinf("hello, world\n")
compiler, or
return 0; linker) reports
} the error?
$ gcc217 hello.c -o hello
hello.c: In function 'main':
hello.c:5: warning: implicit declaration of function
'prinf'
/tmp/ccLSPMTR.o: In function `main':
hello.c:(.text+0x1a): undefined reference to `prinf'
collect2: ld returned 1 exit status
13
Understand Error Messages
#include <stdio.h>
#include <stdlib.h>
enum StateType What are the
{ STATE_REGULAR, errors? (No
STATE_INWORD fair looking at
}
int main(void) the next slide!)
{ printf("just hanging around\n");
return EXIT_SUCCESS;
}
14
Understand Error Messages
#include <stdio.h>
#include <stdlib.h>
enum StateType
{ STATE_REGULAR, What does
STATE_INWORD this error
} message even
int main(void)
{ printf("just hanging around\n"); mean?
return EXIT_SUCCESS;
}
$ gcc217 hello.c -o hello
hello.c:7: error: two or more data types in declaration specifiers
hello.c:7: warning: return type of 'main' is not 'int'
15
Understand Error Messages
Caveats concerning error messages
• Line # in error message may be approximate
• Error message may seem nonsensical
• Compiler may not report the real error
Tips for eliminating error messages
• Clarity facilitates debugging
• Make sure code is indented properly
• Look for missing semicolons
• At ends of structure type definitions
• At ends of function declarations
• Work incrementally
• Start at first error message
• Fix, rebuild, repeat
16
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for familiar bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
17
Think Before Writing
Inappropriate changes could make matters worse, so…
Think before changing your code
• Explain the code to:
• Yourself
• Someone else
• A Teddy bear?
• Do experiments
• But make sure they’re disciplined
18
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for common bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
19
Look for Common Bugs
Some of our favorites:
int i;
…
switch (i) scanf("%d", i);
{ case 0:
… char c;
break; …
case 1: c = getchar();
…
case 2:
… while (c = getchar() != EOF)
} …
if (i = 5)
…
What are
the
if (5 < i < 10) if (i & j)
… … errors?
20
Look for Common Bugs
Some of our favorites:
for (i = 0; i < 10; i++)
{ for (j = 0; j < 10; i++)
{ ...
}
}
What are
for (i = 0; i < 10; i++) the
{ for (j = 10; j >= 0; j++) errors?
{ ...
}
}
21
Look for Common Bugs
Some of our favorites:
{ int i;
…
i = 5; What value is
if (something) written if this
{ int i;
…
statement is
i = 6; present? Absent?
…
}
…
printf("%d\n", i);
…
}
22
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for common bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
23
Divide and Conquer
Divide and conquer: To debug a program…
• Incrementally find smallest input file that illustrates the bug
• Approach 1: Remove input
• Start with file
• Incrementally remove lines
until bug disappears
• Examine most-recently-removed lines
• Approach 2: Add input
• Start with small subset of file
• Incrementally add lines
until bug appears
• Examine most-recently-added lines
24
Divide and Conquer
Divide and conquer: To debug a module…
• Incrementally find smallest client code subset that illustrates the
bug
• Approach 1: Remove code
• Start with test client
• Incrementally remove lines of code until bug disappears
• Examine most-recently-removed lines
• Approach 2: Add code
• Start with minimal client
• Incrementally add lines of test client until bug appears
• Examine most-recently-added lines
25
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for common bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
26
Add More Internal Tests
(5) Add more internal tests
• Internal tests help find bugs (see “Testing” lecture)
• Internal test also can help eliminate bugs
• Validating parameters & checking invariants
can eliminate some functions from the bug hunt
27
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for common bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
28
Display Output
Write values of important variables at critical spots
• Poor: stdout is buffered;;
printf("%d", keyvariable); program may crash
before output appears
• Maybe better: Printing '\n' flushes
the stdout buffer, but
printf("%d\n", keyvariable);
not if stdout is
redirected to a file
• Better:
Call fflush() to flush
printf("%d", keyvariable); stdout buffer
fflush(stdout);
explicitly
29
Display Output
Write debugging
output to stderr;;
debugging output
• Maybe even better:
can be separated
fprintf(stderr, "%d", keyvariable); from normal output
via redirection
• Maybe better still: Bonus: stderr is
unbuffered
FILE *fp = fopen("logfile", "w");
…
fprintf(fp, "%d", keyvariable); Write to a log file
fflush(fp);
30
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for common bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
31
Use a Debugger
Use a debugger
• Alternative to displaying output
32
The GDB Debugger
GNU Debugger
• Part of the GNU development environment
• Integrated with Emacs editor
• Allows user to:
• Run program
• Set breakpoints
• Step through code one line at a time
• Examine values of variables during run
• Etc.
For details see precept tutorial, precept reference sheet,
Appendix 1
33
Agenda
(1) Understand error messages
(2) Think before writing
(3) Look for common bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
(8) Focus on recent changes
34
Focus on Recent Changes
Focus on recent changes
• Corollary: Debug now, not later
Difficult: Easier:
(1) Compose entire program (1) Compose a little
(2) Test entire program (2) Test a little
(3) Debug entire program (3) Debug a little
(4) Compose a little
(5) Test a little
(6) Debug a little
…
35
Focus on Recent Changes
Focus on recent change (cont.)
• Corollary: Maintain old versions
Difficult: Easier:
(1) Change code (1) Backup current version
(2) Note new bug (2) Change code
(3) Try to remember what (3) Note new bug
changed since last (4) Compare code with
version last version to
determine what changed
36
Maintaining Old Versions
To maintain old versions…
Approach 1: Manually copy project directory
…
$ mkdir myproject
$ cd myproject
Create project files here.
$ cd ..
$ cp –r myproject myprojectDateTime
$ cd myproject
Continue creating project files here.
…
37
Maintaining Old Versions
Approach 2: Use the Revision Control System (RCS)
• A simple version control system
• Provided with many Linux distributions
• Available on CourseLab
• Allows programmer to:
• Check-in source code files from working copy to repository
• RCS saves old versions
• Check-out source code files from repository to working copy
• Can retrieve old versions
• Appropriate for one-developer projects
Not required for COS 217, but good to know!
See Appendix 2 for details
38
Maintaining Old Versions
Approach 3: Use CVS, Subversion, Git, …
• High-powered version control systems
• Appropriate for multi-developer projects
• Allow repositories to be shared
Beyond our scope, but good to know!
39
Summary
General debugging strategies and tools:
(1) Understand error messages
(2) Think before writing
(3) Look for common bugs
(4) Divide and conquer
(5) Add more internal tests
(6) Display output
(7) Use a debugger
• Use GDB!!!
(8) Focus on recent changes
• Consider using RCS, etc.
40
Appendix 1: Using GDB
An example program Euclid’s algorithm;;
File testintmath.c: Don’t be concerned
with details
#include <stdio.h>
…
int gcd(int i, int j) int main(void)
{ int temp; { int iGcd;
while (j != 0) int iLcm;
{ temp = i % j; iGcd = gcd(8, 12);
i = j; iLcm = lcm(8, 12);
j = temp; printf("%d %d\n", iGcd, iLcm);
} return 0;
return i; }
}
The program is correct
int lcm(int i, int j)
{ return (i / gcd(i, j)) * j;
} But let’s pretend it has a
… runtime error in gcd()…
41
Appendix 1: Using GDB
General GDB strategy:
• Execute the program to the point of interest
• Use breakpoints and stepping to do that
• Examine the values of variables at that point
42
Appendix 1: Using GDB
Typical steps for using GDB:
(a) Build with –g
gcc217 –g testintmath.c –o testintmath
• Adds extra information to executable file that GDB uses
(b) Run Emacs, with no arguments
emacs
(c) Run GDB on executable file from within Emacs
<Esc key> x gdb <Enter key> testintmath <Enter key>
(d) Set breakpoints, as desired
break main
• GDB sets a breakpoint at the first executable line of main()
break gcd
• GDB sets a breakpoint at the first executable line of gcd()
43
Appendix 1: Using GDB
Typical steps for using GDB (cont.):
(e) Run the program
run
• GDB stops at the breakpoint in main()
• Emacs opens window showing source code
• Emacs highlights line that is to be executed next
continue
• GDB stops at the breakpoint in gcd()
• Emacs highlights line that is to be executed next
(f) Step through the program, as desired
step (repeatedly)
• GDB executes the next line (repeatedly)
• Note: When next line is a call of one of your functions:
• step command steps into the function
• next command steps over the function, that is, executes the next line
without stepping into the function
44
Appendix 1: Using GDB
Typical steps for using GDB (cont.):
(g) Examine variables, as desired
print i
print j
print temp
• GDB prints the value of each variable
(h) Examine the function call stack, if desired
where
• GBB prints the function call stack
• Useful for diagnosing crash in large program
(i) Exit gdb
quit
(j) Exit Emacs
<Ctrl-x key> <Ctrl-c key>
45
Appendix 1: Using GDB
GDB can do much more:
• Handle command-line arguments
run arg1 arg2
• Handle redirection of stdin, stdout, stderr
run < somefile > someotherfile
• Print values of expressions
• Break conditionally
• Etc.
46
Appendix 2: Using RCS
Typical steps for using RCS:
(a) Create project directory, as usual
mkdir helloproj
cd helloproj
(b) Create RCS directory in project directory
mkdir RCS
• RCS will store its repository in that directory
(c) Create source code files in project directory
emacs hello.c …
(d) Check in
ci hello.c
• Adds file to RCS repository
• Deletes local copy (don’t panic!)
• Can provide description of file (1st time)
• Can provide log message, typically describing changes
47
Appendix 2: Using RCS
Typical steps for using RCS (cont.):
(e) Check out most recent version for reading
co hello.c
• Copies file from repository to project directory
• File in project directory has read-only permissions
(f) Check out most recent version for reading/writing
co –l hello.c
• Copies file from repository to project directory
• File in project directory has read/write permissions
(g) List versions in repository
rlog hello.c
• Shows versions of file, by number (1.1, 1.2, etc.), with
descriptions
(h) Check out a specified version
co –l –rversionnumber hello.c
48
Appendix 2: Using RCS
RCS can do much more:
• Merge versions of files
• Maintain distinct development branches
• Place descriptions in code as comments
• Assign symbolic names to versions
• Etc.
Recommendation: Use RCS
• ci and co can become automatic!
49