diff agent/src/os/win32/SwDbgSrv.cpp @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c18cbe5936b8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/os/win32/SwDbgSrv.cpp	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1266 @@
+/*
+ * Copyright 2000-2003 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+// A Simple Windows Debug Server.
+//
+// This software provides a socket-based debug server which uses
+// mostly ASCII protocols to communicate with its clients. Since the
+// Windows security model is largely based around being able to run
+// programs on the machine, this server only accepts connections
+// coming from localhost.
+//
+// When run as a service (under Windows NT), this software provides
+// clients the ability to attach to and detach from processes without
+// killing those processes. Ordinarily this is forbidden by the
+// Windows debugging APIs (although more recent debugging environments
+// from Microsoft seem to have circumvented this restriction, perhaps
+// in a different way). This is achieved by forking a persistent
+// subprocess for each debugging session which remains alive as long
+// as the target process is.
+//
+// At this point the client can read information out of the target
+// process's address space. Future work includes exposing more
+// functionality like writing to the remote address space and
+// suspending and resuming threads.
+
+#include <iostream>
+#include <vector>
+#include <stdlib.h>
+// Must come before everything else
+#include <winsock2.h>
+#include <assert.h>
+#include "Dispatcher.hpp"
+#include "Handler.hpp"
+#include "initWinsock.hpp"
+#include "ioUtils.hpp"
+#include "isNT4.hpp"
+#include "Message.hpp"
+#include "nt4internals.hpp"
+#include "ports.h"
+#include "procList.hpp"
+#include "serverLists.hpp"
+#include "Reaper.hpp"
+
+// Uncomment the #define below to get messages on stderr
+// #define DEBUGGING
+
+using namespace std;
+
+static ChildList childList;
+static ClientList clientList;
+static Reaper* reaper = NULL;
+
+// Needed prototypes
+void shutdownChild(ChildInfo* childInfo);
+void detachClient(ClientInfo* clientInfo);
+void shutdownClient(ClientInfo* clientInfo);
+
+char *
+longToDotFormat(long addr)
+{
+  char *temp_s = new char[20];
+
+  sprintf(temp_s, "%d.%d.%d.%d", ((addr & 0xff000000) >> 24),
+          ((addr & 0x00ff0000) >> 16), ((addr & 0x0000ff00) >> 8),
+          (addr & 0x000000ff));
+
+  return temp_s;
+}
+
+// NOTE that we do this query every time. It is a bad idea to cache IP
+// addresses. For example, we might be hosted on a machine using DHCP
+// and the connection addresses might change over time. (Yes, this
+// actually happened.)
+bool
+isConnectionOkay(ULONG connAddr) {
+  if (connAddr == INADDR_LOOPBACK) {
+    return true;
+  }
+
+  const int MAXNAME = 1024;
+  char myname[MAXNAME];
+  gethostname(myname, MAXNAME);
+  struct hostent* myInfo = gethostbyname(myname);
+  if (myInfo == NULL) {
+#ifdef DEBUGGING
+    cerr << "My host information was null" << endl;
+#endif
+  } else {
+    // Run down the list of IP addresses for myself
+    assert(myInfo->h_length == sizeof(ULONG));
+#ifdef DEBUGGING
+    cerr << "My known IP addresses: " << endl;
+#endif
+    for (char** pp = myInfo->h_addr_list; *pp != NULL; pp++) {
+      char* p = *pp;
+      ULONG altAddr = ntohl(*((ULONG*) p));
+#ifdef DEBUGGING
+      char* name = longToDotFormat(altAddr);
+      cerr << name << endl;
+      delete[] name;
+#endif
+      if (altAddr == connAddr) {
+#ifdef DEBUGGING
+        cerr << "FOUND" << endl;
+#endif
+        return true;
+      }
+    }
+#ifdef DEBUGGING
+    cerr << "Done." << endl;
+#endif
+  }
+
+  return false;
+}
+
+SOCKET
+setupListeningSocket(short port) {
+  SOCKET listening = socket(AF_INET, SOCK_STREAM, 0);
+  if (listening == INVALID_SOCKET) {
+    cerr << "Error creating listening socket" << endl;
+    exit(1);
+  }
+
+  int reuseAddress = 1;
+  if (setsockopt(listening, SOL_SOCKET, SO_REUSEADDR,
+                 (char *)&reuseAddress, sizeof(reuseAddress)) == -1) {
+    cerr << "Error reusing address" << endl;
+    exit(1);
+  }
+
+  struct sockaddr_in serverInfo;
+
+  memset((char *)&serverInfo, 0, sizeof(serverInfo));
+  serverInfo.sin_addr.s_addr = INADDR_ANY;
+  serverInfo.sin_family = AF_INET;
+  serverInfo.sin_port = htons(port);
+
+  if (bind(listening, (struct sockaddr *) &serverInfo, sizeof(serverInfo)) < 0) {
+    cerr << "Error binding socket" << endl;
+    exit(1);
+  }
+
+  if (listen(listening, 5) < 0) {
+    cerr << "Error listening" << endl;
+    exit(1);
+  }
+
+  return listening;
+}
+
+/** Accepts a connection from the given listening socket, but only if
+    the connection came from localhost. Returns INVALID_SOCKET if the
+    connection came from any other IP address or if an error occurred
+    during the call to accept(). */
+SOCKET
+acceptFromLocalhost(SOCKET listening) {
+  struct sockaddr_in peerAddr;
+  int peerAddrLen = sizeof(peerAddr);
+  SOCKET fd = accept(listening, (sockaddr*) &peerAddr, &peerAddrLen);
+  if (fd == INVALID_SOCKET) {
+    return fd;
+  }
+
+  if (!isConnectionOkay(ntohl(peerAddr.sin_addr.s_addr))) {
+    // Reject connections from other machines for security purposes.
+    // The Windows security model seems to assume one user per
+    // machine, and that security is compromised if another user is
+    // able to run executables on the given host. (If these
+    // assumptions are not strict enough, we will have to change
+    // this.)
+    shutdown(fd, SD_BOTH);
+    closesocket(fd);
+    return INVALID_SOCKET;
+  }
+
+  // Disable TCP buffering on all sockets. We send small amounts of
+  // data back and forth and don't want buffering.
+  int buffer_val = 1;
+  if (setsockopt(fd, IPPROTO_IP, TCP_NODELAY,
+                 (char *) &buffer_val, sizeof(buffer_val)) < 0) {
+    shutdown(fd, SD_BOTH);
+    closesocket(fd);
+  }
+
+  return fd;
+}
+
+void
+reapCB(void* arg) {
+  ChildInfo* info = (ChildInfo*) arg;
+  ListsLocker ll;
+  DWORD pid = info->getPid();
+  shutdownChild(info);
+#ifdef DEBUGGING
+  cerr << "Reaped child for process " << pid << endl;
+#endif
+}
+
+/** Starts a child process with stdin and stdout redirected to pipes,
+    handles to which are returned. auxHandle1 and auxHandle2 should be
+    closed as well when the child process exits. Returns false if
+    process creation failed. */
+bool
+startChildProcess(DWORD pidToDebug,
+                  DWORD childStdinBufSize,
+                  DWORD childStdoutBufSize,
+                  LPHANDLE childProcessHandle,
+                  LPHANDLE writeToStdinHandle,
+                  LPHANDLE readFromStdoutHandle,
+                  LPHANDLE auxHandle1,
+                  LPHANDLE auxHandle2) {
+  // Code adapted from Microsoft example
+  // "Creating a Child Process with Redirected Input and Output"
+
+  SECURITY_ATTRIBUTES saAttr;
+  BOOL fSuccess;
+
+  HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup,
+    hChildStdoutRd, hChildStdoutWr, hChildStdoutRdDup,
+    hSaveStdin, hSaveStdout;
+
+  // Set the bInheritHandle flag so pipe handles are inherited.
+  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+  saAttr.bInheritHandle = TRUE;
+  saAttr.lpSecurityDescriptor = NULL;
+
+  // The steps for redirecting child process's STDOUT:
+  //   1. Save current STDOUT, to be restored later.
+  //   2. Create anonymous pipe to be STDOUT for child process.
+  //   3. Set STDOUT of the parent process to be write handle to
+  //      the pipe, so it is inherited by the child process.
+  //   4. Create a noninheritable duplicate of the read handle and
+  //      close the inheritable read handle.
+
+  // Save the handle to the current STDOUT.
+  hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
+  // Create a pipe for the child process's STDOUT.
+  if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, childStdoutBufSize)) {
+    return false;
+  }
+  // Set a write handle to the pipe to be STDOUT.
+  if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)) {
+    return false;
+  }
+  // Create noninheritable read handle and close the inheritable read
+  // handle.
+  fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
+                             GetCurrentProcess(), &hChildStdoutRdDup,
+                             0, FALSE,
+                             DUPLICATE_SAME_ACCESS);
+  if( !fSuccess ) {
+    return false;
+  }
+  CloseHandle(hChildStdoutRd);
+
+  // The steps for redirecting child process's STDIN:
+  //   1.  Save current STDIN, to be restored later.
+  //   2.  Create anonymous pipe to be STDIN for child process.
+  //   3.  Set STDIN of the parent to be the read handle to the
+  //       pipe, so it is inherited by the child process.
+  //   4.  Create a noninheritable duplicate of the write handle,
+  //       and close the inheritable write handle.
+  // Save the handle to the current STDIN.
+  hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);
+  // Create a pipe for the child process's STDIN.
+  if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, childStdinBufSize)) {
+    return false;
+  }
+  // Set a read handle to the pipe to be STDIN.
+  if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd)) {
+    return false;
+  }
+  // Duplicate the write handle to the pipe so it is not inherited.
+  fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr,
+                             GetCurrentProcess(), &hChildStdinWrDup, 0,
+                             FALSE,                  // not inherited
+                             DUPLICATE_SAME_ACCESS);
+  if (! fSuccess) {
+    return false;
+  }
+  CloseHandle(hChildStdinWr);
+
+  // Create the child process
+  char cmdLine[256];
+  sprintf(cmdLine, "SwDbgSub.exe %u", pidToDebug);
+  PROCESS_INFORMATION procInfo;
+  STARTUPINFO startInfo;
+  memset((char*) &startInfo, 0, sizeof(startInfo));
+  startInfo.cb = sizeof(startInfo);
+  BOOL res = CreateProcess(NULL,
+                           cmdLine,
+                           NULL,
+                           NULL,
+                           TRUE, // inherit handles: important
+                           0,
+                           NULL,
+                           NULL,
+                           &startInfo,
+                           &procInfo);
+  if (!res) {
+    return false;
+  }
+  // After process creation, restore the saved STDIN and STDOUT.
+  if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin)) {
+    return false;
+  }
+  if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) {
+    return false;
+  }
+
+  // hChildStdinWrDup can be used to write to the child's stdin
+  // hChildStdoutRdDup can be used to read from the child's stdout
+
+  // NOTE: example code closes hChildStdoutWr before reading from
+  // hChildStdoutRdDup. "Close the write end of the pipe before
+  // reading from the read end of the pipe"??? Looks like this is
+  // example-specific.
+
+  // Set up return arguments
+  // hChildStdoutRd and hChildStdinWr are already closed at this point
+  *childProcessHandle = procInfo.hProcess;
+  *writeToStdinHandle = hChildStdinWrDup;
+  *readFromStdoutHandle = hChildStdoutRdDup;
+  *auxHandle1 = hChildStdinRd;
+  *auxHandle2 = hChildStdoutWr;
+  return true;
+}
+
+/** Clears the event and writes the message to the child process */
+bool
+sendMessage(ChildInfo* child, Message* message) {
+  DWORD numBytesWritten;
+  if (!WriteFile(child->getWriteToStdinHandle(),
+                 message, sizeof(Message), &numBytesWritten, NULL)) {
+    return false;
+  }
+  if (numBytesWritten != sizeof(Message)) {
+    return false;
+  }
+  // Follow up "poke" messages with the raw data
+  if (message->type == Message::POKE) {
+    if (!WriteFile(child->getWriteToStdinHandle(),
+                   message->pokeArg.data, message->pokeArg.numBytes, &numBytesWritten, NULL)) {
+      return false;
+    }
+    if (numBytesWritten != message->pokeArg.numBytes) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/** Copies data from child's stdout to the client's IOBuf and sends it
+    along */
+bool
+forwardReplyToClient(ChildInfo* child, ClientInfo* client) {
+  DWORD total = 0;
+  IOBuf::FillState ret;
+
+  do {
+    DWORD temp;
+    ret = client->getIOBuf()->fillFromFileHandle(child->getReadFromStdoutHandle(),
+                                                 &temp);
+    if (ret == IOBuf::DONE || ret == IOBuf::MORE_DATA_PENDING) {
+      if (!client->getIOBuf()->flush()) {
+#ifdef DEBUGGING
+        cerr << "Forward failed because flush failed" << endl;
+#endif
+        return false;
+      }
+      total += temp;
+    }
+  } while (ret == IOBuf::MORE_DATA_PENDING);
+
+  return (ret == IOBuf::FAILED) ? false : true;
+}
+
+//----------------------------------------------------------------------
+// Server Handler
+//
+
+class ServerHandler : public Handler {
+public:
+  ServerHandler();
+
+  // Starts up in Unicode mode by default
+  bool getASCII();
+
+  void setIOBuf(IOBuf* ioBuf);
+
+  void procList(char* arg);
+
+  // Must be called before calling one of the routines below
+  void setClientInfo(ClientInfo* info);
+
+  // Indicates to outer loop that exit was called or that an error
+  // occurred and that the client exited.
+  bool exited();
+  // Clears this state
+  void clearExited();
+
+  void ascii(char* arg);
+  void unicode(char* arg);
+  void attach(char* arg);
+  void detach(char* arg);
+  void libInfo(char* arg);
+  void peek(char* arg);
+  void poke(char* arg);
+  void threadList(char* arg);
+  void dupHandle(char* arg);
+  void closeHandle(char* arg);
+  void getContext(char* arg);
+  void setContext(char* arg);
+  void selectorEntry(char* arg);
+  void suspend(char* arg);
+  void resume(char* arg);
+  void pollEvent(char* arg);
+  void continueEvent(char* arg);
+  void exit(char* arg);
+
+  // This is pretty gross. Needed to make the target process know
+  // about clients that have disconnected unexpectedly while attached.
+  friend void shutdownClient(ClientInfo*);
+private:
+  // Writes: charSize <space> numChars <space> <binary string>
+  // Handles both ASCII and UNICODE modes
+  void writeString(USHORT len, WCHAR* str);
+
+  // Handles only ASCII mode
+  void writeString(USHORT len, char* str);
+
+  ClientInfo* clientInfo;
+  IOBuf* ioBuf;
+  bool _exited;
+  bool _ascii;
+};
+
+static ServerHandler* handler;
+
+ServerHandler::ServerHandler() {
+  _exited = false;
+  _ascii = false;
+  ioBuf = NULL;
+}
+
+bool
+ServerHandler::getASCII() {
+  return _ascii;
+}
+
+void
+ServerHandler::setIOBuf(IOBuf* buf) {
+  ioBuf = buf;
+}
+
+void
+ServerHandler::setClientInfo(ClientInfo* info) {
+  clientInfo = info;
+}
+
+bool
+ServerHandler::exited() {
+  return _exited;
+}
+
+void
+ServerHandler::clearExited() {
+  _exited = false;
+}
+
+void
+ServerHandler::ascii(char* arg) {
+  _ascii = true;
+}
+
+void
+ServerHandler::unicode(char* arg) {
+  _ascii = false;
+}
+
+void
+ServerHandler::procList(char* arg) {
+#ifdef DEBUGGING
+  cerr << "proclist" << endl;
+#endif
+
+  ProcEntryList processes;
+  ::procList(processes);
+
+  ioBuf->writeInt(processes.size());
+
+  for (ProcEntryList::iterator iter = processes.begin();
+       iter != processes.end(); iter++) {
+    ProcEntry& entry = *iter;
+    ioBuf->writeSpace();
+    ioBuf->writeUnsignedInt(entry.getPid());
+    ioBuf->writeSpace();
+    writeString(entry.getNameLength(), entry.getName());
+  }
+
+  ioBuf->writeEOL();
+  ioBuf->flush();
+}
+
+void
+ServerHandler::attach(char* arg) {
+  // If the client is already attached to a process, fail.
+  if (clientInfo->getTarget() != NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->writeEOL();
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get pid
+  DWORD pid;
+  if (!scanUnsignedLong(&arg, &pid)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->writeEOL();
+    ioBuf->flush();
+    return;
+  }
+
+  // See whether this pid is already forked
+  ListsLocker ll;
+  ChildInfo* childInfo = childList.getChildByPid(pid);
+  if (childInfo != NULL) {
+    // If this child already has a client, return false
+    if (childInfo->getClient() != NULL) {
+      ioBuf->writeBoolAsInt(false);
+      ioBuf->writeEOL();
+      ioBuf->flush();
+      return;
+    }
+
+    // Otherwise, can associate this client with this child process
+    childInfo->setClient(clientInfo);
+    clientInfo->setTarget(childInfo);
+
+    // Tell the child we are attaching so it can suspend the target
+    // process
+    Message msg;
+    msg.type = Message::ATTACH;
+    sendMessage(childInfo, &msg);
+
+    ioBuf->writeBoolAsInt(true);
+    ioBuf->writeEOL();
+    ioBuf->flush();
+    return;
+  } else {
+    // Have to fork a new child subprocess
+    HANDLE childProcessHandle;
+    HANDLE writeToStdinHandle;
+    HANDLE readFromStdoutHandle;
+    HANDLE auxHandle1;
+    HANDLE auxHandle2;
+    if (!startChildProcess(pid,
+                           32768,
+                           131072,
+                           &childProcessHandle,
+                           &writeToStdinHandle,
+                           &readFromStdoutHandle,
+                           &auxHandle1,
+                           &auxHandle2)) {
+      ioBuf->writeBoolAsInt(false);
+      ioBuf->writeEOL();
+      ioBuf->flush();
+      return;
+    }
+
+    // See whether the child succeeded in attaching to the process
+    char res;
+    DWORD numRead;
+    if (!ReadFile(readFromStdoutHandle,
+                  &res,
+                  sizeof(char),
+                  &numRead,
+                  NULL)) {
+      ioBuf->writeBoolAsInt(false);
+      ioBuf->writeEOL();
+      ioBuf->flush();
+      return;
+    }
+
+    if (!res) {
+      ioBuf->writeBoolAsInt(false);
+      ioBuf->writeEOL();
+      ioBuf->flush();
+      return;
+    }
+
+    // OK, success.
+    childInfo = new ChildInfo(pid, childProcessHandle,
+                              writeToStdinHandle, readFromStdoutHandle,
+                              auxHandle1, auxHandle2);
+    childList.addChild(childInfo);
+    reaper->registerProcess(childProcessHandle, childInfo);
+    // Associate this client with this child process
+    childInfo->setClient(clientInfo);
+    clientInfo->setTarget(childInfo);
+
+    // Tell the child process to actually suspend the target process
+    Message msg;
+    msg.type = Message::ATTACH;
+    sendMessage(childInfo, &msg);
+
+    // Write result to client
+    ioBuf->writeBoolAsInt(true);
+    ioBuf->writeEOL();
+    ioBuf->flush();
+    return;
+  }
+}
+
+void
+ServerHandler::detach(char* arg) {
+  // If the client is not attached, fail.
+  if (clientInfo->getTarget() == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->writeEOL();
+    ioBuf->flush();
+    return;
+  }
+
+  detachClient(clientInfo);
+
+  ioBuf->writeBoolAsInt(true);
+  ioBuf->writeEOL();
+  ioBuf->flush();
+}
+
+void
+ServerHandler::libInfo(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeInt(0);
+    ioBuf->writeEOL();
+    ioBuf->flush();
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::LIBINFO;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::peek(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeString("B");
+    ioBuf->writeBinChar(0);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get address
+  DWORD address;
+  if (!scanAddress(&arg, &address)) {
+    ioBuf->writeString("B");
+    ioBuf->writeBinChar(0);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get number of bytes
+  DWORD numBytes;
+  if (!scanUnsignedLong(&arg, &numBytes)) {
+    ioBuf->writeString("B");
+    ioBuf->writeBinChar(0);
+    ioBuf->flush();
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::PEEK;
+  msg.peekArg.address = address;
+  msg.peekArg.numBytes = numBytes;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::poke(char* arg) {
+#ifdef DEBUGGING
+  cerr << "ServerHandler::poke" << endl;
+#endif
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get address
+  DWORD address;
+  if (!scanAddress(&arg, &address)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get number of bytes
+  if (!scanAndSkipBinEscapeChar(&arg)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+  DWORD numBytes;
+  if (!scanBinUnsignedLong(&arg, &numBytes)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Raw data is now in "arg"
+  // Send message to child
+  Message msg;
+  msg.type = Message::POKE;
+  msg.pokeArg.address = address;
+  msg.pokeArg.numBytes = numBytes;
+  msg.pokeArg.data = arg;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::threadList(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::THREADLIST;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::dupHandle(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get handle
+  DWORD address;
+  if (!scanAddress(&arg, &address)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::DUPHANDLE;
+  msg.handleArg.handle = (HANDLE) address;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::closeHandle(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    return;
+  }
+
+  // Try to get handle
+  DWORD address;
+  if (!scanAddress(&arg, &address)) {
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::CLOSEHANDLE;
+  msg.handleArg.handle = (HANDLE) address;
+  sendMessage(child, &msg);
+
+  // No reply
+}
+
+void
+ServerHandler::getContext(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get handle
+  DWORD address;
+  if (!scanAddress(&arg, &address)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::GETCONTEXT;
+  msg.handleArg.handle = (HANDLE) address;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::setContext(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get handle
+  DWORD address;
+  if (!scanAddress(&arg, &address)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get context
+  DWORD regs[NUM_REGS_IN_CONTEXT];
+  for (int i = 0; i < NUM_REGS_IN_CONTEXT; i++) {
+    if (!scanAddress(&arg, &regs[i])) {
+      ioBuf->writeBoolAsInt(false);
+      ioBuf->flush();
+      return;
+    }
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::SETCONTEXT;
+  msg.setContextArg.handle = (HANDLE) address;
+  msg.setContextArg.Eax    = regs[0];
+  msg.setContextArg.Ebx    = regs[1];
+  msg.setContextArg.Ecx    = regs[2];
+  msg.setContextArg.Edx    = regs[3];
+  msg.setContextArg.Esi    = regs[4];
+  msg.setContextArg.Edi    = regs[5];
+  msg.setContextArg.Ebp    = regs[6];
+  msg.setContextArg.Esp    = regs[7];
+  msg.setContextArg.Eip    = regs[8];
+  msg.setContextArg.Ds     = regs[9];
+  msg.setContextArg.Es     = regs[10];
+  msg.setContextArg.Fs     = regs[11];
+  msg.setContextArg.Gs     = regs[12];
+  msg.setContextArg.Cs     = regs[13];
+  msg.setContextArg.Ss     = regs[14];
+  msg.setContextArg.EFlags = regs[15];
+  msg.setContextArg.Dr0    = regs[16];
+  msg.setContextArg.Dr1    = regs[17];
+  msg.setContextArg.Dr2    = regs[18];
+  msg.setContextArg.Dr3    = regs[19];
+  msg.setContextArg.Dr6    = regs[20];
+  msg.setContextArg.Dr7    = regs[21];
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::selectorEntry(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get thread handle
+  DWORD address;
+  if (!scanAddress(&arg, &address)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get selector
+  DWORD selector;
+  if (!scanUnsignedLong(&arg, &selector)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::SELECTORENTRY;
+  msg.selectorArg.handle   = (HANDLE) address;
+  msg.selectorArg.selector = selector;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::suspend(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::SUSPEND;
+  sendMessage(child, &msg);
+
+  // No reply
+}
+
+void
+ServerHandler::resume(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::RESUME;
+  sendMessage(child, &msg);
+
+  // No reply
+}
+
+void
+ServerHandler::pollEvent(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::POLLEVENT;
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::continueEvent(char* arg) {
+  ListsLocker ll;
+  ChildInfo* child = clientInfo->getTarget();
+  if (child == NULL) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Try to get bool arg
+  int passEventToClient;
+  if (!scanInt(&arg, &passEventToClient)) {
+    ioBuf->writeBoolAsInt(false);
+    ioBuf->flush();
+    return;
+  }
+
+  // Send message to child
+  Message msg;
+  msg.type = Message::CONTINUEEVENT;
+  msg.boolArg.val = ((passEventToClient != 0) ? true : false);
+  sendMessage(child, &msg);
+
+  // Forward reply to client
+  forwardReplyToClient(child, clientInfo);
+}
+
+void
+ServerHandler::exit(char* arg) {
+  shutdownClient(clientInfo);
+  _exited = true;
+}
+
+void
+ServerHandler::writeString(USHORT len, WCHAR* str) {
+  if (_ascii) {
+    char* cStr = new char[len + 1];
+    sprintf(cStr, "%.*ls", len, str);
+    writeString(len, cStr);
+    delete[] cStr;
+  } else {
+    ioBuf->writeInt(sizeof(unsigned short));
+    ioBuf->writeSpace();
+    ioBuf->writeInt(len);
+    ioBuf->writeSpace();
+    for (int i = 0; i < len; i++) {
+      ioBuf->writeBinUnsignedShort(str[i]);
+    }
+  }
+}
+
+void
+ServerHandler::writeString(USHORT len, char* str) {
+  ioBuf->writeInt(1);
+  ioBuf->writeSpace();
+  ioBuf->writeInt(len);
+  ioBuf->writeSpace();
+  ioBuf->writeString(str);
+}
+
+//
+//----------------------------------------------------------------------
+
+//----------------------------------------------------------------------
+// Shutdown routines
+//
+
+void
+shutdownChild(ChildInfo* childInfo) {
+  childList.removeChild(childInfo);
+  childInfo->closeAll();
+  if (childInfo->getClient() != NULL) {
+    shutdownClient(childInfo->getClient());
+  }
+  delete childInfo;
+}
+
+void
+detachClient(ClientInfo* info) {
+  ListsLocker ll;
+  // May have been dissociated while not under cover of lock
+  if (info->getTarget() == NULL) {
+    return;
+  }
+
+  // Tell the child that we have detached to let the target process
+  // continue running
+  Message msg;
+  msg.type = Message::DETACH;
+  sendMessage(info->getTarget(), &msg);
+
+  // Dissociate the client and the target
+  info->getTarget()->setClient(NULL);
+  info->setTarget(NULL);
+}
+
+void
+shutdownClient(ClientInfo* clientInfo) {
+#ifdef DEBUGGING
+  cerr << "Shutting down client" << endl;
+#endif
+
+  // If we're connected, inform the target process that we're
+  // disconnecting
+  detachClient(clientInfo);
+
+  // Remove this client from the list and delete it
+  clientList.removeClient(clientInfo);
+  if (clientInfo->getTarget() != NULL) {
+    clientInfo->getTarget()->setClient(NULL);
+  }
+  clientInfo->closeAll();
+  delete clientInfo;
+}
+
+//
+//----------------------------------------------------------------------
+
+
+/** Main dispatcher for client commands. NOTE: do not refer to this
+    clientInfo data structure after calling this routine, as it may be
+    deleted internally. */
+void
+readAndDispatch(ClientInfo* clientInfo) {
+  IOBuf::ReadLineResult res;
+  IOBuf* ioBuf = clientInfo->getIOBuf();
+  unsigned long howMany;
+  ioctlsocket(clientInfo->getDataSocket(), FIONREAD, &howMany);
+  if (howMany == 0) {
+    // Client closed down.
+    shutdownClient(clientInfo);
+    return;
+  }
+  // Read and process as much data as possible
+  do {
+    res = ioBuf->tryReadLine();
+    if (res == IOBuf::RL_ERROR) {
+#ifdef DEBUGGING
+      cerr << "Error while reading line" << endl;
+#endif
+      shutdownClient(clientInfo);
+      return;
+    } else if (res == IOBuf::RL_GOT_DATA) {
+#ifdef DEBUGGING
+      cerr << "Got data: \"" << ioBuf->getLine() << "\"" << endl;
+#endif
+      handler->setIOBuf(ioBuf);
+      handler->setClientInfo(clientInfo);
+      handler->clearExited();
+      Dispatcher::dispatch(ioBuf->getLine(), handler);
+    }
+  } while (res == IOBuf::RL_GOT_DATA && (!handler->exited()));
+#ifdef DEBUGGING
+  cerr << "Exiting readAndDispatch" << endl;
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+  initWinsock();
+
+  if (isNT4()) {
+    loadPSAPIDLL(); // Will exit if not present
+  }
+
+  SOCKET clientListeningSock = setupListeningSocket(CLIENT_PORT);
+
+  handler = new ServerHandler();
+  Lists::init();
+
+  reaper = new Reaper(&reapCB);
+  if (!reaper->start()) {
+    exit(1);
+  }
+
+  while (true) {
+    // Select on all sockets:
+    //  - client listening socket
+    //  - sockets for all client connections
+
+    // When one of the client connections closes, close its socket
+    // handles.
+
+    fd_set set;
+    SOCKET maxSock = 0;
+
+    // Set up fd_set
+    {
+      int i;
+      FD_ZERO(&set);
+      FD_SET(clientListeningSock, &set);
+      if (clientListeningSock > maxSock) {
+        maxSock = clientListeningSock;
+      }
+      for (i = 0; i < clientList.size(); i++) {
+        ClientInfo* info = clientList.get(i);
+        if (info->getDataSocket() > maxSock) {
+          maxSock = info->getDataSocket();
+        }
+        FD_SET(info->getDataSocket(), &set);
+      }
+    }
+    struct timeval timeout;
+    timeout.tv_sec = 300; // 5 minutes
+    timeout.tv_usec = 0;
+    int res = select(maxSock, &set, NULL, NULL, &timeout);
+    if (res > 0) {
+
+      ////////////////
+      // New client //
+      ////////////////
+      if (FD_ISSET(clientListeningSock, &set)) {
+        SOCKET fd = acceptFromLocalhost(clientListeningSock);
+        if (fd != INVALID_SOCKET) {
+          // Create new client information object
+          ClientInfo* info = new ClientInfo(fd);
+          // Add to list of clients
+          clientList.addClient(info);
+#ifdef DEBUGGING
+          cerr << "New client" << endl;
+#endif
+        }
+      }
+
+      ///////////////////////////
+      // Commands from clients //
+      ///////////////////////////
+      ClientInfo* clientInfo;
+      if (clientList.isAnyDataSocketSet(&set, &clientInfo)) {
+        readAndDispatch(clientInfo);
+      }
+    } else if (res < 0) {
+      // Looks like one of the clients was killed. Try to figure out which one.
+      bool found = false;
+      fd_set set;
+      struct timeval timeout;
+      timeout.tv_sec = 0;
+      timeout.tv_usec = 0;
+      for (int i = 0; i < clientList.size(); i++) {
+        ClientInfo* info = clientList.get(i);
+        FD_ZERO(&set);
+        FD_SET(info->getDataSocket(), &set);
+        if (select(1 + info->getDataSocket(), &set, NULL, NULL, &timeout) < 0) {
+          found = true;
+          clientList.removeClient(info);
+          info->closeAll();
+          delete info;
+          break;
+        }
+      }
+      if (!found) {
+        // This indicates trouble -- one of our listening sockets died.
+        exit(1);
+      }
+    }
+  }
+
+  return 0;
+}