view agent/src/os/win32/SwDbgSrv.cpp @ 0:a61af66fc99e

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c18cbe5936b8
line wrap: on
line source
/*
 * 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;
}