Troubleshooting code
From LXF Wiki
| Table of contents |
TROUBLESHOOTING YOUR CODE
Debugging Putting defects into your code is something that is easy to do. Swayam Prakasha looks at the slightly more difficult task of getting them out again...
A piece of software may contain bugs defects that prevent programs from performing their required tasks. In other words, there will be a difference between what they are supposed to do and what they actually do. Bugs, and particularly the process of removing them, can consume a significant amount of time in the software development process.
Phases of debugging and review
There are several ways to debug and test a typical Unix program. We usually run a program to find out what's really happening. The debugging process consists of the following four phases: 1 Testing to find out the bugs that exist. 2 Localization determining the line(s) of code responsible. 3 Correction fixing the bug 4 Verification to make sure that the correction works perfectly
It's a good idea to re-read the source code when the program fails to perform the expected task. This process is known as code inspection or code review, and it has two objectives: to detect errors; and to improve the quality of the work. Nowadays, the code review process is considered as a mandatory phase of the software life cycle, and code inspection will involve a group of developers tracing through a few hundred lines of code in detail.
There are also readily available tools on the market that will analyze source code and report on code that might be incorrect. Before a bug can be fixed, it's very important to locate the source of the bug. For example, when a segmentation fault occurs, it makes sense to know the point where this fault is occurring. Once we are able to find this information, we can focus on the values in that method, which called the method, and the exact cause for the error. A debugger enables us to find this information.
Debugging while a program runs
Another widely used method is to collect more information about the code as it runs. It's a common practice to add printf calls, and with these we can easily debug the entire source code. There is a C preprocessor that selectively includes the code so that we only need to recompile the program to include or exclude the debugging code. Let's consider the following source code. We'll save this as "my_code_1.c":
#include<stdio.h>
main()
{
#ifdef DEBUG
printf("You are inside the DEBUG block\n");
#endif
printf("This is the code outside the DEBUG block\n");
}
a. $ cc -o my_code_1 -DDEBUG my_code_1.c
b. $ ./my_code_1
By following steps a and b above, we'll get the output as:
You are inside the DEBUG block This is the code outside the DEBUG block
In the above example, we have enabled the debugging (by using -DDEBUG) and thus the print inside the DEBUG block also got displayed.
The DEBUG macro helps us to debug more complex source code. When we are sure that the code works as expected, we can get rid of the debug blocks. Other macros useful for debugging The C preprocessor also defines a few other macros that will help in debugging the programs. We'll save the following code as "my_code_2.c":
#include<stdio.h>
main()
{
#ifdef DEBUG
printf("We are here : "__DATE__" and "__TIME__"\n");
#endif
printf("This is the code outside the DEBUG block\n");
}
a. $ cc -o my_code_2 -DDEBUG my_code_2.c
b. $ ./my_code_2
Executing a and b at the command prompt will display the time and date the compiler was run.
You'll notice here that the macros are prefixed and suffixed with two underscores.
As we've seen above, the DEBUG, DATE and TIME macros are very useful, and developers frequently use them in debugging the modules. Once we're sure that the source code functions as expected, we can take these macros out of the source code.
Debugging Tool 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's see an example with lint:
$ which lint /usr/bin/lint
We'll consider a simple C program (my_code_3.c):
#include<stdio.h>
main()
{
printf("Welcome to MY_WORLD\n");
}
$lint my_code_3.c
By executing the command shown above, we can see the following as the output:
lint: "my_code_3.c", line 2: warning 517: Function returns no
value.
lint: "my_code_3.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.
Debugging Tool ctags
The ctags program creates an index of functions:
$ ctags
The output of this will be:
Usage: ctags [-BFatuwv] [-f tagsfile] file ...
ctags [-x] file ...
Let's invoke ctags on our program my_code_3.c:
$ ctags my_code_3.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's 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 my_code_3.c
Debugging Tool cflow
This program prints a function call tree. With this diagram, we can see which function calls which others.
$ cflow my_code_3.c 1 main: int(), <my_code_3.c 2> 2 printf: <>
This program will be very useful if we are interested in knowing the structure of the program, and will help the developers to understand how the particular program works.
Debugging Tool gdb
With the help of gdb (GNU debugger), we will be able to see what's happening inside another program while it executes. One of the advantages of gdb is that it's freely available in the market and can be used in many Unix/Linux environments. It's considered as a default debugger on the Linux operating system.
gdb helps us to step through the code line by line as it executes. This will enable the developer to see the flow of logic, to see what happens to the variables and to watch how the various instructions are having an effect on the program.
gdb enables us to analyze the core file generated by a program that has crashed. With this, we will be able to figure out what caused the crash, the last instruction that got executed before the crash, values of variables before the crash and so on.
gdb can only use debugging symbols that are generated by g++. For Sun CC users, there is the dbx debugger which is very similar to gdb.
Various commands with gdb
Let's have a look at some of the widely used gdb commands: Running a program a program can be executed with the
run command.
Example: (gdb) run Examining the values of variables the print command displays the contents of variables and other expressions. Example: (gdb) print var Listing the program we can have a look at the source code from within gdb by using this command. Example: (gdb) list Setting a breakpoint with gdb, we can set breakpoint(s) to see the execution up to 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. Attach to a process enables us to attach to a specified process for debugging purposes. Example: (gdb) attach <pid> The backtrace this shows the sequence of function calls leading up to the current location. Example: (gdb) backtrace
Debugging a sample source code
Let's take a sample program ( my_code_4.c ):
#include<stdio.h>
main()
{
int i = 0;
printf("Welcome to MY_WORLD\n");
for(i=0;i< 10;i++)
printf("Value of I now is %d\n", i);
}
a. $ cc -g -o my_code_4 my_code_4.c
b. $ gdb my_code_4
Compile this source file by executing the command a above to get an executable "my_code_4", and we can get into the debugging mode by executing command b.
HP gdb 3.0.01 for PA-RISC 1.1 or 2.0 (narrow), HP-UX 1 1.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 a very good online help, and we can get an access to it 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
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) list
1 #include<stdio.h>
2 main()
3 {
4 int i = 0;
5 printf("Welcome to MY_WORLD\n");
6 for(i=0;i< 10;i++)
7 printf("Value of I now is %d\n", i);
8 }
Thus list option will display the source code linewise.
Putting a break point in gdb
Let's 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 my_code_4.c, line 4. (gdb) n The program is not being run.
It's clear that since the program is not running we're unable to move to the next statement by printing the option n at the (gdb) prompt. The program must be run before we can explore the various gdb options. This can be achieved as shown below.
(gdb) run my_code_4
Starting program: /home/my_code_4 my_code_4
Breakpoint 1, main () at my_code_4.c:4
4 int i = 0;
(gdb) n
5 printf("Welcome to MY_WORLD\n");
(gdb) n
Welcome to MY_WORLD
6 for(i=0;i< 10;i++)
We can print the value of a variable by using the p option at the (gdb) prompt. In other words, p var at the (gdb) prompt will print the value of the variable var when the program is running. This option provided by gdb is very helpful if we're interested in checking the values of the variables during the execution of the program.
(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)
You'll notice that the printed value of the variable I is 0 and 1, and this value is held in $1 and $2 respectively. LXF

