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