Debugging in Win32 - special requirements
The Debug Loop (of the debugger)
The Message Loop (of the debuggee)
How GoBug monitors events in the debuggee
How GoBug deals with exceptions
Debugging in Win32 - special requirements
A Win32 debugger needs to be especially designed to monitor and deal with system events, messages and actions. In particular, it must be able to monitor and report on the messages to and from the debuggee, since this is the main way the operating system controls and communicates with the debuggee. Messages may be sent by the system either directly to the debuggee or via the message queue (picked up in the message loop). When waiting for a return from GetMessage in the message loop, the debugger must be able to catch and monitor execution elsewhere in the debuggee caused by messages sent to window procedures. The debugger must also be able to debug multi-threaded applications, and monitor the activities of each thread and show the inter-action between threads. It must be able to display virtual memory and stack areas and register values and identify those areas of memory unique to the operating system. It must be able to trace execution into Dlls. It must be able to deal with exceptions caused by the debuggee allowing the debuggee to continue without crashing, and it must be able to report errors reported by the apis. Finally the debugger must be able to close the debuggee in a system-friendly manner.
The Debug loop (of the debugger)
When you choose a program to debug, GoBug creates a new thread which
starts the program using CreateProcess. Then GoBug goes into a debug
loop. This loop surrounds the Windows API WaitForDebugEvent and
ContinueDebugEvent.
| The API WaitForDebugEvent returns upon the happening of
the following:-
(a) after the processor executes an instruction for the debuggee (b) when a message is due to be sent to the debuggee (c) before or upon an event concerning the debuggee (for example creation of windows, change of focus etc). When WaitForDebugEvent returns, GoBug deals with it in "debug loop processing" and then calls WaitForDebugEvent again, completing the loop. |
Of particular interest here are these four facts:-
1. You can see from the above that after an instruction is executed
by the processor for the debuggee, the debuggee enters the debug
loop. When this occurs, the debuggee is completely suspended,
including all the threads of the debuggee. This is GoBug's state
when waiting for user input (F5 to F9).
 Proof that all threads are suspended when a thread is in the debug loop.
2. Despite the fact that all the debuggee's threads are suspended when
the debuggee is in the debug loop, in fact only one of the debuggee's threads
has caused the debuggee to enter the loop. This is the thread for
which the processor last executed an instruction. Further, it is
possible that no up to date information is available from the system about
the other threads.
3. To get into the debug loop the next instruction executed by the processor for any particular
thread of the debuggee, GoBug must set the trap flag in the context of
the thread in the loop. This is what GoBug does unless you press
F8 (run, hook, part log) or F9 (run in the background). If either of these keys are pressed the trap flag is cleared for that thread,
so that from that point on no debuggee's instructions for that thread will
not come into the loop. The result of this is that once you have
pressed F8 or F9, it is possible that you may not be able to regain full debug
control over the thread concerned. However control can usually be regained by setting a breakpoint or by using the hot-key.
4. You can see from the above that it is possible to have different
"run" states for different debuggee threads. For example the main
thread might be running (because you pressed F9 when that thread was in
the loop) but all the other threads might be single-stepping (trap flag
set for those each time).
The Message Loop (of the debuggee)
| This is a diagram of a typical Windows program which uses the Graphical User Interface (GUI). After the program starts, it initialises the main window and then goes into the message loop. In the loop the API GetMessage is called. Typically this API does not return until a key is pressed or there is a mouse click. When such an event occurs, the API DispatchMessage sends the key press or mouse click to the window procedure to be dealt with. It does this by calling the window procedure and then returning. |
How GoBug monitors events
in the debuggee
You can see from the above the GoBug needs to know when messages are
being sent to the debuggee, whether by the system, or by other applications
or by the debuggee itself, whether from the message loop or in some other
way. In addition to this, GoBug needs to know what events are
occurring in the debuggee, for example, whether new windows or threads
are being created or if the debuggee is loading a Dll. Some of these events are reported to GoBug's debug loop by the system as debug events. Other events and all messages are monitored by hooks which is a very small piece of code in GoBugSpy.dll and which loads within the memory context of the debuggee when the hook is required for logging. If you look at the memory areas of the application under test you will see GoBugSpy.dll if it is there. It is unusual for a Dll run by one program to load in the memory context of another, but this is
necessary for the hook to work. GoBugSpy is not loaded if you run the debuggee in the background (F9) or if the application does not rely on the system Graphical User Interface (GUI).
How GoBug closes the debuggee
In Win32 an application often needs to clear up before closing.
This might involve closing files, handles and memory, unloading Dlls or
writing to the registry or to an ini file. For this reason unless the debuggee is a console program, GoBug attempts to close the debuggee in a natural manner - by sending the message WM_QUIT. If this fails to close the debuggee, GoBug will try to call the API ExitProcess on the debuggee's behalf. If that fails, GoBug will terminate its debugging thread (which will in turn close the debuggee's main thread). If that fails too, then after a warning to you, GoBug will force the debuggee closed by calling TerminateProcess. This is not an optimum way to close a process, and can leave some resources open.
How GoBug deals with exceptions
If an exception occurs in a program which is being debugged, the system
sends the exception firstly to the debugger. This causes the debuggee to enter the debug loop. GoBug intervenes and gives you details of the exception which occurred. For more details see Exceptions caused by the debuggee.