JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Oracle Solaris Studio 12.3: dbxtool Tutorial     Oracle Solaris Studio 12.3 Information Library
search filter icon
search icon

Introduction

The Example Program

Configuring dbxtool

Diagnosing a Core Dump

Using Breakpoints and Stepping

Setting Breakpoints

Advantages of Function Breakpoints

Using Watches and Stepping

Discussion

Using Advanced Breakpoint Techniques

The Problem

Step 1: Repeatability

Step 2: First Breakpoint

Step 3: Breakpoint Counts

Step 4: Bounded Breakpoints

Step 5: Looking for a Cause

Step 6: More Breakpoint Counts

Step 7: Narrowing Down the Cause

Step 7: Using Watchpoints

Step 8: Breakpoint Conditions

Step 9: Verifying the Diagnosis by Popping the Stack

Step 10: Using Fix to Further Verify Our Diagnosis

Discussion

Using Breakpoint Scripts to Patch Your Code

Oracle Solaris Studio 12.3: dbxtool Tutorial

December 2011

This tutorial uses a buggy example program to demonstrate how to use dbxtool, the stand-alone graphical user interface (GUI) for the dbx debugger, effectively. It starts with the basics and then moves on to more advanced features.

Introduction

This tutorial uses a “buggy” example program to demonstrate how to use dbxtool, the stand-alone graphical user interface (GUI) for the dbx debugger, effectively. It starts with the basics and then moves on to more advanced features.

The Example Program

This tutorial uses a simplified and somewhat artificial simulation of the dbx debugger. The source code for this C++ program is available in the sample applications zip file on the Oracle Solaris Studio 12.3 Sample Applications web page at http://www.oracle.com/technetwork/server-storage/solarisstudio/downloads/solaris-studio-samples-1408618.html.

  1. If you have not already done so, download the sample applications zip file, and unpack the file in a location of your choice. The debug_tutorial application is located in the Debugger subdirectory of the SolarisStudioSampleApplications directory.

  2. Build the program:

    make
    CC -g   -c  main.cc
    CC -g   -c  interp.cc
    CC -g   -c  cmd.cc
    CC -g   -c  debugger.cc
    CC -g   -c  cmds.cc
    CC -g   main.o interp.o cmd.o debugger.o cmds.o -o a.out

The program is made up of the following modules:

cmd.h
cmd.cc
Class Cmd, a base for implementing debugger commands
interp.h
interp.cc
Class Interp, a simple command interpreter
debugger.h
debugger.cc
Class Debugger, mimics the main semantics of a debugger
cmds.h
cmds.cc
Implementations of various debugging commands
main.h
main.cc
The main() function and error handling. Sets up an Interp, creates various commands and assigns them to the Interp. Runs the Interp.

Run the program and try a few dbx commands:

$ a.out
> display var
will display 'var'
> stop in X
> run running ...
stopped in X
var = {
        a = '100'
        b = '101'
        c = '<error>'
        d = '102'
        e = '103'
        f = '104'
}
> quit
Goodby
$

Configuring dbxtool

Start dbxtool by typing:

installation_directory/bin/dbxtool

The first time you start dbxtool, the window looks like the following:

image:dbxtool Window

If you are reading this tutorial in your web browser, it is probably taking up half your screen, so you will find it beneficial to customize dbxtool to make it a half-screen application as well.

The following are examples of the various ways you can customize dbxtool.

Diagnosing a Core Dump

Now that you have configured dbxtool to suit your preferences, let's find some bugs.

Run the example program again, except this time press Return without entering a command:

$ a.out
> display var
will display 'var'
> 
Segmentation Fault (core dumped)
$

Now start dbxtool with the executable and the core file:

$ dbxtool a.out core

Tip - Notice that the dbxtool command accepts the same arguments as the dbx command.


dbxtool displays something like the following:

image:dbxtool window displaying debugger console window and location of call to strcmp

Here are some things to notice:

Functions usually fail when they are passed bad values as parameters. Here are some ways to check the values passed to strcmp():

Now it is abundantly clear that the value of name should not be NULL. But which code passed this bad value to Interp::find()? To find out you can:

This code is unfamiliar. And a quick glance doesn't really point to any clues other than that the value of argv[0] is NULL.

It might easier to debug this problem dynamically using breakpoints and stepping.

Using Breakpoints and Stepping

Breakpoints let you stop a program a bit before the manifestation of a bug and step through the code in the hope of discovering what went wrong.

If you haven't already done so, now might be a good time to undock the Process I/O window.

You ran the program from the command line earlier. Now reproduce the bug by running the program in dbxtool:

  1. Click the Run button image:Run buttonon the toolbar or type run in the Debugger Console window.

  2. Press Return in the Process I/O window. This time, an alert box tells you about the SEGV:

    image:Signal Caught alert box displaying SEG
  3. In the alert box, click Discard and Pause. The Editor window once again highlights the call to strcmp() in Interp::find().

  4. Click the Make Caller Current button image:Make Caller Current buttonin the toolbar to go to the unfamiliar code you saw earlier in Interp::dispatch(). Now you can set a breakpoint a bit before the call to find(). Later you can step through the code in the hope of learning why things went wrong.

Setting Breakpoints

There are several ways to set a breakpoint. First, if the line numbers are not showing, enable line numbers in the editor by right-clicking in the left margin and selecting the Show Line Numbers checkbox.

By now the Editor is getting cluttered:

image:Cluttered Editor window

You can clean up this clutter using the Breakpoints window.

  1. Click the Breakpoints tab (or maximize it if you minimized it earlier).

  2. Select the line breakpoint and one of the function breakpoints, right-click, and choose Delete.

Advantages of Function Breakpoints

Setting a line breakpoint by toggling in the editor might be intuitive. However, many dbx users prefer function breakpoints for the following reasons:

Using Watches and Stepping

So, now you have a single breakpoint at Interp::dispatch(). If you click Run image:Run buttonagain and press Return in the Process I/O window, the program stops at the first line of the dispatch()function that contains executable code.

image:Editor window with execution stopped at line 122

You already know that the culprit is the argv[0] being passed to find(), so keep an eye on argv using watches:

  1. Select an instance of argv in the Editor window.

  2. Right-click and choose New Watch. The New Watch dialog box opens seeded with the selected text:

    image:New Watch dialog box
  3. Click OK.

  4. Open the Watches window by choosing Window > Watches.

  5. Expand argv.

    image:Watches window with argv expanded

    What is all of this garbage? Notice that argv is uninitialized and because it's a local variable, it might “inherit” random values left on the stack from previous calls. Could this be the cause of problems? Let's not get ahead of ourselves and proceed methodically.

  6. Click Step Over image:Step Over buttontwo times until the green PC arrow points to int argc = 0;.

  7. It's clear that argc is going to be an index into argv so keep an eye on it as well and create a watch for it also. Notice that it is also currently uninitialized and might contain garbage values.

  8. Because you created the watch for argc second it appears under argv in the Watches window. It would be nice if it showed up on the first row of the window. You could delete the watches and re-enter them in the desired order. However, in this case, there's a quick trick you can use. Clicking the Name column header sorts the column. Click it until you get something like the following (notice the sort triangle):

    image:Watches window with sorted watches
  9. Click Step Over image:Step Over button. Notice how argc now shows its initialized value of 0. It is displayed in bold to signify that the value just changed.

    image:Watches window with argc value in bold
  10. Our application is going to call strtok(). Click Step Over to step over the function, and observe, for example, by using balloon evaluation, that token is NULL.


    Tip - What does strok() do? You can read the strok(3) man page, but in short, it helps break up a string, for example, line, into tokens delimited by one of DELIMETERS/


  11. Clicking Step Over again assigns the token to argv and then there is a call to strtok() in a loop. As you step over, you don't enter the loop (there are no more tokens) and instead a NULL is assigned. Step over that assignment too and you are at the threshold of the call to find. If you recall, this is where our program crashed.

  12. Double check that the program crashes here by stepping over the call to find(). Sure enough, the Signal Caught alert box is displayed again.

    image:Signal Caught alert box

    Click Discard and Pause as before.

  13. So the first call to find() after stopping in Interp::dispatch is indeed where things go wrong.

    This may have been obvious but the point is to illustrate that you can quickly get back to where you were. Here's how:

    1. Click Make Caller Current image:Make Caller Current button.

    2. Toggle a line breakpoint at the call site of find().

    3. Open the Breakpoints window and disable the Interp::dispatch() function breakpoint.

      dbxtool should look like this:

      image:Editor window showing two breakpoints with one disabled
    4. The downward arrow indicates that two breakpoints are set on line 141 and that one of them is disabled.

  14. Click Run image:Run button and press Return in the Process I/O window, and the program will end up right back in front of the call to find(). (Notice how the Run button evokes re-starting. When debugging you re-start much more often that just start.)


    Tip - If you rebuild your program, for example, after discovering and fixing bugs, you need not exit dbxtool and restart it. When you click the Run button, dbx detects that the program (or any of its constituents) has been recompiled, and reloads it.

    So it is more efficient to simply keep dbxtool on your desktop, perhaps minimized, ready to use on your debugging problems.


  15. So where's the bug? Look at the watches again:

    image:Watches window

    Here you can make the great intuitive leap that argv[0] is NULL because the first call to strtok() returns NULL and that is because the line was empty and had no tokens.


    Tip - Shall you fix this bug before proceeding with the remainder of this tutorial?

    You can. You can also choose to remember not to press Return and create empty lines.

    Or, if you will mostly be running the program under the debugger, you can “patch” it in the debugger, as described in Using Breakpoint Scripts to Patch Your Code.


The developer of the example code should probably have tested for this condition and bypassed the rest of Interp::dispatch().

Discussion

The above example llustrates the most common debugging pattern, where one stops the misbehaving program at some point before things have gone wrong and then steps through the code comparing the intent of the code with the way the code actually behaves.

Could you have found the bug more directly, without all the stepping and watching? In fact, yes, but you will first have to learn some more techniques for using breakpoints.

Using Advanced Breakpoint Techniques

This section demonstrates some advanced techniques for using breakpoints:

This section, and the example program, are inspired by an actual bug discovered in dbx using much the same sequence described in this section..

The source code includes a sample input file named in, which triggers a bug in our example program. in contains the following:

display nonexistent_var    # should yield an error
display var
stop in X    # will cause one "stopped" message and display
stop in Y    # will cause second "stopped" message and display
run
cont
cont
run
cont
cont

Notice that there are no empty lines so as not to trigger the bug you discovered in the previous section.

When you run the program with the input file, the output is as follows:

$ a.out < in
> display nonexistent_var 
error: Don't know about 'nonexistent_var'
> display var 
will display 'var'
> stop in X 
> stop in Y 
> run 
running ...
stopped in X
var = {
      a = '100'
      b = '101
      c = '<error>'
      d = '102
      e = '103'
      f = '104'
      }
> cont 
stopped in Y
var = {
      a = '105'
      b = '106'
      c = '<error>'
      d = '107'
      e = '108'
      f = '109'
      }
> cont 
exited
> run 
running ...
stopped in X
var = {
      a = '110'
      b = '111'
error: cannot get value of 'var.c'
      c = '<error>'
      d = '112'
      e = '113'
      f = '114'
      }
> cont 
stopped in Y
var = {
      a = '115'
      b = '116'
error: cannot get value of 'var.c'
      c = '<error>'
      d = '117'
      e = '118'
      f = '119'
      }
> cont 
exited
> quit
Goodby

This output might seem voluminous but the point of this example is to illustrate techniques to be used with long running, complex programs where stepping through code or tracing just aren't practical.

Notice that when showing the value of field c, you get a value of <error>. Such a situation might occur if the field contains a bad address.

The Problem

Notice that when you ran the program a second time, you received additional error messages that you didn't get on the first run:

error: cannot get value of 'var.c'

The error() function uses a variable, err_silent, to silence error messages in certain circumstances. For example, in the case of the display command, instead of displaying an error message, problems are displayed as c = '<error>'.

Step 1: Repeatability

The first step is to set up a debug target and configure it so the bug can easily be repeated by clicking Run image:Run button.

Start debugging the program as follows:

  1. If you haven't yet compiled the example program, do so by following the instructions in The Example Program.

  2. Choose Debug > Debug Executable.

  3. In the Debug Executable dialog box, browse for or type in the path to the executable.

  4. In the Arguments field, type:

    < in
  5. The directory portion of the executable path is displayed in the Run Directory field.

  6. Click Debug.

    image:Debug Executable dialog box

In a real world situation, you might want to populate the Environment field as well.

You can change any properties of the configuration by choosing Debug > Configure Current Session.

When debugging a program, dbxtool creates a debug target. You can always use the same debugging configuration by choosing Debug > Debug Recent and then choosing the desired executable.

It is sometimes easier to set many of these properties from the dbx command line. They will be stored in the debug target configuration.

The purpose of much of what follows is to sustain easy repeatability as you add breakpoints so that you can always go to a location of interest by clicking Run without having to click Continue on various intermediate breakpoints.

Step 2: First Breakpoint

Let's put a breakpoint inside the error() function in the case where it prints an error message. This breakpoint will be a line breakpoint on line 33.

In a larger program, one can easily change the current function in the Editor window by typing the following, for example, in the Debugger Console window:

(dbx) func error

The lavender stripe indicates the match found by the func command.

  1. Create the line breakpoint by clicking in the left margin of the Editor window on top of the number 33.

    image:Editor window with lavender stripe on line 31 and breakpoint on line 33
  2. Click Run image:Run button to run the program and upon hitting the breakpoint, look at the stack trace. It shows the error message being emitted due to the simulated command in the in file:

    > display var    # should yield an error

    The call to error() is expected behaviour.

    image:Call Stack window with error message frame
  3. Click Continue image:Continue button to continue the process and hit the breakpoint again. This time you receive the unexpected error message.

    image:Call Stack window with error message frame

Step 3: Breakpoint Counts

It would be better to arrive at this location repeatedly on each run without having to click Continue after the first hit of the breakpoint due to the command:

> display var # should yield an error

You can edit the program or input script and eliminate the first troublesome display command. However, the specific input sequence you are working with might be a key to reproducing this bug so let's not perturb the situation.

Because you are interested in the second time you reach this breakpoint let's set its count to 2:

  1. In the Breakpoints window, right-click the breakpoint and choose Customize.

  2. In the Customize Breakpoint dialog box, type 2 in the Count Limit field.

  3. Click OK.

    image:Customize breakpoint dialog box

Now you can repeatedly arrive at the location you are interested in.

Step 4: Bounded Breakpoints

In this case it was trivial to choose a count of 2. But sometimes the place at which you are interested in stopping is called many times. Later you'll see how you can easily choose a good count value. But for now let's explore another way of stopping in error() only in the invocation you are interested in.

  1. Open the Customize Breakpoint dialog box as before for the breakpoint inside error() and disable breakpoint counts by selecting Always stop from the drop-down list for the Count Limit.

  2. Now click Run and pay attention to the stack trace the two times you stop in error(). The first time it looks like this:

image:Call Stack window

The second time it looks like this:

image:Call Stack window

You'd like to arrange to stop at this breakpoint when it's called all the way from runProgram (frame [7]). To do so, open the Customize Breakpoint dialog box again and set the While In field to runProgram.

image:Customize Breakpoint window

Now, again, you can repeatedly and trivially arrive at the point you're interested in.

Step 5: Looking for a Cause

Why is the unwanted error message being emitted? Obviously it is because err_silent is not > 0. Let's look at the value of err_silent with balloon evaluation. Put your cursor over err_silent in line 31 and wait for its value to be displayed.

image:Editor window with balloon evaluation showing err_silent = 0

Let's follow the stack to see where err_silent was set. Two clicks of Make Caller Current image:Make Caller Current buttonget you to evaluateField(), which has already called evaluateFieldPrepare() simulating a complex function that might be manipulating err_silent.

image:Editor window with lavender stripe in evaluateField

Another click of Make Caller Current gets you to printField(). Here err_silent is being incremented. printField() has also already called printFieldPrepare(), also simulating a complex function that might be manipulating err_silent.

image:Editor window with lavender strip in printField

Notice how an err_silent++ and an err_silent-- bracket some code.

So it could be that err_silent goes wrong in either printFieldPrepare() or evaluateFieldPrepare(), or that it is already wrong when control gets to printField().

Let's find out whether it was wrong before or after the call to printField()by putting a breakpoint in printField().

Step 6: More Breakpoint Counts

Set a breakpoint in printField().

  1. Select printField(), right-click, and choose New Breakpoint.

  2. The New breakpoint type pre-selected and the Function field pre-populated with printfield.

  3. Click OK.

    image:New Breakpoint dialog box
  4. Click Run image:Run button. The first time you hit the breakpoint is during the first run, on the first stop, and on the first field, var.a. err_silent is 0, which is OK.

    image:Editor window with balloon evaluation
  5. Click Continue. err_silent is still OK.

  6. Click Continue again. err_silent is still OK.

It is going to take a while until you reach the particular call to printField() that resulted in the unwanted error message. You need to use a breakpoint count on the printField breakpoint. But what shall the count be set to? In this simple example, one could attempt to count the runs and the stops and the fields being displayed but in practice things might not be so predictable. However there is a way to figure what the count should be semi-automatically.

  1. Open the Customize Breakpoint dialog box for the breakpoint on printField() and set the Count Limit field to infinity.

    image:Customize Breakpoint dialog box

    This setting means that you will never stop at this breakpoint. However, it will still be counting.

  2. At this point, it will be helpful to have the Breakpoints window show more properties, such as counts.

    1. Click the Change Visible Columns button image:Change Visible Columns button at the top right corner of the Breakpoints window.

    2. Select Count Limit, Count, and While In.

    3. Click OK.

      image:Change Visible Columns dialog box
  3. Re-run the program. You will hit the breakpoint inside error(); the one bounded by runProgram().

  4. Now look at the count for the breakpoint on printField().

    image:Breakpoints window

    It's 15 and that is the count you want.

  5. Click the drop-down list in the Count Limit column and select Use current Count value to transfer the current count to the count limit, and press Return.

Now if you run the program you will stop in printField() the last time it's called before the unwanted error message!

Step 7: Narrowing Down the Cause

Use balloon evaluation to inspect err_silent again. Now it is -1. Most likely one err_silent-- too many, or one err_silent++ too few, was executed before you got to printField().

How can you locate this mismatched pair of err_silents? In a small program like this example it can be done by careful code inspection. But in a large program there might be a prohibitive number of pairings of

err_silent++;
err_silent--;

A quicker way to locate the mismatched pair is by using watchpoints.


Tip - It might also be the case that it is not a mismatched set of err_silent++; and err_silent--; at all, but a rogue pointer overwriting the contents of err_silent. Watchpoints would be more effective in catching such a problem.


Step 7: Using Watchpoints

To create a watchpoint on err_silent:

  1. Select the err_silent variable, right-click, and choose New Breakpoint.

  2. Set Breakpoint Type to Access. Notice how the Settings section changes and how the Address field is & err_silent.

  3. Select After in the When field.

  4. Select Write in the Operation field.

  5. Click OK.

    image:New Breakpoint window
  6. Now run the program. You stop in init(). Things look OK here, that is, err_silent was incremented to 1 and execution stopped after that.

  7. Click Continue. You stop in init() again.

  8. Click Continue again. You stop in init() again.

  9. Click Continue again. You stop in init() again.

  10. Click Continue again. Now you stop in stopIn(). Things look OK here too, that is, no -1s.

It might take a while before err_silent is set to -1 and you don't want to be slavishly clicking Continue lest your eyes glaze over and you miss the time it actually changed to -1. But there is a better way.

Step 8: Breakpoint Conditions

To add a condition to your watchpoint:

  1. In the Breakpoints window, right-click the After write breakpoint and choose Customize.

  2. Be sure that After is selected in the When field.


    Tip - Selecting After is important because you want to know what the value of err_silent was changed to.


  3. Set the Condition field to err_silent == -1.

  4. Click OK.

    image:Customize Breakpoint dialog box

Now re-run the program. You stop in checkThings(). This is the first time err_silent is set to -1. As you look for the matching err_silent++ you see what looks like a bug: err_silent is incremented only in the else portion of the function.

image:Editor windowshowing program stopped in checkThings

Could this be the bug you've been looking for?

Step 9: Verifying the Diagnosis by Popping the Stack

Let's double check that you indeed went through the else block of the function.

One way to do so would be to set a breakpoint on checkThings() and run the program. But checkThings() might be called many times. You can use breakpoint counts or bounded breakpoints to get to the right invocation of checkThings() but there's a quicker way to replay what was recently executed.

You find yourself at the call to checkThings(). In fact, the process state has reverted to the beginning of the line containing the call to checkThings().

Now you can click Step Into image:Step Into button and observe as checkThings() is called again. As you step through checkThings()(), you can verify that indeed the process executes the if block where err_silent is not incremented and then is decremented to -1.

image:Editor window showing program stopped in if section of checkThings

It looks like you have found the programming error. But let's triple check it.

Step 10: Using Fix to Further Verify Our Diagnosis

Let's fix the code in place and verify that the bug has indeed gone away.

  1. Fix the code by putting the err_silent++ above the if statement so it looks like this:

    image:Editor window showing err_silent++ moved
  2. Choose Debug > Apply Code Changes.

  3. Disable the printField breakpoint and the watchpoint but leave the breakpoint in error() enabled.

    image:Breakpoints window
  4. Re-run the program.

You'll note that the program completes without hitting the breakpoint in error() and its output is as expected.

image:Process I/O window

Discussion

The above still illustrates the same pattern as discussed at the end of Using Breakpoints and Stepping, that is, where one stops the misbehaving program at some point before things have gone wrong and then steps through the code comparing the intent of the code with the way the code actually behaves. The main difference is that finding the point before things have gone wrong is a bit more involved.

Using Breakpoint Scripts to Patch Your Code

In Using Breakpoints and Stepping, you discovered a bug where an empty line yields a NULL first token and causes a SEGV. Here's one way to quickly hack around it:

  1. Delete all of the breakpoints you created in previous sections.

  2. Delete the <in argument in the Debug Executable dialog box.

  3. Toggle a line breakpoint at line 130 in interp.cc:

    image:Editor window with breakpoint on line 130
  4. In the Breakpoints window, right-click the breakpoint you just created (newer breakpoints are added at the bottom) and choose Customize.

  5. In the Customize Breakpoint dialog box, type token == 0 in the Condition field.

  6. Select Run Script from the Action drop-down list.

  7. In the Script field, type assign token = line.


    Tip - Why not assign token = “dummy”? Because dbx cannot allocate the dummy string in the debugged process. On the other hand, line is known to be equal to "".


    The dialog box should look like this:

    image:Custom breakpoint dialog box
  8. Click OK.

Now, if you run the program and enter an empty line, instead of crashing, it will behave like this:

image:Process I/O window

How this works might be clearer if you look at the command that dbxtool sent to dbx:

when at "interp.cc":130 -if token == 0 { assign token = line; }