Advertisment

Beginner’s Guide to Debugging

author-image
PCQ Bureau
New Update

Often new software contains bugs, which do not allow it to perform the required tasks. In other words, there is a difference between what it does and what it is supposed to do. The bugs and the process of their removal can consume a significant amount of time in the software development process. There are several ways to debug and test a typical UNIX program. One usually runs a program to find out what is really happening. The debugging process consists of the following four phases:



testing (finding out if bugs exist), localization (determining the line(s) of code responsible), correction (fixing the bug) and verification (making sure that the correction works perfectly). 

Advertisment

It is a good idea to re-read the source code when a program fails to perform the expected task. This process is known as Code Inspection (Code Reviews). The Code Review process aims at two objectives: to detect errors and to improve the quality of work. Nowadays, the Code Review process is considered one of the mandatory phases of the software lifecycle. It is a process wherein a group of developers trace through a few hundred lines of code in detail. Tools are readily available in the market which analyze source code and report on code that might be incorrect.

Another widely used method is to collect more information about the code as it runs. It is common practice to add printf calls, and with these one can easily debug the entire source code. There is a C preprocessor that selectively includes the code so you only need to recompile the program to include or exclude the debugging code. Consider the following source code.

#include



main()


{


#ifdef DEBUG


printf(“You are inside the DEBUG block\n”);


#endif


printf(“This is the code outside the DEBUG block\n”);


}





Advertisment

Save this as debug1.c



Follow the following steps.

$ cc —o debug1 —DDEBUG debug1.c



$ ./debug1

This will display the following:

Advertisment

You are inside the DEBUG block



This is the code outside the DEBUG block

In the above example, we have enabled debugging (by using —DDEBUG) and thus the print inside the DEBUG block also got displayed. The DEBUG macro helps debug one more complex source code. When we are sure that the code works as expected, we can get rid of the debug blocks. The C preprocessor also defines a few other macros that will help in debugging the programs. Save the following code as debug2.c

#include



main()


{


#ifdef DEBUG


printf(“We are here : “__DATE__” and “__TIME__”\n”);


#endif


printf(“This is the code outside the DEBUG block\n”);


}





Advertisment

And execute these: 

$ cc —o debug2 —DDEBUG debug2.c



$ ./debug2

This will now display the time and date the compiler was run. Note that the macros are prefixed and suffixed with two underscores.

Advertisment

Debugging tools



lint. Original UNIX systems provided a utility called lint. It will detect cases where variables were used before being set and where function arguments were not used, among other things.

Let us see an example with lint:

$ which lint



/usr/bin/lint

Advertisment

Consider a simple C program (hello.c)

#include



main()


{


printf(“Welcome to MY_WORLD\n”);


}


Execute this command:

Advertisment

# lint hello.c

You can see the following as the output:

lint: “hello.c”, line 2: warning 517: Function returns no value.



lint: “hello.c”, line 2: warning 843: “main” returns random value to invocation 


environment


name declared but never used or defined


snprintf     stdio.h(593)


__bufendtab     stdio.h(647)


vsnprintf     stdio.h(594)


__iob     stdio.h(172)


function returns value which is always ignored


printf







Thus the first warning displayed by lint clearly says that main is not returning any value. 

ctags. The ctags program creates an index of functions. Let us invoke ctags on our program hello.c

$ ctags hello.c

This will create a file tags in the current directory and each line in the file consists of a function name, the file it is declared in and a regular expression that can be used to find the function definition within the file. If you want to view the output on the standard output, go for this:

$ ctags —x hello.c

cflow. This program prints a function call tree. With this diagram, one can see which function calls others:

$ cflow hello.c 



1 main: int(),


2 printf: <>

This program will be very useful if one is interested in knowing the structure of the program. This will help the developers to understand how the particular program works.

gdb. With the help of gdb (GNU debugger), one will be able to see what is happening inside another program while it executes it. One of the advantages of gdb is that it is freely available and can be used on many UNIX / LINUX environments.

It is considered as a default debugger on LINUX operating system. Let us have a look at some of the widely used gdb commands.

  • Running a program. One can execute the program with the Run command. 



    Example: (gdb) run 
  • Examining the values of variables. The print displays the contents of variables and other expressions



    Example: (gdb) print var
  • Listing the program . One can have a look at the source code from within gdb by using this command



    Example: (gdb) list
  • Setting a breakpoint. With gdb, one can set breakpoint(s) to see the execution till that particular point. The breakpoints cause the program to

    stop and the control will be returned to the debugger. By typing help breakpoint at (gdb) prompt displays more information about these breakpoints.
  • The backtrace. This shows the sequence of function calls leading up to the current location



    Example: (gdb) backtrace

Let us take another sample program ( foo.c )






#include


main()


{


int i = 0;


printf(“Wecome to MY_WORLD\n”);


for(i=0;i< 10;i++)


printf(“Value of I now is %d\n”, i);


}







Compile this source file by executing the following command to get an executable

foo:

$ cc —g -o foo foo.c

Now we are all set to run gdb:

$ gdb foo



HP gdb 3.0.01 for PA-RISC 1.1 or 2.0 (narrow), HP-UX 11.00.


Copyright 1986 - 2001 Free Software Foundation, Inc.


Hewlett-Packard Wildebeest 3.0.01 (based on GDB) is covered by the


GNU General Public License. Type show copying to see the conditions to


change it and/or distribute copies. Type show warranty for warranty/support.


(gdb)




The above (gdb) prompt indicates that we are in debug mode. gdb has good online help that can be accessed either by man gdb or by typing help at the (gdb) prompt as shown below.

(gdb) help



List of classes of commands:


aliases – Aliases of other commands


breakpoints – Making program stop at certain points


data – Examining data


files – Specifying and examining files


internals – Maintenance commands


obscure – Obscure features


running – Running the program


stack – Examining the stack


status – Status inquiries


support – Support facilities


tracepoints – Tracing of program execution without stopping the program


user-defined – User-defined commands











The list option will display the source code in a linear fashion.

(gdb) list



1 #include


2 main()


3 {


4 int i = 0;


5 printf(“Wecome to MY_WORLD\n”);


6 for(i=0;i< 10;i++)


7 printf(“Value of I now is %d\n”, i);


}






Let us try to put a breakpoint in the source file by using the option b at the gdb prompt.

(gdb) b main



Breakpoint 1 at 0x28d4: file foo.c, line 4.


(gdb) n


The program is not being run.

It is clear that since the program is not running, we are not able to move to the next statement (by printing the option n at the gdb prompt). Thus the program needs to be run before we can explore the various gdb options. This can be achieved as shown below.

(gdb) run foo



Starting program: /home/foo foo


Breakpoint 1, main () at foo.c:4


4 int i = 0;


(gdb) n


5 printf(“Wecome to MY_WORLD\n”);


(gdb) n


Wecome to MY_WORLD


6 for(i=0;i< 10;i++)






One can print the value of a variable by using the p option at the gdb prompt: 

(gdb) n



7 printf(“Value of I now is %d\n”, i);


(gdb) n


Value of I now is 0


6 for(i=0;i< 10;i++)


(gdb) p i


$1 = 0


(gdb) n


7 printf(“Value of I now is %d\n”, i);


(gdb) p i


$2 = 1


(gdb)









It can be noted that the printed value of the variable i is 0 and 1 and this value is held in $1 and $2 respectively. The GNU debugger is a very powerful tool that can be used to debug any program of any complexity. It is important to note here that the programs should be compiled with cc —g in order to do gdb offer the full benefits.

Swayam Prakash

Advertisment