view agent/src/os/win32/SwDbgSub.cpp @ 1334:193a468093fa hs17-b15

6955813: Fix incorrect Oracle rebranding headers from 6941466 Summary: Redo the header changes to fix new copyright notice style Reviewed-by: ohair
author trims
date Wed, 26 May 2010 00:30:39 -0700
parents 885e7f460925
children
line wrap: on
line source

/*
 * Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

// This is the source code for the subprocess forked by the Simple
// Windows Debug Server. It assumes most of the responsibility for the
// debug session, and processes all of the commands sent by clients.

// Disable too-long symbol warnings
#pragma warning ( disable : 4786 )

#include <iostream>
#include <vector>
#include <stdlib.h>
#include <assert.h>
// Must come before windows.h
#include <winsock2.h>
#include <windows.h>
#include "IOBuf.hpp"
#include "libInfo.hpp"
#include "LockableList.hpp"
#include "Message.hpp"
#include "Monitor.hpp"
#include "nt4internals.hpp"

// Uncomment the #define below to get messages on stderr
// #define DEBUGGING

using namespace std;

DWORD pid;
HANDLE procHandle;
IOBuf* ioBuf;

// State flags indicating whether the attach to the remote process
// definitively succeeded or failed
volatile bool attachFailed    = false;
volatile bool attachSucceeded = false;

// State flag indicating whether the target process is suspended.
// Modified by suspend()/resume(), viewed by debug thread, but only
// under cover of the threads lock.
volatile bool suspended       = false;

// State flags indicating whether we are considered to be attached to
// the target process and are therefore queuing up events to be sent
// back to the debug server. These flags are only accessed and
// modified under the cover of the eventLock.
Monitor* eventLock;
// The following is set to true when a client is attached to this process
volatile bool generateDebugEvents = false;
// Pointer to current debug event; non-NULL indicates a debug event is
// waiting to be sent to the client. Main thread sets this to NULL to
// indicate that the event has been consumed; also sets
// passEventToClient, below.
volatile DEBUG_EVENT* curDebugEvent = NULL;
// Set by main thread to indicate whether the most recently posted
// debug event should be passed on to the target process.
volatile bool passEventToClient = true;

void conditionalPostDebugEvent(DEBUG_EVENT* ev, DWORD* continueOrNotHandledFlag) {
  // FIXME: make it possible for the client to enable and disable
  // certain types of events (have to do so in a platform-independent
  // manner)
  switch (ev->dwDebugEventCode) {
  case EXCEPTION_DEBUG_EVENT:
    switch (ev->u.Exception.ExceptionRecord.ExceptionCode) {
    case EXCEPTION_BREAKPOINT:  break;
    case EXCEPTION_SINGLE_STEP: break;
    case EXCEPTION_ACCESS_VIOLATION: break;
    default: return;
    }
  }
  eventLock->lock();
  if (generateDebugEvents) {
    curDebugEvent = ev;
    while (curDebugEvent != NULL) {
      eventLock->wait();
    }
    if (passEventToClient) {
      *continueOrNotHandledFlag = DBG_EXCEPTION_NOT_HANDLED;
    } else {
      *continueOrNotHandledFlag = DBG_CONTINUE;
    }
  }
  eventLock->unlock();
}


//----------------------------------------------------------------------
// Module list
//

vector<LibInfo> libs;

//----------------------------------------------------------------------
// Thread list
//

struct ThreadInfo {
  DWORD tid;
  HANDLE thread;

  ThreadInfo(DWORD tid, HANDLE thread) {
    this->tid = tid;
    this->thread = thread;
  }
};

class ThreadList : public LockableList<ThreadInfo> {
public:
  bool removeByThreadID(DWORD tid) {
    for (InternalListType::iterator iter = internalList.begin();
         iter != internalList.end(); iter++) {
      if ((*iter).tid == tid) {
        internalList.erase(iter);
        return true;
      }
    }
    return false;
  }
  HANDLE threadIDToHandle(DWORD tid) {
    for (InternalListType::iterator iter = internalList.begin();
         iter != internalList.end(); iter++) {
      if ((*iter).tid == tid) {
        return (*iter).thread;
      }
    }
    return NULL;
  }
};

ThreadList threads;

//----------------------------------------------------------------------
// INITIALIZATION AND TERMINATION
//

void
printError(const char* prefix) {
  DWORD detail = GetLastError();
  LPTSTR message;
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM,
                0,
                detail,
                0,
                (LPTSTR) &message,
                1,
                NULL);
  // FIXME: This is signaling an error: "The handle is invalid." ?
  // Do I have to do all of my WaitForDebugEvent calls from the same thread?
  cerr << prefix << ": " << message << endl;
  LocalFree(message);
}

void
endProcess(bool waitForProcess = true) {
  NT4::unloadNTDLL();
  if (waitForProcess) {
    // Though we're exiting because of an error, do not tear down the
    // target process.
    WaitForSingleObject(procHandle, INFINITE);
  }
  CloseHandle(procHandle);
  exit(0);
}

DWORD WINAPI
debugThreadEntry(void*) {
#ifdef DEBUGGING
  DWORD lastMsgId = 0;
  int count = 0;
#endif

  if (!DebugActiveProcess(pid)) {
    attachFailed = true;
    return 0;
  }

  // Wait for debug events. We keep the information from some of these
  // on the side in anticipation of later queries by the client. NOTE
  // that we leave the process running. The main thread is responsible
  // for suspending and resuming all currently-active threads upon
  // client attach and detach.

  while (true) {
    DEBUG_EVENT ev;
    if (!WaitForDebugEvent(&ev, INFINITE)) {
#ifdef DEBUGGING
      if (++count < 10) {
        // FIXME: This is signaling an error: "The handle is invalid." ?
        // Do I have to do all of my WaitForDebugEvent calls from the same thread?
        printError("WaitForDebugEvent failed");
      }
#endif
    } else {

#ifdef DEBUGGING
      if (ev.dwDebugEventCode != lastMsgId) {
        lastMsgId = ev.dwDebugEventCode;
        count = 0;
        cerr << "Debug thread received event " << ev.dwDebugEventCode << endl;
      } else {
        if (++count < 10) {
          cerr << "Debug thread received event " << ev.dwDebugEventCode << endl;
        }
      }
#endif

      DWORD dbgContinueMode = DBG_CONTINUE;

      switch (ev.dwDebugEventCode) {
      case LOAD_DLL_DEBUG_EVENT:
        conditionalPostDebugEvent(&ev, &dbgContinueMode);
        break;

      case UNLOAD_DLL_DEBUG_EVENT:
        conditionalPostDebugEvent(&ev, &dbgContinueMode);
        break;

      case CREATE_PROCESS_DEBUG_EVENT:
        threads.lock();
        // FIXME: will this deal properly with child processes? If
        // not, is it possible to make it do so?
#ifdef DEBUGGING
        cerr << "CREATE_PROCESS_DEBUG_EVENT " << ev.dwThreadId
             << " " << ev.u.CreateProcessInfo.hThread << endl;
#endif
        if (ev.u.CreateProcessInfo.hThread != NULL) {
          threads.add(ThreadInfo(ev.dwThreadId, ev.u.CreateProcessInfo.hThread));
        }
        threads.unlock();
        break;

      case CREATE_THREAD_DEBUG_EVENT:
        threads.lock();
#ifdef DEBUGGING
        cerr << "CREATE_THREAD_DEBUG_EVENT " << ev.dwThreadId
             << " " << ev.u.CreateThread.hThread << endl;
#endif
        if (suspended) {
          // Suspend this thread before adding it to the thread list
          SuspendThread(ev.u.CreateThread.hThread);
        }
        threads.add(ThreadInfo(ev.dwThreadId, ev.u.CreateThread.hThread));
        threads.unlock();
        break;

      case EXIT_THREAD_DEBUG_EVENT:
        threads.lock();
#ifdef DEBUGGING
        cerr << "EXIT_THREAD_DEBUG_EVENT " << ev.dwThreadId << endl;
#endif
        threads.removeByThreadID(ev.dwThreadId);
        threads.unlock();
        break;

      case EXCEPTION_DEBUG_EVENT:
        //      cerr << "EXCEPTION_DEBUG_EVENT" << endl;
        switch (ev.u.Exception.ExceptionRecord.ExceptionCode) {
        case EXCEPTION_BREAKPOINT:
          //        cerr << "EXCEPTION_BREAKPOINT" << endl;
          if (!attachSucceeded && !attachFailed) {
            attachSucceeded = true;
          }
          break;

        default:
          dbgContinueMode = DBG_EXCEPTION_NOT_HANDLED;
          break;
        }
        conditionalPostDebugEvent(&ev, &dbgContinueMode);
        break;

      case EXIT_PROCESS_DEBUG_EVENT:
        endProcess(false);
        // NOT REACHED
        break;

      default:
#ifdef DEBUGGING
        cerr << "Received debug event " << ev.dwDebugEventCode << endl;
#endif
        break;
      }

      ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, dbgContinueMode);
    }
  }
}

bool
attachToProcess() {
  // Create event lock
  eventLock = new Monitor();

  // Get a process handle for later
  procHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
  if (procHandle == NULL) {
    return false;
  }

  // Start up the debug thread
  DWORD debugThreadId;
  if (CreateThread(NULL, 0, &debugThreadEntry, NULL, 0, &debugThreadId) == NULL) {
    // Failed to make background debug thread. Fail.
    return false;
  }

  while ((!attachSucceeded) && (!attachFailed)) {
    Sleep(1);
  }

  if (attachFailed) {
    return false;
  }

  assert(attachSucceeded);

  return true;
}

bool
readMessage(Message* msg) {
  DWORD numRead;
  if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE),
                msg,
                sizeof(Message),
                &numRead,
                NULL)) {
    return false;
  }
  if (numRead != sizeof(Message)) {
    return false;
  }
  // For "poke" messages, must follow up by reading raw data
  if (msg->type == Message::POKE) {
    char* dataBuf = new char[msg->pokeArg.numBytes];
    if (dataBuf == NULL) {
      return false;
    }
    if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE),
                  dataBuf,
                  msg->pokeArg.numBytes,
                  &numRead,
                  NULL)) {
      delete[] dataBuf;
      return false;
    }
    if (numRead != msg->pokeArg.numBytes) {
      delete[] dataBuf;
      return false;
    }
    msg->pokeArg.data = (void *) dataBuf;
  }
  return true;
}

void
handlePeek(Message* msg) {
#ifdef DEBUGGING
  cerr << "Entering handlePeek()" << endl;
#endif

  char* memBuf = new char[msg->peekArg.numBytes];
  if (memBuf == NULL) {
    ioBuf->writeString("B");
    ioBuf->writeBinChar(0);
    ioBuf->flush();
    delete[] memBuf;
    return;
  }

  // Try fast case first
  DWORD numRead;
  BOOL res = ReadProcessMemory(procHandle,
                               (LPCVOID) msg->peekArg.address,
                               memBuf,
                               msg->peekArg.numBytes,
                               &numRead);
  if (res && (numRead == msg->peekArg.numBytes)) {

    // OK, complete success. Phew.
#ifdef DEBUGGING
    cerr << "Peek success case" << endl;
#endif
    ioBuf->writeString("B");
    ioBuf->writeBinChar(1);
    ioBuf->writeBinUnsignedInt(numRead);
    ioBuf->writeBinChar(1);
    ioBuf->writeBinBuf(memBuf, numRead);
  } else {
#ifdef DEBUGGING
    cerr << "*** Peek slow case ***" << endl;
#endif

    ioBuf->writeString("B");
    ioBuf->writeBinChar(1);

    // Use VirtualQuery to speed things up a bit
    DWORD numLeft = msg->peekArg.numBytes;
    char* curAddr = (char*) msg->peekArg.address;
    while (numLeft > 0) {
      MEMORY_BASIC_INFORMATION memInfo;
      VirtualQueryEx(procHandle, curAddr, &memInfo, sizeof(memInfo));
      DWORD numToRead = memInfo.RegionSize;
      if (numToRead > numLeft) {
        numToRead = numLeft;
      }
      DWORD numRead;
      if (memInfo.State == MEM_COMMIT) {
        // Read the process memory at this address for this length
        // FIXME: should check the result of this read
        ReadProcessMemory(procHandle, curAddr, memBuf,
                          numToRead, &numRead);
        // Write this out
#ifdef DEBUGGING
        cerr << "*** Writing " << numToRead << " bytes as mapped ***" << endl;
#endif
        ioBuf->writeBinUnsignedInt(numToRead);
        ioBuf->writeBinChar(1);
        ioBuf->writeBinBuf(memBuf, numToRead);
      } else {
        // Indicate region is free
#ifdef DEBUGGING
        cerr << "*** Writing " << numToRead << " bytes as unmapped ***" << endl;
#endif
        ioBuf->writeBinUnsignedInt(numToRead);
        ioBuf->writeBinChar(0);
      }
      curAddr += numToRead;
      numLeft -= numToRead;
    }
  }

  ioBuf->flush();
  delete[] memBuf;
#ifdef DEBUGGING
  cerr << "Exiting handlePeek()" << endl;
#endif
}

void
handlePoke(Message* msg) {
#ifdef DEBUGGING
  cerr << "Entering handlePoke()" << endl;
#endif
  DWORD numWritten;
  BOOL res = WriteProcessMemory(procHandle,
                                (LPVOID) msg->pokeArg.address,
                                msg->pokeArg.data,
                                msg->pokeArg.numBytes,
                                &numWritten);
  if (res && (numWritten == msg->pokeArg.numBytes)) {
    // Success
    ioBuf->writeBoolAsInt(true);
#ifdef DEBUGGING
    cerr << " (Succeeded)" << endl;
#endif
  } else {
    // Failure
    ioBuf->writeBoolAsInt(false);
#ifdef DEBUGGING
    cerr << " (Failed)" << endl;
#endif
  }
  ioBuf->writeEOL();
  ioBuf->flush();
  // We clean up the data
  char* dataBuf = (char*) msg->pokeArg.data;
  delete[] dataBuf;
#ifdef DEBUGGING
  cerr << "Exiting handlePoke()" << endl;
#endif
}

bool
suspend() {
  if (suspended) {
    return false;
  }
  // Before we suspend, we must take a snapshot of the loaded module
  // names and base addresses, since acquiring this snapshot requires
  // starting and exiting a thread in the remote process (at least on
  // NT 4).
  libs.clear();
#ifdef DEBUGGING
  cerr << "Starting suspension" << endl;
#endif
  libInfo(pid, libs);
#ifdef DEBUGGING
  cerr << "  Got lib info" << endl;
#endif
  threads.lock();
#ifdef DEBUGGING
  cerr << "  Got thread lock" << endl;
#endif
  suspended = true;
  int j = 0;
  for (int i = 0; i < threads.size(); i++) {
    j++;
    SuspendThread(threads.get(i).thread);
  }
#ifdef DEBUGGING
  cerr << "Suspended " << j << " threads" << endl;
#endif
  threads.unlock();
  return true;
}

bool
resume() {
  if (!suspended) {
    return false;
  }
  threads.lock();
  suspended = false;
  for (int i = 0; i < threads.size(); i++) {
    ResumeThread(threads.get(i).thread);
  }
  threads.unlock();
#ifdef DEBUGGING
  cerr << "Resumed process" << endl;
#endif
  return true;
}

int
main(int argc, char **argv)
{
  if (argc != 2) {
    // Should only be used by performing CreateProcess within SwDbgSrv
    exit(1);
  }

  if (sscanf(argv[1], "%u", &pid) != 1) {
    exit(1);
  }

  // Try to attach to process
  if (!attachToProcess()) {
    // Attach failed. Notify parent by writing result to stdout file
    // handle.
    char res = 0;
    DWORD numBytes;
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), &res, sizeof(res),
              &numBytes, NULL);
    exit(1);
  }

  // Server is expecting success result back.
  char res = 1;
  DWORD numBytes;
  WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), &res, sizeof(res),
            &numBytes, NULL);

  // Initialize our I/O buffer
  ioBuf = new IOBuf(32768, 131072);
  ioBuf->setOutputFileHandle(GetStdHandle(STD_OUTPUT_HANDLE));

  // At this point we are attached. Enter our main loop which services
  // requests from the server. Note that in order to handle attach/
  // detach properly (i.e., resumption of process upon "detach") we
  // will need another thread which handles debug events.
  while (true) {
    // Read a message from the server
    Message msg;
    if (!readMessage(&msg)) {
      endProcess();
    }

#ifdef DEBUGGING
    cerr << "Main thread read message: " << msg.type << endl;
#endif

    switch (msg.type) {
    // ATTACH and DETACH messages MUST come in pairs
    case Message::ATTACH:
      suspend();
      eventLock->lock();
      generateDebugEvents = true;
      eventLock->unlock();
      break;

    case Message::DETACH:
      eventLock->lock();
      generateDebugEvents = false;
      // Flush remaining event if any
      if (curDebugEvent != NULL) {
        curDebugEvent = NULL;
        eventLock->notifyAll();
      }
      eventLock->unlock();
      resume();
      break;

    case Message::LIBINFO:
      {
        if (!suspended) {
          ioBuf->writeInt(0);
        } else {
          // Send back formatted text
          ioBuf->writeInt(libs.size());
          for (int i = 0; i < libs.size(); i++) {
            ioBuf->writeSpace();
            ioBuf->writeInt(1);
            ioBuf->writeSpace();
            ioBuf->writeInt(libs[i].name.size());
            ioBuf->writeSpace();
            ioBuf->writeString(libs[i].name.c_str());
            ioBuf->writeSpace();
            ioBuf->writeAddress(libs[i].base);
          }
        }
        ioBuf->writeEOL();
        ioBuf->flush();
        break;
      }

    case Message::PEEK:
      handlePeek(&msg);
      break;

    case Message::POKE:
      handlePoke(&msg);
      break;

    case Message::THREADLIST:
      {
        if (!suspended) {
          ioBuf->writeInt(0);
        } else {
          threads.lock();
          ioBuf->writeInt(threads.size());
          for (int i = 0; i < threads.size(); i++) {
            ioBuf->writeSpace();
            ioBuf->writeAddress((void*) threads.get(i).thread);
          }
          threads.unlock();
        }
        ioBuf->writeEOL();
        ioBuf->flush();
        break;
      }

    case Message::DUPHANDLE:
      {
        HANDLE dup;
        if (DuplicateHandle(procHandle,
                            msg.handleArg.handle,
                            GetCurrentProcess(),
                            &dup,
                            0,
                            FALSE,
                            DUPLICATE_SAME_ACCESS)) {
          ioBuf->writeBoolAsInt(true);
          ioBuf->writeSpace();
          ioBuf->writeAddress((void*) dup);
        } else {
          ioBuf->writeBoolAsInt(false);
        }
        ioBuf->writeEOL();
        ioBuf->flush();
        break;
      }

    case Message::CLOSEHANDLE:
      {
        CloseHandle(msg.handleArg.handle);
        break;
      }

    case Message::GETCONTEXT:
      {
        if (!suspended) {
          ioBuf->writeBoolAsInt(false);
        } else {
          CONTEXT context;
          context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
          if (GetThreadContext(msg.handleArg.handle, &context)) {
            ioBuf->writeBoolAsInt(true);
            // EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP, DS, ES, FS, GS,
            // CS, SS, EFLAGS, DR0, DR1, DR2, DR3, DR6, DR7
            // See README-commands.txt
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Eax);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Ebx);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Ecx);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Edx);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Esi);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Edi);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Ebp);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Esp);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Eip);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegDs);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegEs);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegFs);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegGs);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegCs);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegSs);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.EFlags);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr0);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr1);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr2);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr3);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr6);
            ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr7);
          } else {
            ioBuf->writeBoolAsInt(false);
          }
        }
        ioBuf->writeEOL();
        ioBuf->flush();
        break;
      }

    case Message::SETCONTEXT:
      {
        if (!suspended) {
          ioBuf->writeBoolAsInt(false);
        } else {
          CONTEXT context;
          context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
          context.Eax    = msg.setContextArg.Eax;
          context.Ebx    = msg.setContextArg.Ebx;
          context.Ecx    = msg.setContextArg.Ecx;
          context.Edx    = msg.setContextArg.Edx;
          context.Esi    = msg.setContextArg.Esi;
          context.Edi    = msg.setContextArg.Edi;
          context.Ebp    = msg.setContextArg.Ebp;
          context.Esp    = msg.setContextArg.Esp;
          context.Eip    = msg.setContextArg.Eip;
          context.SegDs  = msg.setContextArg.Ds;
          context.SegEs  = msg.setContextArg.Es;
          context.SegFs  = msg.setContextArg.Fs;
          context.SegGs  = msg.setContextArg.Gs;
          context.SegCs  = msg.setContextArg.Cs;
          context.SegSs  = msg.setContextArg.Ss;
          context.EFlags = msg.setContextArg.EFlags;
          context.Dr0    = msg.setContextArg.Dr0;
          context.Dr1    = msg.setContextArg.Dr1;
          context.Dr2    = msg.setContextArg.Dr2;
          context.Dr3    = msg.setContextArg.Dr3;
          context.Dr6    = msg.setContextArg.Dr6;
          context.Dr7    = msg.setContextArg.Dr7;
          if (SetThreadContext(msg.setContextArg.handle, &context)) {
            ioBuf->writeBoolAsInt(true);
          } else {
            ioBuf->writeBoolAsInt(false);
          }
        }
        ioBuf->writeEOL();
        ioBuf->flush();
        break;
      }

    case Message::SELECTORENTRY:
      {
        LDT_ENTRY entry;

        if (GetThreadSelectorEntry(msg.selectorArg.handle,
                                   msg.selectorArg.selector,
                                   &entry)) {
          ioBuf->writeBoolAsInt(true);
          ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.LimitLow);
          ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.BaseLow);
          ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.BaseMid);
          ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.Flags1);
          ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.Flags2);
          ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.BaseHi);
        } else {
          ioBuf->writeBoolAsInt(false);
        }

        ioBuf->writeEOL();
        ioBuf->flush();
        break;
      }

    case Message::SUSPEND:
      suspend();
      break;

    case Message::RESUME:
      resume();
      break;

    case Message::POLLEVENT:
      eventLock->lock();
      if (curDebugEvent == NULL) {
        ioBuf->writeBoolAsInt(false);
      } else {
        ioBuf->writeBoolAsInt(true);
        ioBuf->writeSpace();
        threads.lock();
        ioBuf->writeAddress((void*) threads.threadIDToHandle(curDebugEvent->dwThreadId));
        threads.unlock();
        ioBuf->writeSpace();
        ioBuf->writeUnsignedInt(curDebugEvent->dwDebugEventCode);
        // Figure out what else to write
        switch (curDebugEvent->dwDebugEventCode) {
        case LOAD_DLL_DEBUG_EVENT:
          ioBuf->writeSpace();
          ioBuf->writeAddress(curDebugEvent->u.LoadDll.lpBaseOfDll);
          break;

        case UNLOAD_DLL_DEBUG_EVENT:
          ioBuf->writeSpace();
          ioBuf->writeAddress(curDebugEvent->u.UnloadDll.lpBaseOfDll);
          break;

        case EXCEPTION_DEBUG_EVENT:
          {
            DWORD code = curDebugEvent->u.Exception.ExceptionRecord.ExceptionCode;
            ioBuf->writeSpace();
            ioBuf->writeUnsignedInt(code);
            ioBuf->writeSpace();
            ioBuf->writeAddress(curDebugEvent->u.Exception.ExceptionRecord.ExceptionAddress);
            switch (curDebugEvent->u.Exception.ExceptionRecord.ExceptionCode) {
            case EXCEPTION_ACCESS_VIOLATION:
              ioBuf->writeSpace();
              ioBuf->writeBoolAsInt(curDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[0] != 0);
              ioBuf->writeSpace();
              ioBuf->writeAddress((void*) curDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[1]);
              break;

            default:
              break;
            }
            break;
          }

        default:
          break;
        }
      }
      eventLock->unlock();
      ioBuf->writeEOL();
      ioBuf->flush();
      break;

    case Message::CONTINUEEVENT:
      eventLock->lock();
      if (curDebugEvent == NULL) {
        ioBuf->writeBoolAsInt(false);
      } else {
        curDebugEvent = NULL;
        passEventToClient = msg.boolArg.val;
        ioBuf->writeBoolAsInt(true);
        eventLock->notify();
      }
      eventLock->unlock();
      ioBuf->writeEOL();
      ioBuf->flush();
      break;
    }
  }

  endProcess();

  // NOT REACHED
  return 0;
}