Mercurial > hg > graal-compiler
view agent/src/os/win32/SwDbgSub.cpp @ 238:3df2fe7c4451
Merge
author | trims |
---|---|
date | Fri, 25 Jul 2008 11:29:03 -0700 |
parents | a61af66fc99e |
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. * */ // This is the source code for the subprocess forked by the Simple // Windows Debug Server. It assumes most of the responsibility for the // debug session, and processes all of the commands sent by clients. // Disable too-long symbol warnings #pragma warning ( disable : 4786 ) #include <iostream> #include <vector> #include <stdlib.h> #include <assert.h> // Must come before windows.h #include <winsock2.h> #include <windows.h> #include "IOBuf.hpp" #include "libInfo.hpp" #include "LockableList.hpp" #include "Message.hpp" #include "Monitor.hpp" #include "nt4internals.hpp" // Uncomment the #define below to get messages on stderr // #define DEBUGGING using namespace std; DWORD pid; HANDLE procHandle; IOBuf* ioBuf; // State flags indicating whether the attach to the remote process // definitively succeeded or failed volatile bool attachFailed = false; volatile bool attachSucceeded = false; // State flag indicating whether the target process is suspended. // Modified by suspend()/resume(), viewed by debug thread, but only // under cover of the threads lock. volatile bool suspended = false; // State flags indicating whether we are considered to be attached to // the target process and are therefore queuing up events to be sent // back to the debug server. These flags are only accessed and // modified under the cover of the eventLock. Monitor* eventLock; // The following is set to true when a client is attached to this process volatile bool generateDebugEvents = false; // Pointer to current debug event; non-NULL indicates a debug event is // waiting to be sent to the client. Main thread sets this to NULL to // indicate that the event has been consumed; also sets // passEventToClient, below. volatile DEBUG_EVENT* curDebugEvent = NULL; // Set by main thread to indicate whether the most recently posted // debug event should be passed on to the target process. volatile bool passEventToClient = true; void conditionalPostDebugEvent(DEBUG_EVENT* ev, DWORD* continueOrNotHandledFlag) { // FIXME: make it possible for the client to enable and disable // certain types of events (have to do so in a platform-independent // manner) switch (ev->dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: switch (ev->u.Exception.ExceptionRecord.ExceptionCode) { case EXCEPTION_BREAKPOINT: break; case EXCEPTION_SINGLE_STEP: break; case EXCEPTION_ACCESS_VIOLATION: break; default: return; } } eventLock->lock(); if (generateDebugEvents) { curDebugEvent = ev; while (curDebugEvent != NULL) { eventLock->wait(); } if (passEventToClient) { *continueOrNotHandledFlag = DBG_EXCEPTION_NOT_HANDLED; } else { *continueOrNotHandledFlag = DBG_CONTINUE; } } eventLock->unlock(); } //---------------------------------------------------------------------- // Module list // vector<LibInfo> libs; //---------------------------------------------------------------------- // Thread list // struct ThreadInfo { DWORD tid; HANDLE thread; ThreadInfo(DWORD tid, HANDLE thread) { this->tid = tid; this->thread = thread; } }; class ThreadList : public LockableList<ThreadInfo> { public: bool removeByThreadID(DWORD tid) { for (InternalListType::iterator iter = internalList.begin(); iter != internalList.end(); iter++) { if ((*iter).tid == tid) { internalList.erase(iter); return true; } } return false; } HANDLE threadIDToHandle(DWORD tid) { for (InternalListType::iterator iter = internalList.begin(); iter != internalList.end(); iter++) { if ((*iter).tid == tid) { return (*iter).thread; } } return NULL; } }; ThreadList threads; //---------------------------------------------------------------------- // INITIALIZATION AND TERMINATION // void printError(const char* prefix) { DWORD detail = GetLastError(); LPTSTR message; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, detail, 0, (LPTSTR) &message, 1, NULL); // FIXME: This is signaling an error: "The handle is invalid." ? // Do I have to do all of my WaitForDebugEvent calls from the same thread? cerr << prefix << ": " << message << endl; LocalFree(message); } void endProcess(bool waitForProcess = true) { NT4::unloadNTDLL(); if (waitForProcess) { // Though we're exiting because of an error, do not tear down the // target process. WaitForSingleObject(procHandle, INFINITE); } CloseHandle(procHandle); exit(0); } DWORD WINAPI debugThreadEntry(void*) { #ifdef DEBUGGING DWORD lastMsgId = 0; int count = 0; #endif if (!DebugActiveProcess(pid)) { attachFailed = true; return 0; } // Wait for debug events. We keep the information from some of these // on the side in anticipation of later queries by the client. NOTE // that we leave the process running. The main thread is responsible // for suspending and resuming all currently-active threads upon // client attach and detach. while (true) { DEBUG_EVENT ev; if (!WaitForDebugEvent(&ev, INFINITE)) { #ifdef DEBUGGING if (++count < 10) { // FIXME: This is signaling an error: "The handle is invalid." ? // Do I have to do all of my WaitForDebugEvent calls from the same thread? printError("WaitForDebugEvent failed"); } #endif } else { #ifdef DEBUGGING if (ev.dwDebugEventCode != lastMsgId) { lastMsgId = ev.dwDebugEventCode; count = 0; cerr << "Debug thread received event " << ev.dwDebugEventCode << endl; } else { if (++count < 10) { cerr << "Debug thread received event " << ev.dwDebugEventCode << endl; } } #endif DWORD dbgContinueMode = DBG_CONTINUE; switch (ev.dwDebugEventCode) { case LOAD_DLL_DEBUG_EVENT: conditionalPostDebugEvent(&ev, &dbgContinueMode); break; case UNLOAD_DLL_DEBUG_EVENT: conditionalPostDebugEvent(&ev, &dbgContinueMode); break; case CREATE_PROCESS_DEBUG_EVENT: threads.lock(); // FIXME: will this deal properly with child processes? If // not, is it possible to make it do so? #ifdef DEBUGGING cerr << "CREATE_PROCESS_DEBUG_EVENT " << ev.dwThreadId << " " << ev.u.CreateProcessInfo.hThread << endl; #endif if (ev.u.CreateProcessInfo.hThread != NULL) { threads.add(ThreadInfo(ev.dwThreadId, ev.u.CreateProcessInfo.hThread)); } threads.unlock(); break; case CREATE_THREAD_DEBUG_EVENT: threads.lock(); #ifdef DEBUGGING cerr << "CREATE_THREAD_DEBUG_EVENT " << ev.dwThreadId << " " << ev.u.CreateThread.hThread << endl; #endif if (suspended) { // Suspend this thread before adding it to the thread list SuspendThread(ev.u.CreateThread.hThread); } threads.add(ThreadInfo(ev.dwThreadId, ev.u.CreateThread.hThread)); threads.unlock(); break; case EXIT_THREAD_DEBUG_EVENT: threads.lock(); #ifdef DEBUGGING cerr << "EXIT_THREAD_DEBUG_EVENT " << ev.dwThreadId << endl; #endif threads.removeByThreadID(ev.dwThreadId); threads.unlock(); break; case EXCEPTION_DEBUG_EVENT: // cerr << "EXCEPTION_DEBUG_EVENT" << endl; switch (ev.u.Exception.ExceptionRecord.ExceptionCode) { case EXCEPTION_BREAKPOINT: // cerr << "EXCEPTION_BREAKPOINT" << endl; if (!attachSucceeded && !attachFailed) { attachSucceeded = true; } break; default: dbgContinueMode = DBG_EXCEPTION_NOT_HANDLED; break; } conditionalPostDebugEvent(&ev, &dbgContinueMode); break; case EXIT_PROCESS_DEBUG_EVENT: endProcess(false); // NOT REACHED break; default: #ifdef DEBUGGING cerr << "Received debug event " << ev.dwDebugEventCode << endl; #endif break; } ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, dbgContinueMode); } } } bool attachToProcess() { // Create event lock eventLock = new Monitor(); // Get a process handle for later procHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (procHandle == NULL) { return false; } // Start up the debug thread DWORD debugThreadId; if (CreateThread(NULL, 0, &debugThreadEntry, NULL, 0, &debugThreadId) == NULL) { // Failed to make background debug thread. Fail. return false; } while ((!attachSucceeded) && (!attachFailed)) { Sleep(1); } if (attachFailed) { return false; } assert(attachSucceeded); return true; } bool readMessage(Message* msg) { DWORD numRead; if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), msg, sizeof(Message), &numRead, NULL)) { return false; } if (numRead != sizeof(Message)) { return false; } // For "poke" messages, must follow up by reading raw data if (msg->type == Message::POKE) { char* dataBuf = new char[msg->pokeArg.numBytes]; if (dataBuf == NULL) { return false; } if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), dataBuf, msg->pokeArg.numBytes, &numRead, NULL)) { delete[] dataBuf; return false; } if (numRead != msg->pokeArg.numBytes) { delete[] dataBuf; return false; } msg->pokeArg.data = (void *) dataBuf; } return true; } void handlePeek(Message* msg) { #ifdef DEBUGGING cerr << "Entering handlePeek()" << endl; #endif char* memBuf = new char[msg->peekArg.numBytes]; if (memBuf == NULL) { ioBuf->writeString("B"); ioBuf->writeBinChar(0); ioBuf->flush(); delete[] memBuf; return; } // Try fast case first DWORD numRead; BOOL res = ReadProcessMemory(procHandle, (LPCVOID) msg->peekArg.address, memBuf, msg->peekArg.numBytes, &numRead); if (res && (numRead == msg->peekArg.numBytes)) { // OK, complete success. Phew. #ifdef DEBUGGING cerr << "Peek success case" << endl; #endif ioBuf->writeString("B"); ioBuf->writeBinChar(1); ioBuf->writeBinUnsignedInt(numRead); ioBuf->writeBinChar(1); ioBuf->writeBinBuf(memBuf, numRead); } else { #ifdef DEBUGGING cerr << "*** Peek slow case ***" << endl; #endif ioBuf->writeString("B"); ioBuf->writeBinChar(1); // Use VirtualQuery to speed things up a bit DWORD numLeft = msg->peekArg.numBytes; char* curAddr = (char*) msg->peekArg.address; while (numLeft > 0) { MEMORY_BASIC_INFORMATION memInfo; VirtualQueryEx(procHandle, curAddr, &memInfo, sizeof(memInfo)); DWORD numToRead = memInfo.RegionSize; if (numToRead > numLeft) { numToRead = numLeft; } DWORD numRead; if (memInfo.State == MEM_COMMIT) { // Read the process memory at this address for this length // FIXME: should check the result of this read ReadProcessMemory(procHandle, curAddr, memBuf, numToRead, &numRead); // Write this out #ifdef DEBUGGING cerr << "*** Writing " << numToRead << " bytes as mapped ***" << endl; #endif ioBuf->writeBinUnsignedInt(numToRead); ioBuf->writeBinChar(1); ioBuf->writeBinBuf(memBuf, numToRead); } else { // Indicate region is free #ifdef DEBUGGING cerr << "*** Writing " << numToRead << " bytes as unmapped ***" << endl; #endif ioBuf->writeBinUnsignedInt(numToRead); ioBuf->writeBinChar(0); } curAddr += numToRead; numLeft -= numToRead; } } ioBuf->flush(); delete[] memBuf; #ifdef DEBUGGING cerr << "Exiting handlePeek()" << endl; #endif } void handlePoke(Message* msg) { #ifdef DEBUGGING cerr << "Entering handlePoke()" << endl; #endif DWORD numWritten; BOOL res = WriteProcessMemory(procHandle, (LPVOID) msg->pokeArg.address, msg->pokeArg.data, msg->pokeArg.numBytes, &numWritten); if (res && (numWritten == msg->pokeArg.numBytes)) { // Success ioBuf->writeBoolAsInt(true); #ifdef DEBUGGING cerr << " (Succeeded)" << endl; #endif } else { // Failure ioBuf->writeBoolAsInt(false); #ifdef DEBUGGING cerr << " (Failed)" << endl; #endif } ioBuf->writeEOL(); ioBuf->flush(); // We clean up the data char* dataBuf = (char*) msg->pokeArg.data; delete[] dataBuf; #ifdef DEBUGGING cerr << "Exiting handlePoke()" << endl; #endif } bool suspend() { if (suspended) { return false; } // Before we suspend, we must take a snapshot of the loaded module // names and base addresses, since acquiring this snapshot requires // starting and exiting a thread in the remote process (at least on // NT 4). libs.clear(); #ifdef DEBUGGING cerr << "Starting suspension" << endl; #endif libInfo(pid, libs); #ifdef DEBUGGING cerr << " Got lib info" << endl; #endif threads.lock(); #ifdef DEBUGGING cerr << " Got thread lock" << endl; #endif suspended = true; int j = 0; for (int i = 0; i < threads.size(); i++) { j++; SuspendThread(threads.get(i).thread); } #ifdef DEBUGGING cerr << "Suspended " << j << " threads" << endl; #endif threads.unlock(); return true; } bool resume() { if (!suspended) { return false; } threads.lock(); suspended = false; for (int i = 0; i < threads.size(); i++) { ResumeThread(threads.get(i).thread); } threads.unlock(); #ifdef DEBUGGING cerr << "Resumed process" << endl; #endif return true; } int main(int argc, char **argv) { if (argc != 2) { // Should only be used by performing CreateProcess within SwDbgSrv exit(1); } if (sscanf(argv[1], "%u", &pid) != 1) { exit(1); } // Try to attach to process if (!attachToProcess()) { // Attach failed. Notify parent by writing result to stdout file // handle. char res = 0; DWORD numBytes; WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), &res, sizeof(res), &numBytes, NULL); exit(1); } // Server is expecting success result back. char res = 1; DWORD numBytes; WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), &res, sizeof(res), &numBytes, NULL); // Initialize our I/O buffer ioBuf = new IOBuf(32768, 131072); ioBuf->setOutputFileHandle(GetStdHandle(STD_OUTPUT_HANDLE)); // At this point we are attached. Enter our main loop which services // requests from the server. Note that in order to handle attach/ // detach properly (i.e., resumption of process upon "detach") we // will need another thread which handles debug events. while (true) { // Read a message from the server Message msg; if (!readMessage(&msg)) { endProcess(); } #ifdef DEBUGGING cerr << "Main thread read message: " << msg.type << endl; #endif switch (msg.type) { // ATTACH and DETACH messages MUST come in pairs case Message::ATTACH: suspend(); eventLock->lock(); generateDebugEvents = true; eventLock->unlock(); break; case Message::DETACH: eventLock->lock(); generateDebugEvents = false; // Flush remaining event if any if (curDebugEvent != NULL) { curDebugEvent = NULL; eventLock->notifyAll(); } eventLock->unlock(); resume(); break; case Message::LIBINFO: { if (!suspended) { ioBuf->writeInt(0); } else { // Send back formatted text ioBuf->writeInt(libs.size()); for (int i = 0; i < libs.size(); i++) { ioBuf->writeSpace(); ioBuf->writeInt(1); ioBuf->writeSpace(); ioBuf->writeInt(libs[i].name.size()); ioBuf->writeSpace(); ioBuf->writeString(libs[i].name.c_str()); ioBuf->writeSpace(); ioBuf->writeAddress(libs[i].base); } } ioBuf->writeEOL(); ioBuf->flush(); break; } case Message::PEEK: handlePeek(&msg); break; case Message::POKE: handlePoke(&msg); break; case Message::THREADLIST: { if (!suspended) { ioBuf->writeInt(0); } else { threads.lock(); ioBuf->writeInt(threads.size()); for (int i = 0; i < threads.size(); i++) { ioBuf->writeSpace(); ioBuf->writeAddress((void*) threads.get(i).thread); } threads.unlock(); } ioBuf->writeEOL(); ioBuf->flush(); break; } case Message::DUPHANDLE: { HANDLE dup; if (DuplicateHandle(procHandle, msg.handleArg.handle, GetCurrentProcess(), &dup, 0, FALSE, DUPLICATE_SAME_ACCESS)) { ioBuf->writeBoolAsInt(true); ioBuf->writeSpace(); ioBuf->writeAddress((void*) dup); } else { ioBuf->writeBoolAsInt(false); } ioBuf->writeEOL(); ioBuf->flush(); break; } case Message::CLOSEHANDLE: { CloseHandle(msg.handleArg.handle); break; } case Message::GETCONTEXT: { if (!suspended) { ioBuf->writeBoolAsInt(false); } else { CONTEXT context; context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; if (GetThreadContext(msg.handleArg.handle, &context)) { ioBuf->writeBoolAsInt(true); // EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP, DS, ES, FS, GS, // CS, SS, EFLAGS, DR0, DR1, DR2, DR3, DR6, DR7 // See README-commands.txt ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Eax); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Ebx); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Ecx); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Edx); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Esi); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Edi); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Ebp); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Esp); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Eip); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegDs); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegEs); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegFs); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegGs); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegCs); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.SegSs); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.EFlags); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr0); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr1); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr2); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr3); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr6); ioBuf->writeSpace(); ioBuf->writeAddress((void*) context.Dr7); } else { ioBuf->writeBoolAsInt(false); } } ioBuf->writeEOL(); ioBuf->flush(); break; } case Message::SETCONTEXT: { if (!suspended) { ioBuf->writeBoolAsInt(false); } else { CONTEXT context; context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; context.Eax = msg.setContextArg.Eax; context.Ebx = msg.setContextArg.Ebx; context.Ecx = msg.setContextArg.Ecx; context.Edx = msg.setContextArg.Edx; context.Esi = msg.setContextArg.Esi; context.Edi = msg.setContextArg.Edi; context.Ebp = msg.setContextArg.Ebp; context.Esp = msg.setContextArg.Esp; context.Eip = msg.setContextArg.Eip; context.SegDs = msg.setContextArg.Ds; context.SegEs = msg.setContextArg.Es; context.SegFs = msg.setContextArg.Fs; context.SegGs = msg.setContextArg.Gs; context.SegCs = msg.setContextArg.Cs; context.SegSs = msg.setContextArg.Ss; context.EFlags = msg.setContextArg.EFlags; context.Dr0 = msg.setContextArg.Dr0; context.Dr1 = msg.setContextArg.Dr1; context.Dr2 = msg.setContextArg.Dr2; context.Dr3 = msg.setContextArg.Dr3; context.Dr6 = msg.setContextArg.Dr6; context.Dr7 = msg.setContextArg.Dr7; if (SetThreadContext(msg.setContextArg.handle, &context)) { ioBuf->writeBoolAsInt(true); } else { ioBuf->writeBoolAsInt(false); } } ioBuf->writeEOL(); ioBuf->flush(); break; } case Message::SELECTORENTRY: { LDT_ENTRY entry; if (GetThreadSelectorEntry(msg.selectorArg.handle, msg.selectorArg.selector, &entry)) { ioBuf->writeBoolAsInt(true); ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.LimitLow); ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.BaseLow); ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.BaseMid); ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.Flags1); ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.Flags2); ioBuf->writeSpace(); ioBuf->writeAddress((void*) entry.HighWord.Bytes.BaseHi); } else { ioBuf->writeBoolAsInt(false); } ioBuf->writeEOL(); ioBuf->flush(); break; } case Message::SUSPEND: suspend(); break; case Message::RESUME: resume(); break; case Message::POLLEVENT: eventLock->lock(); if (curDebugEvent == NULL) { ioBuf->writeBoolAsInt(false); } else { ioBuf->writeBoolAsInt(true); ioBuf->writeSpace(); threads.lock(); ioBuf->writeAddress((void*) threads.threadIDToHandle(curDebugEvent->dwThreadId)); threads.unlock(); ioBuf->writeSpace(); ioBuf->writeUnsignedInt(curDebugEvent->dwDebugEventCode); // Figure out what else to write switch (curDebugEvent->dwDebugEventCode) { case LOAD_DLL_DEBUG_EVENT: ioBuf->writeSpace(); ioBuf->writeAddress(curDebugEvent->u.LoadDll.lpBaseOfDll); break; case UNLOAD_DLL_DEBUG_EVENT: ioBuf->writeSpace(); ioBuf->writeAddress(curDebugEvent->u.UnloadDll.lpBaseOfDll); break; case EXCEPTION_DEBUG_EVENT: { DWORD code = curDebugEvent->u.Exception.ExceptionRecord.ExceptionCode; ioBuf->writeSpace(); ioBuf->writeUnsignedInt(code); ioBuf->writeSpace(); ioBuf->writeAddress(curDebugEvent->u.Exception.ExceptionRecord.ExceptionAddress); switch (curDebugEvent->u.Exception.ExceptionRecord.ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: ioBuf->writeSpace(); ioBuf->writeBoolAsInt(curDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[0] != 0); ioBuf->writeSpace(); ioBuf->writeAddress((void*) curDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[1]); break; default: break; } break; } default: break; } } eventLock->unlock(); ioBuf->writeEOL(); ioBuf->flush(); break; case Message::CONTINUEEVENT: eventLock->lock(); if (curDebugEvent == NULL) { ioBuf->writeBoolAsInt(false); } else { curDebugEvent = NULL; passEventToClient = msg.boolArg.val; ioBuf->writeBoolAsInt(true); eventLock->notify(); } eventLock->unlock(); ioBuf->writeEOL(); ioBuf->flush(); break; } } endProcess(); // NOT REACHED return 0; }