Service Interruptus

The ManuSoft web server has been humming along steadily for the last 7 years on an old Compaq server running Windows 2008. You may have noticed that it’s currently offline. Actually the server is still humming along just fine – it’s just that nobody can connect to it.

I had some connections with the IT guy at a local printing business, and for practically no cost I had been using two static IP addresses over their T1 connection. Unfortunately the IT guy has since moved on, leaving nobody there with the ability to manage my connection. My sweet deal was destined to collapse eventually. That’s exactly what happened last week when they had some configuration changes made to their network.

I’ve now moved this blog to the cloud (Windows Azure), and I will be moving the rest of the services to the cloud over the next week or two. Wish me luck!

Writing a Custom Debugger on Windows

Recently I had to debug an intermittent access violation exception in one of my AutoCAD plug-ins. I needed to get the exception while the debugger was attached so I could break the process and analyze the state of memory before AutoCAD’s global exception handler got control. Unfortunately, sometimes it took several hundred runs before the exception occurred.

To make matters worse, AutoCAD was throwing exceptions on shutdown that had nothing to do with my code. If I started AutoCAD manually under the Visual Studio debugger, I had to dismiss those shutdown exceptions manually every time. I needed a way to script the task of starting AutoCAD under the debugger hundreds of times in a row until the exception finally occurred.

After some futile attempts to script the Visual Studio debugger, I decided to just write my own custom debugger. With my own custom debugger, I could easily write code to decide at runtime which exceptions to ignore and which were of interest. The exception I was interested in always occurred while accessing a memory location that ended in 0x30, so my custom debugger ignores access violations that don’t fit the pattern.

I had also planned to see if I could suspend the main thread, detach my custom debugger, and manually attach the Visual Studio debugger when my target exception was triggered so that I could utilize the more advanced VS debugger UI. In the end I never got around to trying the debugger switch because I discovered the problem by simply displaying some relevant details in a simple message box when the target exception occurred.

As you can see, I hardcoded the application path and command line, which includes a script file that simply quit AutoCAD after it started. I then used a batch file to run the custom debugger in a loop, thereby starting and quitting AutoCAD each time through the loop. I made this debugger a console app, but it could just as well be a window app.

I don’t claim to know anything about writing debuggers; this is just something I cobbled together after doing a bit of searching on the web. I decided to share the code in case someone else runs into a similar debugging scenario.

int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory( &pi, sizeof(pi) );
static TCHAR szCmdLine[] = _T(" /nologo /b C:\Test\quit.scr");
CreateProcess ( _T("C:\Program Files\Autodesk\AutoCAD 2014\acad.exe"), szCmdLine, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL,NULL, &si, &pi );

DWORD dwContinueStatus = DBG_CONTINUE;
DEBUG_EVENT debug_event = {0};
bool bFirstBreakpoint = false;
bool bContinueDebugging = true;
while( bContinueDebugging )
{
if( !WaitForDebugEvent( &debug_event, INFINITE ) )
return 0;
//process event
switch( debug_event.dwDebugEventCode )
{
case EXIT_PROCESS_DEBUG_EVENT :
{
bContinueDebugging = false;
}
break;

case EXCEPTION_DEBUG_EVENT:
{
dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
EXCEPTION_DEBUG_INFO& exception = debug_event.u.Exception;
switch( exception.ExceptionRecord.ExceptionCode )
{
case STATUS_BREAKPOINT:
{
if( !bFirstBreakpoint )
{
bFirstBreakpoint = true;
break;
}
dwContinueStatus = DBG_EXCEPTION_HANDLED;
DebugActiveProcessStop(pi.dwProcessId);
bContinueDebugging = false;
MessageBox(NULL,_T("Debug break!"), _T("Breakpoint"), MB_OK);
}
break;

case STATUS_ACCESS_VIOLATION:
if( exception.dwFirstChance == 1 )
{
if( ((DWORD_PTR)exception.ExceptionRecord.ExceptionAddress & 0xFF) == 0x30 )
{
DebugActiveProcessStop(pi.dwProcessId);
DWORD64 Rip = (DWORD64)-1;
HANDLE hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, debug_event.dwThreadId );
CONTEXT Context;
Context.ContextFlags = CONTEXT_ALL;
if( GetThreadContext(hThread, &Context) )
Rip = Context.Rip;
MEMORY_BASIC_INFORMATION mbi = { 0 };
VirtualQueryEx( pi.hProcess, exception.ExceptionRecord.ExceptionAddress, &mbi, 0x36 );
TCHAR szMsg[4096];
_stprintf_s( szMsg, _T("Access violation (%016p) at address %016prnMem State:rnBaseAddress=%016prnAllocationBase=%016prnAllocationProtect=%08xrnRegionSize=%08xrnState=%08xrnProtect=%08xrnType=%08x"), exception.ExceptionRecord.ExceptionCode, exception.ExceptionRecord.ExceptionAddress, mbi.BaseAddress, mbi.AllocationBase, mbi.AllocationProtect, mbi.RegionSize, mbi.State, mbi.Protect, mbi.Type );
MessageBox( NULL, szMsg, _T("Exception"), MB_OK );

}
bContinueDebugging = false;
}
break;

default:
break;
}
}
break;
}
ContinueDebugEvent( debug_event.dwProcessId, debug_event.dwThreadId, dwContinueStatus );
}

return 0;
}

AutoCAD 2015: Managing the Application Manager

AutoCAD 2015 includes a new feature called Application Manager. I’m sure it serves a lofty purpose, but it comes across a lot like the slimy Norton and Adobe updaters that are really just Trojans in disguise. It gets installed by default, with no option to prevent installation. To Autodesk’s credit, they do provide instructions for preventing installation of Application Manager, and instructions for removing it after the fact. Uninstalling requires several additional clicks, as if they really, really want you to think twice before taking such a drastic measure.

I don’t want anything starting when I log into Windows except the bare minimum, so I uninstalled Application Manager forthwith. It can be installed and started manually if I decide to use it later.


appmgrsettings

If you decide to use Application Manager, there are some configurable settings. There is even a UI for most of the settings (such as disabling the automatic startup), but there’s a catch: to use the UI for changing settings, you first have to agree to the Autodesk Privacy Statement (and give Autodesk access to information about your installed software). I’m sure this is just an oversight, but the paranoid will not find it comforting.

Application Manager settings are stored in a plain text file, located by default at:

“%AppData%AutodeskAutodesk Application Manager.ini”

If you don’t want to agree to Autodesk’s terms, you can still change settings by editing the .ini file in a text editor like Notepad (just enter the file path above in Start -> Run). For example, change the line to AutoRun=false if you want to disable “Automatically start when you log into the computer”. Note that this setting is somewhat misleading: Application Manager always starts regardless, but it quickly exits again if AutoRun is set to false. If you already agreed to the privacy policy, but have since changed your mind, you can set PrivacyPolicyLevel=0.

Using /delayload to specify dependent DLL path

Let’s say you have an ObjectARX module with an implicit dependency on another DLL. Your installer puts both your ARX module and the dependent DLL into a private application folder. Windows must be able to resolve the implicit DLL dependency at load time, otherwise it will fail to load your ARX module. Alas, Windows will not automatically search for the dependent DLL in your application folder, even though your ARX module is located there. Therefore your ARX module won’t load because the dependent DLL cannot be resolved.

To address this problem, you may be tempted to add your application folder to the AutoCAD support path so that Windows can find your dependent DLL. This is a Very Bad Idea – please don’t ever do it! It imposes a completely unnecessary burden on end users, and it’s not scaleable because it could cause the maximum support path length to be exceeded. A better solution is to change the implicit dependency into an explicit dependency by using the linker’s delayload feature.

Making the change is easy. First, change your ARX project’s linker settings to delayload the dependent DLL:

delayload

Next, implement a delayload hook in one of your source files to explicitly load the dependent DLL from the same folder as your ARX module:

#include <delayimp.h>

#pragma comment(lib, "delayimp")

HMODULE MyExplicitLoadLibrary( LPCSTR pszModuleName )
{
  if( lstrcmpiA( pszModuleName, "MyDependent.dll" ) == 0 )
  {
    CHAR szPath[MAX_PATH] = "";
    //_hdllInstance is the HMODULE of *this* module
    DWORD cchPath = GetModuleFileNameA( _hdllInstance, szPath, MAX_PATH );
    while( cchPath > 0 )
    {
      switch( szPath[cchPath - 1] )
      {
        case '\':
        case '/':
        case ':':
          break;
        default:
          --cchPath;
          continue;
      }
      break; //stop searching; found path separator
    }
    lstrcpynA( szPath + cchPath, pszModuleName, MAX_PATH - cchPath );
    return LoadLibraryA( szPath ); //call with full path to dependent DLL
  }
  return NULL;
}

FARPROC WINAPI MyDliNotifyHook( unsigned dliNotify, PDelayLoadInfo pdli )
{
  if( dliNotify == dliNotePreLoadLibrary )
    return (FARPROC)MyExplicitLoadLibrary( pdli->szDll );
  return NULL;
}
extern "C" PfnDliHook __pfnDliNotifyHook2 = MyDliNotifyHook;

When you use this technique, you do have to ensure that the delayload hook is added before any function in the dependent DLL is called. This is not a problem in most cases, but it could be a consideration if your ARX module initializes global objects whose constructors must call functions in the dependent DLL. This is just one more reason why you should avoid global variables.