[Footnote 65] [Footnote 66] [Footnote 67] [Footnote 68] [Footnote 69] [Footnote 70] [Footnote 71] [Footnote 72] [Footnote 73] [Footnote 74]
The COM Library and the COM Network Protocol provide support for debugging engines on the client and the server side of a remote COM invocation to cooperate in allowing the overall application to be debugged. This section describes the runtime infrastructure provided by the Microsoft Windows implementation of the COM Library by which that is accomplished; other implementations will provide similar infrastructures, though in practice the details of such support will be highly sensitive to the mechanisms by which debugging engines are supported on the given platform. This section also specifies the standard data formats transmitted between client and server by which this cooperation is carried out.
The following a brief example of the sort of debugging session scenario which can be supported with this infrastructure.
Suppose the programmer is debugging an application with is an OLE document container, and that the application is presently stopped in the debugger at a point in the code where the container is about to invoke a method in some interface on one of its contained objects, the implementation of which happens to be in another executable. That is, the pointer that the container has in hand actually points to an occurrence of part of the remoting infrastructure known as an ``interface proxy'' (see above). Interface proxies and the rest of the remoting infrastructure are not (normally) part of the programmer's concern when debugging client and server applications, as the whole raison d'être of the RPC infrastructure is to be transparent, is to make remote object invocations appear to be local ones. Unless the programmer is debugging the remoting infrastructure himself, this should apply to debugging as well.
This perspective leads to some of the following scenarios that need to be supportable by the debugger. If the programmer Single Steps into the function invocation, then the debugger should next stop just inside the real implementation of the remote server object, having transparently passed through the RPC infrastructure. (Notice that before the Step command is executed, the remote process may not presently have the debugger [Footnote 65] attached to it, and so the act of doing the step may need to cause the debugger to attach itself.) The programmer will now be able to step line by line through the server's function. When he steps past the closing brace of the function, he should wind up back in the debugger of the client process immediately after the function call.
A similar scenario is one where we skip the incoming single step but instead, out of the blue, hit a breakpoint in the server, then start single stepping. This, too, should single step over the end of the server function back into the client process. The twist is that this time, the client debugger may not presently be running, and therefore may need to be started.
The ability for debuggers to support scenarios such as these is provided
by hooks in the client and server side RPC infrastructure.
If requested by
the
debugger, at certain important times, these hooks inform the debugger of the
fact that a transmission of a remote call about to be made or that transmission
of return values is about to occur.
That is, when the COM Library is about
to
make or return from a call to an object, it notifies the debugger of what
is
happening, so that the debugger can take any special actions it desires.
BOOL WINAPI DllDebugObjectRPCHook((
#include <HHHHH>
BOOL fTrace,
LPORPC_INIT_ARGS lpOrpcInitArgs
);
TRUE
if debugging is enabled,
FALSE
otherwise.
Typically
NULL
; see comments below
for MAC COM debuggers or in-process debuggers.
TRUE
The function was successful (the DLL understood and executed the request).
FALSE
The operation failed.
This function is to be exported by name from one or more DLLs that wish
to
be informed when from the user's point of view that debugging is engaged.
Debuggers will should call this function to inform each of their loaded DLLs
that export this function as to whether they are presently being debugged
or
not.
When the debugger wants to enable debugging, it calls
DllDebugObjectRpcHook
with
fTrace=TRUE
and when it wants to disable
it, it calls
DllDebugObjectRpcHook
with
fTrace=FALSE
.
When enabled, debugging
support such as the tracing described herein should be enabled.
Certain of the COM Library DLLs, for example, implement this function. When debugging is enabled, they turn on what is here called COM remote debugging, and which is the focus of this section.
The second argument points to an
ORPC_INIT_ARGS
structure
whose definition is given below.
The
pvPSN
member is used
only on the Macintosh, where the calling debugger is required in this field
to
pass the process serial number of the debuggee's process.
On other systems
pvPSN
should be
NULL
.
The
lpIntfOrpcDebug
member is a pointer to an interface.
This
is used by in-process debuggers and is discussed in more detail later.
Debuggers
that are neither in-process debuggers nor are Macintosh debuggers should pass
NULL
for
lpIntfOrpcDebug.
As one would expect, a debugger calls
DllDebugObjectRPCHook
within the context (that is, within the process) of the relevant debuggee.
Thus,
the implementation of this function most often will merely store the arguments
in
global DLL-specific state.
Further, as this function is called from the debugger, the function can be called when the DLL in which it is implemented is in pretty well any state; no synchronization with other internal DLL state can be relied upon. Thus, it is recommended that the implementation of this function indeed do nothing more than set internal global variables.
typedef struct ORPC_INIT_ARGS { IOrpcDebugNotify __RPC_FAR * lpIntfOrpcDebug; void * pvPSN; // contains ptr to Process Serial No. for Mac COM debugging. DWORD dwReserved1; // For future use, must be 0. DWORD dwReserved2; // For future use, must be 0. } ORPC_INIT_ARGS; typedef ORPC_INIT_ARGS __RPC_FAR * LPORPC_INIT_ARGS; interface IOrpcDebugNotify : IUnknown { VOID ClientGetBufferSize(LPORPC_DBG_ALL); VOID ClientFillBuffer(LPORPC_DBG_ALL); VOID ClientNotify(LPORPC_DBG_ALL); VOID ServerNotify(LPORPC_DBG_ALL); VOID ServerGetBufferSize(LPORPC_DBG_ALL); VOID ServerFillBuffer(LPORPC_DBG_ALL); };
When COM remote debugging is enabled, there are a total of six notifications that occur in the round-trip of one COM RPC call: three on the client side and three on the server side. The overall sequence of events is as follows.
Suppose the client has an interface pointer
pFoo
of type
IFoo*
which happens to be a proxy for another object in
a
remote server process.
interface IFoo : IUnknown { HRESULT Func(); }; IFoo *pFoo;
When the client invokes
pFoo->Func
, it executes code
in the
interface proxy.
This code is responsible for marshaling the arguments into
a buffer,
calling the server, and unmarshaling the return values.
To do so, it draws
on the
services of an
IRpcChannelBuffer()
instance with which
it was
initialized by the COM Library.
To get the buffer, the interface proxy calls
IRpcChannelBuffer::GetBuffer
,
passing in (among other things) the requested size for the buffer.
Before
actually
allocating the buffer, the
GetBuffer
implementation (normally
[Footnote 66]) checks to see if debugging
is enabled
per
DllDebugObjectRPCHook
.
If so, then the channel calls
DebugORPCClientGetBufferSize
(see below for details) to
inform the
debugger that an COM RPC call is about to take place and to ask the debugger
how many
bytes of information
it
would like to transmit to the
remote server
debugger.
The channel then, unbeknownst to the interface proxy, allocates
a buffer with
this many additional bytes in it.
The interface proxy marshals the incoming arguments in the usual way
into the
buffer that it received, then calls
IRpcChannelBuffer::SendReceive
.
Immediately on function entry, the channel again checks to see if debugging
is
enabled.
If so, then it calls
DebugORPCClientFillBuffer
passing in the
pointer to (the debugger's part of) the marshaling buffer.
The debugger will
write some information into the buffer, but this need be of no concern to
the
channel implementation other than that it is to ferry the contents of the
buffer to the
server debugger.
Once
DebugORPCClientFillBuffer
returns,
the
channel implementation of
SendReceive
proceeds as in the
normal case.
We now switch context in our explanation here to the server-side RPC
channel.
Suppose that it has received an incoming call request and has done what it
normally
does just up to the point where it is about to call
IRpcStubBuffer::Invoke
, which when will cause the arguments
to be
unmarshaled, etc.
Just before calling
Invoke
, if there
was any
debugger information (i.e.: it exists in the incoming request and is of non-zero
size)
in the incoming request
or
if debugging is presently
already
enabled per
DllDebugObjectRPCHook
(irrespective of the
presence or size of
the debug info), then the channel is to call
DebugORPCServerNotify
[Footnote 67].
The act of calling this function may in fact
start
a
new debugger
if needed and attach it to this (the server) process; however, this need not
be of concern
to the channel implementation.
Having made the request, the channel proceeds
to call
Invoke
as in the normal case.
The implementation of
Invoke
will unmarshal the incoming
arguments,
then call the appropriate method on the server object.
When the server object
returns,
Invoke
marshals the return values for transmission back
to the client.
As on the client side, the marshaling process begins by calling
IRpcChannelBuffer::GetBuffer
to get a marshaling buffer.
As on the client
side, the server side channel
GetBuffer
implementation
when being debugged
(per the present setting of
DllDebugObjectRPCHook
,
not
per the presence of the incoming debug info) asks the debugger how many bytes
it wishes
to transmit back to the client debugger.
The channel allocates the buffer
accordingly and
returns it to the
Invoke
implementation who marshals the
return values
into it, then returns to its caller.
The caller of
IRpcStubBuffer::Invoke
then checks
to see if he is presently
being debugged.
If so, then he at this time calls
DebugORPCServerFillBuffer
,
passing in the pointer to the debug-buffer that was allocated in the (last,
should there erroneously be more than one) call to
GetBuffer
made inside
Invoke
; should no such call exist, and thus there is no
such buffer,
NULL
is passed.
[Footnote 68]
The bytes
written into the buffer (if any) by the debugger are ferried to the client
side.
We now switch our explanatory context back to the client side.
Eventually
the client channel
either receives a reply from the server containing the marshaled return values
(and possibly
debug info), receives an error indication from the server RPC infrastructure,
or decides to
stop waiting.
That is, eventually the client channel decides that it is about
to return from
IRpcChannel::SendReceive
.
Immediately before doing so,
it checks to see if
it is either already presently being debugged
or
if in
the reply it
received any (non-zero sized) information from the server debugger.
If so,
then it calls
DebugORPCClientNotify
, passing in the server-debugger's
info if it has any;
doing so may start and attach the debugger if needed.
The channel then returns
from
SendReceive
.
The preceding discussion discussed the COM RPC debugging
architecture in terms of six of debugger-notification APIs (DebugORPC
...).
However, rather than being actual API-entry points in a a static-linked or
dynamically-linked library, these notifications use an somewhat unusual calling
convention to communicate with the notification implementations, which are
found inside debugger products.
This somewhat strange calling convention is
used for the following reasons:
Two of the six notifications need to start and attach the debugger if it is not already attached to the relevant process.
The convention used transitions into the debugger code with the least possible disturbance of the debuggee's state and executing the minimal amount of debuggee code. This increases robustness of debugging.
The debugger is necessarily equipped to deal with concurrency issues of other threads executing in the same process. Therefore, it is important to transition to the debugger as fast as possible to avoid inadvertent concurrency problems.
The actual calling convention used is by its nature inherently processor
and
operating-system specific.
On Win32 implementations, the default calling
convention for notifications takes the form of a software exception, which
is
raised by a call to the
RaiseException
Win32 API:
VOID RaiseException( DWORD dwExceptionCode, // exception code DWORD dwExceptionFlags, // continuable exception flag DWORD cArguments, // number of arguments in array CONST DWORD * lpArguments // address of array of arguments );
As used here, the arguments to this raised exception call in order are:
dwExceptionCode
: An exception code
EXCEPTION_ORPC_DEBUG
(0x804F4C45) is used.
The debugger should recognize this exception as a special
one indicating a
COM RPC debug notification.
dwExceptionFlags: This is zero to indicate a continuable exception.
cArguments: One.
lpArguments: The array contains one argument.
This argument is a pointer to a
structure which contains the notification specific information that the COM
RPC
system passes to the debugger.
The definition of this structure
ORPC_DBG_ALL
is
given below.
The same structure is used for all the notifications.
The
structure is just the union of the arguments of the six debugger notification
APIs.
For a particular notification not all the fields in the structure are
meaningful and those that are not relevant have undefined values; details
on
this are below:
typedef struct ORPC_DBG_ALL { BYTE * pSignature; RPCOLEMESSAGE * pMessage; const IID * iid; void * reserved1; void * reserved2; void * pInterface; IUnknown * pUnkObject; HRESULT hresult; void * pvBuffer; ULONG cbBuffer; ULONG * lpcbBuffer; void * reserved3; } ORPC_DBG_ALL;
The
pSignature
member of this structure points to
a sequence of bytes which
contains:
A four-byte sanity-check signature of the ASCII characters
``MARB
'' in
increasing memory order
[Footnote 69].
A 16-byte GUID indicating which notification this is. Each of the six notifications defined here has a different GUID. More notifications and corresponding GUIDs can be defined in the future and be known not to collide with existing notifications.
A four-byte value which is reserved for future use.
This value
is
NULL
currently.
The notifications specified here pass their arguments by filling in the appropriate structure members. See each notification description for details.
Using software exceptions for COM debugging notifications is inconvenient
for
``in-process'' debugging.
In-process debuggers can alternately get these
notifications via direct calls into the debugger's code.
The debugger which
wants to be notified by a direct call passes in an
IOrpcDebugNotify()
interface
in the
LPORPC_INIT_ARGS
argument to
DllDebugObjectRPCHook
.
If this interface pointer is available, COM makes the debug notifications
by calling the methods
on this interface.
The methods all take an
LPORPC_DBG_ALL
as the only
argument.
The information passed in this structure is identical to that passed
when the notification is done by raising a software exception.
What follows is a detailed description of each of the relevant notifications.
Note that in the network case, depending on the notification in question
the
byte order used may be different than that of the local machine.
The byte
order, etc., of the incoming data is provided from the
dataRep
contained the passed
RPCOLEMESSAGE
structure.
Though each function is documented here for purely historical reasons
as if it
were in fact a function call, we have seen above that this is not the case.
Unless otherwise specified, the name of the argument to the
DebugORPC
...
notification call is the same as the name of the structure member in
ORPC_DBG_ALL
used to pass it to the debugger.
So for example
the
pMessage
argument of the
DebugORPCClientGetBufferSize()
notification is passed to the debugger in the
pMessage
structure member of
ORPC_DBG_ALL
.
We trust that readers will not be too confused
by this, and
apologize profusely should this prove not to be the case.
-
DebugORPCClientGetBufferSize()
ULONG DebugORPCClientGetBufferSize((
#include <HHHHH>
RPCOLEMESSAGE * pMessage,
REFIID iid,
void * reserved,
IUnknown * pUnkProxyObject
);
Pointer to identification of the method being invoked, etc.
Contains the IID of the interface being called.
Reserved for future use.
Pointer to an
IUnknown()
(no particular one) on the
object
involved in this invocation.
May legally be
NULL
, though
this reduces debugging
functionality.
Further, this and like-named parameters must consistently be
either
NULL
or non-NULL
in all notifications
in a given client
side COM RPC implementation.
The number of bytes that the client debugger wishes to transmit to the
server debugger.
May legitimately be zero, which indicates that no information need be transmitted.
The
lpcbBuffer
field in the
ORPC_DBG_ALL
structure holds a pointer to a
ULONG
.
The debugger writes
the number
of bytes it wants to transmit with the packet in that location.
Called on the client side in
IRpcChannel::GetBuffer
.
The GUID for this notification is 9ED14F80-9673-101A-B07B-00DD01113F11:
GUID __private_to_macro__ = { /* 9ED14F80-9673-101A-B07B-00DD01113F11 */ 0x9ED14F80, 0x9673, 0x101A, 0xB0, 0x7B, {0x00, 0xDD, 0x1, 0x11, 0x3F, 0x11} };
void DebugORPCClientFillBuffer((
#include <HHHHH>
RPCOLEMESSAGE * pMessage,
REFIID iid,
void * reserved,
IUnknown * pUnkProxyObject,
void * pvBuffer,
ULONG cbBuffer
);
Pointer to identification of the method being invoked, etc.
Contains the IID of the interface being called.
Reserved for future use.
Pointer to an
IUnknown()
(no particular one) on the
object
involved in this invocation.
May legally be
NULL
, though
this
reduces debugging functionality.
Further, this and like-named parameters must
consistently be either
NULL
or non-NULL
in all notifications in a given client side COM RPC implementation.
The debug data buffer which is to be filled.
Is undefined (may
or may not be
NULL
) if
cbBuffer
is
zero.
The size of the data pointed to by pvBuffer.
Called on the client side on entry to
IRpcChannel
::SendReceive
.
See the above overview for further details.
The GUID for this notification is DA45F3E0-9673-101A-B07B-00DD01113F11:
GUID __private_to_macro__ = { /* DA45F3E0-9673-101A-B07B-00DD01113F11 */ 0xDA45F3E0, 0x9673, 0x101A, 0xB0, 0x7B, {0x00, 0xDD, 0x01, 0x11, 0x3F, 0x11} };
void DebugORPCServerNotify((
#include <HHHHH>
RPCOLEMESSAGE * pMessage,
REFIID iid,
IRpcChannelBuffer * pChannel,
void * pInterface,
IUnknown * pUnkObject,
void * pvBuffer,
ULONG cbBuffer
);
As in
IRpcStubBuffer::Invoke
.
Contains the IID of the interface being called.
As in
IRpcStubBuffer::Invoke
.
The COM RPC channel
implementation on the server side.
Contains the pointer to the COM interface instance which contains the
pointer to the
method that will be invoked by this particular remote procedure call.
Debuggers
can
use this information in conjunction with the
iMethod
field
of the
pMessage
structure to get to the address of the method
to be invoked.
Must not be
NULL
.
This pointer is currently
NULL
.
In the future this
might
be used to pass the controlling
IUnknown()
of the server
object whose method is being invoked.
Pointer to the incoming debug information.
Is undefined (may or may
not be
NULL
) if
cbBuffer
is zero.
Size of the data pointed to by pvBuffer. May be zero, but (as described above) a size of zero can only passed if debugging is already enabled.
Called on the server side immediately before calling
IRpcStubBuffer::Invoke
to inform it that there is an
incoming request.
Will start the debugger in this process if need be.
See
the above overview for further details.
The GUID for this notification is 1084FA00-9674-101A-B07B-00DD01113F11:
GUID __private_to_macro__ = { /* 1084FA00-9674-101A-B07B-00DD01113F11 */ 0x1084FA00, 0x9674, 0x101A, 0xB0, 0x7B, {0x00, 0xDD, 0x01, 0x11, 0x3F, 0x11} };
On entry, the members of pMessage are set as follows:
Member Name |
Value on entry to Invoke |
reserved members | Indeterminate. These members are neither to be read nor to be changed by the callee. |
dataRepresentation | Indicates the byte order, etc., of the client debugger. |
pvBuffer | Points to a buffer which contains the marshaled
incoming arguments.
In the case that there are no such arguments (i.e.:
cbBuffer
== 0),
pvBuffer
may be
NULL , but will not
necessarily be so. |
cbBuffer | Size in bytes of the memory buffer to which
pvBuffer
points.
If
pvBuffer
is
NULL ,
then
cbBuffer
will be zero (but the converse is not necessarily
true, as was
mentioned in
pvBuffer). |
iMethod | The zero-based method number in the interface which is being invoked. |
rpcFlags | Indeterminate. Neither to be read nor to be changed by the callee. |
-
DebugORPCServerGetBufferSize()
ULONG DebugORPCServerGetBufferSize((
#include <HHHHH>
RPCOLEMESSAGE * pMessage,
REFIID iid,
IRpcChannelBuffer * pChannel,
void * pInterface,
IUnknown * pUnkObject
);
As in
IRpcStubBuffer::Invoke
.
Contains the IID of the interface being called.
As in
IRpcStubBuffer::Invoke
.
The COM RPC channel
implementation on the server side.
Contains the pointer to the COM interface instance which contains the
pointer to the
method that will be invoked by this particular remote procedure call.
Debuggers
can
use this information in conjunction with the
iMethod
field
of the
pMessage
structure to get to the address of the method
to be invoked.
Must not be
NULL
.
This pointer is currently
NULL
.
In the future this
might
be used to pass the controlling
IUnknown()
of the server
object whose method is being invoked.
return value
ULONG
the number of bytes that the client
debugger wishes to
transmit to the server debugger.
May legitimately be zero, which indicates
that
no information need be transmitted.
Value is actually returned through
lpcbBuffer
member of an
ORPC_DBG_ALL
.
Called on the server side from within IRpcChannelBuffer::GetBuffer
.
See
the above overview for further details.
The GUID for this notification is 22080240-9674-101A-B07B-00DD01113F11:
GUID __private_to_macro__ = { /* 22080240-9674-101A-B07B-00DD01113F11 */ 0x22080240, 0x9674, 0x101A, 0xB0, 0x7B, {0x00, 0xDD, 0x01, 0x11, 0x3F, 0x11} };
void DebugORPCServerFillBuffer((
#include <HHHHH>
RPCOLEMESSAGE * pMessage,
REFIID iid,
IRpcChannelBuffer * pChannel,
void * pInterface,
IUnknown * pUnkObject,
void * pvBuffer,
ULONG cbBuffer
);
As in
IRpcStubBuffer::Invoke
.
Contains the IID of the interface being called.
As in
IRpcStubBuffer::Invoke
.
The COM RPC channel
implementation on the server side.
Contains the pointer to the COM interface instance which contains the
pointer to the
method that will be invoked by this particular remote procedure call.
Debuggers
can
use this information in conjunction with the
iMethod
field
of the
pMessage
structure to get to the address of the method
to be invoked.
Must not be
NULL
.
This pointer is currently
NULL
.
In the future this
might
be used to pass the controlling
IUnknown()
of the server
object whose method is being invoked.
The debug data buffer which is to be filled.
Is undefined (may
or may not be
NULL
) if
cbBuffer
is
zero.
The size of the data pointed to by pvBuffer.
Called on the server side immediately after calling IRpcStubBuffer::Invoke. See the above overview for further details.
The GUID for this notification is 2FC09500-9674-101A-B07B-00DD01113F11:
GUID __private_to_macro__ = { /* 2FC09500-9674-101A-B07B-00DD01113F11 */ 0x2FC09500, 0x9674, 0x101A, 0xB0, 0x7B, {0x00, 0xDD, 0x01, 0x11, 0x3F, 0x11} };
void DebugORPCClientNotify((
#include <HHHHH>
RPCOLEMESSAGE * pMessage,
REFIID iid,
void * reserved,
IUnknown * pUnkProxyObject,
HRESULT hresult,
void * pvBuffer,
ULONG cbBuffer
);
Pointer to identification of the method being invoked, etc.
Contains the IID of the interface being called.
Reserved for future use.
Pointer to an
IUnknown()
(no particular one) on the
object
involved in this invocation.
May legally be
NULL
, though
this
reduces debugging functionality.
Further, this and like-named parameters must
consistently be either
NULL
or non-NULL
in all notifications in a given client side COM RPC implementation.
The
HRESULT
of the RPC call that just occurred.
Pointer to the incoming debug information.
Is undefined (may or may
not be
NULL
) if
cbBuffer
is zero.
Size of the data pointed to by pvBuffer.
Called on the client side immediately before returning from
IRpcChannelBuffer::SendReceive
.
See the above
overview for further details.
The GUID for this notification is 4F60E540-9674-101A-B07B-00DD01113F11:
GUID __private_to_macro__ = { /* 4F60E540-9674-101A-B07B-00DD01113F11 */ 0x4F60E540, 0x9674, 0x101A, 0xB0, 0x7B, {0x00, 0xDD, 0x01, 0x11, 0x3F, 0x11} };
The COM Library system DLLs have code in specially named segments
(sections in
COFF
terminology) to aid debuggers.
The remoting
code in the COM interface proxy and interface stub DLLs and other appropriate
parts
of the runtime are put in segments whose name begins with
``.orpc
''
[Footnote 70].
These segments are henceforth referred to as
.orpc
segments.
A transition of the instruction pointer from a non
.orpc
segment
to a
.orpc
segment indicates that
the program control is entering the RPC layer.
On the client side such a
transition implies that a RPC call is about to happen.
[Footnote 71]
On the server side if a function is returning back to a
.orpc
segment
it implies that the call is going to return back to the client
side.
Application writers who write their own remoting code can also avail
of
this feature by putting their remoting specific code in a
.orpc
segment.
Debuggers can use this naming convention regarding which code lies in
COM RPC
to aid in their user interface as to what code they choose to show the user
and
what code they do not.
When the debugger reaches the code address after
handling the
DebugOrpcServerNotify
exception it should
check if it is still
in a
.orpc
segment.
This implies that the instruction pointer
is still in code
that to the programmer is part of the local-remote transparency magic provided
by COM, and so should be skipped by the debugger.
Similar behavior on the client side after the
DebugOrpcClientNotify
exception
is also desirable.
COM RPC debuggers make use of this mechanism in order to start the debugging
of
a client or server application that is not presently being debugged.
A common
scenario is that of a user wanting to step into a RPC call as she is debugging.
The client side debugger is notified about the RPC call and sends debugger
specific information with the packet.
A
DebugOrpcServerNotify
notification is
raised in the server process.
If the server application is already being
debugged, it recognizes this as a COM RPC notification and handles it.
However
if the server application is not being debugged, the system will launch the
debugger specified in the
AeDebug
entry.
The debugger will
then get the
exception notification and handle it.
To avoid having malicious clients being able to force the debugging
of a remote
server, additional safeguards are required.
The COM RPC system checks that
the
registry key
DebugObjectRPCEnabled
exists on the system.
[Footnote 72]
If this key does not exist, the debug notifications are disabled.
Thus, debugging
will only take place if explicit action has been
taken on a given machine to enable it, and so a remote client cannot cause
debugging (and thus denial of service) to occur on an otherwise secure
machine.
The client side debugger should also ensure that the
AeDebug
\Debugger entry on its
machine is set appropriately.
Before sending any notification, COM sets the
AeDebug
\Auto
entry to 1.
This is
done in order that the system does not put up a dialog box to ask the user
if
she wants to debug the server application.
Instead it directly launches the
debugger.
The scenario where the user steps out of the server application into to a client application which is not being debugged currently is symmetrically identical the preceding insofar as launch of the debugger is concerned.
This section discusses the format of the debug information which
the debugger puts into the buffer in the
DebugORPCClientFillBuffer()
and
DebugORPCServerFillBuffer()
calls.
The structure of this
data is as follows, here
specified in an IDL-like manner.
[Footnote 73]
For historical reasons, this structure has 1-byte alignment of its internal
members.
Again, for historical reasons, the data is always transmitted in
little-endian byte order.
#pragma pack(1) // this structure defined with 1-byte packing alignment struct { DWORD alwaysOrSometimes; // controls spawning of debugger BYTE verMajor; // major version BYTE verMinor; // minor version DWORD cbRemaining; // inclusive of byte count itself GUID guidSemantic; // semantic of this packet [switch_is(guidSemantic)] union { // semantic specific information // case ``step'' semantic, guid = 9CADE560-8F43-101A-B07B-00DD01113F11 BOOL fStopOnOtherSide; // should single step or not? // case ``general'' semantic, guid = D62AEDFA-57EA-11ce-A964-00AA006C3706 USHORT wDebuggingOpCode; // should single step or not, etc. USHORT cExtent; // offset=28 BYTE padding[2]; // offset=30, m.b.z. [size_is(cExtent)] struct { ULONG cb; // offset=32 GUID guidExtent; // the semantic of this extent [size_is(cb)] BYTE *rgbData; }; }; }
The first
DWORD
in the debug packet has a special
meaning assigned to it.
The rest of the debug packet is treated as a stream of bytes by COM and is
simply passed
across the channel to the debugger on the other side.
If the first
DWORD
contains the value
ORPC_DEBUG_ALWAYS
(this is a manifest
constant defined in
the header files) then COM will
always
raise the notification
on the
other side (use of the four bytes ``MARB
'' is for historical
reasons synonymous
with use of
ORPC_DEBUG_ALWAYS
).
If the first
DWORD
in the
debug packet contains the value
ORPC_DEBUG_IF_HOOK_ENABLED
,
then the
notification is raised on the other side of the channel only if COM debugging
has been
enabled in that context; that is, only if
DllDebugObjectRPCHook
has
been called in that process with
fTrace
=
TRUE
.
It is the debugger's responsibility to include enough memory for the first
DWORD
in its response to the
DebugOrpcClientGetBufferSize
or
DebugOrpcServerGetBufferSize
notifications.
The two bytes immediately following the initial
DWORD
contain the major and
minor version numbers of the data format specification.
For packets in the format of the current major version, this is followed by
A
DWORD
which holds the count of bytes
that follow in this data, and which is
inclusive of this byte count itself.
A GUID that identifies the semantic of the packet.
Semantic specific information. The layout of this information is dependent on the GUID that specifies the semantic. These are as follows:
Semantic |
Meaning |
Step | This semantic indicates that the single stepping is to be performed or not. The GUID of this semantic is 9CADE560-8F43-101A-B07B-00DD01113F11. The data of this semantic consists of a boolean value which indicates in the "step out of a server" case whether execution should continue once the other side is reached or one should remain stopped. |
General | This semantic, which has GUID D62AEDFA-57EA-11ce-A964-00AA006C3706, allows for series of tagged bags of data to be passed. Each is byte counted, and has associated with it a GUID. wDebuggingOpCode allows for one of a series of operations to be specified. |
Existing-defined opcodes are as follows. Future opcodes are to be allocated by a central coordinating body.
Opcode |
Meaning
|
0x0000 | No operation |
0x0001 | Single step, stop on the other side, as in the ``Step'' semantic. |
Extents presently defined for use in the General semantic are as follows:
Extent |
Meaning |
Interface pointer |
This semantic has GUID 53199051-57EB-11ce-A964-00AA006C3706.
The contents of
rgbData
for this extent is simply an
OBJREF ,
which is the data structure that describes a marshaled interface pointer,
the data that
results from calling
CoMarshalInterface()
(OBREF s are
described later in this document).
Usually, this
OBJREF
is either the
self-enclosed
LONGOBJREF
variation or a custom-marshaled
variation, but
this is not required.
The
LONGOBJREF
usually contains a
reference count
of zero, allowing this information to be freely discared without a leakage
of state.
Remember
that
OBJREF s are always in little-endian byte order.
An
OBJREF
is converted into its corresponding interface pointer using
CoUnmarshalInterface() . |
With the Interface Pointer extent, an object can be created in the source
debugger's
space that relates to the call being made.
It can then be marshaled, again,
in
the source debugger's process, not the source debuggee; this yields an
OBJREF
.
The
OBJREF
is then transmitted in the course of the call
as an extent in the
passed debug information.
On the destination side, it is conveyed to the
destination debugger, who unmarshals it in its process.
The result is a COM
remoting connection from the source debuggers process to the destination
debugger's process that is semantically tied to a particular COM call that
needs to be debugged.
Interfaces on this object can be then be used to provide stack walk-backs,
remote
memory manipulation, or other debugging functionality.