Mercurial > hg > truffle
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, ®s[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; +}