|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Command | Description |
| file | Loads the executable file that is to be debugged |
| kill | Terminates the program that you are currently debugging |
| list | Lists sections of the source code that was used to generate the executable file |
| next | Advances one line of source code in the current function, without stepping into other functions |
| step | Advances one line of source code in the current function, and does step into other functions |
| run | Executes the program that is currently being debugged |
| quit | Terminates gdb |
| watch | Enables you to examine the value of a program variable whenever the value changes |
| break | Sets a breakpoint in the code; this causes the execution of the program to be suspended whenever this point is reached |
| make | Enables you to remake the executable program without quitting out of gdb or using another window |
| shell | Enables you to execute UNIX shell commands without leaving gdb |
The gdb environment supports many of the same command-editing features as do the UNIX shell programs. You can tell gdb to complete unique commands by pressing the Tab key just as you do when you are using bash or tcsh. If what you have typed in
is not unique, you can make gdb print a list of all the commands that match what you have typed in so far by pressing the Tab key again. You can also scroll up and down through the commands that you have entered previously by pressing the up and down arrow
keys.
This section takes you step by step through a sample gdb session. The sample program that is being debugged is quite simple, but it is sufficient to illustrate how gdb is typically used.
We will start by showing a listing of the program that is to be debugged. The program is called greeting and is supposed to display a simple greeting followed by the greeting printed in reverse order.
#include <stdio.h>
main ()
{
void my_print(char *);
void my_print2(char *);
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
void my_print (char *string)
{
printf ("The string is %s\n", string);
}
void my_print2 (char *string)
{
char *string2;
int size, i;
size = strlen (string);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size - i] = string[i];
string2[size+1] = '\0';
printf ("The string printed backward is %s\n", string2);
}
You should compile the preceding program using the gcc command followed by the filename. To rename the generated binary (instead of using the default a.out filename), use the -o option followed by the binary name:
gcc -o greeting greeting.c
The program, when executed, displays the following output:
The string is hello there The string printed backward is
The first line of output comes out correctly, but the second line prints something that was unexpected. We intended the second line of output to be
The string printed backward is ereht olleh
For some reason the my_print2 function is not working properly. Let's take a look at the problem using gdb. First you need to start gdb, specifying the greeting program as the one to debug. You do this by typing the following command:
gdb greeting
Remember that you must compile the greeting program with the compiler debug options turned on, by typing gcc -q -o greeting greeting.c.
If you forget to pass the program to debug as a parameter to gdb, you can load it in after gdb is started by using the file command at the gdb prompt:
(gdb) file greeting
This command will load the greeting executable just as if you had told gdb to load it on the command line.
You can now run greeting by entering the run command from the gdb prompt. When the program is executed from within gdb, the result should resemble the following:
(gdb) run Starting program: /root/greeting The string is hello there The string printed backward is Program exited with code 040
The output of the greeting program is the same as when we executed the program outside of gdb. The question is, why is the backward print not working? To find the problem we can set a breakpoint at the line after the for statement in the my_print2
function. To do this, list the source file by entering the list command at the gdb prompt:
(gdb) list
Pressing Enter by itself at the gdb prompt will repeat the last command that was entered.
The first time you enter the list command, you get output that resembles the following:
1 #include <stdio.h>
2
3 void main ()
4 {
5 void my_print(char *);
6 void my_print2(char *);
7
8 char my_string[] = "hello there";
9
10 my_print (my string)
If you press Enter, gdb will execute the list command again, giving you the following output:
11 my_print2 (my string);
12 {
13
14 void my_print (char *string)
15 {
16 printf ("The string is %s\n", string);
17 }
18
19 void my_print2 (char *string)
20 {
Pressing Enter one more time will list the rest of the greeting program:
21 char *string2;
22 int size, i;
23
24 size = strlen (string);
25 string2 = (char *) malloc (size + 1);
26 for (i = 0; i < size; i++)
27 string2[size - i] = string[i];
28 string2[size+1] = '\0';
29 printf ("The string printed backward is %s\n", string2);
30 }
By listing the file you can see that the place where you want to set the breakpoint is line 27. Now, to set the breakpoint, type the following command at the gdb command prompt:
(gdb) break 27
gdb should now print a response resembling the following:
Breakpoint 1 at 0x8000570: file greeting.c, line 27 (gdb)
Now you can run the program again by typing the run command. This command will generate the following output:
Starting program: /root/greeting The string is hello there Breakpoint 1, my_print2 (string = 0xbffffe14 "hello there") at greeting.c:27 27 string2[size - i]=string[i]
You can see what is actually going wrong with your program by setting a watch to tell you the value of the string2[size - i] variable expression.
To do this, type
(gdb) watch string2[size - i]
gdb will return the following acknowledgment:
Hardware watchpoint 2: string2[size - i]
Now that a watch has been set, gdb will halt the program and display the new value of variable string2[size - i] each time it changes. But because we already have a breakpoint established at the line where string2[size - i] will be updated, two breaks
for each pass through the loop will actually be generated: once for the breakpoint and again for the watch. To eliminate this redundancy, eliminate the breakpoint by typing disable 1 at the gdb prompt.
Breakpoints are reset by disabling the number of the breakpoint assigned to the code line. For example, when we previously typed break 27, breakpoint 1 was assigned to that line of code. So to clear the breakpoint, we disable breakpoint number 1 by typing disable 1.
Now you can step through the execution of the for loop using the cont (short for "continue") command:
(gdb) cont
After the first time through the loop, gdb tells us that string2[size - i] is 'h'. gdb informs you of this by writing the following message on the screen:
Hardware watchpoint 2, string2[size - i] Old value = 0 '\000' New value = 104 'h' my_print2(string = 0xbffffe14 "hello there") at greeting.c:26 26 for (i=0; i<size; i++)
This is the value that you expected. Continuing through the loop several more times reveals similar results. Everything appears to be functioning normally. When you get to the point where i=10, the value of the string2[size - i] expression is equal to
'e', the value of the size - i expression is equal to 1, and the program is at the last character that is to be copied over into the new string.
You can see the current value of a variable or expression at any time from the gdb prompt by using the print EXP command. For example, try print i or print (size - i) while continuing through the loop.
If you continue through the loop one more time, you see that there was not a value assigned to string2[0], which is the first character of the string. Because the malloc function initializes the memory it assigns to null, the first character in string2
is the null character. This explains why nothing was being printed when you tried to print string2.
Now that you have found the problem, it should be quite easy to fix. You must write the code so that the first character going into string2 is being put into string2 at offset size - 1 instead of string2 at offset size. This is because the size of
string2 is 12, but it starts numbering at offset zero. The characters in the string should start at offset 0 and go to offset 10, with offset 11 being reserved for the null character.
There are many ways to modify this code so that it will work. One way is to keep a separate size variable that is one smaller than the real size of the original string. This solution is shown in the following code:
#include <stdio.h>
void main ()
{
void my_print(char *);
void my_print2(char *);
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
void my_print (char *string)
{
printf ("The string is %s\n", string);
}
void my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = '\0';
printf ("The string printed backward is %s\n", string2);
}
The Red Hat Linux distribution includes a number of C development tools that have not yet been described. This section describes many of these additional tools and their typical uses.
xxgdb is an X Window systembased graphical user interface to gdb. All of the features that exist in the command-line version of gdb are present in xxgdb. xxgdb enables you to perform many of the most commonly used gdb commands by pressing buttons
instead of typing in commands. It also graphically represents where you have placed breakpoints.
You can start xxgdb by typing the following into an Xterm window.
xxgdb
When you initiate xxgdb you can specify any of the command-line options that were available with gdb. xxgdb also has some of its own command-line options. These are described in Table 27.2.
| Option | Description |
| =db_name | Specifies the name of the debugger to be used. The default is gdb. |
| =db_prompt | Specifies the debugger prompt. The default is gdb. |
| =gdbinit | Specifies the filename of the initial gdb command file. The default is .gdbinit. |
| =nx | Tells xxgdb not to execute the .gdbinit file. |
| =bigicon | Uses a large icon size for the xxgdb icon. |
When you start xxgdb, a window, depicted in Figure 27.1, opens on your screen.
Figure 27.1. The xxgdb debugging window.
The bottom pane of the window contains a message that is similar to the one displayed on the screen when you started the command-line version of gdb. Use this pane to enter commands to the xxgdb debugger just as you would in gdb. To give the pane focus,
left-click anywhere in its region and type:
file greeting
This should open the file for debugging and display its source code in the upper pane of the window, as shown in Figure 27.2.
Figure 27.2. The xxgdb window with debug source open.
You could have accomplished the same thing by using the File button shown in the center of the window. In addition, you can use the run, break, and cont buttons much in the same way that we have used them before in gdb, only now graphically. To watch a
variable (as long as it is within context) you can use the display button.
Try experimenting with the different features that this program provides and refer to the xxgdb and gdb manual pages for information on additional capabilities these tools provide.
calls is a program that is not included on the Linux CD-ROM accompanying this guide, but you can obtain a copy from the sunsite FTP site under the directory /pub/Linux/devel/lang/c/calls.tar.Z. Some older CD-ROM distributions of Linux include this file.
Because it is a useful tool, we will cover it here. If you think it will be of use to you, obtain a copy from an FTP or BBS site or another CD-ROM. calls runs the GCC preprocessor on the files that are passed to it on the command line, and displays a
function call tree for the functions that are in those files.
To install calls on your system, perform the following steps while you are logged in as root:
- FTP the file sunsite.unc.edu/pub/Linux/devel/lang/c/calls.tar.z.
- Uncompress and untar the file.
- cd into the calls subdirectory that was created by untarring the file.
- Move the file named calls to the /usr/bin directory.
- Move the file named calls.1 to the /usr/man/man1 directory.
This will install the calls program and man page on your system.
When calls prints out the call trace, it includes the filename in which the function was found in brackets after the function name:
main [program.c]
If the function was not in one of the files that was passed to calls, it does not know where that function lives and prints only the function name:
printf
calls also makes note of recursive and static functions in its output. Recursive functions are represented in the following way:
fact <<< recursive in factorial.c >>>
Static functions are represented as follows:
total [static in calculate.c]
As an example, assume that you executed calls with the following program as input:
##include <stdio.h>
void main ()
{
void my_print(char *);
void my_print2(char *);
char my_string[] = "hello there";
my_print (my_string);
my_print2(my_string);
}
void my_print (char *string)
{
printf ("The string is %s\n", string);
}
void my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = '\0';
printf ("The string printed backward is %s\n", string2);
}
This would generate the following output:
1 main [greeting.c] 2 my_print [greeting.c] 3 printf 4 my_print2 [greeting.c] 5 strlen 6 malloc 7 printf
calls recognizes a number of command-line options that enable you to specify the appearance of the output and what function calls get displayed. For more information on these command-line options, refer to the calls manual page or type calls -h at the
command line.
cproto is a program included on this guide's CD-ROM. cproto reads in C source files and automatically generates function prototypes for all of the functions. Using cproto saves you from having to type in a function definition for all of the functions
that you have written in your programs.
If you ran the following code through the cproto program
#include <stdio.h>
void main ()
{
char my_string[] = "hello there";
my_print (my_string);
my_print2(my_string);
}
void my_print (char *string)
{
printf ("The string is %s\n", *string);
}
void my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = '\0';
printf ("The string printed backward is %s\n", string2);
}
you would get the following output:
/* greeting.c */ void main(void); void my_print(char *string); void my_print2(char *string);
This output could be redirected to an include file and used to define the prototypes for all of the functions.
The indent utility is another programming utility that is included with Linux. This program, in its simplest form, reformats or pretty prints your C code so that it is consistently indented and all opening and closing braces are represented
consistently. There are numerous options that enable you to specify how you want indent to format your code. For information on these options, refer to the indent manual page or type indent -h at the command line.
The following example shows the default output of the indent program.
C code before running indent:
#include <stdio.h>
void main () {
void my_print(char *);
void my_print2(char *);
char my_string[] = "hello there";
my_print (my_string);
my_print2(my_string); }
void my_print (char *string)
{
printf ("The string is %s\n", *string);
}
void my_print2 (char *string) {
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = '\0';
printf ("The string printed backward is %s\n", string2);
}
C code after running indent:
#include <stdio.h>
void main ()
{
void my_print(char *);
void my_print2(char *);
char my_string[] = "hello there";
my_print (my_string);
my_print2 (my_string);
}
void my_print (char *string)
{
printf ("The string is %s\n", *string);
}
void my_print2 (char *string)
{
char *string2;
int size, size2, i;
size = strlen (string);
size2 = size -1;
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size2 - i] = string[i];
string2[size] = '\0';
printf ("The string printed backward is %s\n", string2);
}
Indent does not change how the code compiles; it just changes how the source code looks. It makes the code more readable, which is always a good thing.
gprof is a program that is installed in the /usr/bin directory on your Linux system. It allows you to profile C, Pascal, or Fortran programs to determine where most of the execution time is being spent.
gprof will tell you how many times each function used by your program is called, and also the percentage of the total execution time the program spent on each function. This information can be very useful if you are trying to improve the performance of
a program.
To use gprof on one of your C programs, you must compile the program using gcc's -pg option. This causes the program to create a file called gmon.out each time it is executed. gprof uses the gmon.out file to generate the profile information.
After you run your program and it has created the gmon.out file, you can get its profile by entering the following command:
gprof <program_name>
The program_name parameter is the name of the program that created the gmon.out file.
The profile data that gprof displays to the screen is quite large. If you want to examine this data, you should either redirect gprof's output to a file or use the more or less pipes.
f2c and p2c are two source code conversion programs. f2c converts FORTRAN77 code into either C or C++ code, and p2c converts Pascal code into C code. Both are included in the Linux installation when you install GCC.
If you have some code that has been written using either FORTRAN77 or Pascal that you want to rewrite in C, f2c and p2c can prove to be very useful programs. Both programs produce C code that can typically be compiled directly by gcc without any human
intervention.
If you are converting small, straightforward FORTRAN77 or Pascal programs, you should be able to get away with using f2c or p2c without any options. If you are converting very large programs consisting of many files, you will probably have to use some
of the command-line options that are provided by the conversion program that you are using.
To invoke f2c on a FORTRAN program, enter the following command:
f2c my_fortranprog.f
f2c requires that the program being converted has either a .f or a .F extension.
To convert a Pascal program to C, enter the following command:
p2c my_pascalprogram.pas
Both of these commands create C source code files that have the same name as the original file, except with a .c extension instead of .f or .pas.
For more information on the specific conversion options that are available with f2c or p2c, refer to their respective man pages.
This chapter introduced the GNU C compiler and many of the options that you will typically use when you compile C code. It also introduced the concepts behind debugging code with the GNU debugger, and illustrated the usefulness of some of the other C
utility programs that are either included on the Linux CD-ROM, or available via FTP from sunsite.unc.edu.
If you will be writing C code, the time that you spend learning how to use gdb and some of the other tools mentioned in this chapter will be more than worth the eventual time-saving that you will gain.
The next chapter will discuss many of the same topics, but with a focus on C++ development rather than C development.