Mac OS X Unleashed

Mac OS X Unleashed

By John Ray and William C. Ray

Using the gdb Debugger

If thinking about the problem, trying to do things as correctly as possible, and examining all the debugging information yields only an application that doesn't run correctly, you still have the option of digging around in the code. Thankfully, Apple has provided the GNU debugger, gdb, as part of the development tools. The GNU debugger is to the Unix debugging world what the GNU compiler is to the Unix programming world—a flexible, community-supported, de facto standard for programmer productivity.

The easiest way to explain how to use gdb is to demonstrate its use. The program has copious online help, as well as man pages, and an INFO section available through the emacs M-x info command. Before the demonstration however, Table 17.1 contains a summary of command-line options and common internal commands.

Table 17.1. The Command Documentation Table for the gdb Debugger

gdb GNU debugger
gdb [-help] [-nx] [-q] [-batch] [-cd=<dir>] [-f] [-b <bps>]
[-tty=<dev>] [-s <symfile>] [-e <prog>] [-se <prog>] [-c <core>] [-x
<cmds>] [-d <dir>] [<prog> [<core> | <procID>]
gdb can be used to debug programs written in C, C++, and Modula-2.
Arguments other than options specify an executable file and a core file or process ID. The fist argument encountered with no associated option flag is equivalent to the -se option; the second, if any, is equivalent to the -c option, if it is a file. Options and command-line arguments are processed in sequential order. The order makes a difference when the -x option is specified.
-help  
-h Lists all options with brief explanations.
-symbols=< file >  
-s < file > Reads symbol table from file < file >.
-write Enables writing into executable and core files.
-exec=< file >  
-e < file > ses < file > as the executable file to execute when appropriate, and for examining pure data in conjunction with a core dump.
-se=< file > Reads symbol table from < file > and uses it as the executable file.
-core=< file >  
-c < file > Uses < file > as a core dump to examine.
-command=< file >  
-x < file > Executes gdb commands from < file >.
-directory=< directory >  
-d < directory > Adds < directory > to the path to search for source files.
-nx  
-n Does not execute commands from any .gdbinit files. Normally, commands in these files are executed after all the command options and arguments have been processed.
-quiet  
-q Quiet mode. Does not print the introductory and copyright messages. Also suppresses them in batch mode.
-batch Batch mode. Exits with status 0 after processing all the command files associated with the -x option (and .gdbinit, if not inhibited). Exits with nonzero status if an error occurs in executing the gdb commands in the command files.
-cd=< directory > Runs gdb using < directory > as the working directory rather than using the current directory as the working directory.
-fullname  
-f Outputs information used by emacs-gdb interface.
-b < bps > Sets the line speed (baud rate or bits per second) of any serial interface used by gdb for remote debugging.
-tty=< device > Runs using < device > for your program's standard input and output.
These are some of the more frequently needed gdb commands:
break [< file >]< function > Sets a breakpoint at < function > (in < file >).
run [< arglist >] Starts your program (with < arglist >, if specified).
bt Backtrace. Displays the program stack.
print < expr > Displays the value of an expression.
c Continues running your program (after stopping, such as at a breakpoint).
next Executes the next program line (after stopping); steps over any function calls in the line.
step Executes the next program line (after stopping); steps into any function calls in the line.
help [< name >] Sows information about gdb command < name >, or general information about using gdb.
quit Exits gdb.

To use gdb, you first need something on which to use it. Type in the little program shown in Listing 17.1, just as it appears here. Name the file addme.c.

Example 17.1. The Source for the addme.c Demo C Program/* addme.c

A really silly C demo program */
/* 990325 WCR                              */
/* Usage is <progname> <filename>          */

#include <stdio.h>

int addem(a,b)
int a, b;
{
  return a+b;
}

void main(argc,argv)
int argc;
char *argv[];
{
  int i;
  char infilename[8];
  int j;
  FILE *infile;
  char number[100];
  char *infilename2=infilename;
  strcpy(infilename2,argv[1]);
  i=0; j=0;
  infile = fopen(infilename2,"r");

  if(infile==NULL)
  {
     printf("couldn't open file %s please try again\n",infilename2);
     exit(1);
  }

  i=0;
  while (fgets(number,90,infile) != '\0')
  {
     sscanf(number,"%d",&j);
     i=addem(i,j);
  }
  printf("Your total is %d\n",i);
  exit(0);
}

This simple little C program will take a list of integers from a file, one per line, and add them together. So that you'll have a file to work from, create a file named numbers with the following contents:

1
2
13
15

Make sure that there are no blank lines above or below the data.

Also create a file with a very long name, such as supercalifradgilisti c zowie, and put the same data in it.

Note there's a bit of trickery involved in the way this code is written that's specifically there to generate an error. Even though there are a few errors in this code, some systems are sloppy enough with memory management that the program might run intermittently. Also, if you rearrange the definition of the variables i and j, you decrease the likelihood of a crash. Weird, huh?

So, let's see what we have. Time to compile the program. We don't have a makefile, so we'll have to do it by hand. Issue the command:

cc -g -o addemup addme.c

After a few seconds, your machine should return you to a command line. The compiler should respond with a warning similar to the following:

addme.c: In function `main':
addme.c:14: warning: return type of `main' is not `int'

It should return you to the command line. If it does anything else, for instance outputs

addme.c: In function `main':
addme.c:15: parse error before `char'
addme.c:23: subscripted value is neither array nor pointer

that means you've typed the program in incorrectly. Specifically, if you got this error, in all likelihood you forgot the semicolon after the line that says int argc;. The warning is just that: a warning, not an error. The most recent revision of the C programming language has a preference for a particular return type for the main program, and the compiler is just being pedantic.

After you get the program to compile cleanly with no errors you're ready for the next step—trying it out. Issue the command ./addemup and see what happens. Note that the command is addemup, not something related to addme. I could actually have named it anything I wanted, simply by changing the -o addemup part of the cc command. If you don't specify any output filename, cc will name the output file a.out by default. Also, just so you know, the -g flag tells the compiler to turn on the debugging output. This slows the program, but it gives the debugger important information.

/addemup
Bus Error

Well, that doesn't sound good. What could be wrong? You can probably figure it out just by looking at the code at this point, but on a more complicated program that would be impossible. Instead, let's start the gdb debugger and take a look.

racer-x testaddme 274% gdb ./addemup
GNU gdb 5.0-20001113 (Apple version gdb-186.1) (Sun Feb 18 01:18:32 GMT 2001) (UI_OUT)
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "powerpc-apple-macos10".
Reading symbols for shared libraries .. done
(gdb)

Okay, we're at a prompt. What do we do? The gdb debugger actually has a rather complete selection of online help available. To access the help system, simply enter the command help.

(gdb) help
List of classes of commands:

running — Running the program
stack — Examining the stack
data — Examining data
breakpoints — Making program stop at certain points
files — Specifying and examining files
status — Status inquiries
support — Support facilities
user-defined — User-defined commands
aliases — Aliases of other commands
obscure — Obscure features
internals — Maintenance commands

Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)

I'll leave some of the interesting items here for you to explore, rather than walk you through them. Right now, let's get back to debugging our program. To start the program, simply issue the command r.

(gdb) r
Starting program: /priv/home/ray/testaddme/./addemup
[Switching to thread 1 (process 390 thread 0x1903)]

Program received signal EXC_BAD_ACCESS, Could not access memory.
0x700047d4 in strcpy ()
(gdb)

So, gdb knows something. Not a very intelligible something at this point, but something none the less. Let's see whether it can be a bit more informative.

(gdb) where
#0  0x700047d4 in strcpy ()
#1  0x00001d18 in main (argc=1, argv=0xbffffb00) at addme.c:23
#2  0x00001bf4 in _start ()
#3  0x00001a34 in start ()
#4  0x00000000 in ?? ()
(gdb)

gdb says the program broke in a procedure named strcpy, which was called from a procedure named main, in line 23 of our file addme.c. The start(), and ??() calls are OS X and gdb initializing and starting the program. Let's take a look at this region of the code.

(gdb) l 23
18        char infilename[8];
19        int j;
20        FILE *infile;
21        char number[100];
22        char *infilename2=&infilename;
23        strcpy(infilename2,argv[1]);
24        i=0; j=0;
25        infile = fopen(infilename2,"r");
26
27        if(infile==NULL)
(gdb)

Line 23 has a function strcpy on it. The debugger seems to be on to something here. Let's set a breakpoint (a place we want the program to stop running and wait for us) at line 23 and see what happens.

(gdb) b 23
Breakpoint 1 at 0x2320: file addme.c, line 23.
(gdb)

So far, so good. Now let's run the program again and see where this takes us.

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /priv/home/ray/testaddme/./addemup
[Switching to thread 1 (process 395 thread 0x1a07)]


Breakpoint 1, main (argc=1, argv=0xf7fff744) at addme.c:23
23        strcpy(infilename2,argv[1]);
(gdb)

Note that gdb asked me whether I wanted to restart from the beginning and I told it to go ahead. Now it has run up to our breakpoint and is waiting for me to do something. Even if I don't know quite what strcpy does, there's still something obviously wrong with this line. I know I've got a variable named infilename2 and a funny variable named argv[1]. Let's see what gdb has to say about them.

(gdb) p infilename2
$1 = 0xbffff99c "L\000\000@"
(gdb)

The $1 indicates that it's telling us about the first variable we asked about. The 0xbfff99c is the memory location where it's stored—don't be surprised if yours is different. The L\000\000@ is the current contents of that memory, which currently isn't too informative. (Don't be surprised if yours has something else in whatever memory location shows up on your machine.) What can we tell about this argv[1]?

(gdb) p argv[1]
$2 = 0xbffffba9 0x0
(gdb)

Hmmm… 0x0 is a hexadecimal 0, or NULL in the C world. Examining the code again certainly suggests that something useful should be happening here. It looks like infilename2 gets used to open a file in just a few lines, and neither L\000\000@ nor NULL looks promising as a filename. Nulls get used in C, but frequently they're signs of a problem, so let's think about this.

The program is trying to do something with a variable named argv[1]. The only other place this variable (argv) appears is in the main statement, the statement that starts off the actual program execution. It certainly looks like there should be something other than a NULL here. Wait a minute, what did it say in the comments at the top? It said I needed to give it a filename! I didn't give it a filename, and it's trying to copy something that doesn't exist to get one. Aren't programmers supposed to check for that?

Let's see if I'm right. I'll rerun the program with a filename this time.

(gdb) r numbers
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /priv/home/ray/testaddme/./addemup numbers
[Switching to thread 1 (process 405 thread 0x250b)]

Breakpoint 1, main (argc=2, argv=0xf7fff73c) at addme.c:23
23        strcpy(infilename2,argv[1]);
(gdb)

I started it over, but I forgot to turn off my breakpoint. Still, this is a good opportunity for me to check to see whether I was right.

(gdb) p infilename2
$3 = 0xf7fff200 "\000\000\200\000\000\000 "
(gdb)

That's just as useless as before.

(gdb) p argv[1]
$4 = 0xf7fff80f "numbers"
(gdb)

Now we're getting somewhere! If we remember to give it a filename, it actually gets one! To continue past the breakpoint, I can enter c.

(gdb) c
Continuing.
Your total is 31

Program exited normally.
(gdb)

The program now does exactly what it should. If I'd like to test it again without stopping at the breakpoint, I can delete the breakpoint and run it again.

(gdb) d 1
(gdb) r
Starting program: /priv/home/ray/testaddme/./addemup numbers
[Switching to thread 1 (process 445 thread 0x2721)]

Your total is 31

Program exited normally.
(gdb)

The command d 1 deletes breakpoint 1 (you can have multiples if you need them). Note that I didn't have to give it the command-line argument numbers this time when I hit r because it conveniently remembered command-line arguments between runs. As you can see, it runs properly to completion.

Quitting gdb with the quit command and trying it on the command line produces the same results.

racer-x testaddme 286% ./addemup numbers
Your total is 31
racer-x testaddme 287%

Now let's see whether we can demonstrate another type of error. Do you still remember what your very long filename is? Try using that filename instead of numbers and see what happens.

racer-x testaddme 287% ./addemup supercalifragilisticzowie
couldn't open file supercalifra please try again
racer-x testaddme 288%

Huh? I didn't call it supercalifra. Something happened to my filename. Time to break out gdb again and have another look.

gdb ./addemup
GNU gdb 5.0-20001113 (Apple version gdb-186.1) (Sun Feb 18 01:18:32 GMT 2001) (UI_OUT)
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "powerpc-apple-macos10".
Reading symbols for shared libraries .. done
(gdb)  r supercalifragilisticzowie
Starting program: /priv/home/ray/testaddme/./addemup supercalifragilisticzowie
[Switching to thread 1 (process 551 thread 0x351b)]

couldn't open file supercalifra please try again

Program exited with code 01.
(gdb)

Basically, it says the same thing. There must be something more we can find out, though. Let's look at the code and see if we can figure out where that weird truncation came from.

(gdb) l
14      int argc;
15      char *argv[];
16      {
17        int i;
18        char infilename[8];
19        int j;
20        FILE *infile;
21        char number[100];
22        char *infilename2=infilename;
23        strcpy(infilename2,argv[1]);
(gdb)
24        i=0; j=0;
25        infile = fopen(infilename2,"r");
26
27        if(infile==NULL)
28        {
29           printf("couldn't open file %s please try again\n",infilename2);
30           exit(1);
31        }
32
33        i=0;
(gdb)

Line 29 seems to be where the error message is coming from. Let's set a breakpoint there and see what happens.

(gdb) b 29
Breakpoint 1 at 0x2374: file addme.c, line 29.
(gdb) r
Starting program: /priv/home/ray/testaddme/./addemup supercalifragilisticzowie
[Switching to thread 1 (process 583 thread 0x291b)]


Breakpoint 1, main (argc=2, argv=0xf7fff72c) at addme.c:29
29           printf("couldn't open file %s please try again\n",infilename2);
(gdb)

We're at our breakpoint. infilename2 is supposed to be supercalifragilisti c zowie, and it is

(gdb) p infilename2
$1 = 0xbffff99c "supercalifra"
(gdb)

Something's very wrong here! Time to back up to our trusty breakpoint at line 23 and watch what happens from the top down.

(gdb) r
Starting program: /priv/home/ray/testaddme/./addemup supercalifragilisticzowie

Breakpoint 1, main (argc=2, argv=0xf7fff72c) at addme.c:23
23        strcpy(infilename2,argv[1]);
(gdb) p argv[1]
$1 = 0xf7fff7ff "supercalifragilisticzowie"
(gdb)

So, the previous culprit isn't a problem here.

(gdb) p infilename2
$2 = 0xbffff99c "\000\000\200\000\000\000 "
(gdb)

There's nothing interesting there. Let's see what happens on the next line—use the gdb command n to step to the next line. When you step to the next line, this line executes, so you should expect to see the results of that strcpy after stepping forward.

(gdb) n
24        i=0; j=0;
(gdb) p infilename2
$1 = 0xf7fff6b0 "supercalifragilisticzowie"
(gdb)

As expected, infilename2 contains our atrociously long filename. Nothing wrong here, but by the time it hit line 29, it was broken, so let's step forward again and see what happens.

(gdb) n
25        infile = fopen(infilename2,"r");
(gdb) p
$3 = 0xf7fff6b0 "supercalifra"
(gdb)

Wait a minute! Now it's wrong! What happened? All that the program did was assign both the variables i and j to be zero, and somehow it affected infilename2. You wouldn't think this could happen, variables just changing their values willy-nilly.

In fact, if the program were written properly, this wouldn't happen. As a non-programmer, this is where you usually give up. That isn't to say that the exercise has been useless. With this information, you can more easily explain to the author or online support community what problems you've observed, so they can fix it more easily and quickly. Program authors hate it when they get bug reports that say, "it didn't work." This doesn't mean anything to them because if they could duplicate the problem on their end, they'd probably have found and fixed it already.

By taking these extra steps, the information you can provide about the program's problems can mean the difference between a fix that takes a few minutes to appear and a fix that never appears.

+ Share This