20    Support for Remote Debugging

[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.

20.1    Implementation

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.  

DllDebugObjectRPCHook()

NAME

DllDebugObjectRPCHook() -

Synopsis

#include <HHHHH>

BOOL WINAPI DllDebugObjectRPCHook((
        BOOL fTrace,
        LPORPC_INIT_ARGS lpOrpcInitArgs );

Parameters

fTrace

TRUE if debugging is enabled, FALSE otherwise.

lpOrpcInitArgs

Typically NULL; see comments below for MAC COM debuggers or in-process debuggers.

Return Values

TRUE

The function was successful (the DLL understood and executed the request).

FALSE

The operation failed.

Description

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.

Examples

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);
 };

See Also

(),

20.2    Architectural Overview

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.

20.3    Calling Convention for Notifications

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:

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:

The pSignature member of this structure points to a sequence of bytes which contains:

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.

20.4    Notifications

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()

NAME

DebugORPCClientGetBufferSize() -

Synopsis

#include <HHHHH>

ULONG DebugORPCClientGetBufferSize((
        RPCOLEMESSAGE * pMessage,
        REFIID iid,
        void * reserved,
        IUnknown * pUnkProxyObject );

Parameters

pMessage

Pointer to identification of the method being invoked, etc.

iid

Contains the IID of the interface being called.

reserved

Reserved for future use.

pUnkProxyObject

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.

Return Values

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.

Description

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}
  };

See Also

(),  

DebugORPCClientFillBuffer()

NAME

DebugORPCClientFillBuffer() -

Synopsis

#include <HHHHH>

void DebugORPCClientFillBuffer((
        RPCOLEMESSAGE * pMessage,
        REFIID iid,
        void * reserved,
        IUnknown * pUnkProxyObject,
        void * pvBuffer,
        ULONG cbBuffer );

Parameters

pMessage

Pointer to identification of the method being invoked, etc.

iid

Contains the IID of the interface being called.

reserved

Reserved for future use.

pUnkProxyObject

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.

pvBuffer

The debug data buffer which is to be filled. Is undefined (may or may not be NULL) if cbBuffer is zero.

cbBuffer

The size of the data pointed to by pvBuffer.

Return Values

Description

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}
  };

See Also

(),  

DebugORPCServerNotify()

NAME

DebugORPCServerNotify() -

Synopsis

#include <HHHHH>

void DebugORPCServerNotify((
        RPCOLEMESSAGE * pMessage,
        REFIID iid,
        IRpcChannelBuffer * pChannel,
        void * pInterface,
        IUnknown * pUnkObject,
        void * pvBuffer,
        ULONG cbBuffer );

Parameters

pMessage

As in IRpcStubBuffer::Invoke.

iid

Contains the IID of the interface being called.

pChannel

As in IRpcStubBuffer::Invoke. The COM RPC channel implementation on the server side.

pInterface

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.

pUnkObject

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.

pvBuffer

Pointer to the incoming debug information. Is undefined (may or may not be NULL) if cbBuffer is zero.

cbBuffer

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.

Return Values

Description

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:

1.  pMessage Initialized Values

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.

See Also

(),  

DebugORPCServerGetBufferSize()

NAME

DebugORPCServerGetBufferSize() -

Synopsis

#include <HHHHH>

ULONG DebugORPCServerGetBufferSize((
        RPCOLEMESSAGE * pMessage,
        REFIID iid,
        IRpcChannelBuffer * pChannel,
        void * pInterface,
        IUnknown * pUnkObject );

Parameters

pMessage

As in IRpcStubBuffer::Invoke.

iid

Contains the IID of the interface being called.

pChannel

As in IRpcStubBuffer::Invoke. The COM RPC channel implementation on the server side.

pInterface

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.

pUnkObject

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 Values

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.

Description

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}
  };

See Also

(),  

DebugORPCServerFillBuffer()

NAME

DebugORPCServerFillBuffer() -

Synopsis

#include <HHHHH>

void DebugORPCServerFillBuffer((
        RPCOLEMESSAGE * pMessage,
        REFIID iid,
        IRpcChannelBuffer * pChannel,
        void * pInterface,
        IUnknown * pUnkObject,
        void * pvBuffer,
        ULONG cbBuffer );

Parameters

pMessage

As in IRpcStubBuffer::Invoke.

iid

Contains the IID of the interface being called.

pChannel

As in IRpcStubBuffer::Invoke. The COM RPC channel implementation on the server side.

pInterface

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.

pUnkObject

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.

pvBuffer

The debug data buffer which is to be filled. Is undefined (may or may not be NULL) if cbBuffer is zero.

cbBuffer

The size of the data pointed to by pvBuffer.

Return Values

Description

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}
  };

See Also

(),  

DebugORPCClientNotify()

NAME

DebugORPCClientNotify() -

Synopsis

#include <HHHHH>

void DebugORPCClientNotify((
        RPCOLEMESSAGE * pMessage,
        REFIID iid,
        void * reserved,
        IUnknown * pUnkProxyObject,
        HRESULT hresult,
        void * pvBuffer,
        ULONG cbBuffer );

Parameters

pMessage

Pointer to identification of the method being invoked, etc.

iid

Contains the IID of the interface being called.

reserved

Reserved for future use.

pUnkProxyObject

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.

hresult

The HRESULT of the RPC call that just occurred.

pvBuffer

Pointer to the incoming debug information. Is undefined (may or may not be NULL) if cbBuffer is zero.

cbBuffer

Size of the data pointed to by pvBuffer.

Return Values

Description

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}
  };

See Also

(),

20.5    Special Segments

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.

20.6    Registry specific information

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.

20.7    Format of Debug Information

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

Table 20-1:  Semantic Specific Information

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.

Table 20-2:  Existing Opcodes

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:

Table 20-3:  General Semantic Extents

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() (OBREFs 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 OBJREFs 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.