Sun Studio 12: Debugging a Program With dbx

Chapter 1 Getting Started With dbx

dbx is an interactive, source-level, command-line debugging tool. You can use it to run a program in a controlled manner and to inspect the state of a stopped program. dbx gives you complete control of the dynamic execution of a program, including collecting performance and memory usage data, monitoring memory access, and detecting memory leaks.

You can use dbx to debug an application written in C, C++, or Fortran. You can also, with some limitations (see Limitations of dbx With Java Code), debug an application that is a mixture of JavaTM code and C JNI (Java Native Interface) code or C++ JNI code.

This chapter gives you the basics of using dbx to debug an application. It contains the following sections:

Compiling Your Code for Debugging

You must prepare your program for source-level debugging with dbx by compiling it with the -g option, which is accepted by the C compiler, C++ compiler, Fortran 95 compiler, and Java compiler. For more information, see Compiling a Program for Debugging.

Starting dbx and Loading Your Program

To start dbx, type the dbx command at a shell prompt:

$ dbx

To start dbx and load the program to be debugged:

$ dbx program_name

To start dbx and load a program that is a mixture of Java code and C JNI code or C++ JNI code:

$ dbx program_name{.class | .jar}

You can use the dbx command to start dbx and attach it to a running process by specifying the process ID.

$ dbx - process_id

If you don’t know the process ID of the process, include the pgrep command in the dbx command to find and attach to the process. For example:

$ dbx - ”pgrep Freeway”
Reading -
Attached to process 1855
stopped in _libc_poll at 0xfef9437c
0xfef9437c: _libc_poll+0x0004:   ta     0x8
Current function is main
   48   XtAppMainLoop(app_context);

For more information on the dbx command and start-up options, see dbx Command and the dbx(1) man page, or type dbx -h.

If you are already running dbx, you can load the program to be debugged, or switch from the program you are debugging to another program, with the debug command:

(dbx) debug program_name

To load or switch to a program that includes Java code and C JNI code or C++ JNI code:

(dbx> debug program_name{.class | .jar}

If you are already running dbx, you can also use the debug command to attach dbx to a running process:

(dbx) debug program_name process_id

To attach dbx to a running process that includes Java code and C JNI (Java Native Interface) code or C++ JNI code:

(dbx) debug program_name{.class | .jar} process_id

For more information on the debug command, see debug Command.

Running Your Program in dbx

To run your most recently loaded program in dbx, use the run command. If you type the run command initially without arguments, the program is run without arguments. To pass arguments or redirect the input or output of your program, use the following syntax:

run [ arguments ] [ < input_file ] [ > output_file ]

For example:

(dbx) run -h -p < input > output
Running: a.out
(process id 1234)
execution completed, exit code is 0

When you run an application that includes Java code, the run arguments are passed to the Java application, not to the JVM software. Do not include the main class name as an argument.

If you repeat the run command without arguments, the program restarts using the arguments or redirection from the previous run command. You can reset the options using the rerun command. For more information on the run command, see run Command. For more information on the rerun command, see rerun Command.

Your application may run to completion and terminate normally. If you have set breakpoints, it will probably stop at a breakpoint. If your application contains bugs, it may stop because of a memory fault or segmentation fault.

Debugging Your Program With dbx

You are likely to be debugging your program for one of the following reasons:

Examining a Core File

To determine where your program is crashing, you may want to examine the core file, the memory image of your program when it crashed. You can use the where command (see where Command) to determine where the program was executing when it dumped core.

Note –

dbx cannot tell you the state of a Java application from a core file as it can with native code.

To debug a core file, type:

$ dbx program_name core


$ dbx - core

In the following example, the program has crashed with a segmentation fault and dumped core. The user starts dbx and loads the core file. Then he uses the where command to display a stack trace, which shows that the crash occurred at line 9 of the file foo.c.

% dbx a.out core
Reading a.out
core file header read successfully
program terminated by signal SEGV (no mapping at the fault address)
Current function is main
    9       printf("string ’%s’ is %d characters long\n", msg, strlen(msg));
(dbx) where
  [1] strlen(0x0, 0x0, 0xff337d24, 0x7efefeff, 0x81010100, 0xff0000), at
=>[2] main(argc = 1, argv = 0xffbef39c), line 9 in "foo.c"

For more information on debugging core files, see Debugging a Core File. For more information on using the call stack, see Looking at the Call Stack.

Note –

If your program is dynamically linked with any shared libraries, it is best to debug the core file in the same operating environment in which it was created. For information on debugging a core file that was created in a different operating environment, see Debugging a Mismatched Core File .

Setting Breakpoints

A breakpoint is a location in your program where you want the program to stop executing temporarily and give control to dbx. Set breakpoints in areas of your program where you suspect bugs. If your program crashes, determine where the crash occurs and set a breakpoint just before this part of your code.

When your program stops at a breakpoint, you can then examine the state of program and the values of variables. dbx allows you to set many types of breakpoints (see Using Ctrl+C to Stop a Process).

The simplest type of breakpoint is a stop breakpoint. You can set a stop breakpoint to stop in a function or procedure. For example, to stop when the main function is called:

(dbx) stop in main
(2) stop in main

For more information on the stop in command, see Setting a stop Breakpoint in a Function and stop Command.

Or you can set a stop breakpoint to stop at a particular line of source code. For example, to stop at line 13 in the source file t.c:

(dbx) stop at t.c:13
(3) stop at “t.c”:13

For more information on the stop at command, see Setting a stop Breakpoint at a Line of Source Code and stop Command.

You can determine the line at which you wish to stop by using the file command to set the current file and the list command to list the function in which you wish to stop. Then use the stop at command to set the breakpoint on the source line:

(dbx) file t.c
(dbx) list main
10    main(int argc, char *argv[])
11    {
12        char *msg = "hello world\n";
13        printit(msg);
14    }
(dbx) stop at 13
(4) stop at “t.c”:13

To continue execution of your program after it has stopped at a breakpoint, use the cont command (see Continuing Execution of a Program and cont Command).

To get a list of all current breakpoints use the status command:

(dbx) status
(2) stop in main
(3) stop at "t.c":13

Now if you run your program, it stops at the first breakpoint:

(dbx) run
stopped in main at line 12 in file "t.c"
12        char *msg = "hello world\n";

Stepping Through Your Program

After you have stopped at a breakpoint, you may want to step through your program one source line at a time while you compare its actual state with the expected state. You can use the step and next commands to do so. Both commands execute one source line of your program, stopping when that line has completed execution. The commands handle source lines that contain function calls differently: the step command steps into the function, while the next command steps over the function.

The step up command continues execution until the current function returns control to the function that called it.

The step to command attempts to step into a specified function in the current source line, or if no function is specified, into the last function called as determined by the assembly code for the current source line.

Some functions, notably library functions such as printf, might not have been compiled with the -g option, so dbx cannot step into them. In such cases, step and next perform similarly.

The following example shows the use of the step and next commands as well as the breakpoint set in Setting Breakpoints.

(dbx) stop at 13
(3) stop at "t.c":13
(dbx) run
Running: a.out
stopped in main at line 13 in file "t.c"
   13           printit(msg);
(dbx) next
Hello world
stopped in main at line 14 in file "t.c"
   14   }

(dbx) run
Running: a.out
stopped in main at line 13 in file "t.c"
   13           printit(msg);
(dbx) step
stopped in printit at line 6 in file "t.c"
    6           printf("%s\n", msg);
(dbx) step up
Hello world
printit returns
stopped in main at line 13 in file "t.c"
   13           printit(msg);

For more information on stepping through your program, see Stepping Through a Program. For more information on the step and next commands, see step Command and next Command.

Looking at the Call Stack

The call stack represents all currently active routines—those that have been called but have not yet returned to their respective caller. In the stack, the functions and their arguments are listed in the order in which they were called. A stack trace shows where in the program flow execution stopped and how execution reached this point. It provides the most concise description of your program’s state.

To display a stack trace, use the where command:

(dbx) stop in printf
(dbx) run
(dbx) where
  [1] printf(0x10938, 0x20a84, 0x0, 0x0, 0x0, 0x0), at 0xef763418
=>[2] printit(msg = 0x20a84 "hello world\n"), line 6 in "t.c"
  [3] main(argc = 1, argv = 0xefffe93c), line 13 in "t.c"

For functions that were compiled with the -g option, the arguments names and their types are known so accurate values are displayed. For functions without debugging information hexadecimal numbers are displayed for the arguments. These numbers are not necessarily meaningful. For example, in the stack trace above, frame 1 shows the contents of the SPARC input registers $i0 through $i5; only the contents of registers $i0 through $i1 are meaningful since only two arguments were passed to printf in the example shown in Stepping Through Your Program.

You can stop in a function that was not compiled with the -g option. When you stop in such a function dbx searches down the stack for the first frame whose function is compiled with the -g option—in this case printit()—and sets the current scope (see Program Scope) to it. This is denoted by the arrow symbol (=>).

For more information on the call stack, see Efficiency Considerations.

Examining Variables

While a stack trace may contain enough information to fully represent the state of your program, you may need to see the values of more variables. The print command evaluates an expression and prints the value according to the type of the expression. The following example shows several simple C expressions:

(dbx) print msg
msg = 0x20a84 "Hello world"
(dbx) print msg[0]
msg[0] = ’h’
(dbx) print *msg
*msg = ’h’
(dbx) print &msg
&msg = 0xefffe8b4

You can track when the values of variables and expressions change using data change breakpoints (see Setting Data Change Breakpoints). For example, to stop execution when the value of the variable count changes, type:

(dbx) stop change count

Finding Memory Access Problems and Memory Leaks

Runtime checking consists of two parts: memory access checking, and memory use and leak checking. Access checking checks for improper use of memory by the debugged application. Memory use and leak checking involves keeping track of all the outstanding heap space and then on demand or at termination of the program, scanning the available data spaces and identifying the space that has no references.

Note –

Runtime checking is available only on Solaris platforms.

Memory access checking, and memory use and leak checking, are enabled with the check command. To turn on memory access checking only, type:

(dbx) check -access

To turn on memory use and memory leak checking, type:

(dbx) check -memuse

After turning on the types of runtime checking you want, run your program. The program runs normally, but slowly because each memory access is checked for validity just before it occurs. If dbx detects invalid access, it displays the type and location of the error. You can then use dbx commands such as the where command to get the current stack trace or the print command to examine variables.

Note –

You cannot use runtime checking on an application that is a mixture of Java code and C JNI code or C++ JNI code.

For detailed information on using runtime checking, see Chapter 9, Using Runtime Checking.

Quitting dbx

A dbx session runs from the time you start dbx until you quit dbx; you can debug any number of programs in succession during a dbx session.

To quit a dbx session, type quit at the dbx prompt.

(dbx) quit

When you start dbx and attach it to a running process using the process_id option, the process survives and continues when you quit the debugging session. dbx performs an implicit detach before quitting the session.

For more information about quitting dbx, see Quitting Debugging.

Accessing dbx Online Help

dbx includes a help file that you can access with the help command:

(dbx) help