Controlling Program Execution

This chapter describes how you can control the execution of your program as you debug it. This involves the following menus and windows:

This chapter also describes how to interrupt a running program.

Run menu

The Run menu controls how your program executes. It contains the following items:

Go
Start or resume program execution. Execution resumes at the current location and won't stop until a breakpoint is encountered, an error occurs, or your program terminates.
Run to Cursor
Resume program execution until it executes the location of the cursor in the Source or Assembly window. Execution stops before the cursor position if a breakpoint is encountered or an error occurs.
Execute to
Resume program execution until it executes a specified address. You're prompted to enter an address. It can be the name of a function or an expression that resolves to a code address. See the chapter Expression Handling.

In the dialog, you can click the Symbols... button as a shortcut. You can type a partial symbol name like foo, and the Symbol button shows you a list of symbols that start with foo. You can then choose one of these symbols by clicking on it, or by pressing Enter. Note that the first time you use Symbols... in a debugging session, it may take a while, as the debugger sorts the symbol table for the program.

If your program encounters a breakpoint, or an error occurs before the specified address is executed, the debugger ignores your request to stop at the given address.

Step Over
Trace a single source or assembly line depending on whether the source or assembly window is current. Step Over won't step into any function calls.
Trace Into
This is similar to Step Over except that it steps into any function calls.
Next Sequential
Run until the program executes the next sequential source line or assembly instruction. This is useful if the program is executing the last statement in a loop and you wish to execute until the loop terminates.
Note: When using this command, be sure that the execution path will eventually execute the next statement or instruction. If execution fails to reach this point, then the program may continue to execute until completion. This situation is like setting a breakpoint at a statement or assembly instruction that will never be executed, and then issuing a GO command. In this situation, the application executes until an error occurrs or another breakpoint is encountered.

Until Return
Resume program execution until the currently executing function returns. Execution terminates prior to this if an error occurs or a breakpoint is encountered.
Skip to Cursor
Reposition the instruction pointer at the cursor position, skipping all instructions in between. When you continue execution, the program continues from this point. This is useful if you want to skip an offending line or re-execute something.
Note: Use this menu item with caution. If you skip to an instruction that isn't in the current function, or skip to code that expects a different program state, your program could crash.

Restart
Restart your program from the beginning. All breakpoints in your program are preserved; breakpoints in DLLs aren't.
Debug Startup
Restart your program from the beginning, but stop before system initialization. Normally the debugger puts you at the main() entry point in your application. This option allows you to break much earlier in the initialization process. This feature is useful for debugging runtime startup code, initializers, and constructors for static C++ objects.

All breakpoints in your program are preserved, but those in DLLs aren't.

Save
Save the current debugging session to a file. The file contains commands that allow the debugger to play your debugging session back to its current point in a later session. See the section ``Replay Window'' in this chapter.
Restore
Restore a saved debugging session. If you run the program with different input or if the program is a multi-threaded application, this option might not work properly since external factors may have affected program execution. See the section ``Replay Window'' in this chapter.

Undo menu

The debugger keeps an execution history as you debug your program. This history is accessible using the Undo menu. The effects of program statements as you single step through your program are recorded. So are all interactions that allow you to modify the state of your program, including modifying variable values, memory and registers. Undo and Redo let you browse backward and forward through this execution history. As you use these menu items, all recorded effects are undone or redone, and each of the debugger's windows is updated accordingly.

You can resume program execution at any previous point in the history. The program history has no size restrictions, aside from the amount of memory available to the debugger, so theoretically you could single-step through your entire program and then execute it in reverse.


Note: There are several practical problems that get in the way of this. When you single-step over a call or interrupt instruction, or let the program run normally, the debugger has no way of knowing what kind of side effects occurred. No attempt is made to discover and record these side effects, but the fact that you did step over a call is recorded.

If you try to resume program execution from a point prior to a side effect, the debugger gives you the option of continuing or backing out of the operation. Use caution if you choose to continue. If an important side effect is duplicated, your program could crash.

Of course, reversing execution over functions with no side effects is harmless and can be a useful debugging technique. If you have accidentally stepped over a call that does have a side effect, you can use Replay to restore your program state.


Unwind and Rewind move the debugger's state up and down the call stack. Like Undo, all windows are updated as you browse up and down the stack, and you can resume execution from a point up the call stack. If you try resuming from a point up the call stack, the debugger issues a warning, since it can't completely undo the effects of the call.

Unwind is particularly useful when your program crashes in a routine that doesn't contain debugging information. The strcpy() routine is a good example of this. You can use Unwind to find the call site and inspect the parameters that caused the problem.

The runtime library detects certain classes of errors and diagnoses them as fatal runtime errors. If this occurs when you're debugging, the debugger is activated and the error message is displayed. For example, throwing an exception in C++ without having a catch in place is a fatal runtime error. In C, the abort() and assert() functions are fatal errors. When this happens, you're positioned in an internal C library call. You can use Unwind to find the point in your source code that initiated the error condition.

When Unwind and Undo are used in conjunction, Undo is the primary operation and Unwind is secondary. You can Undo to a previous point in the history and then Unwind the stack. If you try to Unwind the stack first and then use Undo, the Unwind has no effect.

If you modify the machine state in any way when you're browsing backward through the execution history, all forward information from that point is discarded. If you have browsed backward over a side effect the debugger gives you the option of canceling any such operation.

The Undo menu contains the following items:

Undo
Browse backward through the program execution history.
Redo
Browse forward through the program execution history.
Unwind Stack
Move up the call stack one level.
Rewind Stack
Move down the call stack one level.
Home
Return to the currently executing location, reversing the effects of all Undo and Unwind operations.

Replay window


fig: ./images/replay.gif


Choose Replay from the Code menu to open the Replay window. This window displays each of the steps that you have performed during this debugging session that might have affected program flow. Three items are displayed for each step in the replay window:

  • the address the program was executing when you took some action that could affect the program. These actions include setting breakpoints, and tracing and modifying memory.
  • the source or assembly code found at that address
  • a command in the debugger's command language that duplicates the action you took

The most common use for Replay is when you accidentally step over a function call, or the program unexpectedly runs to completion. If this happens, you can open the Replay window and replay your debugging session up to any point prior to the last action you took.

There are special cases where Replay won't perform as expected. Since replay is essentially the same as playing your keystrokes and mouse interactions back to the debugger, your program must behave identically on a subsequent run:

  • any keyboard or mouse interaction that your program expects must be entered the same way
  • if your program expects an input file, you must run it on the same data set
  • your program shouldn't behave randomly or handle asynchronous events
  • your program shouldn't be multi-threaded. If you've just been tracing one thread, your program will replay correctly, but multiple threads might not be scheduled the same way on a subsequent run.

To replay program execution to any point, double-click on that line, or cursor to it and press Enter. Select any line and press the right mouse button to see the following popup menu items:

Goto
Replay the program until it returns to the selected level in the replay history.
Source
Position the source window at the selected line.
Assembly
Show the assembly code for the selected line.

Calls window


fig: ./images/calls.gif


Choose Calls from the Code menu to display the Calls window. This window displays the program's call stack. Each line contains the name of the function that was executing, and the source or assembly code at the call site. You can use Unwind and Rewind to obtain this information, but the Calls windows shows you the entire call stack.

To Unwind to any point in the call stack, double-click on a line, or cursor to it and press Enter. Select a line and press the right mouse button to access the following popup menu items:

Unwind
Unwind the stack to the level of the selected code. This is equivalent to using Unwind or Rewind from the Undo menu.
Break
Set a breakpoint at the return from the selected call.
Goto
Execute until the program returns from the selected call.

Threads window


fig: ./images/threads.gif


Choose Threads from the Code menu to display the Threads window. This window displays the system ID of each thread, the state of the thread, and under some operating systems, system specific information about the thread, including its name and scheduling priority. The state of each thread can be:

current
This is the thread that was running when the debugger was entered. It is the thread that hit a breakpoint or error. When you trace through the application, only the current thread is allowed to run.
runnable
This thread is allowed to run whenever you let your program run, but won't run when you trace the program.
frozen
This thread won't be allowed to run when you resume your program.
dead
Under some operating systems, threads that have been terminated still show up in the list of threads. A dead thread never executes again.

To make any thread current, double-click on it, or cursor to it and press Enter. All other debugger windows update accordingly. Press the right mouse button to access the following popup menu items:

Switch to
Make the selected thread current.
Freeze
Change the state of the selected thread to be frozen. You can't freeze the current thread.
Thaw
Change the state of the selected thread to be runnable. The current thread is always runnable.

Interrupting a running program

It isn't unusual for your code to contain an endless loop that results in the program's getting stuck in one spot. You then want to interrupt the program so that you can see where it's getting stuck.

To do this, switch focus to the debugger console and type Ctrl-Break or Ctrl-C. Alternatively, you may send any unhandled signal to the application being debugged. Consult your QNX system documentation for details.