Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2041 lines
46 KiB

#include "precomp.h"
#pragma hdrstop
#ifdef WIN32S
extern BOOL fProcessingDebugEvent;
extern BOOL fWaitForDebugEvent;
extern DWORD tidExit;
extern BOOL fExitProcessDebugEvent; // set true when the last debug event
#endif
HANDLE hEventContinue;
extern LPDM_MSG LpDmMsg;
extern SYSTEM_INFO SystemInfo;
extern WT_STRUCT WtStruct; // .. for wt
extern DEBUG_ACTIVE_STRUCT DebugActiveStruct; // ... for DebugActiveProcess()
extern PKILLSTRUCT KillQueue;
extern CRITICAL_SECTION csKillQueue;
extern HTHDX thdList;
extern HPRCX prcList;
extern CRITICAL_SECTION csThreadProcList;
extern BOOL fSmartRangeStep;
extern HANDLE hEventNoDebuggee;
extern HANDLE hEventRemoteQuit;
extern BOOL fDisconnected;
extern BOOL fUseRoot;
extern char nameBuffer[];
#if defined(TARGET_MIPS)
MIPSCONTEXTSIZE MipsContextSize;
#endif
BOOL
DbgWriteMemory(
HPRCX hprc,
LPVOID lpOffset,
LPBYTE lpb,
DWORD cb,
LPDWORD pcbWritten
)
/*++
Routine Description:
Write to a flat address in a process.
Arguments:
hprc - Supplies the handle to the process
lpOffset - Supplies address of data in process
lpb - Supplies a pointer to the bytes to be written
cb - Supplies the count of bytes to be written
pcbWritten - Returns the number of bytes actually written
Return Value:
TRUE if successful and FALSE otherwise
--*/
{
BOOL fRet;
assert(hprc->rwHand != (HANDLE)-1);
if (hprc->rwHand == (HANDLE)-1) {
return FALSE;
}
fRet = WriteProcessMemory(hprc->rwHand, lpOffset, lpb, cb, pcbWritten);
#if defined(JLS_RWBP) && DBG
{
DWORD cbT;
LPBYTE lpbT = malloc(cb);
assert( fRet );
assert( *pcbWritten == cb );
fRet = ReadProcessMemory(hprc->rwHand, lpOffset, lpbT, cb, &cbT);
assert(fRet);
assert( cb == cbT);
assert(memcmp(lpbT, lpb) == 0);
free lpbT;
}
#endif
return fRet;
}
BOOL
DbgReadMemory(
HPRCX hprc,
LPVOID lpOffset,
LPVOID lpb,
DWORD cb,
LPDWORD lpRead
)
/*++
Routine Description:
Read from a flat address in a process.
Arguments:
hprc - Supplies the handle to the process
lpOffset - Supplies address of data in process
lpb - Supplies a pointer to the local buffer
cb - Supplies the count of bytes to read
lpRead - Returns the number of bytes actually read
Return Value:
TRUE if successful and FALSE otherwise
--*/
{
#ifndef WIN32S
DWORD cbr;
if (CrashDump) {
cbr = DmpReadMemory( (PVOID)lpOffset, lpb, cb );
if (lpRead) {
*lpRead = cbr;
}
return (cbr > 0) || (cbr == cb);
}
#endif
assert(hprc->rwHand != (HANDLE)-1);
if (hprc->rwHand == (HANDLE)-1) {
return FALSE;
}
if (ReadProcessMemory(hprc->rwHand, lpOffset, lpb, cb, lpRead)) {
return TRUE;
} else {
#if DBG
int e = GetLastError();
#endif
//
// Reads across page boundaries will not work if the
// second page is not accessible.
//
#define PAGE_SIZE (SystemInfo.dwPageSize)
#define PAGE_MASK (~(PAGE_SIZE-1))
DWORD firstsize;
DWORD dwRead;
firstsize = (((DWORD)lpOffset + PAGE_SIZE) & PAGE_MASK) - (DWORD)lpOffset;
if (cb < firstsize) {
firstsize = cb;
}
//
// read from the first page. If the first read fails,
// fail the whole thing.
//
if (!ReadProcessMemory(hprc->rwHand, lpOffset, lpb, firstsize,
lpRead)) {
return FALSE;
}
//
// read intermediate complete pages.
// if any of these reads fail, succeed with a short read.
//
assert(*lpRead == firstsize);
cb -= firstsize;
lpb = (LPVOID)((LPBYTE)lpb + firstsize);
lpOffset = (LPVOID)((LPBYTE)lpOffset + firstsize);
while (cb >= PAGE_SIZE) {
if (!ReadProcessMemory(hprc->rwHand, lpOffset, lpb, PAGE_SIZE,
&dwRead)) {
return TRUE;
} else {
assert(dwRead == PAGE_SIZE);
lpb = (LPVOID)((LPBYTE)lpb + PAGE_SIZE);
lpOffset = (LPVOID)((LPBYTE)lpOffset + PAGE_SIZE);
*lpRead += dwRead;
cb -= PAGE_SIZE;
}
}
if (cb > 0) {
if (ReadProcessMemory(hprc->rwHand, lpOffset, lpb, cb, &dwRead)) {
assert(dwRead == cb);
*lpRead += dwRead;
}
}
return TRUE;
}
}
BOOL
DbgGetThreadContext(
HTHDX hthd,
PCONTEXT lpcontext
)
{
#ifndef WIN32S
if (CrashDump) {
return DmpGetContext(hthd->tid-1, lpcontext);
} else if (hthd->fWowEvent) {
return WOWGetThreadContext(hthd, lpcontext);
} else
#endif
{
#ifndef TARGET_MIPS
return GetThreadContext(hthd->rwHand, lpcontext);
#else
BOOL rc;
DWORD Flags = lpcontext->ContextFlags;
if (MipsContextSize == Ctx32Bit) {
lpcontext->ContextFlags = ((lpcontext->ContextFlags & ~CONTEXT_EXTENDED_INTEGER) | CONTEXT_INTEGER);
}
rc = GetThreadContext(hthd->rwHand, lpcontext);
if (rc) {
if ((Flags & CONTEXT_EXTENDED_INTEGER) == CONTEXT_EXTENDED_INTEGER) {
CoerceContext32To64(lpcontext);
} else if ((Flags & CONTEXT_INTEGER) == CONTEXT_INTEGER) {
CoerceContext64To32(lpcontext);
}
}
return rc;
#endif
}
}
BOOL
DbgSetThreadContext(
HTHDX hthd,
PCONTEXT lpcontext
)
{
assert(!CrashDump);
#ifndef WIN32S
if (CrashDump) {
return FALSE;
} else if (hthd->fWowEvent) {
return WOWSetThreadContext(hthd, lpcontext);
} else
#endif
{
#ifdef TARGET_MIPS
CONTEXT ctx = *lpcontext;
lpcontext = &ctx;
if (MipsContextSize == Ctx32Bit) {
CoerceContext64To32(lpcontext);
} else {
CoerceContext32To64(lpcontext);
}
#endif
return SetThreadContext(hthd->rwHand, lpcontext);
}
}
BOOL
WriteBreakPoint(
IN PBREAKPOINT Breakpoint
)
{
DWORD cb;
BP_UNIT opcode = BP_OPCODE;
BOOL r = AddrWriteMemory(Breakpoint->hprc,
Breakpoint->hthd,
&Breakpoint->addr,
&opcode,
BP_SIZE,
&cb);
return (r && (cb == BP_SIZE));
}
BOOL
RestoreBreakPoint(
IN PBREAKPOINT Breakpoint
)
{
DWORD cb;
BOOL r;
assert(Breakpoint->bpType == bptpExec);
r = AddrWriteMemory(Breakpoint->hprc,
Breakpoint->hthd,
&Breakpoint->addr,
&Breakpoint->instr1,
BP_SIZE,
&cb);
return (r && (cb == BP_SIZE));
}
/****************************************************************************/
/****************************************************************************/
#ifndef PROCESSOR_MIPS_R10000
#define PROCESSOR_MIPS_R10000 10000
#endif
VOID
GetMachineType(
LPPROCESSOR p
)
{
// Look Ma, no ifdefs!!
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
switch (SystemInfo.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_INTEL:
p->Level = SystemInfo.wProcessorLevel;
p->Type = mptix86;
p->Endian = endLittle;
break;
case PROCESSOR_ARCHITECTURE_MIPS:
p->Level = SystemInfo.wProcessorLevel * 1000;
p->Type = mptmips;
p->Endian = endLittle;
break;
case PROCESSOR_ARCHITECTURE_ALPHA:
p->Level = SystemInfo.wProcessorLevel;
p->Type = mptdaxp;
p->Endian = endLittle;
p->Level = 21064; // BUGBUG - why?
break;
case PROCESSOR_ARCHITECTURE_PPC:
p->Level = SystemInfo.wProcessorLevel + 600; // BUGBUG - 603+
p->Type = mptmppc;
p->Endian = endLittle;
break;
default:
assert(!"Unknown target machine");
break;
}
}
HWND
HwndFromPid (
PID pid
)
{
HWND hwnd = GetForegroundWindow();
HWND hwndNext;
DPRINT(4, ( "*HwndFromPid, pid = 0x%lx\n", pid ) );
for (hwndNext = GetWindow ( hwnd, GW_HWNDFIRST );
hwndNext;
hwndNext = GetWindow ( hwndNext, GW_HWNDNEXT )) {
// what we want is windows *without* an owner, hence !GetWindow...
if ( !GetWindow ( hwndNext, GW_OWNER ) &&
IsWindowVisible ( hwndNext ) ) {
PID pidT;
GetWindowThreadProcessId ( hwndNext, &pidT );
DPRINT(4, ("\thwnd 0x%08lx owned by process 0x%lx, ",
hwndNext, pidT ) );
#if DBG
{
char szWindowText[256];
if ( GetWindowText(hwndNext, szWindowText,
sizeof(szWindowText)) ) {
DPRINT(4, ("title = \"%s\"\n", szWindowText) );
} else {
DPRINT(4, ("title = \"<none>\"\n") );
}
}
#endif
if ( pid == pidT ) {
// found a match, return the hwnd
break;
}
} // if ( !GetWindow...
hwndNext = GetWindow ( hwndNext, GW_HWNDNEXT );
} // while ( hwndNext )
return hwndNext;
}
VOID
DmSetFocus (
HPRCX phprc
)
{
PID pidGer; // debugger pid
PID pidCurFore; // owner of foreground window
HWND hwndCurFore; // current foreground window
HWND phprc_hwndProcess;
HWND hwndT;
// decide if we are the foreground app currently
pidGer = GetCurrentProcessId(); // debugger pid
hwndCurFore = GetForegroundWindow();
if ( hwndCurFore &&
GetWindowThreadProcessId ( hwndCurFore, &pidCurFore ) ) {
if ( pidCurFore != pidGer ) {
// foreground is not debugger, bail out
return;
}
}
phprc_hwndProcess = HwndFromPid ( phprc->pid );
if ( !phprc_hwndProcess ) {
// no window attached to pid; bail out
return;
}
// continuing with valid hwnd's and we have foreground window
assert ( phprc_hwndProcess );
// now, get the last active window in that group!
hwndT = GetLastActivePopup ( phprc_hwndProcess );
// NOTE: taskman has a check at this point for state disabled...
// don't know if I should do it either...
SetForegroundWindow ( hwndT );
}
/****************************************************************************/
//
// ContinueDebugEvent() queue.
// We can only have one debug event pending per process, but there may be
// one event pending for each process we are debugging.
//
// There are 200 entries in a static sized queue. If there are ever more
// than 200 debug events pending, AND windbg actually handles them all in
// less than 1/5 second, we will be in trouble. Until then, sleep soundly.
//
/****************************************************************************/
typedef struct tagCQUEUE {
struct tagCQUEUE *next;
DWORD pid;
DWORD tid;
DWORD dwContinueStatus;
} CQUEUE, *LPCQUEUE;
static LPCQUEUE lpcqFirst;
static LPCQUEUE lpcqLast;
static LPCQUEUE lpcqFree;
static CQUEUE cqueue[200];
static CRITICAL_SECTION csContinueQueue;
static BOOL DequeueContinueDebugEvents( void );
/***************************************************************************/
/***************************************************************************/
VOID
QueueContinueDebugEvent(
DWORD dwProcessId,
DWORD dwThreadId,
DWORD dwContinueStatus
)
/*++
Routine Description:
Queue a debug event continue for later execution.
Arguments:
dwProcessId = pid to continue
dwThreadId = tid to continue
dwContinueStatus - Supplies the continue status code
Return Value:
None.
--*/
{
#ifdef WIN32S
HTHDX hthd;
if (dwContinueStatus != DBG_TERMINATE_PROCESS) {
hthd = HTHDXFromPIDTID(dwProcessId, dwThreadId);
if (hthd) {
if (hthd->fContextDirty) {
/*
* Set the child's context
*/
DPRINT(1, ("Context is dirty\n"));
DbgSetThreadContext(hthd, &hthd->context);
hthd->fContextDirty = FALSE;
}
}
}
DPRINT(1, ("ContinueDebugEvent(PID:%x, TID:%x, Stat:%u)\r\n",
dwProcessId, dwThreadId, dwContinueStatus));
ContinueDebugEvent(dwProcessId, dwThreadId, dwContinueStatus);
fProcessingDebugEvent = FALSE;
fWaitForDebugEvent = FALSE;
#else
LPCQUEUE lpcq;
EnterCriticalSection(&csContinueQueue);
lpcq = lpcqFree;
assert(lpcq);
lpcqFree = lpcq->next;
lpcq->next = NULL;
if (lpcqLast) {
lpcqLast->next = lpcq;
}
lpcqLast = lpcq;
if (!lpcqFirst) {
lpcqFirst = lpcq;
}
lpcq->pid = dwProcessId;
lpcq->tid = dwThreadId;
lpcq->dwContinueStatus = dwContinueStatus;
LeaveCriticalSection(&csContinueQueue);
#endif
return;
} /* QueueContinueDebugEvent() */
#ifndef WIN32S
BOOL
DequeueContinueDebugEvents(
VOID
)
/*++
Routine Description:
Remove any pending continues from the queue and execute them.
Arguments:
none
Return Value:
TRUE if one or more events were continued.
FALSE if none were continued.
--*/
{
LPCQUEUE lpcq;
BOOL fDid = FALSE;
HTHDX hthd;
EnterCriticalSection(&csContinueQueue);
while ( lpcq=lpcqFirst ) {
hthd = HTHDXFromPIDTID(lpcq->pid, lpcq->tid);
if (hthd) {
if (hthd->fContextDirty) {
/*
* Set the child's context
*/
DbgSetThreadContext(hthd, &hthd->context);
hthd->fContextDirty = FALSE;
}
hthd->fWowEvent = FALSE;
}
ContinueDebugEvent(lpcq->pid, lpcq->tid, lpcq->dwContinueStatus);
lpcqFirst = lpcq->next;
if (lpcqFirst == NULL) {
lpcqLast = NULL;
}
lpcq->next = lpcqFree;
lpcqFree = lpcq;
fDid = TRUE;
}
LeaveCriticalSection(&csContinueQueue);
return fDid;
} /* DequeueContinueDebugEvents() */
#endif // !WIN32S
VOID
AddQueue(
DWORD dwType,
DWORD dwProcessId,
DWORD dwThreadId,
DWORD dwData,
DWORD dwLen
)
/*++
Routine Description:
Arguments:
dwType
dwProcessId
dwThreadId
dwData
dwLen
Return Value:
none
--*/
{
switch (dwType) {
case QT_CONTINUE_DEBUG_EVENT:
case QT_TRACE_DEBUG_EVENT:
if (CrashDump) {
break;
}
QueueContinueDebugEvent(dwProcessId, dwThreadId, dwData);
break;
case QT_RELOAD_MODULES:
case QT_REBOOT:
case QT_CRASH:
case QT_RESYNC:
assert(!"Unsupported usermode QType in AddQueue.");
break;
case QT_DEBUGSTRING:
assert(!"Is this a bad idea?");
DMPrintShellMsg( "%s", (LPSTR)dwData );
free((LPSTR)dwData);
break;
}
if (dwType == QT_CONTINUE_DEBUG_EVENT) {
SetEvent( hEventContinue );
}
return;
}
BOOL
DequeueAllEvents(
BOOL fForce, // force a dequeue even if the dm isn't initialized
BOOL fConsume // delete all events from the queue with no action
)
{
#ifdef WIN32S
return TRUE;
#else
return DequeueContinueDebugEvents();
#endif
}
VOID
InitEventQueue(
VOID
)
{
int n;
int i;
InitializeCriticalSection(&csContinueQueue);
n = sizeof(cqueue) / sizeof(CQUEUE);
for (i = 0; i < n-1; i++) {
cqueue[i].next = &cqueue[i+1];
}
cqueue[n-1].next = NULL;
lpcqFree = &cqueue[0];
lpcqFirst = NULL;
lpcqLast = NULL;
}
VOID
ProcessQueryTlsBaseCmd(
HPRCX hprcx,
HTHDX hthdx,
LPDBB lpdbb
)
/*++
Routine Description:
This function is called in response to an EM request to get the base
of the thread local storage for a given thread and DLL.
Arguments:
hprcx - Supplies a process handle
hthdx - Supplies a thread handle
lpdbb - Supplies the command information packet
Return Value:
None.
--*/
{
XOSD xosd;
OFFSET offRgTls;
DWORD iTls;
LPADDR lpaddr = (LPADDR) LpDmMsg->rgb;
OFFSET offResult;
DWORD cb;
int iDll;
OFFSET offDll = * (OFFSET *) lpdbb->rgbVar;
/*
* Read number 1. Get the pointer to the Thread Local Storage array.
*/
if ((DbgReadMemory(hprcx, (char *) hthdx->offTeb+0x2c,
&offRgTls, sizeof(OFFSET), &cb) == 0) ||
(cb != sizeof(OFFSET))) {
err:
xosd = xosdUnknown;
Reply(0, &xosd, lpdbb->hpid);
return;
}
/*
* Read number 2. Get the TLS index for this dll
*/
for (iDll=0; iDll<hprcx->cDllList; iDll+=1 ) {
if (hprcx->rgDllList[iDll].fValidDll &&
(hprcx->rgDllList[iDll].offBaseOfImage == offDll)) {
break;
}
}
if (iDll == hprcx->cDllList) {
goto err;
}
if (!DbgReadMemory(hprcx,
(char *) hprcx->rgDllList[iDll].offTlsIndex,
&iTls,
sizeof(iTls),
&cb) ||
(cb != sizeof(iTls))) {
goto err;
}
/*
* Read number 3. Get the actual TLS base pointer
*/
if ((DbgReadMemory(hprcx, (char *)offRgTls+iTls*sizeof(OFFSET),
&offResult, sizeof(OFFSET), &cb) == 0) ||
(cb != sizeof(OFFSET))) {
goto err;
}
memset(lpaddr, 0, sizeof(ADDR));
lpaddr->addr.off = offResult;
#ifdef TARGET_i386
lpaddr->addr.seg = (SEGMENT) hthdx->context.SegDs;
#else
lpaddr->addr.seg = 0;
#endif
ADDR_IS_FLAT(*lpaddr) = TRUE;
LpDmMsg->xosdRet = xosdNone;
Reply( sizeof(ADDR), LpDmMsg, lpdbb->hpid );
return;
} /* ProcessQueryTlsBaseCmd() */
VOID
ProcessQuerySelectorCmd(
HPRCX hprcx,
HTHDX hthdx,
LPDBB lpdbb
)
/*++
Routine Description:
This command is sent from the EM to fill in an LDT_ENTRY structure
for a given selector.
Arguments:
hprcx - Supplies the handle to the process
hthdx - Supplies the handle to the thread and is optional
lpdbb - Supplies the pointer to the full query packet
Return Value:
None.
--*/
{
XOSD xosd;
#if defined( TARGET_i386 )
SEGMENT seg;
seg = *((SEGMENT *) lpdbb->rgbVar);
if (hthdx == hthdxNull) {
hthdx = hprcx->hthdChild;
}
if ((hthdx != NULL) &&
(GetThreadSelectorEntry(hthdx->rwHand, seg, (LDT_ENTRY *) LpDmMsg->rgb))) {
LpDmMsg->xosdRet = xosdNone;
Reply( sizeof(LDT_ENTRY), LpDmMsg, lpdbb->hpid);
return;
}
#endif
#ifdef OSDEBUG4
xosd = xosdInvalidParameter;
#else
xosd = xosdInvalidSelector;
#endif
Reply( sizeof(xosd), &xosd, lpdbb->hpid);
return;
} /* ProcessQuerySelectorCmd */
VOID
ProcessVirtualQueryCmd(
HPRCX hprc,
LPDBB lpdbb
)
{
XOSD xosd = xosdNone;
ADDR addr;
BOOL fRet;
DWORD dwSize;
if (!hprc->rwHand || hprc->rwHand == (HANDLE)(-1)) {
#ifdef OSDEBUG4
xosd = xosdBadProcess;
#else
xosd = xosdInvalidProc;
#endif
}
addr = *(LPADDR)(lpdbb->rgbVar);
if (!ADDR_IS_FLAT(addr)) {
fRet = TranslateAddress(hprc, 0, &addr, TRUE);
assert(fRet);
if (!fRet) {
xosd = xosdBadAddress;
goto reply;
}
}
dwSize = VirtualQueryEx(hprc->rwHand,
(LPCVOID)addr.addr.off,
(PMEMORY_BASIC_INFORMATION)LpDmMsg->rgb,
sizeof(MEMORY_BASIC_INFORMATION));
if (dwSize != sizeof(MEMORY_BASIC_INFORMATION)) {
xosd = xosdUnknown;
goto reply;
}
reply:
LpDmMsg->xosdRet = xosd;
Reply( sizeof(MEMORY_BASIC_INFORMATION), LpDmMsg, lpdbb->hpid );
return;
} /* ProcessVirtualQueryCmd */
VOID
ProcessGetDmInfoCmd(
HPRCX hprc,
LPDBB lpdbb,
DWORD cb
)
{
LPDMINFO lpi = (LPDMINFO)LpDmMsg->rgb;
LpDmMsg->xosdRet = xosdNone;
lpi->fAsync = 1;
#ifdef WIN32S
lpi->fHasThreads = 0;
#else
lpi->fHasThreads = 1;
#endif
lpi->fReturnStep = 0;
//lpi->fRemote = ???
lpi->fAsyncStop = 1;
lpi->fAlwaysFlat = 0;
lpi->fHasReload = 0;
lpi->cbSpecialRegs = 0;
lpi->MajorVersion = 0;
lpi->MinorVersion = 0;
lpi->Breakpoints = bptsExec |
bptsDataC |
bptsDataW |
bptsDataR |
bptsDataExec;
GetMachineType(&lpi->Processor);
//
// hack so that TL can call tlfGetVersion before
// reply buffer is initialized.
//
if ( cb >= (sizeof(DBB) + sizeof(DMINFO)) ) {
memcpy(lpdbb->rgbVar, lpi, sizeof(DMINFO));
}
Reply( sizeof(DMINFO), LpDmMsg, lpdbb->hpid );
} /* ProcessGetDMInfoCmd */
ActionResumeThread(
DEBUG_EVENT * pde,
HTHDX hthd,
DWORD unused,
PSUSPENDSTRUCT pss
)
{
//
// This thread just hit a breakpoint after falling out of
// SuspendThread. Clear the BP, put the original context
// back and continue.
//
RemoveBP( pss->pbp );
hthd->context = pss->context;
hthd->fContextDirty = TRUE;
hthd->pss = NULL;
free(pss);
AddQueue( QT_CONTINUE_DEBUG_EVENT,
hthd->hprc->pid,
hthd->tid,
DBG_CONTINUE,
0);
return 0;
}
BOOL
MakeThreadSuspendItself(
HTHDX hthd
)
/*++
Routine Description:
Set up the thread to call SuspendThread. This relies on kernel32
being present in the debuggee, and the current implementation gives
up if the thread is in a 16 bit context.
The cpu dependent part of this is MakeThreadSuspendItselfHelper,
in mach.c.
Arguments:
hthd - Supplies thread
Return Value:
TRUE if the thread will be suspended, FALSE if not.
--*/
{
PSUSPENDSTRUCT pss;
ADDR addr;
HANDLE hdll;
FARPROC lpSuspendThread;
//
// the only time this should fail is when the debuggee
// does not use kernel32, which is rare.
//
if (!hthd->hprc->dwKernel32Base) {
DPRINT(1, ("can't suspend thread %x: Kernel32 not loaded\n",
(DWORD)hthd));
DMPrintShellMsg("*** Unable to suspend thread.\n");
return 0;
}
//
// Oh, yeah... don't try to do this with a 16 bit thread, either.
// maybe someday...
//
if (hthd->fWowEvent) {
DMPrintShellMsg("*** Can't leave 16 bit thread suspended.\n");
return 0;
}
//
// find the address of SuspendThread
//
hdll = GetModuleHandle("KERNEL32");
assert(hdll || !"kernel32 not found in DM!!!");
if (!hdll) {
return 0;
}
lpSuspendThread = GetProcAddress(hdll, "SuspendThread");
assert(lpSuspendThread || !"SuspendThread not found in kernel32!!!");
if (!lpSuspendThread) {
return 0;
}
//
// this is probably unneccessary, because I think kernel32
// may not be relocated.
//
lpSuspendThread = (FARPROC)((DWORD)lpSuspendThread - (DWORD)hdll
+ hthd->hprc->dwKernel32Base);
pss = malloc(sizeof(*pss));
assert(pss || !"malloc failed in MakeThreadSuspendItself");
if (!pss) {
return 0;
}
//
// Remember the current context
//
hthd->pss = pss;
pss->context = hthd->context;
//
// set a BP on the current PC, and register a persistent
// expected event to catch it later.
//
AddrInit(&addr, 0, 0, (DWORD) PC(hthd), TRUE, TRUE, FALSE, FALSE);
pss->pbp = SetBP( hthd->hprc, hthd, bptpExec, bpnsStop, &addr, (HPID) INVALID);
//
// don't try to step off of BP.
//
pss->atBP = hthd->atBP;
hthd->atBP = NULL;
RegisterExpectedEvent(
hthd->hprc,
hthd,
BREAKPOINT_DEBUG_EVENT,
(DWORD)pss->pbp,
NULL,
(ACVECTOR)ActionResumeThread,
TRUE,
pss);
//
// do machine dependent part
//
MakeThreadSuspendItselfHelper(hthd, lpSuspendThread);
return TRUE;
}
VOID
ProcessIoctlGenericCmd(
HPRCX hprc,
HTHDX hthd,
LPDBB lpdbb
)
{
LPIOL lpiol = (LPIOL)lpdbb->rgbVar;
PIOCTLGENERIC pig = (PIOCTLGENERIC)lpiol->rgbVar;
DWORD len;
ADDR addr;
switch( pig->ioctlSubType ) {
case IG_TRANSLATE_ADDRESS:
memcpy( &addr, pig->data, sizeof(addr) );
if (TranslateAddress( hprc, hthd, &addr, TRUE )) {
memcpy( pig->data, &addr, sizeof(addr) );
len = sizeof(IOCTLGENERIC) + pig->length;
memcpy( LpDmMsg->rgb, pig, len );
LpDmMsg->xosdRet = xosdNone;
Reply( sizeof(IOCTLGENERIC)+pig->length, LpDmMsg, lpdbb->hpid );
} else {
LpDmMsg->xosdRet = xosdUnknown;
Reply( 0, LpDmMsg, lpdbb->hpid );
}
break;
case IG_WATCH_TIME:
WtRangeStep( hthd );
LpDmMsg->xosdRet = xosdNone;
Reply( 0, LpDmMsg, lpdbb->hpid );
break;
case IG_WATCH_TIME_STOP:
WtStruct.fWt = TRUE;
WtStruct.dwType = pig->ioctlSubType;
WtStruct.hthd = hthd;
LpDmMsg->xosdRet = xosdNone;
Reply( 0, LpDmMsg, lpdbb->hpid );
break;
case IG_WATCH_TIME_RECALL:
WtStruct.fWt = TRUE;
WtStruct.dwType = pig->ioctlSubType;
WtStruct.hthd = hthd;
LpDmMsg->xosdRet = xosdNone;
Reply( 0, LpDmMsg, lpdbb->hpid );
break;
case IG_WATCH_TIME_PROCS:
WtStruct.fWt = TRUE;
WtStruct.dwType = pig->ioctlSubType;
WtStruct.hthd = hthd;
LpDmMsg->xosdRet = xosdNone;
Reply( 0, LpDmMsg, lpdbb->hpid );
break;
case IG_THREAD_INFO:
#ifdef WIN32S
LpDmMsg->xosdRet = xosdUnknown;
Reply( 0, LpDmMsg, lpdbb->hpid );
#else
{
typedef NTSTATUS (* QTHREAD)(HANDLE,THREADINFOCLASS,PVOID,ULONG,PULONG);
NTSTATUS Status;
THREAD_BASIC_INFORMATION ThreadBasicInfo;
QTHREAD Qthread;
Qthread = (QTHREAD)GetProcAddress( GetModuleHandle( "ntdll.dll" ), "NtQueryInformationThread" );
if (!Qthread) {
LpDmMsg->xosdRet = xosdUnknown;
Reply( 0, LpDmMsg, lpdbb->hpid );
break;
}
Status = Qthread( hthd->rwHand,
ThreadBasicInformation,
&ThreadBasicInfo,
sizeof(ThreadBasicInfo),
NULL
);
if (!NT_SUCCESS(Status)) {
LpDmMsg->xosdRet = xosdUnknown;
Reply( 0, LpDmMsg, lpdbb->hpid );
}
*(LPDWORD)pig->data = (DWORD)ThreadBasicInfo.TebBaseAddress;
len = sizeof(IOCTLGENERIC) + pig->length;
memcpy( LpDmMsg->rgb, pig, len );
LpDmMsg->xosdRet = xosdNone;
Reply( len, LpDmMsg, lpdbb->hpid );
}
#endif // WIN32S
break;
case IG_TASK_LIST:
#ifdef WIN32S
LpDmMsg->xosdRet = xosdUnknown;
Reply( 0, LpDmMsg, lpdbb->hpid );
#else
{
PTASK_LIST pTaskList = (PTASK_LIST)pig->data;
GetTaskList( pTaskList, pTaskList->dwProcessId );
len = sizeof(IOCTLGENERIC) + pig->length;
memcpy( LpDmMsg->rgb, pig, len );
LpDmMsg->xosdRet = xosdNone;
Reply( sizeof(IOCTLGENERIC)+pig->length, LpDmMsg, lpdbb->hpid );
}
#endif
break;
default:
LpDmMsg->xosdRet = xosdUnknown;
Reply( 0, LpDmMsg, lpdbb->hpid );
break;
}
return;
}
VOID
ProcessIoctlCustomCmd(
HPRCX hprc,
HTHDX hthd,
LPDBB lpdbb
)
{
LPIOL lpiol = (LPIOL)lpdbb->rgbVar;
LPSTR p = lpiol->rgbVar;
LpDmMsg->xosdRet = xosdUnsupported;
//
// parse the command
//
while (*p && !isspace(*p++));
if (*p) {
*(p-1) = '\0';
}
//
// we don't have any custom dot command here yet
// when we do this is what the code should look like:
//
// at this point the 'p' variable points to any arguments
// to the dot command
//
// if (_stricmp( lpiol->rgbVar, "dot-command" ) == 0) {
// -----> do your thing <------
// LpDmMsg->xosdRet = xosdNone;
// }
//
#if 0
if ( !_stricmp(lpiol->rgbVar, "FastStep") ) {
fSmartRangeStep = TRUE;
LpDmMsg->xosdRet = xosdNone;
} else if ( !_stricmp(lpiol->rgbVar, "SlowStep") ) {
fSmartRangeStep = FALSE;
LpDmMsg->xosdRet = xosdNone;
}
#else
LpDmMsg->xosdRet = xosdNone;
#endif
//
// send back our response
//
Reply(0, LpDmMsg, lpdbb->hpid);
} /* ProcessIoctlCustomCmd() */
#ifndef WIN32S
DWORD WINAPI
DoTerminate(
LPVOID lpv
)
{
HPRCX hprcx = (HPRCX)lpv;
if (CrashDump) {
ProcessUnloadCmd(hprcx, NULL, NULL);
return 0;
}
TerminateProcess(hprcx->rwHand, 1);
//
// now that TerminateThread has completed, put priority
// back before calling out of DM
//
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
WaitForSingleObject(hprcx->hExitEvent, INFINITE);
ProcessUnloadCmd(hprcx, NULL, NULL);
return 0;
}
VOID
CompleteTerminateProcessCmd(
VOID
)
{
DEBUG_EVENT devent, *de=&devent;
HANDLE hThread;
DWORD dwTid;
BREAKPOINT *pbpT;
BREAKPOINT *pbp;
PKILLSTRUCT pk;
HPRCX hprc;
HTHDX hthd;
DEBUG_PRINT("CompleteTerminateProcessCmd");
EnterCriticalSection(&csKillQueue);
pk = KillQueue;
if (pk) {
KillQueue = pk->next;
}
LeaveCriticalSection(&csKillQueue);
assert(pk);
if (!pk) {
return;
}
hprc = pk->hprc;
free(pk);
ConsumeAllProcessEvents(hprc, TRUE);
/*
* see if process is already dead
*/
if ((hprc->pstate & ps_dead) || (hprc->rwHand == (HANDLE)INVALID)) {
//
// Queue a continue if any thread is stopped
//
for (hthd = hprc->hthdChild; hthd; hthd = hthd->nextSibling) {
if (hthd->tstate & ts_stopped) {
AddQueue( QT_CONTINUE_DEBUG_EVENT,
hthd->hprc->pid,
hthd->tid,
DBG_CONTINUE,
0);
hthd->tstate &= ~ts_stopped;
hthd->tstate |= ts_running;
}
}
ProcessUnloadCmd(hprc, NULL, NULL);
} else {
for (pbp = BPNextHprcPbp(hprc, NULL); pbp; pbp = pbpT) {
pbpT = BPNextHprcPbp(hprc, pbp);
RemoveBP(pbp);
}
//
// Start another thread to kill the thing. This thread needs to
// continue any threads which are stopped. The new thread will then
// wait until this one (the poll thread) has handled all of the
// events, and then send destruction notifications to the shell.
//
hThread = CreateThread(NULL,
4096,
DoTerminate,
(LPVOID)hprc,
0,
&dwTid);
assert(hThread);
if ( !hThread ) {
return;
}
//
// Yield so DoTerminate can do its thing before we start posting
// ContinueDebugEvents, so we minimize the time that the app
// runs before it is terminated.
//
hprc->pstate |= ps_killed;
SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL);
Sleep(0);
CloseHandle(hThread);
//
// Queue a continue if any thread is stopped
//
for (hthd = hprc->hthdChild; hthd; hthd = hthd->nextSibling) {
if (hthd->tstate & ts_stopped) {
AddQueue( QT_CONTINUE_DEBUG_EVENT,
hthd->hprc->pid,
hthd->tid,
DBG_CONTINUE,
0);
hthd->tstate &= ~ts_stopped;
hthd->tstate |= ts_running;
}
}
}
}
DWORD
ProcessTerminateProcessCmd(
HPRCX hprc,
HTHDX hthd,
LPDBB lpdbb
)
{
PKILLSTRUCT pk;
if (!hprc) {
return FALSE;
}
Unreferenced( lpdbb );
pk = (PKILLSTRUCT)malloc(sizeof(KILLSTRUCT));
pk->hprc = hprc;
EnterCriticalSection(&csKillQueue);
pk->next = KillQueue;
KillQueue = pk;
LeaveCriticalSection(&csKillQueue);
return TRUE;
}
#endif
#ifdef WIN32S
DWORD
ProcessTerminateProcessCmd(
HPRCX hprc,
HTHDX hthd,
LPDBB lpdbb
)
{
DWORD rval;
if (!hprc) {
return FALSE;
}
Unreferenced( lpdbb );
DEBUG_PRINT_2("ProcessTerminateProcessCmd called hprc=0x%x, hthd=0x%x.\n\r",
hprc, hthd);
// Win32s doesn't support TerminateProcess(), but does give us a special
// ContinueDebugEvent flag. If we are stopped at a debug event, we can
// Continue with this flag to terminate the child app.
DEBUG_PRINT("ConsumeAllProcessEvents\r\n");
ConsumeAllProcessEvents(hprc, TRUE);
DEBUG_PRINT("Check process state\r\n");
if ((hprc->pstate & ps_dead) || hprc->rwHand == (HANDLE)INVALID) {
DEBUG_PRINT("Process already dead\r\n");
if (fExitProcessDebugEvent) {
// we saved tidExit when we got the EXIT_PROCESS_DEBUG_EVENT
AddQueue( QT_CONTINUE_DEBUG_EVENT,
hprc->pid,
tidExit,
DBG_CONTINUE,
0);
}
rval = FALSE; // already dead
}
if (fProcessingDebugEvent) {
DEBUG_PRINT_1("Continue with %s\r\n",
(fExitProcessDebugEvent ? "DBG_CONTINUE" : "DBG_TERMINATE_PROCESS"));
for (hthd = hprc->hthdChild; hthd; hthd = hthd->nextSibling) {
if (hthd->tstate & ts_stopped) {
AddQueue( QT_CONTINUE_DEBUG_EVENT,
hthd->hprc->pid,
hthd->tid,
fExitProcessDebugEvent?
DBG_CONTINUE : DBG_TERMINATE_PROCESS,
0);
hthd->tstate &= ~ts_stopped;
hthd->tstate |= ts_running;
}
}
// mark this process as killed
DEBUG_PRINT("Mark process as killed\r\n");
hprc->pstate |= ps_killed;
rval = TRUE; // killed it.
} else {
DEBUG_PRINT("Can't terminate process right now\r\n");
// can't continue debug event right now, so can't terminate.
rval = FALSE;
}
return rval;
}
#endif
VOID
ProcessAllProgFreeCmd(
HPRCX hprcXX,
HTHDX hthd,
LPDBB lpdbb
)
{
HPRCX hprc;
Unreferenced(hprcXX);
Unreferenced(hthd);
for (;;) {
EnterCriticalSection(&csThreadProcList);
for (hprc = prcList; hprc; hprc = hprc->next) {
if (hprc->pstate != (ps_root | ps_destroyed)) {
break;
}
}
LeaveCriticalSection(&csThreadProcList);
if (hprc) {
ProcessTerminateProcessCmd(hprc, hthd, lpdbb);
ProcessUnloadCmd(hprc, hthd, lpdbb);
} else {
break;
}
}
WaitForSingleObject(hEventNoDebuggee, INFINITE);
}
DWORD
ProcessAsyncGoCmd(
HPRCX hprc,
HTHDX hthd,
LPDBB lpdbb
)
{
XOSD xosd = xosdNone;
#ifndef WIN32S
DEBUG_EVENT de;
#endif
DEBUG_PRINT("ProcessAsyncGoCmd called.\n\r");
#ifdef WIN32S
xosd = xosdUnsupported; // can't resume thread in win32s
#else
#ifdef WIN32
if ((hthd->tstate & ts_frozen)) {
if (hthd->tstate & ts_stopped) {
//
// if at a debug event, it won't really be suspended,
// so just clear the flag.
//
hthd->tstate &= ~ts_frozen;
} else if (ResumeThread(hthd->rwHand) == -1L ) {
#ifdef OSDEBUG4
xosd = xosdBadThread;
#else
xosd = xosdInvalidThread;
#endif
} else {
hthd->tstate &= ~ts_frozen;
/*
* deal with dead, frozen, continued thread:
*/
if ((hthd->tstate & ts_dead) && !(hthd->tstate & ts_stopped)) {
de.dwDebugEventCode = DESTROY_THREAD_DEBUG_EVENT;
de.dwProcessId = hprc->pid;
de.dwThreadId = hthd->tid;
NotifyEM(&de, hthd, 0, NULL);
FreeHthdx(hthd);
hprc->pstate &= ~ps_deadThread;
for (hthd = hprc->hthdChild; hthd; hthd = hthd->nextSibling) {
if (hthd->tstate & ts_dead) {
hprc->pstate |= ps_deadThread;
}
}
}
}
}
#endif // WIN32
#endif // !WIN32S
Reply(0, &xosd, lpdbb->hpid);
return(xosd);
}
void
ActionAsyncStop(
DEBUG_EVENT * pde,
HTHDX hthd,
DWORD unused,
BREAKPOINT * pbp
)
/*++
Routine Description:
This routine is called if a breakpoint is hit which is part of a
Async Stop request. When hit is needs to do the following: clean
out any expected events on the current thread, clean out all breakpoints
which are setup for doing the current async stop.
Arguments:
pde - Supplies a pointer to the debug event which just occured
hthd - Supplies a pointer to the thread for the debug event
pbp - Supplies a pointer to the breakpoint for the ASYNC stop
Return Value:
None.
--*/
{
union {
RTP rtp;
char rgb[sizeof(RTP) + sizeof(BPR)];
} rtpbuf;
RTP * prtp = &rtpbuf.rtp;
BPR * pbpr = (BPR *) prtp->rgbVar;
HPRCX hprc = hthd->hprc;
BREAKPOINT * pbpT;
/*
* We no longer need to have this breakpoint set.
*/
RemoveBP( pbp );
/*
* Remove any other breakpoints in this process which are for
* async stop commands
*/
for (pbp = BPNextHprcPbp(hprc, NULL); pbp != NULL; pbp = pbpT) {
pbpT = BPNextHprcPbp(hprc, pbp);
if (pbp->id == (HPID)ASYNC_STOP_BP) {
RemoveBP( pbp );
}
}
/*
* Setup a return packet which says we hit an async stop breakpoint
*/
prtp->hpid = hprc->hpid;
prtp->htid = hthd->htid;
prtp->dbc = dbcAsyncStop;
prtp->cb = sizeof(BPR);
#ifdef TARGET_i386
pbpr->segCS = (SEGMENT) hthd->context.SegCs;
pbpr->segSS = (SEGMENT) hthd->context.SegSs;
pbpr->offEBP = (UOFFSET) hthd->context.Ebp;
#endif
pbpr->offEIP = (DWORD) PC(hthd);
DmTlFunc(tlfDebugPacket, prtp->hpid, sizeof(rtpbuf), (LONG)&rtpbuf);
return;
} /* ActionAsyncStop() */
VOID
ProcessAsyncStopCmd(
HPRCX hprc,
HTHDX hthd,
LPDBB lpdbb
)
/*++
Routine Description:
This function is called in response to a asynchronous stop request.
In order to do this we will set breakpoints the current PC for
every thread in the system and wait for the fireworks to start.
Arguments:
hprc - Supplies a process handle
hthd - Supplies a thread handle
lpdbb - Supplies the command information packet
Return Value:
None.
--*/
{
#ifdef WIN32S
/*
* Win32s doesn't support async stop this way. The user should
* press the debugger hot key at the debuggee console to generate
* an async stop. This may change if BoazF gives us a private API
* to generate the async stop exception.
*/
DEBUG_PRINT("\r\nProcessAsyncStopCmd\r\n");
LpDmMsg->xosdRet = xosdUnsupported;
Reply(0, LpDmMsg, lpdbb->hpid);
return;
#else
CONTEXT regs;
BREAKPOINT * pbp;
ADDR addr;
BOOL fSetFocus = * ( BOOL *) lpdbb->rgbVar;
regs.ContextFlags = CONTEXT_CONTROL;
/*
* Step 1. Enumerate through the threads and freeze them all.
*/
for (hthd = hprc->hthdChild; hthd != NULL; hthd = hthd->nextSibling) {
if (SuspendThread(hthd->rwHand) == -1L) {
; // Internal error;
}
}
/*
* Step 2. Place a breakpoint on every PC address
*/
for (hthd = hprc->hthdChild; hthd != NULL; hthd = hthd->nextSibling) {
#ifndef WIN32S
if (CrashDump) {
DmpGetContext( hthd->tid-1, &regs );
} else
#endif
{
GetThreadContext( hthd->rwHand, &regs );
}
AddrInit(&addr, 0, 0, (DWORD)cPC(&regs), TRUE, TRUE, FALSE, FALSE);
pbp = SetBP(hprc, hthd, bptpExec, bpnsStop, &addr, (HPID) ASYNC_STOP_BP);
RegisterExpectedEvent(hthd->hprc,
hthd,
BREAKPOINT_DEBUG_EVENT,
(DWORD)pbp,
DONT_NOTIFY,
(ACVECTOR)ActionAsyncStop,
FALSE,
pbp);
}
/*
* Step 3. Unfreeze all threads
*/
if (fSetFocus) {
DmSetFocus(hprc);
}
for (hthd = hprc->hthdChild; hthd != NULL; hthd = hthd->nextSibling) {
if (ResumeThread(hthd->rwHand) == -1) {
; // Internal error
}
}
LpDmMsg->xosdRet = xosdNone;
Reply(0, LpDmMsg, lpdbb->hpid);
return;
#endif
} /* ProcessAsyncStopCmd() */
VOID
ProcessDebugActiveCmd(
HPRCX hprc,
HTHDX hthd,
LPDBB lpdbb
)
{
#ifdef WIN32S
Unreferenced(hprc);
Unreferenced(hthd);
LpDmMsg->xosdRet = xosdUnsupported; // can't attatch in win32s
*((DWORD *)LpDmMsg->rgb) = ERROR_NOT_SUPPORTED;
Reply(sizeof(DWORD), LpDmMsg, lpdbb->hpid);
#else
#ifdef OSDEBUG4
LPDAP lpdap = ((LPDAP)(lpdbb->rgbVar));
Unreferenced(hprc);
Unreferenced(hthd);
if (fDisconnected) {
SetEvent( hEventRemoteQuit );
} else if (!StartDmPollThread()) {
//
// CreateThread() failed; fail and send a dbcError.
//
LpDmMsg->xosdRet = xosdUnknown;
Reply(0, LpDmMsg, lpdbb->hpid);
} else if (WaitForSingleObject(DebugActiveStruct.hEventReady, INFINITE)
!= 0) {
//
// the wait failed. why? are there cases where we
// should restart the wait?
//
LpDmMsg->xosdRet = xosdUnknown;
Reply(0, LpDmMsg, lpdbb->hpid);
} else {
ResetEvent(DebugActiveStruct.hEventReady);
ResetEvent(DebugActiveStruct.hEventApiDone);
DebugActiveStruct.dwProcessId = lpdap->dwProcessId;
DebugActiveStruct.hEventGo = lpdap->hEventGo;
DebugActiveStruct.fAttach = TRUE;
*nameBuffer = 0;
// wait for it...
if (WaitForSingleObject(DebugActiveStruct.hEventApiDone, INFINITE) == 0
&& DebugActiveStruct.fReturn != 0) {
LpDmMsg->xosdRet = xosdNone;
//
// the poll thread will reply when creating the "root" process.
//
if (!fUseRoot) {
Reply(0, LpDmMsg, lpdbb->hpid);
}
} else {
LpDmMsg->xosdRet = xosdUnknown;
Reply(0, LpDmMsg, lpdbb->hpid);
}
}
#else // OSDEBUG4
LPDBG_ACTIVE_STRUCT lpdba = ((LPDBG_ACTIVE_STRUCT)(lpdbb->rgbVar));
Unreferenced(hprc);
Unreferenced(hthd);
if (fDisconnected) {
SetEvent( hEventRemoteQuit );
} else if (!StartDmPollThread()) {
LpDmMsg->xosdRet = xosdUnknown;
// Last error is from CreateThread();
*((DWORD *)LpDmMsg->rgb) = GetLastError();
Reply(0, LpDmMsg, lpdbb->hpid);
// wait for attach struct to be available
} else if (WaitForSingleObject(DebugActiveStruct.hEventReady, INFINITE)
!= 0) {
LpDmMsg->xosdRet = xosdUnknown;
*((DWORD *)LpDmMsg->rgb) = GetLastError();
Reply(0, LpDmMsg, lpdbb->hpid);
} else {
ResetEvent(DebugActiveStruct.hEventReady);
ResetEvent(DebugActiveStruct.hEventApiDone);
DebugActiveStruct.dwProcessId = lpdba->dwProcessId;
DebugActiveStruct.hEventGo = lpdba->hEventGo;
DebugActiveStruct.fAttach = TRUE;
*nameBuffer = 0;
// wait for it...
if (WaitForSingleObject(DebugActiveStruct.hEventApiDone, INFINITE) == 0
&& DebugActiveStruct.fReturn != 0) {
LpDmMsg->xosdRet = xosdNone;
//
// the poll thread will reply when creating the "root" process.
//
if (!fUseRoot) {
Reply(0, LpDmMsg, lpdbb->hpid);
}
} else {
DebugActiveStruct.dwProcessId = 0;
DebugActiveStruct.hEventGo = NULL;
LpDmMsg->xosdRet = xosdUnknown;
*((DWORD *)LpDmMsg->rgb) = DebugActiveStruct.dwError;
Reply(0, LpDmMsg, lpdbb->hpid);
}
}
#endif // OSDEBUG4
SetEvent(DebugActiveStruct.hEventReady);
#endif // WIN32S
}
VOID
ProcessRemoteQuit(
VOID
)
{
HPRCX hprc;
BREAKPOINT *pbp;
BREAKPOINT *pbpT;
EnterCriticalSection(&csThreadProcList);
for(hprc=prcList->next; hprc; hprc=hprc->next) {
for (pbp = BPNextHprcPbp(hprc, NULL); pbp; pbp = pbpT) {
pbpT = BPNextHprcPbp(hprc, pbp);
RemoveBP(pbp);
}
}
LeaveCriticalSection(&csThreadProcList);
fDisconnected = TRUE;
ResetEvent( hEventRemoteQuit );
}