There are times when you need to know what is going on beneath the front-end of the application. For example, you may have a failing application, with no useful error message, with a system service that does not operate the way you expect. In these cases, you may not have the application source code, and therefore cannot perform a typical debug process to find out what is wrong. Tracing provides you with an alternative method.
As a developer, the obvious solution to finding a UNIX application problem is to use the debugging feature of your development environment or operating system to examine the source code and find out what is causing the problem.
Most debugging systems allow you to monitor and examine both the execution process as individual lines are executed, as well as to monitor the values of individual variables and structures. With a debugger you can set a specific breakpoint within the code where the execution will stop and you can get information about the callstack -- the current route to the function -- and the variables and their values at that point.
Let's examine, as an example, an application that generates a calculation of a person's age by using the day they were born, and also taking into account leap years and other factors. To debug the application, you need the source code and you also need to compile the application with debugging enabled, as shown here:
$ gcc -g ageindays.c -o ageindays.
To run the application, you supply the user's date of birth and the target date when you want to make a comparison (see Listing 1).
Listing 1. Making a comparison
$ ./ageindays 24/1/1980 22/2/2009 You have been alive 10622 days You were born on 24/1/1980 which is a Thursday
To debug the application, you start by suspecting that the problem is located within a function called
calc_diff, which compares the difference between the first and second dates. You might next create a sequence like the one shown in Listing 2.
Listing 2. Debugging the calc_diff function
$ gdb ageindays GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008) Copyright 2004 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 "i386-apple-darwin"...Reading symbols for shared libraries ... done (gdb) b calc_diff Breakpoint 1 at 0x1bd7: file ageindays.c, line 27. (gdb) r 24/1/1980 26/3/2009 Starting program: /nfs/MC/UnixSrc/c/bio/ageindays 24/1/1980 26/3/2009 Reading symbols for shared libraries ++. done Breakpoint 1, calc_diff (day=26, month=3, year=2009) at ageindays.c:27 27 unsigned long days_diff=0; (gdb) bt #0 calc_diff (day=26, month=3, year=2009) at ageindays.c:27 #1 0x00001e3d in main (argc=3, argv=0xbffff708) at ageindays.c:89 (gdb) p days_diff $1 = 8041 (gdb)
You can see from the output in Listing 2 that you open the debugger, set a breakpoint in
calc_diff() function by specifying it by name, and then
run the program within the debugger, supplying the same arguments as you would on the command line.
Once the debugger hits the breakpoint you created, execution of the application stops,
and you can examine the code of the application and the functions being called. Using
the debugger, you can see the arguments supplied to the function with their values (in this case, the date information supplied for the target date). Once execution stops, you can get a stack trace and see the exact line in the code that called the
calc_diff function, and you can obtain the value of the days_diff variable. Because the execution of the application has been paused completed, you can also modify the value of a variable. This allows you to try out different values within the application to find potential problems.
All of this information is made available because the specific debugging information (the symbols that make up the function and variable names) and other metadata such as the line within the code where the function is defined.
The specific debugging information has to be added to the binary application during building, and more importantly, you have to access the source to be able to compile the application with this debug information included. Debugging a program without being able to identify the function names and variable names is almost impossible.
As a systems administrator, and often as a developer, you are more likely to be interested in finding a fault with a program. This can be why a particular program is causing other problems (such as memory and other errors), or why an application is not behaving as it should, and how it has in the past. Debugging the specifics of the application in this instance are not often useful. Instead, you need to examine how the application is being executed by the operating system.
With debugging, you are examining the execution of the individual functions defined within the application. Debugging concentrates on the application and the functions and structures within it, but typically ignores the system calls and calls to library functions that are made by the application to the operating system. Debugging provides a wealth of information about your application, but not necessarily how the operating system is executing that application.
With tracing, you are monitoring the interaction between the application and the operating system, usually by looking at the operating system functions that are called by the application during its execution.
Despite these semantic differences, the major difference between tracing and debugging is that tracing can take place without you having to have access to the source code, or to compile the application in any special way. This means that you can trace an application that comes with the operating system, or from a third-party vendor to find out what is wrong.
Tracing an application enables you to find out:
- Memory usage and calls to map memory
- Files being opened and closed during execution
- Reads and writes to different files
- Libraries loaded for a given application
Start by examining the output from truss, tools that are available on Solaris and AIX®.