How to JIT debug exceptions from a COM local server in Visual C++ (198623)



The information in this article applies to:

  • Microsoft Visual C++, 32-bit Editions 6.0, when used with:
    • the operating system: Microsoft Windows NT
  • Microsoft Visual C++ .NET (2003)
  • Microsoft Visual C++ .NET (2002)
  • Microsoft Windows 2000 Professional
  • Microsoft Windows 2000 Server
  • Microsoft Windows 2000 Advanced Server
  • Microsoft Visual C++ 2005 Express Edition

This article was previously published under Q198623
Note Microsoft Visual C++ .NET (2002) supports both the managed code model that is provided by the Microsoft .NET Framework and the unmanaged native Microsoft Windows code model. The information in this article applies only to unmanaged Visual C++ code.

Note Microsoft Visual C++ 2005 supports both the managed code model that is provided by the Microsoft .NET Framework and the unmanaged native Microsoft Windows code model.

SUMMARY

When an exception (for example, an access violation) is thrown from within a COM method, the server gets the first chance to handle the exception. If the server does not handle the exception, the COM/remote procedure call (RPC) run time handles it and returns an error indicating the exception was thrown. This behavior is by design.

The result of this is that no exception thrown in a COM method [including DebugBreak()] ever goes unhandled. As a result, just-in-time (JIT) debugging of COM local servers is difficult at best.

This article presents a way to JIT debug some exceptions from a COM local server. The article also explains how to pseudo-JIT debug a COM local server for other exceptions.

MORE INFORMATION

JIT Debug a COM Local Server

Set the following registry key:
HKEY_LOCAL_MACHINE\Software\Microsoft\Ole
IgnoreServerExceptions="Y"

				
When the above key is set, the COM/RPC run time will pass the following exceptions on to the caller (for debug purposes):
  • STATUS_ACCESS_VIOLATION
  • STATUS_POSSIBLE_DEADLOCK
  • STATUS_DATATYPE_MISALIGNMENT
  • STATUS_INSTRUCTION_MISALIGNMENT
  • STATUS_ILLEGAL_INSTRUCTION
  • STATUS_PRIVILEGED_INSTRUCTION
Because the COM/RPC run time no longer handles the above types of exceptions, they go unhandled and JIT debugging is therefore possible. For any other exceptions, use the pseudo-JIT debug methods outlined below (or run the local server from within the debugger).

Pseudo-JIT Debug a COM Local Server

For this method to work, the exception generated must be continuable and must reproduce on continuation. For instance, an access violation is a continuable exception, and if the same instruction is executed twice in the same context, the exception is generated twice. For more information on generating noncontinuable exceptions, please see the Help topic for RaiseException.
  1. All functions must be wrapped in a Win32 structured exception handling try-except block; for example:
    LONG MyUnhandledExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo);
    
    #define PJITTry  \ 
        __try { \ 
            UINT __oldErrMode = SetErrorMode(0);
    
    #define PJITExcept(x) \ 
        SetErrorMode(__oldErrMode); \ 
        } __except (MyUnhandledExceptionFilter(GetExceptionInformation())) {}
    
    HRESULT MyClass::MyMethod (BSTR bstrName) {
        PJITTry {
            return MyClass::_MyMethod(bstrName);
        }
        PJITExcept {}
    
    }
    Note that if you are using C++ exception handling (try-catch), then you must provide stub functions in a different file that call the real functions so you can wrap the function call in a try-except block.

    Depending on your circumstances, the wrapper functions can be introduced in various ways. You need to think about your approach carefully, before beginning, to reduce the overall amount of work. For instance, in the example above, if MyClass had many methods, it may be easier to derive a class and override all interface methods than to rename all of the real methods:
    class MyDebugClass : public MyClass {
        //... lots of methods
        virtual HRESULT MyMethod (BSTR bstrName) {
            PJITTry {
                return MyClass::_MyMethod(bstrName);
            }
            PJITExcept {}
        }
    
    }
    If you use the STDMETHOD and STDMETHODIMP macros, it may be as easy as undefining and redefining these macros.

    There are many other ways to wrap your functions. It is not possible to provide an exhaustive list in this article.

  2. The next step is to include the function below as a separate .cpp file in your project:
    #ifndef _WIN32_WINNT
    #define _WIN32_WINNT 0x400
    #endif
    #include <windows.h>
    #include <tchar.h>
    extern "C"
    LONG MyUnhandledExceptionFilter(EXCEPTION_POINTERS *ExceptionInfo)
    {
        __try {
            CHAR AeDebuggerCmdLine[MAX_PATH];
            if(IsDebuggerPresent()) return EXCEPTION_CONTINUE_SEARCH;
            if (0 == GetProfileString(
                _T("AeDebug"),
                _T("Debugger"),
                NULL,
                AeDebuggerCmdLine,
                sizeof(AeDebuggerCmdLine)-1)
                )
            {
                return EXCEPTION_CONTINUE_SEARCH;
            }
            BOOL b;
            STARTUPINFO si;
            PROCESS_INFORMATION pi;
            CHAR CmdLine[MAX_PATH];
            DWORD Status;
            HANDLE hEvent;
            SECURITY_ATTRIBUTES sa;
            sa.nLength = sizeof(sa);
            sa.lpSecurityDescriptor = NULL;
            sa.bInheritHandle = TRUE;
            hEvent= CreateEvent(&sa,TRUE,FALSE,NULL);
            ZeroMemory(&si,sizeof(si));
            wsprintf(CmdLine,AeDebuggerCmdLine,GetCurrentProcessId(),hEvent);
            si.cb = sizeof(si);
            si.lpDesktop = _T("Winsta0\\Default");
            b =  CreateProcess(NULL,CmdLine,0,0,TRUE,0,0,0,&si,&pi);
            if ( b && hEvent) {
                Status = WaitForSingleObject(hEvent,INFINITE);
                return EXCEPTION_CONTINUE_EXECUTION;
            }
            return EXCEPTION_CONTINUE_SEARCH;
        } // end __try
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            return EXCEPTION_CONTINUE_SEARCH;
        }
    }
    						
    The caveats with this code are:
    • The code requires the exception to be repeatable. When we attach the debugger, we continue execution to cause another exception. If the continued execution doesn't cause the exception again, then we have lost the opportunity to debug.
    • The debugger doesn't stop on first-chance exceptions by default. Because this exception is handled by a RPC, we must force the debugger to stop on the first-chance notification.
  3. Force the debugger to stop on first-chance exceptions as follows:
    • In Visual C++ 2005:
      1. Start Visual Studio 2005.
      2. On the File menu, point to Open, click File, and then locate the executable for the local server.
      3. On the Debug menu, click Exceptions.
      4. In the Exceptions dialog box, click to clear the checkbox right after Win32 Exceptions under Thrown, and then click OK.
      5. On the File menu, click Close Solution.
    • In Visual C++ .NET:
      1. Start Visual Studio .NET
      2. On the File menu, point to Open, click File, and then locate the executable for the local server.
      3. On the Debug menu, click Exceptions.
      4. In the Exceptions dialog box, click to select Win32 Exceptions under Exceptions, and then click to select Break into the debugger under When the exception is thrown.
      5. On the File menu, click Close Solution.
    • In Earlier Versions of Visual C++:
      1. Start Developer Studio.
      2. On the File menu, click Open, and then locate the executable for the local server.
      3. On the Build menu, click Debug, and then click Go (or click Step Into).
      4. On the Debug menu, click Exceptions.
      5. Change all exceptions to stop always.
      6. On the Debug menu, click Stop Debugging.
      7. On the File menu, click Close Workspace. When prompted, save the workspace options.
      This method generates a workspace options file (.opt file) in the same directory as the .exe for the local server. The problem with this method is that the .opt file stores the fully qualified path of the executable, and therefore if you copy the local server to another computer, you must use the same directory structure to use the same .opt file. Otherwise, you must create a new .opt file.
Overall, this method is not quite as good as the general means of JIT debugging. When possible, run your local server from within the debugger. However, for most circumstances where JIT debugging is required, this method should be helpful. There are some circumstances (mentioned earlier) where this method does not work; in those circumstances, you must run your local server from within the debugger.

Modification Type:MajorLast Reviewed:1/5/2006
Keywords:kbDebug kbhowto KB198623 kbAudDeveloper