#define SCCSID "@(#)profile.c	6.3 91/10/17"
#define FNO_NUM FNO_PROFILE_C


/*  Kernal Entry for Profiling
 *
 *  Author:
 *  Wayne Johnson
 *
 *
 *  IBM/Microsoft Confidential
 *
 *  Copyright (c) IBM Corporation 1987, 1989
 *  Copyright (c) Microsoft Corporation 1987, 1989
 *
 *  All Rights Reserved
 *
 *
 *  Exported Routines:
 *      VR32Profile() [external API]
 *      PROAllocThrdPCB
 *      PROSaveThrdPCB
 *      PRODestroyPCB
 *      PROSysArena
 *      PROShrArena
 *
 *  Imported Routines:
 *      TKPidToPTDA
 *      proError
 *      proSetTimer
 *      allocKRHB
 *      freeKRHB
 *      VMLockMem
 *      VMUnlock
 *
 *  History:
 *      01-Nov-1989     created by Wayne Johnson
 */


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



#define INCL_TYPES              /* for basedef types            */
#define INCL_DOSERRORS          /* for bseerr errors            */

#include <fnoperf.h>
#include <basedef.h>            /* standard type definitions    */
#include <osmac.h>
#include <basemac.h>
#include <ostypes.h>            /* more type defs               */
#include <bseerr.h>             /* error messages               */
#include <pgexport.h>           /* page exports                 */
#include <tkexport.h>           /* task exports                 */
#include <kmode.h>              /* thunksef structure defined   */
#include <bsedosp.h>            /* needed for PAGESHIFT defn.   */
        #include <vmexport.h>   /* vm exports                   */
        #include <infoseg.h>
        #include <dossymc.h>
        #include <curdir.h>
        #include <dirent.h>
        #include <find.h>
        #include <sigdef.h>
        #include <sem.h>
        #include <screen.h>
        #include <except.h>
#include <tkptda.h>             /* for ptda types               */
#include <vmowner.h>
        #include <profuser.h>   /* profiling user header                */
#include <proxport.h>           /* profiling kernal header              */


#ifdef PROF_TEST
#include <stdio.h>
#endif


/* function prototypes:  non-exported functions */

INT proCreate ( PID, PIR * );
INT proStart ( PPCB );
INT proStop ( PPCB );
INT proClear ( PPCB );
INT proDump ( PPCB, PDR * );
INT proGetPPCB ( PID, PPCB ** );
VOID proDestroyDeadThrdPCBs( PPCB_TMB );
VOID proSetTimer ( USHORT );
VOID proError ( VOID );
VOID proCollectOn( VOID );
VOID proCollectOff( VOID );


/* Externals */

extern  StartPROFLOCK_TEXT;
extern  EndPROFLOCK_TEXT;

extern  USHORT  usTmrSem;       /* Timer0 access flag.  This is used to know
                                 * the current status of Timer0 used by the
                                 * TMR routines.  Values are:
                                 *      0       clean
                                 *      else not clean and don't use.
                                 */

typedef (PASCAL FAR32 *F32PFNT0)(ULONG);

/* GLOBAL VARIABLES */
PPMB    ppmb = 0;
F32PFNT0 f32pfnPTTimer0 = 0;


/***EP  VR32Profile    -         Dos32Profile
 *
 *  This is the kernal entry point for all profiling sevices.
 *
 *  ENTRY
 *      sef         - system entry frame
 *      ulFunc      - Value to represent what action to carry out.
 *                      One of the following values:
 *                              PROF_CREATE
 *                              PROF_START
 *                              PROF_STOP
 *                              PROF_CLEAR
 *                              PROF_DUMP
 *                              PROF_DESTROY
 *      pid         - Process ID of task being profiled.  Zero for
 *                    system profiling.
 *      ppir        - Pointer to Profile Initialization Record.
 *      ppdr        - Pointer to Profile Dump Record.
 *
 *   EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_INVALID_PARAMETER
 *          errors returned by sub functions
 *
 *   CONTEXT
 *      Called by Profiling APIs
 *
 *   PSEUDO-CODE
 *      call appropriate sub function
 */

INT cdecl VR32Profile( THUNKSEF sef, ULONG ulFunc, PID pid, PIR *ppir, PDR *ppdr){

    PPCB        *ppPCB;
    INT         rc;

    if ( ulFunc == PROF_CREATE ){
        return proCreate ( pid, ppir );
        }

    if (rc = proGetPPCB ( pid, SSToDS( &ppPCB )))
        return rc;

    switch ( ulFunc ){

        case PROF_START:
            return proStart ( *ppPCB );

        case PROF_STOP:
            return proStop ( *ppPCB );

        case PROF_CLEAR:
            return proClear ( *ppPCB );

        case PROF_DUMP:
            return proDump ( *ppPCB, ppdr );

        case PROF_DESTROY:
            return PRODestroyPCB ( ppPCB );

        default:
            return ERROR_INVALID_PARAMETER;
        }/* end of swtich */

    }/* end of VR32Profile */





/***LP   proCreate   - create profiling buffers
 *
 *  This routine creates the profiling buffers and does other initializing
 *  to prepare for profiling.
 *
 *  ENTRY
 *      pid         - Process ID of task being profiled. Ignored
 *                    during system profiling.
 *      ppir        - Pointer to Profile Initialization Record.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_NOT_ENOUGH_MEMORY
 *          ERROR_INVALID_PARAMETER
 *          ERROR_INVALID_PROCID
 *          ERROR_PRF_TIMER_OUT_OF_RANGE
 *          ERROR_PRF_NOT_INITIALIZED
 *          ERROR_PROTECTION_VIOLATION
 *
 *  CONTEXT
 *      Called at initialization time.
 *
 *  PSEUDO-CODE
 *      check parameters
 *      if existing PMB
 *          check for confilcting collection modes
 *          not different, not both system.
 *          check to make sure not enabled for this process
 *      else
 *          build PMB if not existing
 *      build PCB
 *      fill out module list and collection buffers
 *      make multiple copies if in thread mode and multiple threads
 */

// BUGBUG 04-Apr-1990 waynej.  Simplify and clean up error recovery
// Resources to free:   if new PMB (and random buffer if used)
//                             usTmrSem
//                      always PCB(s) (and any asscoiated buffers)

INT proCreate ( PID pid, PIR *ppirUser ) {

    PCB    *ppcb;
    SHORT   i;
    PPTDA   pPTDA;
    USHORT  fsOptions;  /* User profile options */
    USHORT  fsMode;     /* user profile Mode */
    USHORT  fsCollect;  /* user profile collection method */
    USHORT  cRange;     /* number of user ranges */
    MPR    *ampr;       /* array of mpr that get allocated at once */
    PPMB    ppmbNew = 0;/* pointer to PBM that gets created */
    ULONG   cbSampleSpace;/* count of bytes covered by profiler in IP_count */
    PTCB    pTCB;
    INT     rc;
    PIR     pir;        /* Profile Init Record that is copied in through
                         * TKFuBuff */
    PIR    *ppir;
    PROF_RANGE *aRange; /* array of ranges past in from user */

    /* check for valid parameters */
    if ( ppirUser == NULL )
        return ERROR_INVALID_PARAMETER;

    /* BUGBUG 16-Mar-1990 waynej remove this level of indirection.  use only
     * pir, not ppir.
     */

    ppir = SSToDS(&pir);
    if ( rc = TKFuBuff( (PBYTE)ppir, (PBYTE)ppirUser, sizeof(PIR), 0 ))
        return rc;

    if (( ppir->sSamplePeriod < PRO_PERIOD_MIN && ppir->sSamplePeriod )
          || ( ppir->sSamplePeriod > PRO_PERIOD_MAX ))
        return ERROR_PRF_TIMER_OUT_OF_RANGE;

    if (0==(cRange = ppir->cRange))
        return ERROR_INVALID_PARAMETER;

    fsOptions = ppir->fsOptions;
    fsMode    = PROM_FLAGS & fsOptions;
    fsCollect = PROC_FLAGS & fsOptions;

    if ( PROM_SYSTEM != fsMode )
        /* check PID if not system */
        if ( TKPidToPTDA( pid, SSToDS( &pPTDA )))
            return ERROR_INVALID_PROCID;


    /* check for conflicts if already profiling */
    if ( ppmb ){

        if ( ppir->sSamplePeriod &&
             (ppmb->pmb_sSamplePeriod != ppir->sSamplePeriod) )
            return ERROR_PRF_TIMER_RESET;

        /* BUGBUG 13-Mar-1990 waynej make sure both are random, or nonrandom
         * sampled
         */

        if ( fsMode == PROM_SYSTEM ){
            if ( ppmb->pmb_ppcbSystem != NULL )
                return ERROR_PRF_ALREADY_INITIALIZED;
            }
        else
            if ( pPTDA->ptda_pPCB != NULL )
                return ERROR_PRF_ALREADY_INITIALIZED;
        }
    else {
        /* check for timer Availability */

        if ( usTmrSem )
            return ERROR_PRF_TIMER_RESET;


        /* build PMB */

        if ( allocKRHB( sizeof(PMB), PROFOWNER, SSToDS( &ppmbNew )))
            return ERROR_NOT_ENOUGH_MEMORY;

        if ( !ppir->sSamplePeriod )
            ppir->sSamplePeriod = PRO_PERIOD_DEFAULT;

        proSetTimer( ppir->sSamplePeriod );

        ppmbNew->pmb_sSamplePeriod    = ppir->sSamplePeriod;
        ppmbNew->pmb_cProfErrors      = 0;
        ppmbNew->pmb_cSessionsCreated = 0;
        ppmbNew->pmb_cSessionsStarted = 0;
        ppmbNew->pmb_ppcbSystem       = NULL;

        /* take care of random sampling */
        if (fsOptions & PROO_IRREGULAR_SAMPLING ){

            ppmbNew->pmb_cRandomCount = ppir->cRandomCount;

            if ( allocKRHB( ppmbNew->pmb_cRandomCount, PROFOWNER,
                            &ppmbNew->pmb_aRandomCount ))
                return ERROR_NOT_ENOUGH_MEMORY;

            if ( rc = TKFuBuff( (PBYTE)ppmbNew->pmb_aRandomCount,
                                (PBYTE)ppir->aRandomCount,
                                ppmbNew->pmb_cRandomCount *
                                sizeof(ppmbNew->pmb_aRandomCount[0]), 0 ))
                return rc;

            /* set index to next counter */
            ppmbNew->pmb_iRandomCount  = ppmbNew->pmb_cRandomCount - 2;
            ppmbNew->pmb_cRandomWait   = ppmbNew->pmb_aRandomCount
                                         [ ppmbNew->pmb_iRandomCount ];
            ppmbNew->pmb_fRandomSample = TRUE;
            }
        else{
            ppmbNew->pmb_fRandomSample = FALSE;
            }


        /*  Lock down code used during int time */
        if ( VMLockMem( (ULONG)&StartPROFLOCK_TEXT,
                     (PBYTE)&EndPROFLOCK_TEXT - (PBYTE)&StartPROFLOCK_TEXT,
                     VML_LONG, &ppmbNew->pmb_hLock,
                     (struct pagelist_s *) VM_NO_ADDR,
                     (ulong_t *) VM_NO_ADDR))
            return ERROR_NOT_ENOUGH_MEMORY;

        ppmb = ppmbNew;
        }

    /* Build PCB */

    /* Alloc memory for both PCB and MPR list */
    if ( allocKRHB( sizeof(PCB) + sizeof(MPR) * cRange, PROFOWNER,
                    SSToDS( &ppcb ))){
        /* Free previously alllocated memory */
        if ( ppmbNew )
            freeKRHB( ppmbNew );
        return ERROR_NOT_ENOUGH_MEMORY;
        }

    ppcb->pcb_fsOptions     = fsOptions;
    ppcb->pcb_bShiftValue   = 0;
    ppcb->pcb_claddrSampled = 0;
    ppcb->pcb_aladdrSampled = 0;
    ppcb->pcb_pmprHead      = NULL;

    if ( fsCollect == PROC_IP_RECORD ){
        /* Initialize for IP_RECORD collection */
        ppcb->pcb_claddrSampled = ppir->cbRecordBuffer / sizeof(LADDR);
        if ( allocKRHB( ppir->cbRecordBuffer, PROFOWNER,
                        &ppcb->pcb_aladdrSampled )){
            ppcb->pcb_aladdrSampled = 0;
            PRODestroyPCB( SSToDS( &ppcb ));
            return ERROR_NOT_ENOUGH_MEMORY;
            }
        }
    else {
        /* Initialize for IP_COUNT Colection
         *
         * scan through module records and determine how much space we
         * are sampling.  Determine how much we will have to shift the EIP
         * to fit in alloted bufer space.  Return bufsize actually used in
         * the buffer.  Set bShiftValue.
         */

        /* BUGBUG 19-Mar-1990 waynej add single error handler at end of
         * routine.  Goto there in case of early return.  there use smart
         * clean up to chech for each item's existance, and then clean or
         * free as necessary
         */

        if ( allocKRHB( sizeof(PROF_RANGE) * cRange, PROFOWNER,
                        SSToDS(&aRange))){
            PRODestroyPCB( SSToDS( &ppcb ));
            return ERROR_NOT_ENOUGH_MEMORY;
            }
        if ( rc = TKFuBuff( (PBYTE)aRange, (PBYTE)ppir->aRange,
                            sizeof(PROF_RANGE) * cRange, 0 )){

            freeKRHB( aRange );
            PRODestroyPCB( SSToDS( &ppcb ));
            return rc;
            }


        cbSampleSpace = 0;
        for ( i = 0; i < cRange; i++){
            if ( aRange[i].fFullCollection )
                cbSampleSpace += aRange[i].cbRange;
            }

        /* multiply # of bytes by counter size */
        cbSampleSpace *= PRO_IP_COUNTER_SIZE;

        /* commute shift count */
// from lees:  really mean to start i == 0 ???
//  also    :  maybe check cbSampleSpace == cbRecordBuffer before multiplying
//             by PRO_IP_COUNTER_SIZE, and skip shift computation (know it)
        for ( i = 0; cbSampleSpace>>i > ppir->cbRecordBuffer; i++ )
            ;
        ppcb->pcb_bShiftValue = (BYTE)i;
        ppir->cbRecordBuffer = cbSampleSpace>>i;
        }

    /* build MPR list */
    ampr = (PMPR)(ppcb + 1);

    ppcb->pcb_pmprHead = ampr;

    for ( i = 0; i < cRange; i++){
        PMPR         pmprCurrent = &ampr[i];
        PROF_RANGE  *prangeCurrent = &aRange[i];

        /* build Each MPR */
        pmprCurrent->mpr_laddrModule = prangeCurrent->laddrRange;
        pmprCurrent->mpr_cbModule = prangeCurrent->cbRange;

        if ( prangeCurrent->fFullCollection )
            pmprCurrent->mpr_fbCollectType = (BYTE)fsCollect;
        else
            pmprCurrent->mpr_fbCollectType = PROC_MODULE;

        if ( pmprCurrent->mpr_fbCollectType == PROC_IP_COUNT ){
            /* alloc memory for each buffer */
            if ( allocKRHB( ((prangeCurrent->cbRange >> ppcb->pcb_bShiftValue)
                            + 1) * PRO_IP_COUNTER_SIZE, PROFOWNER,
                            &pmprCurrent->mpr_ulProfData )){
                PRODestroyPCB( SSToDS( &ppcb ));
                return ERROR_NOT_ENOUGH_MEMORY;
                }
            proAssert( pmprCurrent->mpr_ulProfData );
            }
        else
            pmprCurrent->mpr_ulProfData = 0;
        if ( i != 0 )
            ampr[i-1].mpr_pmprNext = pmprCurrent;
        }
    ampr[i-1].mpr_pmprNext = NULL;

    /* clear PCB count feilds */
    rc = proClear( ppcb );
    proAssert( rc == NO_ERROR );

    if ( fsMode == PROM_THREAD ){
        /* give PCB to first thread */
        ppcb->pcb_fsOptions |= PROI_EXISTING_THREAD;
        pPTDA->ptda_pTCBHead->TCBpTSD->TSDpPCB = (ULONG)ppcb;

        /* Continue for as many threads as exist */
        for ( pTCB = pPTDA->ptda_pTCBHead->TCBpTCBNext; pTCB != NULL;
              pTCB = pTCB->TCBpTCBNext ){
            if ( rc = PROAllocThrdPCB ( pTCB->TCBpTSD )){
                /* free thread PCBs if unsuccessfull */
                for ( pTCB = pPTDA->ptda_pTCBHead; pTCB != NULL;
                      pTCB = pTCB->TCBpTCBNext )
                    if ( (PPCB *)&pTCB->TCBpTSD->TSDpPCB )
                        PRODestroyPCB( (PPCB *)&pTCB->TCBpTSD->TSDpPCB );
                return rc;
                }
            ((PPCB)pTCB->TCBpTSD->TSDpPCB)->pcb_fsOptions |= PROI_EXISTING_THREAD;
            }

        /* Build PCB_TMB */
        if ( allocKRHB( sizeof(PCB_TMB), PROFOWNER, SSToDS( &ppcb ))){

            /* free thread PCBs if unsuccessfull */
            for ( pTCB = pPTDA->ptda_pTCBHead; pTCB != NULL;
                  pTCB = pTCB->TCBpTCBNext )
                if ( (PPCB *)&pTCB->TCBpTSD->TSDpPCB )
                    PRODestroyPCB( (PPCB *)&pTCB->TCBpTSD->TSDpPCB );
            return ERROR_NOT_ENOUGH_MEMORY;
            }
        ((PPCB_TMB)ppcb)->fsOptions = fsOptions | PROI_PCB_TMB;
        ((PPCB_TMB)ppcb)->pPTDA     = pPTDA;
        ((PPCB_TMB)ppcb)->cPDTR     = 0;
        ((PPCB_TMB)ppcb)->ppdtrHead = NULL;
        }

    if ( PROM_SYSTEM == fsMode )
        ppmb->pmb_ppcbSystem = ppcb;
    else
        pPTDA->ptda_pPCB = (ULONG)ppcb;

    ppmb->pmb_cSessionsCreated++;
    freeKRHB( aRange );

    return NO_ERROR;
    }







/***LP   proStart   - Begin profiling
 *
 *  This routine enables profiling data collection.  It also starts the
 *  timer if that has not already been done.
 *
 *  ENTRY
 *      pPCB        - Profile Control block of current session.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_PRF_ALREADY_STARTED
 *
 *  CONTEXT
 *      Called after initialization To begin Profiling
 *
 *  PSEUDO-CODE
 *      check for not already started
 *      set enabled bit
 *      inc counter
 *      start timer if not already done
 */

INT proStart ( PPCB pPCB ){

    if ( pPCB->pcb_fsOptions & PROI_ENABLED )
        return ERROR_PRF_ALREADY_STARTED;
    pPCB->pcb_fsOptions |= PROI_ENABLED;

    if (!(ppmb->pmb_cSessionsStarted++)){
        /* enable interupt */
        proCollectOn();
        }
    return NO_ERROR;
    }




/***LP   proStop   - Stop profiling
 *
 *  This routine disables profiling data collection.
 *
 *  ENTRY
 *      pPCB        - Profile Control block of current session.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_PRF_NOT_STARTED
 *
 *  CONTEXT
 *      Called after initialization
 *
 *  PSEUDO-CODE
 *      check profiling running
 *      clear enabled bit
 *      dec prof_users counter
 *      if no one else is taking data, turn off timer
 */

INT proStop( PPCB pPCB){
    if ( !( pPCB->pcb_fsOptions & PROI_ENABLED ))
        return ERROR_PRF_NOT_STARTED;
    pPCB->pcb_fsOptions &= ~PROI_ENABLED;

    if (!(--ppmb->pmb_cSessionsStarted))
        /* clear interupt */
        proCollectOff();
return NO_ERROR;
}




/***LP  proClear - Clear all sampled data
 *
 *  This routine discards all collected data and gives clean buffers to
 *  continue to collect with.
 *
 *  ENTRY
 *      pPCB        - Profile Control block of current session.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_PRF_NOT_INITIALIZED
 *
 *  CONTEXT
 *      Called after initialization.  pPCBs have already be validated as
 *      non-zero.
 *
 *  PSEUDO-CODE
 *      stop sampling
 *      if PCB_TMB
 *          free dead thread PCBs
 *          call proClear for each living thread
 *      else
 *          clear IP_Record buffer if it exists
 *          walk MPR chain
 *              if IP_count
 *                  clear buffer
 *              if module_count
 *                  clear counter
 *      restart sampling if was going before
 */

INT proClear ( PPCB pPCB ){

    BOOL        fRestart = FALSE;       /* TRUE = restart after clearing */
    INT         iError = NO_ERROR;      /* Error returned by function */
    PMPR        pmprCurrent;
    INT         i;
    PTCB        pTCB;
    PULONG      aIPCount;

    /* stop profiling for all but thread pcbs (for thread pcbs, it has
     * already been stopped on the process level.
     */

    if ((pPCB->pcb_fsOptions & (PROI_PCB_TMB|PROM_FLAGS)) != PROM_THREAD)
        if ( !proStop( pPCB ) == ERROR_PRF_NOT_STARTED )
            fRestart = TRUE;

    if ( pPCB->pcb_fsOptions & PROI_PCB_TMB ){

        /* Take care of individual thread PCBs */

        proDestroyDeadThrdPCBs( (PPCB_TMB)pPCB );
        ((PPCB_TMB)pPCB)->ppdtrHead = NULL;

        /* clear all living thread PCBs */
        for ( pTCB = ((PPCB_TMB)pPCB)->pPTDA->ptda_pTCBHead; pTCB != NULL;
              pTCB = pTCB->TCBpTCBNext )
            proClear( (PPCB)pTCB->TCBpTSD->TSDpPCB );
        }
    else{

        /* clear this PCB */

        pPCB->pcb_cticIdle = 0;
        pPCB->pcb_cticV86  = 0;
        pPCB->pcb_cticSYS  = 0;
        pPCB->pcb_cticEXE  = 0;
        pPCB->pcb_cticSHR  = 0;

        /* reset the index to the IP_RECORD buffer.  If we are not in that
         * mode 0 is what this feild defaults to anyway */

        pPCB->pcb_iladdrSampled = 0;

        /* reset IP and MODULE counts */

        for ( pmprCurrent = pPCB->pcb_pmprHead; pmprCurrent != NULL;
              pmprCurrent = pmprCurrent->mpr_pmprNext) {
            if ( pmprCurrent->mpr_fbCollectType == PROC_IP_COUNT) {
                if ( pmprCurrent->mpr_ulProfData ){
                    aIPCount = (ULONG *)pmprCurrent->mpr_ulProfData;
                    for (i= pmprCurrent->mpr_cbModule>>pPCB->pcb_bShiftValue;
                         i >= 0; i--)
                        aIPCount[i] = 0;
                    }
                else
                    iError = ERROR_PRF_NOT_INITIALIZED;
                }
            else
                pmprCurrent->mpr_ulProfData = 0;

            }/* end of for loop */
        }/* end of else */

    if ( fRestart )
        proStart( pPCB );

    return iError;
    }



/***LP  proDump - Dump sampled data
 *
 *  This routine dumps collected data.
 *
 *  ENTRY
 *      pPCB        - Profile Control block of current session.
 *      pPDR        - Profile Dump Record
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_INVALID_PARAMETER
 *          ERROR_BUFFER_OVERFLOW
 *          ERROR_MOD_NOT_FOUND
 *          ERROR_SYS_INTERNAL
 *          ERROR_PRF_NOT_INITIALIZED
 *          ERROR_PROTECTION_VIOLATION
 *
 *  CONTEXT
 *      Called after initialization.  pPCBs have already be validated as
 *      non-zero.
 *
 *  PSEUDO-CODE
 *      check for valid parameters
 *      stop sampling
 *      if thread mode
 *          get thread PCB
 *      switch on type of info requested.
 *          PCB Info
 *              check buff size
 *              fill buffer
 *          MPR LIST
 *              count MPRs
 *              check buff size
 *              fill array with MPRs
 *          IP_Record Buffer
 *              check buff size
 *              fill array with LADDRs
 *          IP_Count Buffer
 *              find MPR with specified LADDR
 *              check buff size
 *              fill buffer with EIP COUNTS
 *      restart sampling if was going before
 */


INT proDump ( PPCB pPCB, PDR *ppdrUser ){

    BOOL        fRestart = TRUE;        /* restart profiling after clearing */
    INT         iError = NO_ERROR;      /* return code */
    USHORT      cLiveThreads = 0;       /* only valid if in thread mode */
    USHORT      cDeadThreads = 0;       /* only valid if in thread mode */
    TID         TID = 0;                /* Thread ID if in thread mode */
    PPCB        ppcbSession = pPCB;     /* used for starting and stoping the
                                         * profiling session.  will always be
                                         * the same as pPCB, except in thread
                                         * mode, when it will need to be the
                                         * PCB_TMB.
                                         */
    PDR         pdr;                    /* profile dump record which user
                                         * data is copied to */
    PDR        *pPDR;                   /* pointer to pdr.  BUGBUG, this
                                         * included to make an easy transfer
                                         * from the old code.  it will go
                                         * away when properly fixed
                                         */


    if ( ppdrUser == NULL)
        return ERROR_INVALID_PARAMETER;
    pPDR = SSToDS( &pdr );
    if ( TKFuBuff( (PBYTE)pPDR, (PBYTE)ppdrUser, sizeof(PDR), 0 ))
        return ERROR_PROTECTION_VIOLATION;
    if ( pPDR->abDumpData == NULL )
        return ERROR_INVALID_PARAMETER;

    if ( proStop( ppcbSession ) == ERROR_PRF_NOT_STARTED )
        fRestart = FALSE;

    /* get appropriate PCB and TID if in thread mode */
    if (( pPCB->pcb_fsOptions & PROM_FLAGS ) == PROM_THREAD ){

        proAssert( pPCB->pcb_fsOptions & PROI_PCB_TMB );
        cLiveThreads = ((PPCB_TMB)pPCB)->pPTDA->ptda_cTCB;
        cDeadThreads = ((PPCB_TMB)pPCB)->cPDTR;

        if ( pPDR->iThread < cLiveThreads ){

            /* traverse live thread list */

            PTCB        pTCB;
            USHORT      iThread;

            iThread = pPDR->iThread;
            pTCB = ((PPCB_TMB)pPCB)->pPTDA->ptda_pTCBHead;
            while ( pTCB && iThread ){
                pTCB = pTCB->TCBpTCBNext;
                iThread--;
                }
            if ( pTCB ){
                pPCB = (PPCB)pTCB->TCBpTSD->TSDpPCB;
                TID = pTCB->TCBOrdinal;
                }
            else
                iError = ERROR_SYS_INTERNAL;
            }
        else {
            /* traverse dead thread list */

            PPDTR       pPDTR;
            USHORT      iThread;

            pPDTR = ((PPCB_TMB)pPCB)->ppdtrHead;
            iThread = pPDR->iThread - cLiveThreads;
            while ( pPDTR && iThread ){
                pPDTR = pPDTR->ppdtrNext;
                iThread--;
                }
            if ( pPDTR ){
                pPCB = pPDTR->pPCB;
                TID = pPDTR->tid;
                }
            else
                /* less threads than number requested */
                iError = ERROR_INVALID_PARAMETER;
            }
        proAssert(( pPCB->pcb_fsOptions & PROM_FLAGS ) == PROM_THREAD );
        }

    if ( iError ){
        if ( fRestart )
            proStart( ppcbSession );
        return iError;
        }

    switch ( pPDR->bFunc){

        case PROD_PCB:{

            PCBD        pcbd;   /* pcb dump buffer */
            PPCBD       pPCBD;  /* a flat-relative pointer to the above */

            /* check for sufficient buffer size */
            if ( pPDR->cbDumpData < sizeof(PCBD))
                iError = ERROR_BUFFER_OVERFLOW;
            else{
                proAssert( pPCB->pcb_pmprHead );
                pPCBD = SSToDS( &pcbd.pcb );
                pPCBD->pcb = *pPCB;
                pcbd.pcb.pcb_aladdrSampled = 0;
                pcbd.pcb.pcb_pmprHead      = 0;
                pcbd.sSamplePeriod         = ppmb->pmb_sSamplePeriod;
                pcbd.cProfErrors           = ppmb->pmb_cProfErrors;
                pcbd.cThreads              = cLiveThreads + cDeadThreads;
                pcbd.TID                   = TID;
                iError = TKSuBuff( (PBYTE)pPDR->abDumpData, SSToDS(&pcbd),
                                   sizeof(pcbd), 0);
                }
            break;
            }

        case PROD_MPR_LIST:{

            BYTE        cMPR;
            PMPR        pmprCurrent;
            PMPR        aMPR;   /* array to load up MPRs to MPRD */
            BYTE        i;
            ULONG       cbMPRDF;/* count of bytes in the MPRDF */
            PMPRDF      pMPRDF; /* pointer to kernal MPRDF */

            /* count MPRs */
            cMPR = 0;
            pmprCurrent = pPCB->pcb_pmprHead;
            while ( pmprCurrent ){
                pmprCurrent = pmprCurrent->mpr_pmprNext;
                cMPR++;
                }

            cbMPRDF = sizeof(MPRDH) + cMPR * sizeof(MPR);

            /* check for sufficient buffer size */
            if ( pPDR->cbDumpData < cbMPRDF )
                iError = ERROR_BUFFER_OVERFLOW;
            else{

                if ( allocKRHB( cbMPRDF, PROFOWNER, SSToDS( &pMPRDF )))
                    return ERROR_NOT_ENOUGH_MEMORY;

                pMPRDF->Header.cMPR = cMPR;

                /* copy MPRs into array, 1 by 1 */

                aMPR = &pMPRDF->mprFirst;
                pmprCurrent = pPCB->pcb_pmprHead;
                for ( i = 0; i < cMPR; i++ ){
                    proAssert( pmprCurrent );
                    aMPR[i] = *pmprCurrent;
                    aMPR[i].mpr_pmprNext = 0;
                    if ( aMPR[i].mpr_fbCollectType == PROC_IP_COUNT )
                        aMPR[i].mpr_ulProfData = 0;
                    pmprCurrent = pmprCurrent->mpr_pmprNext;
                    }
                iError = TKSuBuff( pPDR->abDumpData, (PBYTE)pMPRDF,
                                   cbMPRDF, 0);
                freeKRHB( (PBYTE)pMPRDF );
                }
            break;
            }

        case PROD_IP_RECORD:{

            ULONG       cLADDR;
            PIPRDF      pIPRDF;

            if ( pPCB->pcb_fsOptions & PROI_IP_RECORD_FULL )
                cLADDR = pPCB->pcb_claddrSampled;
            else
                cLADDR = pPCB->pcb_iladdrSampled;

            /* check for sufficient buffer size */
            if ( pPDR->cbDumpData < sizeof(IPRDH) + cLADDR * sizeof(LADDR) )
                iError = ERROR_BUFFER_OVERFLOW;
            else{
                /* copy array of LADDRs down */
                pIPRDF = (PIPRDF)pPDR->abDumpData;
                if ( iError = TKSuDWordNF( cLADDR, &pIPRDF->Header.cLADDR ))
                    goto Exit_proDump;
                iError = TKSuBuff( (PBYTE)&pIPRDF->laddrFirst,
                                   (PBYTE)pPCB->pcb_aladdrSampled,
                                   cLADDR * sizeof(LADDR), 0);
                }
            break;
            }

        case PROD_IP_COUNT:{
            // BUGBUG replace with run length encoding routine

            ULONG       cCount;
            PMPR        pmprCurrent;
            PIPCDF      pIPCDF;

            /* find MPR with specified address */
            pmprCurrent = pPCB->pcb_pmprHead;
            while ( pmprCurrent &&
                pmprCurrent->mpr_laddrModule != pPDR->laddrModule){
                pmprCurrent = pmprCurrent->mpr_pmprNext;
                }

            if ( !pmprCurrent )
                iError = ERROR_MOD_NOT_FOUND;
            else{
                cCount = (pmprCurrent->mpr_cbModule >> pPCB->pcb_bShiftValue)
                         + 1;

                /* check for sufficient buffer size */
                if ( pPDR->cbDumpData < sizeof(IPCDH) + cCount
                     * PRO_IP_COUNTER_SIZE )
                    iError = ERROR_BUFFER_OVERFLOW;
                else{
                    pIPCDF = (PIPCDF)pPDR->abDumpData;

                    if ( iError = TKSuDWordNF( cCount, &pIPCDF->Header.cCount ))
                        goto Exit_proDump;

                    if ( iError = TKSuDWordNF( pmprCurrent->mpr_cbModule,
                                      &pIPCDF->Header.cbModule ))
                        goto Exit_proDump;

                    /* copy array of IP Counts down */

                    iError = TKSuBuff( (PBYTE)&pIPCDF->ulCountFirst,
                                       (PBYTE)pmprCurrent->mpr_ulProfData,
                                       cCount * PRO_IP_COUNTER_SIZE, 0);
                    }
                }
            break;
            }

        default:
            iError = ERROR_INVALID_PARAMETER;
            break;
        }

    Exit_proDump:

    if ( fRestart )
        proStart( ppcbSession );

    return iError;
    }




/***EP  PRODestroyPCB   - Frees PCB Memory and zeros pointer to it
 *
 *  This routine destroys the PCB, all sub structures, and free allocated
 *  memory.
 *
 *  ENTRY
 *          ppPCB           - Pointer to Pointer to PCB.  This allows us to
 *                            clear pointer to the PCB.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_PRF_NOT_INITIALIZED
 *
 *  CONTEXT
 *      Called when we no longer need a PCB.  During a proDestroy or process
 *      termination.
 *
 *  PSEUDO-CODE
 *      check for any pcb
 *      stop profiling if running
 *      if PCB_TMB
 *          delete PCBs for dead threads
 *          delete PCBs for living threads
 *      else
 *          if (IP_RECORD)
 *              free record buffer
 *          else
 *              walk mpr list freeing ip_count buffers
 *      free pcb
 *      if no more sessions left created
 *          free PMB
 *          return timer to TMR
 *
 */

INT PRODestroyPCB( PPCB *ppPCB ){

    BOOL        fError = FALSE;
    PMPR        pmprCurrent;
    PTCB        pTCB;
    USHORT      fsOptions;

    if ( !*ppPCB )
        return ERROR_PRF_NOT_INITIALIZED;

    fsOptions = (*ppPCB)->pcb_fsOptions;

    /* stop profiling for all but thread pcbs (for thread pcbs, profiling has
     * already been stopped on the process level)
     */

    if ((fsOptions & (PROI_PCB_TMB|PROM_FLAGS)) != PROM_THREAD)
        proStop( *ppPCB );

    if ( fsOptions & PROI_PCB_TMB ){

        /* delete all living thread PCBs */
        for ( pTCB = ((PPCB_TMB)*ppPCB)->pPTDA->ptda_pTCBHead; pTCB != NULL;
              pTCB = pTCB->TCBpTCBNext )
            PRODestroyPCB( (PPCB *)&pTCB->TCBpTSD->TSDpPCB );

        proDestroyDeadThrdPCBs( (PPCB_TMB)*ppPCB );
        }

    else{
        /* delete this PCB structure */

        if (((*ppPCB)->pcb_fsOptions & PROC_FLAGS) == PROC_IP_RECORD )

            /* Free IP_Record Buffer */
            if ( (*ppPCB)->pcb_aladdrSampled )
                freeKRHB( (*ppPCB)->pcb_aladdrSampled );
            else
                fError = TRUE;
        else{

            /* Free IP_Count Buffers */
            for ( pmprCurrent = (*ppPCB)->pcb_pmprHead; pmprCurrent != NULL;
                  pmprCurrent = pmprCurrent->mpr_pmprNext)
                if ( pmprCurrent->mpr_fbCollectType == PROC_IP_COUNT )
                    if ( pmprCurrent->mpr_ulProfData )
                        freeKRHB( (CHAR *)pmprCurrent->mpr_ulProfData );
                    else
                        fError = TRUE;
            }/* end of else */
        }/* end of else */

    freeKRHB( *ppPCB );
    *ppPCB = NULL;

    /* BUGBUG 16-Mar-1990 waynej add flag to pcb that is set during creation
     * to distinguish between regular destroy and creation failure clean up.
     * We do not want to decrement the ppmb->pmb_cSessionsCreated for
     * creation failure clean up.
     */

    /* delete the PMB if not a thread PCB, PMB exists, and no more sessions */
    if ( (fsOptions & (PROI_PCB_TMB|PROM_FLAGS)) != PROM_THREAD
         && ppmb && --ppmb->pmb_cSessionsCreated <= 0 ){

        /* free PMB if no one is profilng */
        proAssert( ppmb->pmb_ppcbSystem == NULL );
        proAssert( ppmb->pmb_cSessionsStarted == 0 );
        proAssert( ppmb->pmb_cSessionsCreated == 0 );
        VMUnlock( &ppmb->pmb_hLock );
        freeKRHB( ppmb );
        ppmb = 0;
        /* reset timer */
        proSetTimer( 0 );
        }

    if ( fError )
        return ERROR_PRF_NOT_INITIALIZED;
    else
        return NO_ERROR;
    }







/***LP  proGetPPCB      -Gets pointer to the PCB for the specified process
 *
 *  This routine gets the appropriate PCB for calling profile funcitons.
 *
 *  ENTRY
 *
 *      pid             - Process ID of task being profiled.  Zero = system
 *                        profiling.
 *      ppPCB           - Pointer to Pointer to PCB.  This allows us to
 *                        return a pointer to the PCB.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_INVALID_PROCID
 *          ERROR_PRF_NOT_INITIALIZED
 *
 *  CONTEXT
 *      Called after initialization by the Profiling worker functions.
 *
 *  PSEUDO-CODE
 *      Check for profiling initialized
 *      if sys profiling
 *          return sys_PCB
 *      else
 *          return ptda_PCB
 *
 */

INT proGetPPCB ( PID pid, PPCB **pppPCB ){

    PPTDA       pPTDA;


    if ( !ppmb )
        return ERROR_PRF_NOT_INITIALIZED;
    if ( pid ){
        if ( TKPidToPTDA( pid, SSToDS( &pPTDA )))
            return ERROR_INVALID_PROCID;
        *pppPCB = (PPCB *)&pPTDA->ptda_pPCB;
        }
    else{
        *pppPCB = &ppmb->pmb_ppcbSystem;
        }
    if ( *pppPCB && **pppPCB )
        return NO_ERROR;
    else
        return ERROR_PRF_NOT_INITIALIZED;
    }



/***LP  proDestroyDeadThrdPCBs - Free list of dead thread PCBs
 *
 *
 *  ENTRY
 *      pPCB_TMB                - Pointer to PROFILE CONTROL BLOCK - Thread
 *                                Master Block
 *
 *  EXIT
 *      NO_ERROR
 *
 *  CONTEXT
 *      Called during clearing or destroying when in thread mode.
 *
 *  PSEUDO-CODE
 *      while elements in list
 *          Destroy current thread PCB
 *          free PDTR
 *          next PDTR
 *
 */


VOID proDestroyDeadThrdPCBs( PPCB_TMB   pPCB_TMB ){

    PPDTR       ppdtrCurrent = pPCB_TMB->ppdtrHead;
    PPDTR       ppdtrNext;

    pPCB_TMB->cPDTR = 0;

    while ( ppdtrCurrent != NULL ){

        proAssert( ppdtrCurrent->pPCB != NULL );

        /* free PCB */
        PRODestroyPCB( &ppdtrCurrent->pPCB );

        ppdtrNext = ppdtrCurrent->ppdtrNext;

        /* free PDTR */
        freeKRHB( ppdtrCurrent );

        ppdtrCurrent = ppdtrNext;
        }
    }




/***EP  PROAllocThrdPCB - Creates a duplicate PCB Copy in a New thread
 *
 *  This routine duplicates the PCB attached to thread 1 with
 *
 *  ENTRY
 *      pTSD            - Pointer to the TSD of the new thread.  This is the
 *                        structure that the new PCB will be attached to.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_INVALID_PROCID
 *          ERROR_PRF_NOT_INITIALIZED
 *
 *  CONTEXT
 *      Called after initialization by the Profiling worker functions.
 *
 *  PSEUDO-CODE
 *      Get PCB from thread 1 to use a source
 *      Check for PCB initialized
 *      traverse mprs to get count
 *      alloc PCB (+ MPRs)
 *      fill in PCB feilds
 *      if IP_RECORD
 *          alloc record buffer
 *      while not end of mpr list
 *          fill in feilds
 *          if IP_count
 *              alloc buffer
 *
 */

INT PROAllocThrdPCB ( struct tsd_s * pTSD ){

    PPCB        ppcbSrc;
    PPCB        ppcbDest;
    USHORT      cmpr = 0;
    PMPR        pmprCurrent;
    PMPR        ampr;
    INT         i;
    INT         rc;


    /* Get Source PCB.  Thread number one will always have a good PCB
     * if profiling is enabled.
     */

    ppcbSrc = (PPCB)(pTSD->TSDpTCB->TCBpPTDA->ptda_pTCBHead->TCBpTSD->TSDpPCB);

    /* check for non-null pointers */
    if ( !ppcbSrc ){
        proError();
        return ERROR_PRF_NOT_INITIALIZED;
        }

    /* zero pointer in case of failing alloc */
    pTSD->TSDpPCB = NULL;

    /* Get number of MPRs in chain */
    pmprCurrent = ppcbSrc->pcb_pmprHead;
    while ( pmprCurrent != NULL ){
        cmpr++;
        pmprCurrent = pmprCurrent->mpr_pmprNext;
        }

    /* Alloc memory for both PCB and MPR list */
    if ( allocKRHB( sizeof(PCB) + (sizeof(MPR)) * cmpr, PROFOWNER,
         SSToDS( &ppcbDest ))){
        proError();
        return ERROR_NOT_ENOUGH_MEMORY;
        }

    /* copy settings */
    ppcbDest->pcb_fsOptions     = ppcbSrc->pcb_fsOptions & ~PROI_STATUS_FLAGS;
    ppcbDest->pcb_bShiftValue   = ppcbSrc->pcb_bShiftValue;
    ppcbDest->pcb_claddrSampled = ppcbSrc->pcb_claddrSampled;

    if (( ppcbSrc->pcb_fsOptions & PROC_FLAGS) == PROC_IP_RECORD ){
        proAssert( ppcbSrc->pcb_aladdrSampled != NULL );
        proAssert( ppcbSrc->pcb_claddrSampled != 0 );

        /* alloc aladdrSampled */
        if ( allocKRHB( ppcbSrc->pcb_claddrSampled, PROFOWNER,
                        &ppcbDest->pcb_aladdrSampled )){
            ppcbDest->pcb_aladdrSampled = 0;
            ppcbDest->pcb_pmprHead      = 0;
            PRODestroyPCB( SSToDS( &ppcbDest ));
            proError();
            return ERROR_NOT_ENOUGH_MEMORY;
            }
        }

    /* build MPR list */
    ampr = (PMPR)(ppcbDest + 1);

    ppcbDest->pcb_pmprHead = ampr;

    pmprCurrent = ppcbSrc->pcb_pmprHead;
    for ( i = 0; i < cmpr; i++){
        ampr[i] = *pmprCurrent;

        if ( pmprCurrent->mpr_fbCollectType == PROC_IP_COUNT ){
            proAssert( pmprCurrent->mpr_ulProfData != NULL );

            /* alloc IP_CountBuffer */
            if ( allocKRHB( ((ampr[i].mpr_cbModule >>
                            ppcbSrc->pcb_bShiftValue) + 1) *
                            PRO_IP_COUNTER_SIZE, PROFOWNER,
                            (CHAR *)&ampr[i].mpr_ulProfData )){

                ampr[i-1].mpr_pmprNext = NULL;
                PRODestroyPCB( SSToDS( &ppcbDest ));
                proError();
                return ERROR_NOT_ENOUGH_MEMORY;
                }
            }
        else
            ampr[i].mpr_ulProfData = 0;

        if ( i != 0 )
            ampr[i-1].mpr_pmprNext = &ampr[i];

        pmprCurrent = pmprCurrent->mpr_pmprNext;
        }
    ampr[i-1].mpr_pmprNext = NULL;

    /* clear PCB count feilds */
    rc = proClear( ppcbDest );
    proAssert( rc == NO_ERROR );

    pTSD->TSDpPCB = (ULONG)ppcbDest;

    return NO_ERROR;
    }




/***EP  PROSaveThrdPCB - Save PCB from thread
 *
 *  This routine gets the appropriate PCB for calling profile funcitons.
 *
 *  ENTRY
 *      pTSD            - Pointer to the TSD of the dying thread.
 *
 *  EXIT
 *      SUCCESS
 *          NO_ERROR
 *      FAILURE
 *          ERROR_PRF_NOT_INITIALIZED
 *
 *  CONTEXT
 *      Called when thread terminates.
 *
 *  PSEUDO-CODE
 *      check for pcb_tmb
 *      append thread to dead thread chain
 *      set dead thread bit.
 *
 */

INT PROSaveThrdPCB ( struct tsd_s *pTSD ){

    PPCB_TMB    pPCB_TMB;
    PPDTR       ppdtrOldList;

    /* this shouldn't be getting called unless PCB is valid */
    proAssert( pTSD->TSDpPCB != NULL );
    if ( !( pPCB_TMB = (PPCB_TMB)pTSD->TSDpTCB->TCBpPTDA->ptda_pPCB ))
        return ERROR_PRF_NOT_INITIALIZED;

    if ( !(pPCB_TMB->fsOptions & ( PROI_PCB_TMB | PROM_THREAD ))) {
        proError();
        return ERROR_PRF_NOT_INITIALIZED;
        }

    ppdtrOldList = pPCB_TMB->ppdtrHead;

    /* alloc PDTR */
    if ( allocKRHB( sizeof( PDTR ), PROFOWNER, &pPCB_TMB->ppdtrHead )){
        pPCB_TMB->ppdtrHead = ppdtrOldList;
        proError();
        return ERROR_NOT_ENOUGH_MEMORY;
        }

    /* fill in feilds */
    pPCB_TMB->cPDTR ++;
    pPCB_TMB->ppdtrHead->tid = pTSD->TSDpTCB->TCBOrdinal;
    pPCB_TMB->ppdtrHead->pPCB = (PPCB)pTSD->TSDpPCB;
    pPCB_TMB->ppdtrHead->ppdtrNext = ppdtrOldList;
    pPCB_TMB->ppdtrHead->pPCB->pcb_fsOptions |= PROI_KILLED_THREAD;

    return NO_ERROR;
    }



/* Lock the following code down for sampling */

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


/***EP  PROSysArena - use VMSysArena macro to test if address in system arena
 *
 *  This routine call VMSysArena() to determine whether the linear address
 *  is in the system arena.  Called from proLogProfileData(), at profile
 *  timer interrupt time.
 *
 *  N.B.  We should really add an assembler version of VMSysArena to
 *  dosinc\vmexport.h, but h2inc doesn't handle the VMAR structure
 *  properly yet (unions are bogus in vmexport.inc).
 *
 *  ENTRY
 *      p               - flat 32 linear address
 *
 *  EXIT
 *      returns the TRUE/FALSE value returned from VMSysArena()
 *
 *  CONTEXT
 *      Called at timer interrupt time when profiling, from proLogProfileData.
 *
 *
 */

INT PROSysArena ( LADDR p ){
        return ( VMSysArena(p) );
    }


/***EP  PROShrArena - use VMShrArena macro to test if address in shared arena
 *
 *  This routine call VMShrArena() to determine whether the linear address
 *  is in the shared arena.  Called from proLogProfileData(), at profile
 *  timer interrupt time.
 *
 *  N.B.  We should really add an assembler version of VMShrArena to
 *  dosinc\vmexport.h, but h2inc doesn't handle the VMAR structure
 *  properly yet (unions are bogus in vmexport.inc).
 *
 *  ENTRY
 *      p               - flat 32 linear address
 *
 *  EXIT
 *      returns the TRUE/FALSE value returned from VMShrArena()
 *
 *  CONTEXT
 *      Called at timer interrupt time when profiling, from proLogProfileData.
 *
 *
 */

INT PROShrArena ( LADDR p ){
        return ( VMShrArena(p) );
    }
