Using the graphical interface of debug

Debugging multithreaded applications

To be able to debug a multi-threaded application effectively, you must be very familiar with the application and how each of the threads synchronize with each other. Doing so will prevent deadlock conditions from occurring while performing a debugging session unless such condition is one of the bugs in the program being analyzed.

To illustrate the use of the features that may be relevant to debugging a multithreaded application, let us recreate session 10 of ``Using the command line interface of debug'' using the graphical interface of debug. The problem in traverse can be detected easily using the command line interface, so this session is not meant so much for the analysis of the program but rather to present some of the features of the graphical interface.

Getting started

The selection sequence

   File --> Windows
means to select the File menu button which pops up a set of options among which you select the Windows option. This notation will be used throughout this session.

To get things started, perform the following steps:

  1. Invoke the debugger as you did in the section ``The debugging session''.

  2. Then create the program traverse via the selection sequence:
       Debug --> Create
    from the Source window. Enter traverse /var/lp /var/cron in the ``Command Line'' field of the ``Create'' popup.

  3. Run traverse ``twice'' by selecting the ``Run'' option in the button bar two times in succession.

  4. Bring up the Process window via the selection sequence:
       File --> Windows --> Process
    from the Source window.

You should have the following configuration:

The field that we are interested in is the ``State'' field of the ``Process pane''. This field shows that p1.1 is in a Stopped state while p1.2 and p1.3 are Off lwp. Stopped is a common state that applies to threads and processes. This is acquired through any event that causes the process or thread to stop its execution (for example, a breakpoint or a halt command).

What are the thread states?

Aside from the other states that apply to processes, a thread can be in a [Core] Off lwp state, or a [Core] Suspended state (where Core appears if you are debugging a multithreaded core image). When threads are multiplexed, they can be scheduled off of a LWP by the threads library or by a call to thr_yield(3thread). In this case, the thread enters the Off lwp state until it picks up an LWP again. A thread, multiplexed or bound, can enter the Suspended state through a thr_suspend(3thread) call. It resumes execution via a thr_continue(3thread) call. While in either of these two states, you cannot issue the run or halt command on a thread. However, you can perform all other operations such as setting breakpoints and setting or displaying variable values.

Selections for multiple threads

The application of commands depends on the following conditions:

If none of the objects in the ``Process pane'' are selected (highlighted), any command button you select or any popup window you bring up will apply to the current object denoted by the pointing finger.

Some commands are not allowed if more than one object is selected or if the selected objects are in different states. For example, if you select p1.1 and p1.3, and you popup the Control menu, the ``Run'' option is desensitized because p1.3 is in the Off lwp state. In fact, none of the options in the ``Control menu'' are available in this case. If both threads are in the Stopped state, then, the ``Run'' command should be available.

Multithread versus multiprocess

There is a parallelism between debugging a multi-threaded application and debugging multiple processes (see the previous section ``Debugging multiple processes''). Threads can be likened to processes in that they execute on their own and vie for system resources. One difference is that threads belong to a single process and are spawned by that single process as that process executes. These threads share a single address space. Processes are invoked and may not be related to one another. From a debugging perspective, if you need to view multiple threads' stack and symbols simultaneously (as well as process' stacks and symbols), you will need multiple window sets.

Creating a window set for each thread

A stack trace is helpful in analyzing the execution of individual threads. The graphical debugger provides a convenient way of simultaneously depicting the stack on a per thread basis by allowing you to create a window set for each thread. Continuing where we left off in our current session, you can do this by performing the following steps for each thread:

  1. Select p1.2 by clicking on it in the ``Process pane''. Make sure that this is the only object highlighted.

  2. Perform the selection sequence:
       File --> New Window Set
    This creates a separate Source window for p1.2.

  3. Perform steps 1 and 2 on p1.3.
You should get a configuration similar to the following:

The Debug: Source window is the window set for the thread p1.1, Debug 2: Source is that for p1.2, and Debug 3: Source is the window set for p1.3. The information for each window gets updated when traverse executes.

Granularity default settings

The granularity settings (in the ``Granularity'' popup window) specifies the debugger's behavior with respect to the application of commands to programs, processes and threads. These settings dictate whether the commands you issue to debug should apply only to specific threads, processes or the entire program.

When you popup the Granularity window (Properties --> Granularity) you will notice that the default setting for commands that create events applies to the parent program and that for other commands applies to the thread only.

What does this mean to a multithreaded program such as traverse? If you wanted to set a breakpoint at the add_to_list call only for the thread p1.2, you need to reset the granularity setting from Parent Program to Thread Only for the Events apply to: portion. Otherwise, setting such a breakpoint will apply to all the applicable threads in the program even if you had selected p1.2 only.

Granularity, events and window sets

The effect of the Granularity setting applies to individual window sets. Thus, if you set the event command setting in Debug:Source (the first window set containing p1.1) to apply to threads only, and you set a breakpoint at the add_to_list call from this same window, it will be set in thread p1.1 only. You will not realize this event when you run the program because thread p1.1 does not call this function; it is called only from threads p1.2 and p1.3.

To illustrate:

  1. Go to Debug:Source.

  2. Do the selection sequence;
       Properties --> Granularity
    and change the Events apply to: setting to Thread Only. Then, click on the OK button.

  3. Do the selection sequence:
       Event --> Stop on Function
    Choose traverse from the Objects: list to display the list of functions. Then, choose the function add_to_list as in the following:

  4. Run the program until it gets to p1.3. You will have to let both threads p1.1 and p1.2 run before p1.3 has a chance to run. Do that by selecting ``Run'' in the first window set (with the Source window labeled Debug: Source), and then select ``Run'' in the second window set (with the Source window labeled Debug 2:Source). Notice that you never experience the breakpoint.
You will then have a configuration that looks like this:

Note that since threads p1.1 and p1.2 are both in the Off lwp, the debugger will not allow you to try to run either one.

Streamlining threads debugging

By default, whenever a thread changes state the debugger will halt execution of the thread and beep to notify the user of the state change. By changing the debugger's default "thread action", you can let thread state changes (including creation of a new thread) take place without stopping execution. The "thread action" resource may be set at debugger invocation (see ``Invoking the debugger'' and ``Resource settings'') or through the ``Output Action'' popup window while the debugger is running.

Also, if you are interested in debugging only one thread (if, for example, the problem seems to be in the main thread and all other threads do some secondary task), you may release all the other threads by selecting those threads in the Process window and then selecting the Release Running command button. (In the debugger's default configuration Release Running is in the ``Release'' submenu under the ``Debug menu''.) Releasing the threads will cause the debugger to effectively ignore them and you can debug the remaining thread as if it were a single process. However, be aware that this is probably not useful if the bug you are tracking has to do with any interactions between the threads.

Next topic: When the debugger doesn't respond
Previous topic: Debugging multiple processes

© 2004 The SCO Group, Inc. All rights reserved.
UnixWare 7 Release 7.1.4 - 27 April 2004