Walk Through 1
Walk Through 1
Walk Through 1
Engine Walkthrough 1
Jackson Davis Software Engineer, Visual Studio Debugger
The Visual Studio Sample Debug Engine is intended to demonstrate what is required
to implement a custom debug engine that integrates with the Visual Studio
Debugger. It is a basic 32-bit Win32 Debugger which supports applications built in
debug mode with Visual C++. The sample makes use of the Win32 Debugger API
and the DIA symbol API to enable debugging 32-bit x86 win32 applications.
However, its usage of both is limited and is intended only as an implementation
detail of the sample.
2) A back end implemented in mixed mode C++ which interacts with the win32
debugging API and DIA, the public symbol interfaces. This project is called
Microsoft.VisualStudio.Debugger.SampleEngineWorker.
3) A Visual Studio Package which is used to launch Visual C++ projects using
the new engine. This project is named ProjectLauncher. It adds a new
command button ProjectLauncher to the Visual Studio Tools menu which
will launch a form that displays the current projects in Visual Studio that can
be launched with the new engine.
A few definitions are in order:
1) AD7 (Active Debugging 7) the name of the interfaces between the SDM and
debug engines. AD7 is defined in msdbg.idl and is imported into managed
code via Microsoft.VisualStudio.Debugger.InteropA.dll.
The interfaces in AD7 are documented on MSDN at
http://msdn2.microsoft.com/en-us/library/bb147088.aspx.
3) The SDM stands for session debug manager. It is a debug engine multiplexer.
Admittedly, thats a confusing explanation. Essentially, the SDMs job to
combine all of the events and commands for the various debug engines into
one unified stream for the UI. The debugger UI only displays one view of
what is being debugged at a time. Even if the user is debugging multiple
processes or threads, they are only looking at one of them.
Getting Started
The sample Visual Studio Debug Engine requires Microsoft Visual Studio 2013,
though there is nothing in it which cannot be made to work in many previous
versions of Visual Studio. The sample solution can be downloaded from
http://code.msdn.microsoft.com/debugenginesample.
12) Make sure your project is selected in Project To Debug and choose
Launch
13) Your application should launch using the sample engine and the
breakpoint should be hit:
If you can complete these steps, you are ready to dig into the sample source code.
An Architectural Overview
In Visual Studio, debugger control and debuggee data flow between the Debugger
UI and the Debug Engines in two directions. Requests from the user (such as launch
process, create breakpoint, continue running, ) flow from the Debugger UI,
through the SDM, and into the appropriate engine(s). Events about the state of the
debuggee process (thread create, breakpoint hit, module load) flow from the
engines, into the SDM, and into the Debugger UI. Most debug engines (including this
sample) require at least two threads: one to handle requests coming down from the
SDM and another to listen to events from the debuggee process.
There are two types of debuggee events in AD7: stopping and non-stopping. A
stopping event is an event that causes the debugger UI to enter break mode. A
good example of a stopping event is IDebugBreakpointEvent2 which is sent when
the debuggee hits a breakpoint. Non-Stopping events are events that do not cause
the debugger to enter break-mode. An example non-stopping event is
IDebugModuleLoadEvent2 which is sent when a new module is loaded into the
debuggee. Non-stopping events usually result in some visual state changing (such
as a new module in the modules window or a notification in the output window), but
the debugger does not enter break-mode.
When an engine is sending an Asynchronous event, the SDM queues the event up to
eventually be sent to the debugger UI and then returns control to the engine
immediately. If the event was a stopping event, then the engine waits to continue
execution in the debuggee until the UI asks it to continue (usually because the user
hits F5). If the event was non-stopping, the engine fires the event, and immediately
continues execution in the debuggee.
When an engine sends a synchronous event, the engine waits for the UI to respond
with a call to IDebugEngine2:: ContinueFromSynchronousEvent. The only
synchronous event in this sample is AD7ProgramDestroyEvent.
Events are sent from the engine to the SDM by passing an instance of an event
interface (such as IDebugModuleLoadEvent2) to the instance of
IDebugEventCallback which is given to the Debug Engine by the SDM when
debugging is being started. In the sample engine, AD7Engine (the root object of the
engine) holds a reference to an instance of EngineCallback which manages sending
events to the SDM. The event interfaces are implemented in AD7Events.cs. There
are four base classes that define the synchronous / stopping behavior. All the event
objects derive from one of the four base classes: AD7AsynchronousEvent,
AD7StoppingEvent, AD7SynchronousEvent, and AD7SynchronousStoppingEvent.
Since the sample engine is a 32-bit win32 debugger, it relies on the win32
debugging API to notify it about events in the debuggee. The primary function for
this api is kernel32 WaitForDebugEvent. WaitForDebugEvent returns a
DEBUG_EVENT structure for each of the supported events in the debuggee. These
include: process create, thread create, module load, exception, module unload,
thread destroy, process destroy and rip. Win32 debuggers receive these events by
calling WaitForDebugEvent in a loop. In the sample engine, the call to
WaitForDebugEvent occurs in WorkerApi.cpp WaitForAndDispatchDebugEvent which
is called in a loop in OpertionThread.cs ThreadFunc. This loop checks if a new debug
event has been sent by calling WaitForDebugEvent with a timeout. If none had been
received, it then checks to see if a new request from the user has been sent. It is
done this way because several of the debugging API functions require that they are
called on the same thread as WaitForDebugEvent.
This diagram shows some of the major objects in the sample and how they relate to
each other.
Some Important objects in the engine front-end:
AD7Engine is the root object of the sample engine. It represents the engine as well
as the process being debugged. AD7Engine maintains collections of the threads and
modules in the process being debugged.
1. Launch Visual Studio 2013 as an administrator and open the engine solution
2. Build the solution
3. Open AD7Engine.cs and set a breakpoint in the method LaunchSuspended.
a. AD7Engine.cs can be found Solution Explorer in the AD7Impl folder of
the Microsoft.VisualStudio.Debugger.SampleEngine project
4. Start Debugging
5. A new instance of Visual Studio should launch.
6. Create a new native Visual C++ console project in the new instance of visual
studio. Name it Test Project
7. Add the following code to main in TestProject
10.Click on Launch
11.The instance of Visual Studio that contains TestProject should now enter
debug mode
12.The breakpoint you added in LaunchSuspended should be hit:
13.You are now ready be begin stepping through the launch process.
14.As you can see, the code in LaunchSuspend does the following:
a. Constructs the debuggee command line from the exe and args
parameters
i. string commandLine = EngineUtils.BuildCommandLine(exe, args)
b. Creates an instance of ProcessLaunchInfo which is used to pass
information about what to launch to the back-end.
c. Creates an instance of the worker thread object which creates a the
debug event thread and sets up some ManualResetEvents used to
notify the event thread when the request thread needs it to perform an
operation
i. m_pollThread = new WorkerThread();
d. Creates a new instance of EngineCallback passing in the instance of
IDebugEventCallback2 that was passed by the sdm.
e. Requests that the event thread actually launch the process:
m_pollThread.RunOperation(new Operation(delegate
{
m_debuggedProcess = Worker.LaunchProcess(
m_engineCallback, processLaunchInfo);
}));
f. The launch must occur on the event thread per win32 debugging api
requirements. The RunOperation method of m_polllThread blocks the
current thread and uses the manual reset events to get to the other
thread. RunOpertation takes an instance of Operation as its parameter
which takes a delegate. In this case, it is an anonymous delegate that
is calling the LaunchProcess method. The delegate is executed on the
other thread.
15.To see the actual process launch, open up WorkerApi.cpp in
Microsoft.VisualStudio.Debugging.SampleEngineWorker and set a breakpoint
in Worker::LaunchProcess and Hit F5 from where you currently are in
AD7Engine::LaunchSuspended
16.Once the breakpoint in LaunchProcess is hit, you can see in the callstack,
which you are now executing on the event thread of the sample engine.
17. If you switch back to the Main Thread via the threads window, you can see
the main thread of Visual Studio is blocked waiting for the call to launch to
complete.
If you look up the callstack from where you are in Worker::LaunchProcess to the
ThreadFunc frame, you can see the debug event loop. ThreadFunc is the debug loop
for the sample engine and does the following:
1) Check for and dispatch debug events (wait timeout for new debug events set
to 50ms)
2) Check for and execute requests from the UI
3) Repeat.
Continuing on from LaunchProcess:
3. Hit F5 to run
4. The breakpoint in DispatchDebugEvent will now get hit
1. When handling the create process event, the sample debug engine will:
a. Attempt to load symbols for the primary exe in the process (which was
saved when the instance of DebuggedProcess was created).
b. Create an instance of DebuggedThread to represent the main thread in
the application
c. Save the main module and main thread objects for a later call to
DebuggedProcess::ResumeFromLaunch.
d. No debug events are sent to the UI while processing the
CREATE_PROCESS_DEBUG_EVENT for a launch. The call to
DispatchDebugEvent unwinds and leaves the debuggee broken in
anticipation of the UI calling IDebugEngineLaunch2.ResumeProcess
Now, navigate back into the C# front end, open AD7Engine.cs and set a breakpoint
in IDebugEngineLaunch2.ResumeProcess. This is the next method the debugger will
call after LaunchSuspended and is used to resume execution of the debuggee after
launch. Remember, that the handler for CREATE_PROCESS_DEBUG_EVENT did not
continue the event so the debuggee process is still suspended. Hit F5 and wait for
the breakpoint in ResumeProcess to get hit:
In this case, the anonymous delegate being passed to Operation is executing the
ResumeFromLaunch method of DebuggedProcess. To step through the call to
ResumeFromLaunch: