/*static char *SCCSID = "@(#)dbgee.c    6.10 92/03/01";*/
#define SCCSID  "@(#)dbgee.c    6.10 92/03/01"

/***    DBGEE.C - Debuggee support for DosDebug
 *
 *      THIS IS AN OS/2 VERSION 2.x SPECIFIC FILE
 *
 *       IBM/Microsoft Confidential
 *
 *       Copyright (c) IBM Corporation 1987, 1989
 *       Copyright (c) Microsoft Corporation 1987, 1989
 *
 *       All Rights Reserved
 *
 *      This file implements the debuggee context routines required
 *      by the DosDebug API.
 *
 *      The functions in this file must be locked when in use unless otherwise
 *      noted.
 *****************************************************************************
 *  Change                                                               Chg *
 *  Activity      Date    BY   Reason                                    Num *
 *  --------      ----    ---  -------                                   --- *
 *  PTR B727806 11/04/91  ARJ  Handle Interrupt 1 Notifications          001 *
 *  PTR B729735 11/14/91  ARJ  Correct IFDEF STATEMENTS (No CHG NUM)         *
 *  PTR B729722 11/27/91  ARJ  Correct Bogus WatchPoint Notification     002 *
 *  PTR B730813 12/15/92  ARJ  Be selective handling INT 1 for B727806   003 *
 *  PTR B732898 02/13/92  ARJ  DosPtrace Needs Ring 0 address not Ring 3 004 *
 *  PTR B731664 02/14/92  ARJ  Support DBG_X_STACK_INVALID documentation 005 *
 *  PTR B732889 02/24/92  ARJ  Correct TRAP "E" & Bogus Notifications    006 *
 *  PTR B732471 02/28/92  ARJ  B727806 and B730813 were removed by           *
 *                             B732889 which corrected this problem          *
 *  DEF   56453 10/28/92  WWM  Clear VDM/DPMI watchpts when VDM closed   007 *
 *  DEF   66967 08/23/92  ARJ  Include Stepping IN tryagain logic        008 *
 *  DEF   70301 10/01/93  ARJ  Support Sever Connection option PJ09240   009 *
 *  DEF   80689 03/25/94  AJR  Support registration of GRESEM            010 *
 *  DEF  114816 04/18/95  ARJ  Support for Private DLL's                 011 *
 *****************************************************************************
 */

#define INCL_TYPES                      // for basedef.h typedefs
#include <basemac.h>
#include <basedef.h>
#define FNO_NUM         FNO_DBGEE_C
#include <fnotask.h>
#include <osmac.h>
#include <osconst.h>
#include <ostypes.h>
#include <infoseg.h>
#include <vmowner.h>
#include <dossymc.h>
#include <sigdef.h>
#include <except.h>
#include <bsexcpt.h>
#include <mi.h>
#include <pgexport.h>
#include <vmexport.h>
#include <selxport.h>
#include <seldesc.h>
#include <tkexport.h>
#include <curdir.h>
#include <dirent.h>
#include <find.h>
#include <screen.h>
#include <sem.h>
#include <tkptda.h>
#include <ldrxport.h>
#include <error.h>
#include <kmode.h>
#include <proc.h>

#include <dbgapi.h>     /* (debug)      API definitions         */
#include <dbgex.h>      /* (debug)      External definitions    */
#include <dbgin.h>      /* (debug)      Internal definitions    */


#ifdef INCDEBUG

extern CRI criISF;              /* Interrupt stack frame descriptor */
extern CRI criTSF;              /* Trap stack frame descriptor */

extern semList *pSemListHead;

#ifdef DBGSTRICT
extern DBGBugBug;               /* DosDebug debug flag         */
#endif

/***LK   vmOffGSR  - Is MODIFIED by Dbgenable, Dbgdisable and         A011 *
  *                  Dbgprocterm                                      A011 *
  * vmOffGSR  =  0 means LOAD DLL's into Global Shared Region         A011 *
  * vmOffGSR !=  0 means DO NOT LOAD DLL's into Global Shared Region  A011 *
 */
    extern SHORT   vmOffGSR;                               /*         A011 */

/***LK+ DBG_V_ - Debug Notification Validity Flags
 *
 *      A combination of the following flags is required for calling
 *      DbgHandler or DbgNotify.  If TRUE, the particular parameter
 *      is valid, and is used for the notification.     If not, that
 *      parameter is unused for the particular notification, and the
 *      uDB field it corresponds to will not be modified.
 *
 *      If DBG_V_INDIRECT is specified, the passed-in notification is
 *      ignored, and is replaced with an indirect notification.  An
 *      indirect notification includes watchpoint, LibLoad, and any
 *      Stop notifications.  If there is no indirect notification
 *      to perform, no notification is performed at all.
 *
 *      DbgHandler may be called from within this file ONLY.
 *
 */

#define DBG_V_VALUE     0x01L
#define DBG_V_ADDR      0x02L
#define DBG_V_BUFFER    0x04L
#define DBG_V_LEN       0x08L
#define DBG_V_INDEX     0x10L
#define DBG_V_MTE       0x20L
#define DBG_V_INDIRECT  0x80L   // Get Indirect Notification, if any

/*end*/

/***LP  dbgUserCSEIP - return the user's linearized CS:EIP
 *
 *      This function returns the specified thread's linearized
 *      CS:EIP (or NULL if the conversion cannot be made).
 *
 *      dbgUserCSEIP (ptcb,paddr,puleip)
 *
 *      ENTRY   ptcb            - TCB pointer
 *              paddr           - Place to return linear CS:EIP
 *              puleip          - Place to return relative EIP (optional)
 *              psizeCS         - Place to return size of CS (optional)          // B729833
 *                                   0 = 16 bit CS, 1 = 32 bit CS                // B729833
 *      RETURN  int             - error code (NO_ERROR if successful)
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

int
dbgUserCSEIP(tcb_t *ptcb,ulong_t *paddr,ulong_t *puleip,ulong_t *psizeCS)        // B729833
{
    int rc;                             /* Return code */
    ulong_t ulcs;                       /* User's CS */
    struct seldesc_s seldesc;           /* CS selector information */

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgUserCSEIP     ");   }
#endif

    if ((rc = KMQueryClientRegister(ptcb,
      KM_REG_CS,SSToDS(&ulcs)) == NO_ERROR) &&
      (rc = KMQueryClientRegister(ptcb,KM_REG_EIP,paddr)) == NO_ERROR) {
                                        /* If CS:EIP obtained */
        if (puleip != NULL)
           *puleip = *paddr;            /* Return EIP if wanted */

        if ((rc = SELGetDescInfo((SEL) ulcs,ptcb->TCBpPTDA->ptda_handle,
           SSToDS(&seldesc))) == NO_ERROR) {

              if (psizeCS != NULL)      /* Return size of CS if wanted */        // B729833
                 *psizeCS = (seldesc.desc_attr & D_DBIG ? 1: 0);                 // B729833

              if (ulcs & SEL_LDTBIT)           /* If LDT selector */
                 *paddr += seldesc.desc_laddr; /* Add base if available */
        }
    }
    return(rc);                         /* Return user address */
}


/***LP  dbgFinishXchgOp - finish exchange opcode command
 *
 *      This procedure turns off single stepping and places the
 *      specified opcode at the specified address.  It then runs
 *      any threads that were held.
 *
 *      dbgFinishXchgOp ()
 *
 *      ENTRY   NONE
 *      RETURN  NONE
 *
 *      EFFECTS The XchgOp command is finished.
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void
dbgFinishXchgOp()
{
    uDB_t *pudb;                        /* Pointer to copy of user buffer */
    int rc;                             /* Return code */

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgFinishXchOp   ");   }
#endif

    pPTDACur->ptda_flDbg &= ~DBGP_SSTEP;/* Turn off single-stepping */
    pTCBCur->TCB_flDbg &= ~DBGT_XCHGOP; /* Turn off xchgop bit */
    pudb = &(pPTDACur->ptda_pdcb->pkDB->uDBTemp);
                                        /* Point at uDB */
    rc = dbgReadWrite(pudb->Addr,pPTDACur->ptda_handle,sizeof(uchar_t),0,NULL,
      (uchar_t *) &pudb->Value + 1,TK_UM_DEST);
                                        /* Write "go" opcode */
    dbgAssert(rc == NO_ERROR);          /* BUGBUG: Must succeed? */
    dbgAssert(pPTDACur->ptda_pdcb->sDbgeeSStepTCB == pTCBCur);
                                        /* Must be stepping this thread */
    pPTDACur->ptda_pdcb->sDbgeeSStepTCB = NULL;
                                        /* No longer stepping anyone */
    dbgAssert(pPTDACur->ptda_flDbg & DBGP_HOLD);
                                        /* Must be holding other threads */
    pPTDACur->ptda_flDbg &= ~DBGP_HOLD; /* Stop holding others */
    TKWakeup((void *) DBGID_FZLOCK(pPTDACur->ptda_pdcb),0,NULL);
                                        /* Wake them up */
}

#endif /* INCDEBUG */


/***EP  DbgTrap - Notify debugger of a single step or breakpoint trap
 *
 *      DbgTrap is used to notify the debugger of Single Step, or Breakpoint.
 *
 *      This should only be called if the current process
 *      is being debugged, and the exception did not occur
 *      at ring 0, and the exception would have normally
 *      resulted in process termination.
 *
 *      lTrapNum is the trap number.
 *
 *      This function is swappable.
 *
 *      ENTRY
 *              lTrapNum - Trap Number
 *
 *      EXIT
 *              Return Value == XCPT_CONTINUE_EXECUTION
 *                      Debugger handled the event, resume user code
 *
 *              Return Value == XCPT_CONTINUE_SEARCH
 *                      Debuggger did not handled the event.
 *                      Action:
 *                      Terminate the process, or enter the kernel debugger.
 *
 *      Pseudocode:
 *      current thread exception state = XCPT_CONTINUE_EXECUTION
 *      calculate ring 3 linear instruction pointer
 *      if (Single Step trap)
 *        if (thread not single stepping)
 *          clear thread single step flag
 *          return(XCPT_CONTINUE_EXECUTION)
 *
 *        if (thread supposed to skip single step)
 *          clear thread performed notification flag
 *          return(XCPT_CONTINUE_EXECUTION)
 *
 *        if (using DOSPTRACE and faulting CS is 32 BIT)
 *          return(XCPT_CONTINUE_EXECUTION)
 *
 *        if (thread is range stepping)
 *          if (ring 3 linear instruction pointer is within range)
 *            update last address executed
 *            return(XCPT_CONTINUE_EXECUTION)
 *          DbgHandler(DBG_N_RangeStep)
 *        else
 *          DbgHandler(DBG_N_SingleStep)
 *
 *      else if (Breakpoint trap)
 *        set EIP to breakpoint instruction
 *        if (using DOSPTRACE and faulting CS is 32 BIT)
 *          return(XCPT_CONTINUE_EXECUTION)
 *        set eflags resume flag
 *        DbgHandler(DBG_N_Breakpoint)
 *
 *      return(thread exception state)
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

int DbgTrap (lTrapNum)
ulong_t lTrapNum;               // Hardware trap number
{

#ifdef INCDEBUG

    ulong_t UserEIP,            // Faulting thread's EIP register
            UserEflags,         // Faulting thread's EFLAGS register
            sizeCS;             // Faulting CS size (0 = 16 Bit, 1 = 32 Bit)
    char *pchLIP;               // Faulting ring 3 address
    int rt;

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgTrap          ");   }
#endif

    pTCBCur->TCB_flDbg |= DBGT_XCPTHANDLED;
    pTCBCur->TCB_flDbg &= ~DBGT_XCPTSTOP;

    // calculate linearized CS:EIP and CS size of faulting thread

    rt = dbgUserCSEIP(pTCBCur,SSToDS(&pchLIP),SSToDS(&UserEIP),
       SSToDS(&sizeCS));
    dbgAssert(rt == NO_ERROR);

    if (lTrapNum == EXCSSI) {           // Single Step Trap
        if (!(pTCBCur->TCB_flDbg & DBGT_SSTEP) ||
            (pPTDACur->ptda_pdcb->flState & DBGDCBS_SEMLIST)) {

           /*
            * This thread is not single stepping but the TF bit is on,
            * or this thread erroneously has the TF bit on because
            * another thread is processing the semList.
            * We set TCB_flDbg to match the TF bit, DbgValidateTF
            * will clear the TF bit during ExitKmode
            */

           pTCBCur->TCB_flDbg |= DBGT_SSTEP;
           return(XCPT_CONTINUE_EXECUTION);
        }

        if (pTCBCur->TCB_flDbg & DBGT_NOTIFIED) {
            /* Thread is single stepping, but we have just performed
             * a notification while inside the kernel with a call gate
             * return frame on the kernel stack.  We should clear the
             * performed notification flag and ignore this trap.
             */

            pTCBCur->TCB_flDbg &= ~DBGT_NOTIFIED;
            return(XCPT_CONTINUE_EXECUTION);
        }

        /* If a single step trap hit in 32 bit code while using
         * DOSPTRACE ignore it.
         */
        if ((pPTDACur->ptda_flDbg & DBGP_PTRACE) && (sizeCS))                    // B729833
            return(XCPT_CONTINUE_EXECUTION);                                     // B729833

        if (pPTDACur->ptda_flDbg & DBGP_RANGESTEP) {
            if ((ulong_t)pchLIP > pTSDCur->TSDlDbgRangeStart &&
              (ulong_t)pchLIP < pTSDCur->TSDlDbgRangeEnd) {
                pTSDCur->TSDlDbgLastAddr = (ulong_t) pchLIP;
                return(XCPT_CONTINUE_EXECUTION);
            }

            rt = DbgHandler(DBG_V_VALUE|DBG_V_ADDR, DBG_N_RangeStep,
                           pTSDCur->TSDlDbgLastAddr, pchLIP);
        }
        else if (pTCBCur->TCB_flDbg & DBGT_XCHGOP) {
            dbgFinishXchgOp();          /* Put in true opcode and go */
            return(XCPT_CONTINUE_EXECUTION);
                                        /* Back to work */
        }

        /*
         * Default case: Send a pre-first exception notification to the
         *               debugger.
         */

        else rt = DbgHandler(DBG_V_VALUE|DBG_V_ADDR|DBG_V_BUFFER,
                             DBG_N_Exception,
                             DBG_X_PRE_FIRST_CHANCE,
                             pchLIP,
                             XCPT_SINGLE_STEP);

    }
    else if (lTrapNum == EXCBP) {               // Breakpoint Trap
        /* If a breakpoint trap hit in 32 bit code while using
         * DOSPTRACE ignore it.
         */
        if ((pPTDACur->ptda_flDbg & DBGP_PTRACE) && (sizeCS))                    // B729833
           return(XCPT_CONTINUE_EXECUTION);                                      // B729833

        if (pchLIP != NULL) {
            uchar_t bUserCode;

            if (TKFuByteNF(SSToDS(&bUserCode), pchLIP-1) != NO_ERROR) {
                dbgAssert(FALSE);
                bUserCode = MI_INT3;
            }

            // Turn breakpoint trap into breakpoint fault by decrementing EIP
            // and setting the RF flag bit.

            switch(bUserCode) {
                case 0x3:                       // INT 3 (0xCD 0x03)
                    UserEIP--;
                    pchLIP--;
                    // fall through
                case MI_INT3:           // INT 3 (MI_INT3)
                    UserEIP--;
                    pchLIP--;

                    if (KMQueryClientRegister(pTCBCur, KM_REG_EFLAG,
                                              SSToDS(&UserEflags)) != NO_ERROR)
                        dbgAssert(FALSE);

                    UserEflags |= F_RF;

                    if (KMSetClientRegister(pTCBCur, KM_REG_EFLAG, UserEflags) !=
                        NO_ERROR ||
                        KMSetClientRegister(pTCBCur, KM_REG_EIP, UserEIP) !=
                        NO_ERROR)
                        dbgAssert(FALSE);

                    break;
                default:
                    dbgAssert(FALSE);
            }
        }
        if (pTCBCur->TCB_flDbg & DBGT_XCHGOP)
            dbgFinishXchgOp();          /* Finish XchgOp if any */

        /*
         * Default case: Send a pre-first exception notification to the
         *               debugger.
         */

        rt = DbgHandler(DBG_V_VALUE|DBG_V_ADDR|DBG_V_BUFFER,
                             DBG_N_Exception,
                             DBG_X_PRE_FIRST_CHANCE,
                             pchLIP,
                             XCPT_BREAKPOINT);

    }
    else dbgAssert(FALSE);

    if (rt == NO_ERROR) {
       if (pTCBCur->TCB_flDbg & DBGT_XCPTSTOP) {
         return(XCPT_CONTINUE_STOP);
       } else if (pTCBCur->TCB_flDbg & DBGT_XCPTHANDLED) {
         return(XCPT_CONTINUE_EXECUTION);
       }
    }

    return(XCPT_CONTINUE_SEARCH);

#endif /* INCDEBUG */

}

#ifdef INCDEBUG

/***EP  DbgException - Notify debugger of an exception
 *
 *      DbgException is used to notify the debugger of an Exception.
 *
 *      This should only be called if the current process
 *      is being debugged, and the exception did not occur
 *      at ring 0, and the exception would have normally
 *      resulted in process termination.
 *
 *      pR0* and pR3* are copies of the same information in system
 *      and kernel space. The 3 'chances' to handle an exception are
 *      designated by the ulChance value.
 *
 *      This function is swappable.
 *
 *      ENTRY   pR0ERepRec - Kernel copy of Exception Report Record
 *              pR3ERepRec - Debuggee context Exception Report Record
 *              pR0Context - Kernel copy of Exception Context
 *              pR3Context - Debuggee context Exception Context
 *              ulChance   - 'Chance' to handle exception (pre-1st, 1st, last)
 *
 *      EXIT
 *              Return Value == XCPT_CONTINUE_EXECUTION
 *                      Treat exception as HANDLED (resume execution)
 *
 *              Return Value == XCPT_CONTINUE_SEARCH
 *                      Treat exception as XCPT_CONTINUE_SEARCH
 *
 *      Pseudocode:
 *      current thread exception state = XCPT_CONTINUE_SEARCH
 *      DbgHandler(DBG_N_Exception)
 *      return(thread exception state)
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

int DbgException (struct _EXCEPTIONREPORTRECORD *pR0ERepRec,
                  struct _EXCEPTIONREPORTRECORD *pR3ERepRec,
                  struct _CONTEXT *pR0Context,
                  struct _CONTEXT *pR3Context,
                  ulong_t ulChance)
{
    int rt;                                                /*         A004 */

#ifdef DBGSTRICT
    if ( (DBGBugBug & DBGEEc) || (DBGBugBug & DBGXCPTo) )
    {  printf("\n\n Dbgee.c *** DdgException,             \\
                 \n pR0ERepRec->ExceptionNum      %4x,    \\
                 \n pR0ERepRec->ExceptionAddress  %4x,    \\
                 \n pR0Context->ctx_SegGs         %4x,    \\
                 \n pR0Context->ctx_SegFs         %4x,    \\
                 \n pR0Context->ctx_SegEs         %4x,    \\
                 \n pR0Context->ctx_SegDs         %4x,    \\
                 \n pR0Context->ctx_RegEbp        %4x,    \\
                 \n pR0Context->ctx_RegEip        %4x,    \\
                 \n pR0Context->ctx_SegCs         %4x,    \\
                 \n pR0Context->ctx_EFlags        %4x,    \\
                 \n pR0Context->ctx_RegEsp        %4x,    \\
                 \n pR0Context->ctx_SegSs         %4x",
                        pR0ERepRec->ExceptionNum    ,
                        pR0ERepRec->ExceptionAddress,
                        pR0Context->ctx_SegGs       ,
                        pR0Context->ctx_SegFs       ,
                        pR0Context->ctx_SegEs       ,
                        pR0Context->ctx_SegDs       ,
                        pR0Context->ctx_RegEbp      ,
                        pR0Context->ctx_RegEip      ,
                        pR0Context->ctx_SegCs       ,
                        pR0Context->ctx_EFlags      ,
                        pR0Context->ctx_RegEsp      ,
                        pR0Context->ctx_SegSs       );   }
#endif

    pTCBCur->TCB_flDbg &= ~(DBGT_XCPTHANDLED | DBGT_XCPTSTOP);

    if (pTCBCur->TCB_flDbg & DBGT_XCHGOP)
        dbgFinishXchgOp();              /* Finish XchgOp if any */

    /*************************************************************************/
    /* Single Step and Break Point notifications should come through    A006 */
    /* DBGTRAP, Therefore if I receive a Single Step in this routine    A006 */
    /* it is an error and should be handled by DBGTRAP.                 A006 */
    /* This is a should not occur condition this means DR7 got set      A006 */
    /* incorrectly from a watchpoint notification or single step.       A006 */
    /*************************************************************************/
    if ( ( ( pPTDACur->ptda_flDbg  &  DBGP_LINKED)   /* if linked       A006 */
        &&                                           /*     and         A006 */
         ( pR0ERepRec->ExceptionNum == XCPT_SINGLE_STEP ) ) /*SingleStep A006*/
       ||                                            /* or              A006 */
       ( ( pTCBCur->TCB_flDbg   &   DBGP_LINKED)     /* if linked       A006 */
        &&                                           /* and     Single  A006 */
         ( pR0ERepRec->ExceptionNum == XCPT_SINGLE_STEP ) ) ) /* Step   A006 */
          {
             rt = DbgTrap(EXCSSI);                 /* do not report     A006 */
#ifdef DBGSTRICT
             printf("\nDbgee.c *** DBGEXCEPTION Error *** rc is %4x",rt);
             dbgAssert( (pR0ERepRec->ExceptionNum == XCPT_SINGLE_STEP) );
#endif
             return(rt);                   /* return XCPT_CONTINUE_...  A006 */
          }
    else  /* Start This a normal Exception    */           /*           A001 */
    {   /*  handle as DosPtrace  */                        /*           A004 */
       if ( (pPTDACur->ptda_flDbg & DBGP_PTRACE) )         /*           A004 */
       {                                                   /*           A004 */
          DbgHandler(                               /*           A004 */
             DBG_V_VALUE | DBG_V_ADDR | DBG_V_BUFFER | DBG_V_LEN,
                   DBG_N_Exception,
                   ulChance,
                   pR0ERepRec->ExceptionAddress,
                   pR0ERepRec,                /* need ring zero addr A004 */
                   pR0Context );              /* for DosPtrace       A004 */
       }                                                   /*           A004 */
       else                                                /*           A004 */
       {  /*  handle as DosDebug   */                     /* Exception  A001 */
         if (ulChance == DBG_X_STACK_INVALID)             /*            A005 */
          {                                               /*            A005 */
             DbgHandler(                              /*            A005 */
                DBG_V_VALUE | DBG_V_ADDR | DBG_V_BUFFER,  /*            A005 */
                      DBG_N_Exception,                    /*            A005 */
                      ulChance,                           /*            A005 */
                      pR0ERepRec->ExceptionAddress,       /*            A005 */
                      pR0ERepRec->ExceptionNum);          /*            A005 */
          } /* if end */                                  /*            A005 */
         else                                             /*            A005 */
          {
             DbgHandler(
                DBG_V_VALUE | DBG_V_ADDR | DBG_V_BUFFER | DBG_V_LEN,
                      DBG_N_Exception,
                      ulChance,
                      pR0ERepRec->ExceptionAddress,
                      pR3ERepRec,
                      pR3Context );
          } /* else end */
       }

       if (pTCBCur->TCB_flDbg & DBGT_XCPTSTOP) {
          return(XCPT_CONTINUE_STOP);
       } else if (pTCBCur->TCB_flDbg & DBGT_XCPTHANDLED) {
          return(XCPT_CONTINUE_EXECUTION);
       } else {
          return(XCPT_CONTINUE_SEARCH);
       }

    }   /* End This is Normal Exception  */                /*           A001 */
}

/***EP  DbgNewProc - Notify the debugger of a new process
 *
 *      DbgNewProc is called to notify the debugger of a
 *      newly exec'ed process.
 *
 *      This should only be called at the time of a DosExecPgm,
 *      just after the new process has been made runnable, and
 *      only if DBGP_TREE is set in ptda-> flDbg for the current
 *      process.
 *
 *      This function is swappable.
 *
 *      ENTRY
 *              lPid = Process ID of newly execed process.
 *      EXIT
 *              No Return Value
 *
 *      Pseudocode:
 *      DbgHandler(DBG_N_NewProc)
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void DbgNewProc (lPid)
ulong_t lPid;                   // New process ID
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgNewProc       ");   }
#endif

    DbgHandler(DBG_V_VALUE | DBG_V_ADDR, DBG_N_NewProc, lPid, NULL);
}

/***EP  DbgAliasFree - Notify the debugger that an alias needs to be freed
 *
 *      DbgAliasFree is called from VMFreeMem to notify the debugger
 *      that an aliased object is being freed.  DbgAliasFree must
 *      be called before the object is freed, and will return after
 *      the debugger's next GO, SStep, or Terminate command.
 *
 *      DbgAliasFree should only be used if the object is actually
 *      being freed - it should not be called if pages within the
 *      object are being invalidated.  An alias may contain any
 *      number of invalid pages.
 *
 *      ENTRY
 *              pchAlias = Address of debugger's alias to be freed.
 *              hptda    = Alias context handle (debugger ptda handle)
 *
 *      EXIT
 *              No Return Value
 *
 *      Pseudocode:
 *      if (internal alias)
 *        TKYield
 *      else if (debugger dead || debugger does not own alias)
 *        VMFreeMem(alias)
 *      else
 *        DbgHandler(DBG_N_AliasFree)
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

void DbgAliasFree (pchAlias, hptda)
char *pchAlias;                 // alias address
ushort_t hptda;                 // ptda handle of alias owner
{
    PTDA *pptda;                // debugger pptda

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgAliasFree     ");   }
#endif

    //  If this is an internal used alias (in the system arena),
    //  just yield to let it finish.
    if (VMSysArena((ulong_t)pchAlias))
        TKYield();

    //  If communication with the debugger is no longer possible or this
    //  process' debugger does not own the alias (as can happen if the
    //  debugger is itself a debuggee), then free the alias immediately.
    else if ((pPTDACur->ptda_flDbg & DBGP_ALLDONE) ||
      TKPidToPTDA((USHORT) pPTDACur->ptda_pdcb->lDbgerPid,
      SSToDS(&pptda)) != NO_ERROR || pptda->ptda_handle != hptda)
        VMFreeMem((ulong_t) pchAlias, hptda, NULL);

    else
        DbgHandler(DBG_V_ADDR|DBG_V_BUFFER, DBG_N_AliasFree, NULL, NULL,
                   pchAlias);
}

/***LP  DbgThreadCreate - Notify the debugger of a new thread
 *
 *      DbgThreadCreate generates a Thread Creation notification,
 *      and calls DbgNotify to return it to the debugger.
 *
 *      The debuggee thread must be within a Debugging critical
 *      section before calling this procedure.
 *
 *      This function is swappable.
 *
 *      ENTRY
 *              DBGT_CREATE bit set in ptcbCur-> flDbg
 *
 *      EXIT
 *              No Return Value
 *              DBGT_CREATE bit cleared in ptcbCur-> flDbg
 *
 *      Pseudocode:
 *      DbgNotify(DBG_N_ThreadCreate)
 *      mark thread as having performed creation notification
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void DbgThreadCreate ()
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgThreadCreate  ");   }
#endif

    DbgNotify(0, DBG_N_ThreadCreate);
    pTCBCur->TCB_flDbg &= ~DBGT_CREATE;
}


#endif /* INCDEBUG */

/***EP  DbgThreadTerm - Notify the debugger that a thread is terminating
 *
 *      If the terminating thread is a VDM, then this process
 *      frees the VDM watchpoint block.  For a protected mode thread,
 *      DbgThreadTerm handles thread termination notification,
 *      and the 'thread was the only one thawed' case.
 *
 *      If the current thread is the only thread that was thawed,
 *      we set the panic stop flag in ptda->flDbg, and let the
 *      frozen threads exit the kernel.  This way, if a thread
 *      exits with all other Debugged threads frozen, the debugger
 *      gets awakened, rather than hanging.
 *
 *      A Panic stop will not occur if this is the final thread
 *      in the process.
 *
 *      DbgThreadTerm can be entered before a debugger gets linked,
 *      if a KillProcess occured, so we check DBGP_LINKED, and
 *      skip the notification if a debugger is not linked.
 *
 *      Threads may terminate while frozen, so DbgThreadTerm must also
 *      update the count of frozen threads.
 *
 *      Since VDM threads are not DosDebug clients per se, this procedure
 *      is in the resident segment instead of the locked DosDebug segment.
 *      See dbgwp.c for details on the VDM watchpoints.
 *
 *      ENTRY
 *              lExitCode = Thread's proposed ExitCode.
 *      EXIT
 *              No Return Value
 *
 *      Pseudocode:
 *
 *      if (thread is a VDM thread)
 *        if (thread count is one)
 *          free the watchpoint block
 *        return
 *
 *      if (debuggee is not linked)
 *        return
 *
 *      if (debuggee has given DBG_N_ThreadCreate notification)
 *        DbgHandler(DBG_N_ThreadTerm)
 *
 *      clear pending LibLoad notifications
 *      clear pending LibFree notifications
 *
 *      if (debuggee has at least one other thread) {
 *        if (this thread is frozen) {
 *          if (this is thread 1) {
 *            // this is thread 1 but it is not the last thread, so
 *            // some other thread will do a DbgThreadTerm
 *            set TK_FF_TRC on this thread to catch this thread at
 *              exit kmode
 *
 *          } else {
 *            thaw this thread (it will not leave the kernel anyways)
 *            decrement count of frozen threads
 *            // there must be some other nonfrozen thread because
 *            // the debugger verifies that the count of frozen threads
 *            // does not equal the count of debuggee threads before
 *            // it tries to run the debuggee.
 *          }
 *        } else if (# frozen threads == # debuggee threads - 1) {
 *          set debuggee panic stop
 *          set TK_FF_TRC on debuggee process
 *          run all debuggee threads
 *        }
 *      } else {
 *        // This is the last thread.  It cannot be frozen because
 *        // the debugger verifies that the count of frozen threads
 *        // does not equal the count of debuggee threads before it
 *        // tries to run the debuggee.
 *        ;        // continue
 *      }
 */

#pragma code_seg()                              /* resident DEBUG code */

void DbgThreadTerm (lExitCode)
ulong_t lExitCode;                              /* Thread's exit code */
{

#ifdef INCDEBUG

    ULONG iwp;

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgThreadTerm    ");   }
#endif

    /*
     *  Handle the VDM thread case.
     */
    if (pPTDACur->ptda_ProcType == PT_VDM) {    /* If VDM process */
        if (pPTDACur->ptda_cTCB == 1) {         /* If last thread */

            /* Free the watchpoints associated with this VDM            A007 */
            /* DBGWPFreeVDM will free the block                              */
            for (iwp = 0;
                 pPTDACur->ptda_pdcb && (iwp < DR_COUNT);
                 iwp++) {                                            /* A007 */
                if (((dbgvdm_t *)(pPTDACur->ptda_pdcb))->dbgv_mpiwpidr[iwp] !=
                    (uchar_t)'\xFF') {                               /* A007 */
                    DBGWPFreeVDM(iwp);                               /* A007 */
                }
            }
//D007      VMFreeKHB(VM_HKH_PUB_RESRW, pPTDACur->ptda_pdcb);
                                                /* Free the heap block */
//D007      pPTDACur->ptda_pdcb = NULL;         /* No block any more */
        }
        return;
    }

    /*
     *  Handle the normal protected mode case.
     */
    if (pPTDACur->ptda_flDbg & DBGP_LINKED) {
        if (pTCBCur->TCBOrdinal != 1)
            pTCBCur->TCB_flDbg |= DBGT_DEAD;

        if (!(pTCBCur->TCB_flDbg & DBGT_CREATE))
            DbgHandler(DBG_V_VALUE | DBG_V_ADDR, DBG_N_ThreadTerm,
                       lExitCode, NULL, NULL, NULL, NULL);

        if (pTCBCur->TCB_flDbg & DBGT_LIBLOAD)
            TKLibiDbgDone(DBG_LOADDLL);

        if (pTCBCur->TCB_flDbg & DBGT_LIBFREE)
            TKLibiDbgDone(DBG_FREEDLL);

        if (pPTDACur->ptda_pTCBHead->TCBpTCBNext != NULL) {
            if (pTCBCur->TCB_flDbg & DBGT_FROZEN) {
                if (pTCBCur->TCBOrdinal == 1)
                    pTCBCur->TCBForcedActions |= TK_FF_TRC;
                else {
                    pTCBCur->TCB_flDbg & ~DBGT_FROZEN;
                    pPTDACur->ptda_pdcb->sFrozen--;
                }
            } else if (pPTDACur->ptda_pdcb->sFrozen ==
                       pPTDACur->ptda_cTCB - (ushort_t) 1) {
                pPTDACur->ptda_flDbg |= DBGP_PANIC;
                pPTDACur->ptda_ForcedActions |= TK_FF_TRC;
                TKRun((void *)DBGID_FZLOCK(pPTDACur->ptda_pdcb),NULL);
            }
        }
    }
#endif /* INCDEBUG */

}


/***EP  DbgProcTerm - Notify the debugger that the process is terminating
 *
 *      DbgProcTerm handles debugged process termination notification,
 *      and is called from from ProcessKill on debugged process
 *      termination.
 *
 *      The debugger may or may not be running at this point.  If
 *      it is not, (signalled by flags in the debuggee ptda) the
 *      notification will not occur.
 *
 *      The debugger will be notified only if DBGP_LINKED is set.
 *      The notification will fail in DbgHandler if DBGP_ALLDONE
 *      is set.
 *
 *      ENTRY
 *              None
 *
 *      EXIT
 *              No Return Value
 *
 *      Pseudocode:
 *      if (debuggee linked)
 *        DbgHandler(DBG_N_ProcTerm)
 *        free global and local watchpoints
 *      mark debuggee to prevent future notifications (DBGP_ABORTALL)
 *      set debuggee DEAD, REPLIED flags
 *      frozen thread count = 0
 *      run debugger
 *      debuggee DCB = NULL
 *      DbgReleaseDCB(DCB)
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

void DbgProcTerm ()
{

#ifdef INCDEBUG

    register DCB_t *pdcb;       // debuggee DCB

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgProcTerm      ");   }
#endif

    pdcb = pPTDACur->ptda_pdcb;

    if ((pPTDACur->ptda_flDbg) & DBGP_LINKED) {
        register int iapwpcb;   // watchpoint array index

        DbgHandler(DBG_V_VALUE | DBG_V_ADDR | DBG_V_INDEX, DBG_N_ProcTerm,
                   (ulong_t) pPTDACur->ptda_ulExitCode, NULL, NULL, NULL,
                   (ulong_t) pPTDACur->ptda_ulExitType, NULL);

        for (iapwpcb = 0; iapwpcb < DR_COUNT; iapwpcb++) {
            // free this process' global watchpoints
            if (DBGapgwpcb[iapwpcb] != NULL &&
                DBGapgwpcb[iapwpcb]->wpcb_pptda == pPTDACur)
                DbgWPFree(pPTDACur, (ulong_t)DBGapgwpcb[iapwpcb]->wpcb_bWid);

            // free local watchpoints
            if (pdcb->dcb_aplwpcb[iapwpcb] != NULL)
                DbgWPFree(pPTDACur, (ulong_t)pdcb->dcb_aplwpcb[iapwpcb]->wpcb_bWid);
        }
 /** vmOffGSR  - Decremented here so DLL's will go to the GSR          A011 *
   * vmOffGSR  =  0 means LOAD DLL's into Global Shared Region         A011 *
   * vmOffGSR !=  0 means DO NOT LOAD DLL's into Global Shared Region  A011 *
  */
     if ( vmOffGSR >  0 )                                   /*         A011 */
     {  vmOffGSR-- ; }                                      /*         A011 */
     else                                                   /*         A011 */
       if ( vmOffGSR <  0 )                                 /*         A011 */
       {  dbgAssert(vmOffGSR == DBG_N_Error); }             /*         A011 */
    }

    pPTDACur->ptda_flDbg |= DBGP_ABORTALL;
    pdcb->flState |= DBGDCBS_DBGEEDEAD | DBGDCBS_REPLIED;
    pdcb->sFrozen = 0;

    if (pdcb->pkDB)
        pdcb->pkDB->uDBTemp.Cmd = DBG_N_Success;

    TKRun((void *) DBGID_ERLOCK(pdcb),NULL);
    pPTDACur->ptda_pdcb = NULL;
    DbgReleaseDCB(pdcb);

#endif /* INCDEBUG */
}


#ifdef INCDEBUG

/***EP  DBGEntryEvent - handle a debug entry event
 *
 *      DBGEntryEvent is called from KMEnterKmodeEvents in
 *      response to TK_EF_TRC being set in TCBEntryActions.
 *
 *      At the momement, the only possible entry event is
 *      the end of a single step operation that is part of
 *      an Exchange Opcode, Single Step, and Go command.
 *      The correct response is to replace the opcode, turn
 *      off single stepping, and release all the other
 *      threads, if any.  This is what is done in DbgTrap
 *      when a single step as part of the XchgOp operation
 *      is detected.
 *
 *      DBGEntryEvent ()
 *
 *      ENTRY   NONE
 *      RETURN  NONE
 *
 *      EFFECTS The XchgOp command is finished.
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void
DBGEntryEvent()
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgEntryEvent    ");   }
#endif

    dbgAssert((pTCBCur->TCB_flDbg & DBGT_XCHGOP) &&
      (pPTDACur->ptda_flDbg & DBGP_SSTEP));
                                        /* Must be expected event */
    dbgFinishXchgOp();                  /* Finish exchange opcode command */
}


/***EP  DbgEvent - Handle an asyncronous debug event via TK_FF_TRC
 *
 *      DbgEvent is entered from CheckForceFlags or Exit_KMode if
 *      the TK_FF_TRC force flag is set.
 *
 *      TK_FF_TRC signals an asynchronous Debug Event.  This Event
 *      may include Initialization, Async Stop, Panic Stop,
 *      or Freeze events.
 *
 *      Which Debug event(s) occured is determined by checking
 *      the pptda->ptda_flDbg and the ptcb->flDbg flags.  See the code
 *      for details.
 *
 *      ENTRY
 *              None
 *      EXIT
 *              None
 *
 *      Pseudocode :
 *
 *      if (if not processing semList &
 *        indirect event - panic stop, async stop, initial event,
 *        libload event, or watchpoint event)
 *
 *        if (indirect notification failed)
 *          clear TK_FF_TRC  // try again later...
 *      else
 *        if ((process hold AND NOT (single step this thread))
 *            || (freeze this thread)) {
 *
 *          if (process hold AND thread is semowner) {
 *            if debugee not ready
 *               block (short term)
 *            else
 *               clear TK_FF_TRC
 *          }
 *          else
 *            block
 *        }
 *      else
 *        clear TK_FF_TRC
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

void DbgEvent ()
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgEvent         ");   }
#endif

    if (!(pPTDACur->ptda_pdcb->flState & DBGDCBS_SEMLIST) &&
        ((pPTDACur->ptda_flDbg & (DBGP_ASYNC | DBGP_PANIC |
        DBGP_WPHIT | DBGP_INIT)) || (pTCBCur->TCB_flDbg &
        (DBGT_LIBLOAD | DBGT_CREATE | DBGT_LIBFREE)))) {
       if (DbgHandler(DBG_V_INDIRECT) == TRUE) {
           // notification failed
           pTCBCur->TCBForcedActions &= ~((ulong_t)TK_FF_TRC);
       }
    }
    else
tryagain:                                /* need to include stepping M008 */
       if (((pPTDACur->ptda_flDbg & DBGP_HOLD) &&
           (pTCBCur != pPTDACur->ptda_pdcb->sDbgeeSStepTCB)) ||
           (pTCBCur->TCB_flDbg & DBGT_FROZEN)) {

          /*
           * If this process is in a DbgEnterCrit but this thread
           * is a semowner, check to see if the debugee is ready,
           * if not put this thread to sleep on a short term BlockID
           */
          if ((pPTDACur->ptda_flDbg & DBGP_HOLD) &&
              (pTCBCur->TCB_flDbg & DBGT_SEMOWNER)) {

             if (pPTDACur->ptda_pdcb->flState & DBGDCBS_DBGEENOTREADY) {
                DbgBlock(DBGID_SELOCK(pPTDACur->ptda_pdcb));
                goto tryagain;
             }
             else
                /* Allow this thread to run */
                pTCBCur->TCBForcedActions &= ~((ulong_t)TK_FF_TRC);
          }
          else {
             DbgBlock(DBGID_FZLOCK(pPTDACur->ptda_pdcb));
             /*
              * If we are woken up and we are still in a CritSec
              * we were woken up by DbgProcessSemList to check if
              * this thread was marked as a semowner while it was
              * blocked.
              */
             if (pPTDACur->ptda_flDbg & DBGP_HOLD)
                goto tryagain;
          }
       }
    else
        pTCBCur->TCBForcedActions &= ~((ulong_t)TK_FF_TRC);
}

/***LP  DbgLibLoad - Directly notify the debugger of a module load
 *
 *      DbgLibLoad generates the next pending DBG_N_ModuleLoad
 *      notification, and calls DbgNotify to return it to the
 *      debugger.
 *
 *      DbgLibLoad generates no more than one notification per call.
 *
 *      If no more DBG_N_ModuleLoad notifications exist, the
 *      DBGT_LIBLOAD bit will be cleared, to establish that
 *      there are no further pending DBG_N_LibLoad notifications
 *      from the current thread.  If DBGT_LIBLOAD is clear at
 *      the end of this call, then the notification did not occur.
 *
 *      The debuggee thread must be within a Debugging critical
 *      section before calling this procedure.
 *
 *      This function is swappable.
 *
 *      ENTRY
 *              DBGT_LIBLOAD bit set in ptcbCur-> flDbg
 *
 *      EXIT
 *              No Return Value
 *              DBGT_LIBLOAD bit possibly cleared in ptcbCur-> flDbg
 *
 *      Pseudocode:
 *      if (module loaded)
 *        DbgNotify(DBG_N_ModuleLoad)
 *      else
 *        TKLibiDbgDone(DBG_LOADDLL)
 *        clear DBGT_LIBLOAD
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void DbgLibLoad ()
{
    VMHOB hMTE;                 // library MTE

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgLibLoad       ");   }
#endif

    if (TKLibiDbgNextDLL(DBG_LOADDLL, SSToDS(&hMTE)) != NO_ERROR) {
        TKLibiDbgDone(DBG_LOADDLL);
        pTCBCur->TCB_flDbg &= ~DBGT_LIBLOAD;
    } else
        DbgNotify(DBG_V_VALUE | DBG_V_ADDR, DBG_N_ModuleLoad, (ulong_t) hMTE, 0);
}

/***LP  DbgLibFree - Directly notify the debugger of a library free
 *
 *      DbgLibFree generates the next pending DBG_N_ModuleFree
 *      notification, and calls DbgNotify to return it to the
 *      debugger.
 *
 *      DbgLibFree generates no more than one notification per call.
 *
 *      If no more DBG_N_ModuleFree notifications exist, the
 *      DBGT_LIBFREE bit will be cleared, to establish that
 *      there are no further pending DBG_N_ModuleFree notifications
 *      from the current thread.  If DBGT_LIBFREE is clear at
 *      the end of this call, then the notification did not occur.
 *
 *      The debuggee thread must be within a Debugging critical
 *      section before calling this procedure.
 *
 *      This function is swappable.
 *
 *      ENTRY
 *              DBGT_LIBFREE bit set in ptcbCur-> flDbg
 *
 *      EXIT
 *              No Return Value
 *              DBGT_LIBFREE bit possibly cleared in ptcbCur-> flDbg
 *
 *      Pseudocode:
 *      if (module freed)
 *        DbgNotify(DBG_N_ModuleFree)
 *      else
 *        TKLibiDbgDone(DBG_FREEDLL)
 *        clear DBGT_LIBFREE
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void DbgLibFree ()
{
    VMHOB hMTE;                 // library MTE

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgLibFree       ");   }
#endif

    if (TKLibiDbgNextDLL(DBG_FREEDLL, SSToDS(&hMTE)) != NO_ERROR) {
        TKLibiDbgDone(DBG_FREEDLL);
        pTCBCur->TCB_flDbg &= ~DBGT_LIBFREE;
    } else
        DbgNotify(DBG_V_VALUE | DBG_V_ADDR, DBG_N_ModuleFree, (ulong_t) hMTE, 0);
}

/***LP  DbgWatchpoint - Directly notify the debugger of a watchpoint hit
 *
 *      DbgWatchpoint generates the next pending DBG_N_Watchpoint
 *      notification, and calls DbgNotify to return it to the
 *      debugger.
 *
 *      DbgWatchpoint generates no more than one notification per
 *      call.
 *
 *      For watchpoints hit in ring 3 or at interrupt time, the faulting
 *      linear address is already in the watchpoint control block.  For
 *      watchpoints hit in ring 0 at task time, the faulting ring 3 address
 *      must be calcuated here.
 *
 *      If no more DBG_N_Watchpoint notifications exist, the
 *      DBGP_WPHIT bit will be cleared, to establish that there
 *      are no further pending DBG_N_Watchpoint notifications
 *      from the current thread.  If DBGP_WPHIT is clear at the
 *      end of this call, then the notification did not occur.
 *
 *      The debuggee thread must be within a Debugging critical
 *      section before calling this procedure.
 *
 *      The WPCB is freed before the notification completes.
 *
 *      ENTRY
 *              DBGP_WPHIT bit set in pptdaCur->flDbg
 *
 *      EXIT
 *              No Return Value
 *
 *      Pseudocode:
 *      if (no watchpoint hit)
 *        clear DBGP_WPHIT
 *      else
 *        extract LIP, Pid, Tid, MTE, watchpoint ID
 *        DbgWPFree(watchpoint) // release it
 *        if (hit occurred at task time and at ring 0)
 *          calculate thread's ring 3 IP
 *        DbgNotify(DBG_N_Watchpoint)
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

void DbgWatchpoint ()
{
    WPCB_t *pwpcb = NULL;               // hit watchpoint control block
    int iapwpcb;                        // watchpoint array index
    USHORT  not_GblWPhit = 0;           /* Global Watchpoint indicator  A002 */
    USHORT  not_LclWPhit = 0;           /* Local  Watchpoint indicator  A002 */

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgWatchPoint    ");   }
#endif

    for (iapwpcb = 0; iapwpcb < DR_COUNT; iapwpcb++) {
        if ((pwpcb = DBGapgwpcb[iapwpcb]) != NULL &&
            pwpcb->wpcb_pptda == pPTDACur &&
            pwpcb->wpcb_bfState == WPCBS_HIT)           // global WP hit
            break;
        else                                            /*             A002 */
            not_GblWPhit ++;                            /*             A002 */

        if ((pwpcb = pPTDACur->ptda_pdcb->dcb_aplwpcb[iapwpcb]) != NULL &&
            pwpcb->wpcb_bfState == WPCBS_HIT)           // local WP hit
            break;
        else                                            /*             A002 */
            not_LclWPhit ++;                            /*             A002 */
    }

    if ( (not_GblWPhit == 4) && (not_LclWPhit == 4) )   /*             C002 */
        pPTDACur->ptda_flDbg &= ~DBGP_WPHIT;
    else {
        char *pchLIP = (char *) pwpcb->wpcb_laddr;
        ulong_t lPid    = (ulong_t) pwpcb->wpcb_Pid;
        ulong_t lTid = (ulong_t) pwpcb->wpcb_Tid;
        ulong_t lhMTE = (ulong_t) pwpcb->wpcb_MTE;
        ulong_t lWid = (ulong_t) pwpcb->wpcb_bWid;

        if (DbgWPFree(pPTDACur, lWid))
            dbgAssert(FALSE);

        if (pchLIP == NULL && lTid != 0) {
            // watchpoint hit occurred at Task Time in ring 0
            PTDA *pptda = pPTDACur;     // faulting thread's pPTDA
            tcb_t *ptcb;                // faulting thread's pTCB

            // calculate linearized faulting address
            //  need faulting pPTDA, pTCB, CS, EIP

            if ((lPid == (ulong_t) pPTDACur->ptda_Pid ||
              TKPidToPTDA((USHORT) lPid, SSToDS(&pptda)) == NO_ERROR) &&
              TKTidTo_pTCB(pptda, lTid, SSToDS(&ptcb)) == NO_ERROR)
                dbgUserCSEIP(ptcb,SSToDS(&pchLIP),NULL,NULL);                    // B729833
        }

        DbgNotify(DBG_V_VALUE|DBG_V_ADDR|DBG_V_LEN|DBG_V_INDEX|DBG_V_MTE,
                DBG_N_Watchpoint, lPid, pchLIP, NULL, lTid, lWid, lhMTE);
    }
}

/***LP  DbgHandler - Handle upper level debuggee communications
 *
 *      DbgHandler handles the debuggee thread serialization
 *      and notification selection just prior to returning the
 *      notification to the debugger.
 *
 *      In the case of a possible pending or Stop notification,
 *      DBG_V_INDIRECT should be set in flValid.  This instructs
 *      DbgHandler that the passed-in notification is not
 *      usable, and that pending notifications should be
 *      checked.  If there is no actual pending notification
 *      to be returned, DbgHandler will simply do nothing.
 *
 *      The actual notification is returned to the debugger via
 *      DbgNotify.
 *
 *      The ordering of the indirect notifications IS significant,
 *      especially between INIT, ASYNC, and PANIC stops.
 *
 *      ENTRY
 *              flValid : Bit flags, (defined by DBG_V) (Mandatory)
 *              lCmd    : Notification number (DBG_N_)  (Mandatory)
 *              lValue  : Value for uDB.Value           (Optional)
 *              pchAddr : Value for uDB.Addr            (Optional)
 *              pchBuffer:Value for uDB.Buffer          (Optional)
 *              lLen    : Value for uDB.Len             (Optional)
 *              lIndex  : Value for uDB.Index           (Optional)
 *              lMTE    : Value for uDB.MTE             (Optional)
 *
 *      EXIT
 *              Return Value == FALSE
 *                      Notification completed OK.
 *
 *              Return Value == TRUE (nonzero)
 *                      Notification was not possible.
 *
 *      Pseudocode:
 *      DbgEnterCrit
 *      if (debuggee not linked)
 *        wait for link
 *        if (debuggee not linked)
 *          DbgExitCrit
 *          return(TRUE)
 *      if (still able to send notifications)
 *        DbgNotify(lCmd)
 *      while (still able to send notifications)
 *        if (init event)
 *          set RECEIVED
 *          DbgNotify(DBG_N_Success)
 *          continue
 *
 *        if (DBGP_EXELOAD)
 *          DbgNotify(DBG_N_ModuleLoad)
 *          clear DBGP_EXELOAD
 *          continue
 *
 *        if (DBGT_CREATE)
 *          DbgThreadCreate()
 *          continue
 *
 *        if (DBGT_LIBLOAD)
 *          DbgLibLoad()
 *          continue
 *
 *        if (DBGT_LIBFREE)
 *          DbgLibFree()
 *          continue
 *
 *        if (DBGP_WPHIT)
 *          DbgWatchpoint()
 *          continue
 *
 *        if (DBGP_ASYNC)
 *          DbgNotify(DBG_N_AsyncStop)
 *          continue
 *
 *        if (DBGP_PANIC)
 *          DbgNotify(DBG_N_Success)
 *          continue
 *
 *        break
 *
 *      DbgExitCrit
 *      return(notified ? nonzero : 0)
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

int cdecl DbgHandler (flValid, lCmd, lValue, pchAddr,
                      pchBuffer, lLen, lIndex, lMTE)
ulong_t flValid;                        // valid parameters flag
long    lCmd;                           // notification
long    lValue;                         // notification value field
char    *pchAddr;                       // notification address field
char    *pchBuffer;                     // notification buffer field
ulong_t lLen;                           // notification len field
ulong_t lIndex;                         // notification index field
ulong_t lMTE;                           // notification MTE filed
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgHandler       ");   }
#endif

    if (DbgEnterCrit(((flValid & DBG_V_INDIRECT) ? 1: 0)))
        return (TRUE);                  // DbgEnterCrit failed.

    if (!(pPTDACur->ptda_flDbg & DBGP_LINKED)) {
        TKBlock((void *)DBGID_EELOCK(pPTDACur->ptda_pdcb), DBGTIMEOUT, 0);

        if (!(pPTDACur->ptda_flDbg & DBGP_LINKED)) {
            TKDieSoonPTDA(pPTDACur, 0, TC_SIGNAL, NULL);
            pPTDACur->ptda_flDbg |= DBGP_MANUALTERM;
            DbgExitCrit();
            return(TRUE);
        }
    }

    if (pPTDACur->ptda_pdcb->flSever == DBGS_NO_SEVER)              /*  A009 */
       if (!(pPTDACur->ptda_flDbg & DBGP_ALLDONE))                  /*  A009 */
          if (pPTDACur->ptda_pdcb->flNoSever_Rpt ==                 /*  A009 */
              DBGS_NOSEVER_MSG_ON)  {                               /*  A009 */
                 pPTDACur->ptda_pdcb->flNoSever_Rpt =               /*  A009 */
                                 DBGS_NOSEVER_MSG_OFF;  /* set to zero  A009 */
                 DbgNotify(                                          /* A009 */
                           DBG_V_VALUE | DBG_V_ADDR | DBG_V_BUFFER | DBG_V_LEN,
                           DBG_N_Error,                              /* A009 */
                           ERROR_EXCL_SEM_ALREADY_OWNED,             /* A009 */
                           NULL,                                     /* A009 */
                           NULL,                                     /* A009 */
                           NULL);                                    /* A009 */
          }                                                          /* A009 */

    if (!(pPTDACur->ptda_flDbg & DBGP_ALLDONE) &&
          !(flValid & DBG_V_INDIRECT))
        DbgNotify(flValid, lCmd, lValue, pchAddr, pchBuffer, lLen, lIndex, lMTE);

    while (!(pPTDACur->ptda_flDbg & DBGP_ALLDONE)) {
        // indirect notifications
        if (pPTDACur->ptda_flDbg & DBGP_INIT) {
            pPTDACur->ptda_pdcb->flState |= DBGDCBS_RECEIVED;
            DbgNotify(flValid,DBG_N_Success);
            continue;
        }

        if (pPTDACur->ptda_flDbg & DBGP_EXELOAD) {
            DbgNotify(DBG_V_VALUE | DBG_V_ADDR, DBG_N_ModuleLoad,
                      (ulong_t) pPTDACur->ptda_module, 0);
            pPTDACur->ptda_flDbg &= ~DBGP_EXELOAD;
            continue;
        }

        if (pTCBCur->TCB_flDbg & DBGT_CREATE) {
            DbgThreadCreate();
            continue;
        }

        if (pTCBCur->TCB_flDbg & DBGT_LIBLOAD) {
            DbgLibLoad();
            continue;
        }

        if (pTCBCur->TCB_flDbg & DBGT_LIBFREE) {
            DbgLibFree();
            continue;
        }

        if (pPTDACur->ptda_flDbg & DBGP_WPHIT) {
            DbgWatchpoint();
            continue;
        }

        if (pPTDACur->ptda_flDbg & DBGP_ASYNC) {
            DbgNotify(flValid,DBG_N_AsyncStop);
            continue;
        }

        if (pPTDACur->ptda_flDbg & DBGP_PANIC) {
            DbgNotify(flValid,DBG_N_Success);
            continue;
        }

        break;                  // no more indirect notifications
    }

    DbgExitCrit();
    return(!(pPTDACur->ptda_pdcb->flState & DBGDCBS_NOTIFIED));
}

/***LP  DbgNotify - Directly notify the debugger of an event
 *
 *      DbgNotify notifies the debugger of a DosDebug event.
 *
 *      DbgNotify is the only debuggee context procedure allowed
 *      to read or modify the kDB.
 *
 *      DbgNotify handles exactly one notification.
 *
 *      ------- WARNING WARNING WARNING WARNING -------
 *
 *      A pointer to the kDB is passed to the debuggee, and the
 *      debuggee thread is allowed to directly read and modify the
 *      debugger thread's kernel stack (in the kDB area) for the
 *      duration of this procedure.
 *
 *      The debuggee kernel code MUST verify that the pointer to
 *      the kDB (in the DCB) is nonzero before attempting to access
 *      the kDB, or it will trash the kernel stack of the debugger
 *      thread.  The passed pointer will be relative to DGroup.
 *
 *      ------- WARNING WARNING WARNING WARNING -------
 *
 *      ENTRY
 *              flValid : Bit flags, (defined by DBG_V) (Mandatory)
 *              lCmd    : Notification number (DBG_N_)  (Mandatory)
 *              lValue  : Value for uDB.Value           (Optional)
 *              pchAddr : Value for uDB.Addr            (Optional)
 *              pchBuffer:Value for uDB.Buffer          (Optional)
 *              lLen    : Value for uDB.Len             (Optional)
 *              lIndex  : Value for uDB.Index           (Optional)
 *              lMTE    : Value for uDB.MTE             (Optional)
 *
 *      EXIT
 *              Return Value == FALSE
 *                      Notification Completed OK
 *
 *              Return Value == TRUE
 *                      No notification possible
 *
 *      Pseudocode:
 *
 *      clear panic flag, async flag, init flag, rangestep flag
 *      clear NOTIFIED flag
 *
 *      if (RECEIVED and (pkDB != NULL)) {
 *        Stuff notification.
 *        set REPLIED flag.
 *        pkDB = NULL
 *        TKRun(Debugger Block ID)
 *      }
 *
 *      while (NOT ALLDONE AND (pkDb == NULL))
 *        DbgBlock(Debuggee Block ID)
 *
 *      if (NOT ALLDONE) {
 *        set RECEIVED flag
 *        set NOTIFIED flag
 *      }
 *
 *      return(notified ? FALSE : TRUE)
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

int cdecl DbgNotify (flValid, lCmd, lValue, pchAddr,
                     pchBuffer, lLen, lIndex, lMTE)
ulong_t flValid;                        // valid parameters flag
long    lCmd;                           // notification
long    lValue;                         // notification value field
char    *pchAddr;                       // notification address field
char    *pchBuffer;                     // notification buffer field
ulong_t lLen;                           // notification len field
ulong_t lIndex;                         // notification index field
ulong_t lMTE;                           // notification MTE filed
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgNotify        ");   }
#endif

    dbgAssert(pPTDACur->ptda_pdcb != NULL);

    pPTDACur->ptda_flDbg &= ~(DBGP_PANIC|DBGP_ASYNC|DBGP_INIT|DBGP_RANGESTEP);
    pPTDACur->ptda_pdcb->flState &= ~DBGDCBS_NOTIFIED;

    /*
     * If the notification is for an exception, single-step, or
     * breakpoint then set a flag indicating the DBG_C_CONTINUE
     * reply is required.
     */

    if (lCmd == DBG_N_Exception) {
       pPTDACur->ptda_flDbg |= DBGP_XCPTPENDING;
    }

    if ((pPTDACur->ptda_pdcb->flState & DBGDCBS_RECEIVED) &&
         (pPTDACur->ptda_pdcb->pkDB != NULL)) {

        pPTDACur->ptda_pdcb->pkDB->uDBTemp.Cmd = lCmd;

        if (flValid & DBG_V_VALUE)
            pPTDACur->ptda_pdcb->pkDB->uDBTemp.Value = lValue;

        if (flValid & DBG_V_ADDR)
            pPTDACur->ptda_pdcb->pkDB->uDBTemp.Addr = (ulong_t) pchAddr;

        if (flValid & DBG_V_BUFFER)
            pPTDACur->ptda_pdcb->pkDB->uDBTemp.Buffer = (ulong_t) pchBuffer;

        if (flValid & DBG_V_LEN)
            pPTDACur->ptda_pdcb->pkDB->uDBTemp.Len = lLen;

        if (flValid & DBG_V_INDEX)
            pPTDACur->ptda_pdcb->pkDB->uDBTemp.Index = lIndex;

        if (flValid & DBG_V_MTE)
            pPTDACur->ptda_pdcb->pkDB->uDBTemp.MTE = lMTE;

        pPTDACur->ptda_pdcb->flState |= DBGDCBS_REPLIED;
        pPTDACur->ptda_pdcb->pkDB = NULL;
        TKRun((void*)DBGID_ERLOCK(pPTDACur->ptda_pdcb),NULL);
    }

    while ((!(pPTDACur->ptda_flDbg & DBGP_ALLDONE)) &&
          (pPTDACur->ptda_pdcb->pkDB == NULL))
        DbgBlock(DBGID_EELOCK(pPTDACur->ptda_pdcb));

    if (!(pPTDACur->ptda_flDbg & DBGP_ALLDONE)) {
        pPTDACur->ptda_pdcb->flState |= DBGDCBS_RECEIVED;
        pTCBCur->TCB_flDbg |= DBGT_NOTIFIED;
                                        /* This thread made notification */
    }

    return(!(pPTDACur->ptda_pdcb->flState & DBGDCBS_NOTIFIED));
}

/***LP  DbgBlock - Block the current debuggee thread until told to execute
 *
 *      DbgBlock marks the current thread as freezing, blocks,
 *      and then marks the thread as not freezing.  This keeps
 *      the DBG_C_ThrdStat command accurate.
 *
 *      The Block is not verified in any way.  It could have been
 *      an interrupted block or a normal wakup.
 *
 *      This function is swappable.
 *
 *      ENTRY
 *              BlockID : Block ID used for call to TKBlock
 *                      This block ID is generated by one of the
 *                      DBGID_ macros.
 *
 *      EXIT
 *              No Return Value
 *
 *      Pseudocode:
 *      set thread FREEZING
 *      block
 *      clear thread FREEZING
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")

void DbgBlock (lBlockID)
    void *lBlockID;                             // block ID
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgBlock         ");   }
#endif

    pTCBCur->TCB_flDbg |= DBGT_FREEZING;
    TKSleep(lBlockID,-1,0,0);
    pTCBCur->TCB_flDbg &= ~DBGT_FREEZING;
}

/***LP  DbgEnterCrit - Enter DbgHandler critical section
 *
 *      DbgEnterCrit places the debuggee process into a Debugging
 *      critical section, to serialize Debuggee access to the kDB.
 *
 *      Only one notification may be returned to the debugger
 *      at any one time, so access to the kDB must be serialized
 *      between threads of the debuggee process.
 *
 *      DbgEnterCrit may not be called twice by the same thread,
 *      or a debuggee process hang will result.
 *
 *      A debuggee thread may not access the kDB nor return pending
 *      notifications without entering the debug critical section.
 *
 *      DbgEnterCrit sets TK_FF_TRC on the debuggee process as soon
 *      as it is able to enter the critical section, to hold other
 *      debuggee threads in the kernel.
 *
 *      When processing the semList we might clear TK_FF_TRC in the PTDA's
 *      forceflags, if we do, we set the TK_FF_TRC flag in the TCB's
 *      forceflags of every thread in this process, allowing us to control
 *      blocking on a per thread basis instead of a per process basis.
 *
 *      ENTRY
 *              indirect = 1 - If we are entering a critical section
 *                             because of an indirect notification
 *              indirect = 0 - otherwise
 *
 *              Current process must be debugged.
 *
 *      EXIT
 *              Return Value == FALSE
 *                      Debuggee thread serialized OK
 *
 *              Return Value == TRUE (nonzero)
 *                      Notification Impossible
 *
 *      Pseudocode:
 *      while (a thread is active && !ALLDONE)
 *        DbgBlock(debuggee block id)
 *
 *      if (ALLDONE)
 *        clear pending debug actions
 *        return(TRUE)
 *
 *      // this thread now owns the critical section
 *      active Tid = current thread's Tid
 *      sDbgeeSStepTCB = NULL
 *      set DBGP_HOLD
 *      set TK_FF_TRC
 *
 *      if (DbgProcessSemList(EnterCrit,0) == ERROR)
 *          return(TRUE);
 *
 *      return(FALSE)
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

int DbgEnterCrit (indirect)
ulong_t indirect;                               /* indirect notification flg */
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgEnterCrit     ");   }
#endif

    /*
     * If we receive a notification while processing the semList
     * sever the debugger/debuggee connection.
     */
    if (pPTDACur->ptda_pdcb->flState & DBGDCBS_SEMLIST) {
       DbgSeverConnection();
       return(TRUE);
    }

 /* dbgAssert(pPTDACur->ptda_pdcb->sActiveTid != pTCBCur->TCBOrdinal)  D009 */
    while (pPTDACur->ptda_pdcb->sActiveTid != 0 &&
      !(pPTDACur->ptda_flDbg & DBGP_ALLDONE))
        DbgBlock(DBGID_FZLOCK(pPTDACur->ptda_pdcb));

    if (pPTDACur->ptda_flDbg & DBGP_ALLDONE) {
                                        /* If debuggee dying or debugger gone */
        pPTDACur->ptda_ForcedActions &= ~TK_FF_TRC;
        pTCBCur->TCBForcedActions &= ~TK_FF_TRC;
                                        /* Clear pending debug actions */
        return(TRUE);                   /* Report failure */
    }

    pPTDACur->ptda_pdcb->sActiveTid = pTCBCur->TCBOrdinal;
    pPTDACur->ptda_pdcb->sDbgeeSStepTCB = NULL;
    pPTDACur->ptda_flDbg |= DBGP_HOLD;
    pPTDACur->ptda_ForcedActions |= TK_FF_TRC;

    if (DbgProcessSemList(f_ENTERCRIT,indirect) != 0)
        return(TRUE);

    return(FALSE);
}

/***LP  DbgExitCrit - Exit DbgHandler critical section
 *
 *      DbgExitCrit releases control of the DbgHandler for this
 *      process.  DbgEnterCrit must have previously been called.
 *
 *      ENTRY
 *              Current process must be debugged.
 *
 *      EXIT
 *              No Return Value
 *
 *      Pseudocode:
 *      sActiveTid = 0
 *
 *      if (single step a specific thread)
 *        execute that thread
 *      else
 *        DbgProcessSemList(ExitCrit,0)
 *        clear DBGP_HOLD
 *        run all threads
 *
 *      clear TK_FF_TRC in per process forced actions
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

void DbgExitCrit ()
{

    tcb_t *ptcb = pPTDACur->ptda_pdcb->sDbgerSStepTCB;
    pPTDACur->ptda_pdcb->sActiveTid = 0;

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgExitCrit      ");   }
#endif

    if ((pPTDACur->ptda_pdcb->sDbgeeSStepTCB = ptcb) == NULL) {

        /* Process the semList */
        DbgProcessSemList(f_EXITCRIT,0);

        //  Run all threads by releasing the HOLD and waking them up.

        pPTDACur->ptda_flDbg &= ~DBGP_HOLD;
        TKRun((void *)DBGID_FZLOCK(pPTDACur->ptda_pdcb),NULL);
    } else {
        //  Single step one thread

        pPTDACur->ptda_pdcb->sDbgerSStepTCB = NULL;
        if (ptcb->TCB_flDbg & DBGT_FREEZING)
            //  The thread we want to step is in DbgBlock, so force it out.

            TKForceThread(NULL, ptcb);
    }

    pPTDACur->ptda_ForcedActions &= ~TK_FF_TRC;
}

/***LP  DbgProcessSemList - Process the semaphore lists
 *
 *      ENTRY
 *              f_ENTERCRIT - Process the semList's for a DbgEnterCrit
 *
 *                 parm1 = 1 - indirect notification
 *                         0 - other type of notification
 *
 *                 Return Value == 0
 *                         Debuggee thread serialization OK
 *
 *                 Return Value == 1
 *                         Notification Impossible
 *
 *              f_EXITCRIT - Process the semList's for a DbgExitCrit
 *
 *              f_SEMWAKE  - Process the semList's for a sem wake
 *                           Return values checked in RamSemWakeSub in sem.asm
 *
 *                 parm1 = Address of the semaphore which is being cleared
 *
 *                 Return Value == 0
 *                         Do the ProcRun.
 *
 *                 Return Value == 1
 *                         Don't do the ProcRun, DbgProcessSemList has
 *                         done a TKWakeup.
 *
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

int DbgProcessSemList (int flags, ulong_t parm1)
{
   int         rt = 0,
               semOwnersFound = 0,
               semsOwnedByTid = 0,
               ActiveTidIsSemOwner = 0,
               Wakes = 0;
   ulong_t     i;
   semList     *pSemListCurr;
   DCB_t       *pdcb = pPTDACur->ptda_pdcb;
   FSRST       *pFSRST;
   GRESEMT     *pGRESEMT;                                            /* A010 */
   tcb_t       *pTCB;
   ushort_t    CurrPid = pPTDACur->ptda_Pid;
   ushort_t    ActiveTid = pPTDACur->ptda_pdcb->sActiveTid;
   ushort_t    SemPid,
               SemTid;
   ljmp_t      lj;                              /* TKlongjmp buffer */

   /* If process SemList's for an EnterCrit or a SemWake */
   if (flags & (f_ENTERCRIT | f_SEMWAKE)) {

      /* Indicate that an async stop event has occured */
      if ((flags & f_ENTERCRIT) && (parm1 == 1) &&
          ((pPTDACur->ptda_flDbg & DBGP_ASYNC_MASK) == DBGP_ASYNC))
         pdcb->flState |= DBGDCBS_ASYNC;

      /*
       * We mark the debuggee as not ready because we might block after
       * marking some threads as semowners. If any of the debuggee's
       * threads starts running and they have been marked as semowners we
       * check to see if the debuggee is ready, if not we put them to sleep
       * on a short term BlockID until we finish processing the semList
       * chain, then we wake them up.
       * We also wake up threads that might have started to run, blocked
       * because of the DBGP_HOLD and then were marked as semowners.
       */
      pdcb->flState |= DBGDCBS_DBGEENOTREADY;

      if (flags & f_ENTERCRIT)
         pdcb->flState |= DBGDCBS_SEMLIST;
      else {
         pPTDACur->ptda_flDbg |= DBGP_HOLD;
         pPTDACur->ptda_ForcedActions |= TK_FF_TRC;
      }

      /* Process each sem in the semList */
      pSemListCurr = pSemListHead;

      while (pSemListCurr) {
         for (i = 0; i < pSemListCurr->count; i++) {

            if ((pSemListCurr->sem[i].flags & (DBSEM_INVALID | DBSEM_FSRST))
                == DBSEM_FSRST) {

               if (flags & f_SEMWAKE) {

                  if (parm1 == (ulong_t)
                      &(((FSRST *)(pSemListCurr->sem[i].addr))->fs_RAMSem)) {

                     pFSRST = (FSRST *)pSemListCurr->sem[i].alias;

                     /* Install fault handler */
                     if (TKCatchFault(SSToDS(&lj)) == NO_ERROR) {
                        /*
                         * If we are releasing the RamSem of an FSRST that
                         * is in the semList, marks its proc with the
                         * debuggee's proc id and the thread id with the
                         * current tid, done so subsystems (e.g. pmwin) think
                         * the sem is owned and call into the kernel),
                         * mark the RamSem owner field with a special owner.
                         */
                        dbgAssert(pFSRST->fs_ProcID == 0);
                        dbgAssert(pFSRST->fs_ThrdID == 0);
                        dbgAssert(pFSRST->fs_RAMSem.RamSemOwner == 0);
                        dbgAssert(pFSRST->fs_Usage == 0);

                        pFSRST->fs_ProcID = CurrPid;
                        pFSRST->fs_ThrdID = pTCBCur->TCBOrdinal;
                        pFSRST->fs_RAMSem.RamSemOwner = DBG_RAMSEM_OWNED;
                        TKClrFault();    /* Clear fault handler */
                     }
                     else {
                        pSemListCurr->sem[i].flags |= DBSEM_INVALID;
                        continue;
                     }
                     continue;
                  }
               }

               pFSRST = (FSRST *)pSemListCurr->sem[i].alias;

               /* Install fault handler */
               if (TKCatchFault(SSToDS(&lj)) == NO_ERROR) {

                  SemPid = pFSRST->fs_ProcID;
                  SemTid = pFSRST->fs_ThrdID;

                  /* Check if semaphore is owned by the debuggee */
                  if (SemPid == CurrPid) {

                     /* If sem not currently owned by DosDebug */
                     if (pFSRST->fs_RAMSem.RamSemOwner != DBG_RAMSEM_OWNED) {

                        /*
                         * If Sever connection is wanted              A009
                         *
                         * Mark the RamSem as someone is waiting on it's
                         * release. Done so the subsystems (e.g. pmwin)
                         * call into the kernel to release it.
                         */
                         if ((pdcb->flSever == DBGS_SEVER) ||       /*  A009 */
                              (pdcb->flState & DBGDCBS_ASYNC))      /*  A009 */
                             pFSRST->fs_RAMSem.RamSemFlag |= RAMSEM_WAITING;

                        /*
                         * If the thread sending the notification is a
                         * semowner, sever the debugger/debuggee connection
                         * if the event that has occured is something other
                         * than an async stop.
                         */

                        if (SemTid == ActiveTid) {

                         /* If Sever connection is wanted keep going    A009 */
                         if ((pdcb->flSever == DBGS_SEVER) ||       /*  A009 */
                              (pdcb->flState & DBGDCBS_ASYNC)) {    /*  A009 */
                             ActiveTidIsSemOwner = 1;
                             rt = 1;
                         }                                            /* A009*/

                             if (!(pdcb->flState & DBGDCBS_ASYNC)) {

                                /* Sever the connection now              A009*/
                                if (pdcb->flSever == DBGS_SEVER) {    /* A009*/
                                   TKClrFault();    /* Clear fault handler */
                                   DbgSeverConnection();
                                   goto dpslx;
                                }
                                else {                                /* A009*/
                                   pdcb->pkDB->uDBTemp.Cmd =          /* A009*/
                                      DBG_N_Error;                    /* A009*/
                                   pdcb->pkDB->uDBTemp.Value =        /* A009*/
                                       ERROR_EXCL_SEM_ALREADY_OWNED;  /* A009*/
                                   pdcb->flNoSever_Rpt =              /* A009*/
                                       DBGS_NOSEVER_MSG_ON;           /* A009*/
                                 }                                    /* A009*/

                             } /* End (!(pdcb->flState & DBGDCBS_ASYNC)) A009*/

                        } /* End SemTid == ActiveTid                         */

                      /* If Sever connection is wanted keep going       A009 */
                      if ((pdcb->flSever == DBGS_SEVER) ||          /*  A009 */
                           (pdcb->flState & DBGDCBS_ASYNC)) {       /*  A009 */
                        /* Mark the thread owning the sem as a semowner */
                        pTCB = pPTDACur->ptda_pTCBHead;
                        while (pTCB != NULL && pTCB->TCBOrdinal != SemTid)
                           pTCB = pTCB->TCBpTCBNext;
                        dbgAssert (pTCB != NULL);
                        pTCB->TCB_flDbg |= DBGT_SEMOWNER;

                        semOwnersFound++;
                        if (SemTid == pTCBCur->TCBOrdinal)
                           semsOwnedByTid++;
                      }                                               /* A009*/
                     }
                }
                  TKClrFault();    /* Clear fault handler */
               }
               else {
                  pSemListCurr->sem[i].flags |= DBSEM_INVALID;
                  continue;
               }
            }
            //A010  ***** START HERE ***** D80689
            else if ((pSemListCurr->sem[i].flags &
      (DBSEM_INVALID | DBSEM_GRESEMT)) == DBSEM_GRESEMT) {

               if (flags & f_SEMWAKE) {

                  if (parm1 == (ulong_t)
                      &((GRESEMT *)(pSemListCurr->sem[i].addr))) {

                     pGRESEMT = (GRESEMT *)pSemListCurr->sem[i].alias;

                     /* Install fault handler */
                     if (TKCatchFault(SSToDS(&lj)) == NO_ERROR) {
                        /*
                         * If we are releasing the semaphore of an GRESEMT
                         * that is in the semList, marks its proc with the
                         * debuggee's proc id and the thread id with the
                         * current tid, done so subsystems (e.g. pmwin) think
                         * the sem is owned and call into the kernel),
                         * mark the semaphore owner field with a special owner.
                         */
                        dbgAssert(pGRESEMT->gs_ulProcessThread == 0);
                        dbgAssert(pGRESEMT->gs_ulCallerAddr == 0);
                        dbgAssert(pGRESEMT->gs_ulWaitingCount == 0);

                        pGRESEMT->gs_ulProcessThread = pTCBCur->TCBOrdinal<<16;
                        pGRESEMT->gs_ulProcessThread += CurrPid;
                        pGRESEMT->gs_ulCallerAddr = DBG_GRESEM_OWNED;
                        TKClrFault();    /* Clear fault handler */
                     }
                     else {
                        pSemListCurr->sem[i].flags |= DBSEM_INVALID;
                        continue;
                     }
                     continue;
                  }
               }

               pGRESEMT = (GRESEMT *)pSemListCurr->sem[i].alias;

               /* Install fault handler */
               if (TKCatchFault(SSToDS(&lj)) == NO_ERROR) {

                  SemPid = pGRESEMT->gs_ulProcessThread & 0xFFFF;
                  SemTid = (pGRESEMT->gs_ulProcessThread & 0xFFFF0000) >> 16;

                  /* Check if semaphore is owned by the debuggee */
                  if (SemPid == CurrPid) {

                     /* If sem not currently owned by DosDebug */
                     if (pGRESEMT->gs_ulCallerAddr != DBG_GRESEM_OWNED) {

                        /*
                         * If Sever connection is wanted              A009
                         *
                         * Mark the GRESEM as someone is waiting on it's
                         * release. Done so the subsystems (e.g. pmwin)
                         * call into the kernel to release it.
                         */
                         if ((pdcb->flSever == DBGS_SEVER) ||       /*  A009 */
                            (pdcb->flState & DBGDCBS_ASYNC))      /*  A009 */
                            pGRESEMT->gs_ulWaitingCount++;

                        /*
                         * If the thread sending the notification is a
                         * semowner, sever the debugger/debuggee connection
                         * if the event that has occured is something other
                         * than an async stop.
                         */

                        if (SemTid == ActiveTid) {

                         /* If Sever connection is wanted keep going    A009 */
                         if ((pdcb->flSever == DBGS_SEVER) ||       /*  A009 */
                              (pdcb->flState & DBGDCBS_ASYNC)) {    /*  A009 */
                             ActiveTidIsSemOwner = 1;
                             rt = 1;
                         }                                            /* A009*/

                             if (!(pdcb->flState & DBGDCBS_ASYNC)) {

                                /* Sever the connection now              A009*/
                                if (pdcb->flSever == DBGS_SEVER) {    /* A009*/
                                   TKClrFault();    /* Clear fault handler */
                                   DbgSeverConnection();
                                   goto dpslx;
                                }
                                else {                                /* A009*/
                                   pdcb->pkDB->uDBTemp.Cmd =          /* A009*/
                                      DBG_N_Error;                    /* A009*/
                                   pdcb->pkDB->uDBTemp.Value =        /* A009*/
                                       ERROR_EXCL_SEM_ALREADY_OWNED;  /* A009*/
                                   pdcb->flNoSever_Rpt =              /* A009*/
                                       DBGS_NOSEVER_MSG_ON;           /* A009*/
                                 }                                    /* A009*/

                             } /* End (!(pdcb->flState & DBGDCBS_ASYNC)) A009*/

                        } /* End SemTid == ActiveTid                         */

                      /* If Sever connection is wanted keep going       A009 */
                      if ((pdcb->flSever == DBGS_SEVER) ||          /*  A009 */
                           (pdcb->flState & DBGDCBS_ASYNC)) {       /*  A009 */
                        /* Mark the thread owning the sem as a semowner */
                        pTCB = pPTDACur->ptda_pTCBHead;
                        while (pTCB != NULL && pTCB->TCBOrdinal != SemTid)
                           pTCB = pTCB->TCBpTCBNext;
                        dbgAssert (pTCB != NULL);
                        pTCB->TCB_flDbg |= DBGT_SEMOWNER;

                        semOwnersFound++;
                        if (SemTid == pTCBCur->TCBOrdinal)
                           semsOwnedByTid++;
                      }                                               /* A009*/
                     }
                }
                  TKClrFault();    /* Clear fault handler */
               }
               else {
                  pSemListCurr->sem[i].flags |= DBSEM_INVALID;
                  continue;
               }
            }     //A010 ***** END HERE ***** D80689
         }
         pSemListCurr = pSemListCurr->next;
      }

      pdcb->flState &= ~DBGDCBS_DBGEENOTREADY;

      /*
       * NOTE: When we wake threads in this function we wake any we might
       *       have put to sleep while processing the semList and any
       *       that were already blocked when we marked them as semowners.
       */
      if (flags & f_ENTERCRIT) {

         /*
          * If semOwners have been found mark all the TCB's in the debuggee
          * with TK_FF_TRC and turn off the TK_FF_TRC in the PTDA.
          * (This allows us to control blocking on a per thread basis
          * instead of a per process basis).
          */
         if (semOwnersFound) {

            for (pTCB = pPTDACur->ptda_pTCBHead; pTCB != NULL;
                pTCB = pTCB->TCBpTCBNext)
               pTCB->TCBForcedActions |= TK_FF_TRC;

            pPTDACur->ptda_ForcedActions &= ~TK_FF_TRC;

            TKRun((void *)DBGID_SELOCK(pdcb),NULL);
            TKRun((void *)DBGID_FZLOCK(pdcb),NULL);

            /*
             * If the ActiveTid is a not a semowner, put this thread to
             * sleep on a special BlockID.
             * We will be woken up when the debuggee no longer owns
             * any semaphores in the semList or a DbgSeverConnection
             * occurrs.
             */
            if (!ActiveTidIsSemOwner) {

               pdcb->flState &= ~DBGDCBS_ASYNC;

dpslx2:        DbgBlock(DBGID_SPLOCK(pdcb));

               /* If a DbgSeverConnection has occured */
               if (!(pPTDACur->ptda_flDbg & DBGP_LINKED)) {
                  rt = 1;
                  goto dpslx;
               }
            }
         }

         if (!ActiveTidIsSemOwner) {

dpslx1:     pdcb->flState &= ~DBGDCBS_SEMLIST;

            /*
             * We might have put the debugger to sleep if it attempted to do
             * a DosDebug command while we were processing the semList's;
             * so wake up all threads blocked on short term debugger BlockID's
             */
            TKRun((void *)DBGID_SRLOCK(pdcb),NULL);
         }
      }
      else {

         pPTDACur->ptda_flDbg &= ~DBGP_HOLD;
         pPTDACur->ptda_ForcedActions &= ~TK_FF_TRC;

         /*
          * If this tid no longer owns any sems in the semList, mark it
          * as not being a semowner and set the TK_FF_TRC flag.
          */
         if (semsOwnedByTid == 0) {
            pTCBCur->TCB_flDbg &= ~DBGT_SEMOWNER;
            pTCBCur->TCBForcedActions |= TK_FF_TRC;
         }

         /* If the debuggee no longer owns any sems in the semList */
         if (semOwnersFound == 0) {
dpslx:
            pSemListCurr = pSemListHead;
            while (pSemListCurr) {
               for (i = 0; i < pSemListCurr->count; i++) {

                  if ((pSemListCurr->sem[i].flags & (DBSEM_INVALID |
                      DBSEM_FSRST)) == DBSEM_FSRST) {

                     pFSRST = (FSRST *)pSemListCurr->sem[i].alias;

                     /* Install fault handler */
                     if (TKCatchFault(SSToDS(&lj)) == NO_ERROR) {
                        /*
                         * If the sem is owned by the DosDebug set
                         * the sem as unOwned
                         */
                        if ((pFSRST->fs_ProcID == CurrPid) &&
                            (pFSRST->fs_RAMSem.RamSemOwner ==
                            DBG_RAMSEM_OWNED)) {
                           pFSRST->fs_ProcID = 0;
                           pFSRST->fs_ThrdID = 0;
                           pFSRST->fs_RAMSem.RamSemOwner = 0;

                           /*
                            * We must wake up all threads that requested this
                            * semaphore while DosDebug owned it. This is
                            * normally done when we return to RamSemWakeSub,
                            * but we may block before returning and cause
                            * a deadlock.
                            * We check to see if the current sem address is
                            * equal to parm1 below, because RamSemWake in
                            * sem.asm clears the waiting flag for the sem it
                            * is currently waking.
                            *
                            * NOTE: We don't wakeup mux waiters.
                            */

                           if ((parm1 == (ulong_t)&(((FSRST *)
                               (pSemListCurr->sem[i].addr))->fs_RAMSem)) ||
                               (pFSRST->fs_RAMSem.RamSemFlag & RAMSEM_WAITING)){

                              TKWakeup((void *)((RAMSEM_BLOCK_ID << 16) |
                                 pFSRST->fs_RAMSem.RamSemID),0,NULL);
                              pFSRST->fs_RAMSem.RamSemFlag &= ~RAMSEM_WAITING;
                              Wakes++;
                           }
                        }
                        TKClrFault();    /* Clear fault handler */
                     }
                     else {
                        pSemListCurr->sem[i].flags |= DBSEM_INVALID;
                        continue;
                     }
                  }
        // A010  ***** START HERE 2 ***** D80689
                  else if ((pSemListCurr->sem[i].flags & (DBSEM_INVALID |
                      DBSEM_GRESEMT)) == DBSEM_GRESEMT) {

                     pGRESEMT = (GRESEMT *)pSemListCurr->sem[i].alias;

                     /* Install fault handler */
                     if (TKCatchFault(SSToDS(&lj)) == NO_ERROR) {
                        /*
                         * If the sem is owned by the DosDebug set
                         * the sem as unOwned
                         */
                        if (((pGRESEMT->gs_ulProcessThread & 0xFFFF) == CurrPid)
          && (pGRESEMT->gs_ulCallerAddr == DBG_GRESEM_OWNED)) {
                           pGRESEMT->gs_ulProcessThread = 0;
                           pGRESEMT->gs_ulCallerAddr = 0;

                           /*
                            * We must wake up all threads that requested this
                            * semaphore while DosDebug owned it. This is
                            * normally done when we return to RamSemWakeSub,
                            * but we may block before returning and cause
                            * a deadlock.
                            * We check to see if the current sem address is
                            * equal to parm1 below, because RamSemWake in
                            * sem.asm clears the waiting flag for the sem it
                            * is currently waking.
                            *
                            * NOTE: We don't wakeup mux waiters.
                            */

                           if ((parm1 == (ulong_t)&((GRESEMT *)
            (pSemListCurr->sem[i].addr))) ||
                                (pGRESEMT->gs_ulWaitingCount)){

                             TKWakeup((void *)(pGRESEMT->gs_ulEventHandle),
                                  0,NULL);
                             pGRESEMT->gs_ulWaitingCount--;
                             Wakes++;
                           }
                        }
                        TKClrFault();    /* Clear fault handler */
                     }
                     else {
                        pSemListCurr->sem[i].flags |= DBSEM_INVALID;
                        continue;
                     }
                  }   // A010 ***** END HERE 2 ***** D80689
               }
               pSemListCurr = pSemListCurr->next;
            }

            /*
             * This is the last thread in the debuggee which was a
             * sem owmner. If there were no errors, run the thread which
             * owns the CritSec, and  put this thread to sleep.
             *
             * NOTE: If the DBGDCBS_ASYNC flag is on, the ActiveTid
             *       is the thread sending the notification (this condition
             *       is only allowed for an async stop event, if it occurs
             *       under other conditions the debugger/debuggee connection
             *       would have been severed) turn off the flag and allow
             *       the async stop notification to be issued.
             */
            if ( ((rt == 0) &&
                 (pPTDACur->ptda_pdcb->flSever == DBGS_SEVER)) ||   /*  A009 */
                 ((rt == 0) && (pdcb->flState & DBGDCBS_ASYNC)) ) { /*  A009 */

               TKRun(DBGID_SPLOCK(pdcb), NULL);

               if (pdcb->flState & DBGDCBS_ASYNC) {
                  pdcb->flState &= ~DBGDCBS_ASYNC;
                  rt = 2;
                  goto dpslx1;
               }

               DbgBlock(DBGID_FZLOCK(pdcb));
            }
            else                                                   /*  A009 */
               goto dpslx1;                                        /*  A009 */
         }
         else {

            TKRun((void *)DBGID_SELOCK(pdcb),NULL);
            TKRun((void *)DBGID_FZLOCK(pdcb),NULL);

            /*
             * If there ARE more sems owned by this tid in the semList
             * let it continue running, otherwise block it.
             */
            if (semsOwnedByTid == 0) {

               if ((pdcb->flState & DBGDCBS_ASYNC) &&
                   (pTCBCur->TCBOrdinal == ActiveTid))
                  goto dpslx2;

               DbgBlock(DBGID_FZLOCK(pdcb));
            }
         }
      }
   }
   else {  /* If DbgProcessSemList ExitCrit */

      /*
       * Turn off DBGT_SEMOWNER for all threads in this process.
       * Turn off TK_FF_TRC for threads without indirect
       * notifications (terminates per thread serialization).
       */
      for (pTCB = pPTDACur->ptda_pTCBHead; pTCB != NULL;
          pTCB = pTCB->TCBpTCBNext) {
         pTCB->TCB_flDbg &= ~DBGT_SEMOWNER;
         if (!(pTCB->TCB_flDbg & DBGT_INDIRECT))
            pTCB->TCBForcedActions &= ~TK_FF_TRC;
      }

      if (pdcb->flState & DBGDCBS_ASYNC) {
          pdcb->flState &= ~DBGDCBS_ASYNC;
          rt = 1;    /* Set so we don't do additional processing */
          goto dpslx;
      }
   }

   /*
    * If the ActiveTid is sending the async stop notification
    * ExitCritSec and set the force flag so when we attempt to exit
    * the kernel the notification will be sent.
    */
   if (rt == 2) {
      DbgExitCrit();
      pPTDACur->ptda_ForcedActions |= TK_FF_TRC;
   }

   /*
    * If we have already done the TKWakeup, rt is set to 1 so
    * RamSemWakeSub does not do a ProcRun
    */
   if (Wakes)
      rt = 1;

   return(rt);
}

/***LP  DbgSeverConnection - Sever the debugger/debuggee connection.
 *
 *      ENTRY
 *              ulong_t tid = tid of the thread causing the sever
 *
 *      EXIT
 *              NONE
 */

#pragma code_seg("DEBUGLOCK_TEXT", "CODE")      /* lock DEBUG code */

void DbgSeverConnection (void)
{
   DCB_t *pdcb = pPTDACur->ptda_pdcb;
   int    iapwpcb;                      /* watchpoint array index */

   if (!(pPTDACur->ptda_flDbg & DBGP_ALLDONE)) {

   if ((pdcb->flSever == DBGS_SEVER) ||         /* If sever wanted     A009 */
       (pdcb->flState & DBGDCBS_ASYNC) ) {                         /*  A009 */

      if (pdcb->flState & DBGDCBS_ASYNC)
         DbgExitCrit();

      /* Set flags */
      pdcb->flState |= DBGDCBS_DBGEEDEAD;
      pPTDACur->ptda_flDbg &= ~(DBGP_LINKED | DBGP_SSTEP);
      pPTDACur->ptda_flDbg |= DBGP_ALLDONE;
      pPTDACur->ptda_ForcedActions &= ~TK_FF_TRC;

      /* Read the regs of the thread which is causing the sever */
      dbgAssert(pdcb->pkDB != NULL);
      pdcb->pkDB->uDBTemp.Tid = pTCBCur->TCBOrdinal;
      DbgReadReg(pdcb->pkDB, pdcb);
      dbgAssert(pdcb->pkDB->uDBTemp.Cmd != DBG_N_Error);

      /*
       * Finish initing the uDBTemp buffer, it will be written
       * out by DbgkDBWrite to the users debug buffer when the
       * debugger wakes up
       */
      pdcb->pkDB->uDBTemp.Cmd = DBG_N_Error;
      pdcb->pkDB->uDBTemp.Value = 0;

      /* Free this process's global and local watchpoints */
      for (iapwpcb = 0; iapwpcb < DR_COUNT; iapwpcb++) {
          if (DBGapgwpcb[iapwpcb] != NULL &&
              DBGapgwpcb[iapwpcb]->wpcb_pptda == pPTDACur)
              DbgWPFree(pPTDACur, (ulong_t)DBGapgwpcb[iapwpcb]->wpcb_bWid);
          if (pdcb->dcb_aplwpcb[iapwpcb] != NULL)
              DbgWPFree(pPTDACur, (ulong_t)pdcb->dcb_aplwpcb[iapwpcb]->wpcb_bWid);
      }

      DbgProcessSemList(f_EXITCRIT,0);

      /* Kill debuggee */
      TKDieSoonPTDA(pPTDACur, 0, TC_HARDERR, 0);
      TKRun(DBGID_FZLOCK(pdcb), NULL);
      TKRun(DBGID_SELOCK(pdcb), NULL);
      TKRun(DBGID_SPLOCK(pdcb), NULL);
      TKRun(DBGID_EELOCK(pdcb), NULL);
      TKRun(DBGID_SRLOCK(pdcb), NULL);
      TKRun(DBGID_ERLOCK(pdcb), NULL);

   }                                            /* END If sever wanted A009 */
   else  /* pdcb->flSever == DBGS_NO_SEVER Do not sever the connection A009 */
   {                                                            /*     A009 */
      pdcb->pkDB->uDBTemp.Tid = pTCBCur->TCBOrdinal;            /*     A009 */
      pdcb->pkDB->uDBTemp.Cmd = DBG_N_Error;                    /*     A009 */
      pdcb->pkDB->uDBTemp.Value = ERROR_EXCL_SEM_ALREADY_OWNED; /*     A009 */
      pdcb->flNoSever_Rpt = DBGS_NOSEVER_MSG_ON;    /* notify debugger A009 */
   }                          /* END Else do not sever the connection  A009 */

   }
}

/***EP  DbgDLLEvent - Mark thread to pick up DLL notification
 *
 *      DbgDLLEvent is entered from LibiStartDispatch if the thread is
 *      from a debugged process.
 *
 *      The threads flags are set to pick up the DLL notification
 *      before it leaves the kernel.
 *
 *      This function is swappable.
 *
 *      Pseudocode:
 *      if (!ALLDONE)
 *        set TK_FF_TRC on this thread
 *        if (module loaded)
 *          set DBGT_LIBLOAD
 *        else
 *          set DBGT_LIBFREE
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void DbgDLLEvent (flEvent, ptcb)
ulong_t flEvent;
tcb_t *ptcb;
{

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgDLLEvent      ");   }
#endif

    if (!(pPTDACur->ptda_flDbg & DBGP_ALLDONE)) {
        ptcb->TCBForcedActions |= TK_FF_TRC;
        if (flEvent == DBG_LOADDLL)
            ptcb->TCB_flDbg |= DBGT_LIBLOAD;
        else
            ptcb->TCB_flDbg |= DBGT_LIBFREE;
    }
}

/***EP  DbgValidateTF - Validate this thread's user TF bit
 *
 *      Each debuggee thread calls this routine to validate it's TF bit
 *      before it exits the kernel.
 *
 *      This function is swappable.
 *
 *      ENTRY   None
 *
 *      EXIT    TF bit validated
 *
 *      Pseudocode:
 *      if (single stepping)
 *        if (this thread is not already single stepping)
 *          set F_TRACE in eflags
 *          mark thread as single stepping
 *        if (thread will exit with IRETD)
 *          clear DBGT_NOTIFIED to prevent skipping SingleStep
 *      else // going
 *        clear DBGT_NOTIFIED to prevent skipping SingleStep
 *        if (this thread is single stepping)
 *          clear F_TRACE in eflags
 *          mark thread as not single stepping
 */

#pragma code_seg("DEBUGSWAP_TEXT", "CODE")      /* swap DEBUG code */

void DbgValidateTF()
{
    ulong_t eflag;                              // Eflags register image

#ifdef DBGSTRICT
    if (DBGBugBug & DBGEEc)
    {  printf("\nDbgee.c *** DdgValidateTF    ");   }
#endif

    if (pPTDACur->ptda_flDbg & DBGP_SSTEP) {
                                        /* If supposed to single step */
        if (!(pTCBCur->TCB_flDbg & DBGT_SSTEP)) {
            if (KMQueryClientRegister(pTCBCur, KM_REG_EFLAG, SSToDS(&eflag)) !=
                NO_ERROR)
                dbgAssert(FALSE);

            eflag |= F_TRACE;

            if (KMSetClientRegister(pTCBCur, KM_REG_EFLAG, eflag) != NO_ERROR)
                dbgAssert(FALSE);

            pTCBCur->TCB_flDbg |= DBGT_SSTEP;
        }
        if (pTCBCur->TCB_pcriFrameType == &criISF ||
          pTCBCur->TCB_pcriFrameType == &criTSF)
            pTCBCur->TCB_flDbg &= ~DBGT_NOTIFIED;
                                        /* User will IRETD, so clear flag */
    } else {                            /* Else supposed to "go" */
        pTCBCur->TCB_flDbg &= ~DBGT_NOTIFIED;
                                        /* No SingleStep to ignore */
        if (pTCBCur->TCB_flDbg & DBGT_SSTEP) {
            if (KMQueryClientRegister(pTCBCur, KM_REG_EFLAG, SSToDS(&eflag)) !=
                NO_ERROR)
                dbgAssert(FALSE);

            eflag &= ~F_TRACE;

            if (KMSetClientRegister(pTCBCur, KM_REG_EFLAG, eflag) != NO_ERROR)
                dbgAssert(FALSE);

            pTCBCur->TCB_flDbg &= ~DBGT_SSTEP;
        }
    }
}

#endif /* DEBUG */
