594 lines
14 KiB
C
594 lines
14 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1990 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
bowtimer.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This module implements all of the timer related routines for the NT
|
|||
|
browser
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Larry Osterman (LarryO) 21-Jun-1990
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
21-Jun-1990 LarryO
|
|||
|
|
|||
|
Created
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
|
|||
|
#include "precomp.h"
|
|||
|
#pragma hdrstop
|
|||
|
|
|||
|
|
|||
|
BOOL bEnableExceptionBreakpoint = FALSE;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
BowserTimerDpc(
|
|||
|
IN PKDPC Dpc,
|
|||
|
IN PVOID Context,
|
|||
|
IN PVOID SystemArgument1,
|
|||
|
IN PVOID SystemArgument2
|
|||
|
);
|
|||
|
|
|||
|
VOID
|
|||
|
BowserTimerDispatcher (
|
|||
|
IN PVOID Context
|
|||
|
);
|
|||
|
|
|||
|
#ifdef ALLOC_PRAGMA
|
|||
|
#pragma alloc_text(PAGE, BowserInitializeTimer)
|
|||
|
#pragma alloc_text(PAGE4BROW, BowserUninitializeTimer)
|
|||
|
#pragma alloc_text(PAGE4BROW, BowserStartTimer)
|
|||
|
#pragma alloc_text(PAGE4BROW, BowserStopTimer)
|
|||
|
#pragma alloc_text(PAGE4BROW, BowserTimerDispatcher)
|
|||
|
#endif
|
|||
|
|
|||
|
LONG
|
|||
|
BrExceptionFilter( EXCEPTION_POINTERS * pException)
|
|||
|
{
|
|||
|
//
|
|||
|
// Note: BrExceptionFilter is defined only for checked builds (ifdef DBG)
|
|||
|
//
|
|||
|
|
|||
|
DbgPrint("[Browser] exception 0x%lx.\n", pException->ExceptionRecord->ExceptionCode );
|
|||
|
if ( bEnableExceptionBreakpoint &&
|
|||
|
pException->ExceptionRecord->ExceptionCode != STATUS_INSUFFICIENT_RESOURCES &&
|
|||
|
pException->ExceptionRecord->ExceptionCode != STATUS_WORKING_SET_QUOTA ) {
|
|||
|
DbgBreakPoint();
|
|||
|
}
|
|||
|
return EXCEPTION_EXECUTE_HANDLER;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
BowserInitializeTimer(
|
|||
|
IN PBOWSER_TIMER Timer
|
|||
|
)
|
|||
|
{
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
KeInitializeTimer(&Timer->Timer);
|
|||
|
|
|||
|
KeInitializeEvent(&Timer->TimerInactiveEvent, NotificationEvent, TRUE);
|
|||
|
|
|||
|
KeInitializeSpinLock(&Timer->Lock);
|
|||
|
|
|||
|
ExInitializeWorkItem(&Timer->WorkItem, BowserTimerDispatcher, Timer);
|
|||
|
|
|||
|
Timer->AlreadySet = FALSE;
|
|||
|
Timer->Canceled = FALSE;
|
|||
|
Timer->SetAgain = FALSE;
|
|||
|
|
|||
|
Timer->Initialized = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
BowserUninitializeTimer(
|
|||
|
IN PBOWSER_TIMER Timer
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
Prepare the timer for being uninitialized.
|
|||
|
|
|||
|
Arguments:
|
|||
|
IN PBOWSER_TIMER Timer - Timer to stop
|
|||
|
|
|||
|
Return Value
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
KIRQL OldIrql;
|
|||
|
|
|||
|
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
|||
|
|
|||
|
Timer->Initialized = FALSE;
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
//
|
|||
|
// First stop the timer.
|
|||
|
//
|
|||
|
|
|||
|
BowserStopTimer(Timer);
|
|||
|
|
|||
|
//
|
|||
|
// Now wait to make sure that any the timer routine that is currently
|
|||
|
// executing the timer completes. This allows us to make sure that we
|
|||
|
// never delete a transport while a timer routine is executing.
|
|||
|
//
|
|||
|
|
|||
|
KeWaitForSingleObject(&Timer->TimerInactiveEvent, Executive, KernelMode, FALSE, NULL);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// Notes for stopping a timer
|
|||
|
// ==========================
|
|||
|
//
|
|||
|
// Timers run through various states. During some of them they cannot be
|
|||
|
// cancelled. In order to guarantee that we can reliably stop and start
|
|||
|
// timers, they can only be started or stopped at LOW_LEVEL (ie, not from
|
|||
|
// DPC_LEVEL.
|
|||
|
//
|
|||
|
// If the timer is not running then StopTimer does nothing.
|
|||
|
//
|
|||
|
// If queued inside the kernel timer package then KeCancelTimer will work
|
|||
|
// and the timer contents cleaned up.
|
|||
|
//
|
|||
|
// If the kernel timer package has queued the dpc routine then KeCancelTimer
|
|||
|
// will fail. We can flag the timer as canceled. BowserTimerDispatcher will
|
|||
|
// cleanup the timer when it fires.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
// Notes for starting a timer
|
|||
|
// ==========================
|
|||
|
//
|
|||
|
// If StartTimer is called on a clean timer then it sets the contents
|
|||
|
// appropriately and gives the timer to the kernel timer package.
|
|||
|
//
|
|||
|
// If the timer is canceled but not cleaned up then StartTimer will update
|
|||
|
// the contents of the timer to show where the new TimerRoutine and TimerContext.
|
|||
|
// it will indicate that the timer is no longer canceled and is now SetAgain.
|
|||
|
//
|
|||
|
// If the timer is already SetAgain then StartTimer will update the contents
|
|||
|
// of the timer to hold the new TimerRoutine and TimerContext.
|
|||
|
//
|
|||
|
// When BowserTimerDispatcher is called on a SetAgain timer, it sets the timer
|
|||
|
// to its normal state and gives the timer to the kernel timer package.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
BowserStartTimer(
|
|||
|
IN PBOWSER_TIMER Timer,
|
|||
|
IN ULONG MillisecondsToExpireTimer,
|
|||
|
IN PBOWSER_TIMER_ROUTINE TimerRoutine,
|
|||
|
IN PVOID Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
Set Timer to call TimerRoutine after MillisecondsToExpire. TimerRoutine
|
|||
|
is to be called at normal level.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IN PBOWSER_TIMER Timer
|
|||
|
IN ULONG MillisecondsToExpireTimer
|
|||
|
IN PBOWSER_TIMER_ROUTINE TimerRoutine
|
|||
|
IN PVOID Context - Parameter to TimerRoutine
|
|||
|
|
|||
|
Return Value
|
|||
|
BOOLEAN - TRUE if timer set.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
LARGE_INTEGER Timeout;
|
|||
|
BOOLEAN ReturnValue;
|
|||
|
KIRQL OldIrql;
|
|||
|
|
|||
|
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
ASSERT (KeGetCurrentIrql() == LOW_LEVEL);
|
|||
|
|
|||
|
Timeout.QuadPart = (LONGLONG)MillisecondsToExpireTimer * (LONGLONG)(-10000);
|
|||
|
// Timeout = LiNMul(MillisecondsToExpireTimer, -10000);
|
|||
|
|
|||
|
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
|||
|
|
|||
|
if (!Timer->Initialized) {
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
dprintf(DPRT_TIMER, ("BowserStartTimer %lx, TimerRoutine %x. Set to expire at %lx%lx (%ld/%ld ms)\n", Timer, TimerRoutine, Timeout.HighPart, Timeout.LowPart, -1 * Timeout.LowPart, MillisecondsToExpireTimer));
|
|||
|
|
|||
|
//
|
|||
|
// We shouldn't be able to start the timer while it is
|
|||
|
// already running unless its also cancelled.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
if (Timer->AlreadySet == TRUE) {
|
|||
|
|
|||
|
if (Timer->Canceled) {
|
|||
|
|
|||
|
//
|
|||
|
// This timer has been canceled, but the canceled routine
|
|||
|
// hasn't run yet.
|
|||
|
//
|
|||
|
// Flag that the timer has been re-set, and return to
|
|||
|
// the caller. When the BowserTimerDispatch is finally
|
|||
|
// executed, the new timer will be set.
|
|||
|
//
|
|||
|
|
|||
|
Timer->Timeout = Timeout;
|
|||
|
|
|||
|
Timer->TimerRoutine = TimerRoutine;
|
|||
|
|
|||
|
Timer->SetAgain = TRUE;
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
return(TRUE);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
InternalError(("Timer started without already being set"));
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
return(FALSE);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
ASSERT (!Timer->Canceled);
|
|||
|
|
|||
|
ASSERT (!Timer->SetAgain);
|
|||
|
|
|||
|
Timer->Timeout = Timeout;
|
|||
|
|
|||
|
Timer->TimerRoutine = TimerRoutine;
|
|||
|
|
|||
|
Timer->TimerContext = Context;
|
|||
|
|
|||
|
Timer->AlreadySet = TRUE;
|
|||
|
|
|||
|
Timer->Canceled = FALSE;
|
|||
|
|
|||
|
Timer->SetAgain = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Set the inactive event to the not signalled state to indicate that
|
|||
|
// there is timer activity outstanding.
|
|||
|
//
|
|||
|
|
|||
|
KeResetEvent(&Timer->TimerInactiveEvent);
|
|||
|
|
|||
|
//
|
|||
|
// We are now starting the timer. Initialize the DPC and
|
|||
|
// set the timer.
|
|||
|
//
|
|||
|
|
|||
|
KeInitializeDpc(&Timer->Dpc,
|
|||
|
BowserTimerDpc,
|
|||
|
Timer);
|
|||
|
|
|||
|
ReturnValue = KeSetTimer(&Timer->Timer, Timeout, &Timer->Dpc);
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
return ReturnValue;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
BowserStopTimer(
|
|||
|
IN PBOWSER_TIMER Timer
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
Stop the timer from calling the TimerRoutine.
|
|||
|
|
|||
|
Arguments:
|
|||
|
IN PBOWSER_TIMER Timer - Timer to stop
|
|||
|
|
|||
|
Return Value
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
KIRQL OldIrql;
|
|||
|
|
|||
|
//
|
|||
|
// Do an unsafe test to see if the timer is already set, we can return.
|
|||
|
//
|
|||
|
|
|||
|
if (!Timer->AlreadySet) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
//
|
|||
|
// You can only stop a timer at LOW_LEVEL
|
|||
|
//
|
|||
|
|
|||
|
ASSERT (KeGetCurrentIrql() == LOW_LEVEL);
|
|||
|
|
|||
|
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
|||
|
|
|||
|
dprintf(DPRT_TIMER, ("BowserStopTimer %lx\n", Timer));
|
|||
|
|
|||
|
//
|
|||
|
// If the timer isn't running, just early out.
|
|||
|
//
|
|||
|
|
|||
|
if (!Timer->AlreadySet) {
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
Timer->Canceled = TRUE;
|
|||
|
|
|||
|
if (!KeCancelTimer(&Timer->Timer)) {
|
|||
|
|
|||
|
//
|
|||
|
// The timer has already fired. It could be in the dpc queue or
|
|||
|
// the work queue. The timer is marked as canceled.
|
|||
|
//
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The timer was still in the kernel timer package so we cancelled the
|
|||
|
// timer completely. Return timer to initial state.
|
|||
|
//
|
|||
|
|
|||
|
Timer->AlreadySet = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// The timer isn't canceled, so it can't be reset.
|
|||
|
//
|
|||
|
|
|||
|
Timer->SetAgain = FALSE;
|
|||
|
|
|||
|
Timer->Canceled = FALSE;
|
|||
|
|
|||
|
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
// DbgPrint("Cancel timer %lx complete\n", Timer);
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
BowserTimerDpc(
|
|||
|
IN PKDPC Dpc,
|
|||
|
IN PVOID Context,
|
|||
|
IN PVOID SystemArgument1,
|
|||
|
IN PVOID SystemArgument2
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
This routine is called when the timeout expires. It is called at Dpc level
|
|||
|
to queue a WorkItem to a system worker thread.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IN PKDPC Dpc,
|
|||
|
IN PVOID Context,
|
|||
|
IN PVOID SystemArgument1,
|
|||
|
IN PVOID SystemArgument2
|
|||
|
|
|||
|
Return Value
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PBOWSER_TIMER Timer = Context;
|
|||
|
|
|||
|
ASSERT (Dpc == &Timer->Dpc);
|
|||
|
|
|||
|
// DbgPrint("Timer %lx fired\n", Timer);
|
|||
|
|
|||
|
//
|
|||
|
// Due to bug 245645 we need to queue in delayed worker queue rather then execute timed tasks.
|
|||
|
// OLD WAY: ExQueueWorkItem(&Timer->WorkItem, DelayedWorkQueue);
|
|||
|
//
|
|||
|
|
|||
|
BowserQueueDelayedWorkItem( &Timer->WorkItem );
|
|||
|
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER(SystemArgument1);
|
|||
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
BowserTimerDispatcher (
|
|||
|
IN PVOID Context
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Call the TimerRoutine and cleanup.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IN PVOID Context - Original parameter supplied to BowserStartTimer
|
|||
|
|
|||
|
Return Value
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
IN PBOWSER_TIMER Timer = Context;
|
|||
|
|
|||
|
BowserReferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
DISCARDABLE_CODE( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
try {
|
|||
|
KIRQL OldIrql;
|
|||
|
PBOWSER_TIMER_ROUTINE RoutineToCall;
|
|||
|
PVOID ContextForRoutine;
|
|||
|
|
|||
|
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
|||
|
|
|||
|
//
|
|||
|
// If the timer was uninitialized, return right away.
|
|||
|
//
|
|||
|
|
|||
|
if (!Timer->Initialized) {
|
|||
|
|
|||
|
dprintf(DPRT_TIMER, ("Timer %lx was uninitialized. Returning.\n", Timer));
|
|||
|
|
|||
|
//
|
|||
|
// Set the inactive event to the signalled state to indicate that
|
|||
|
// the outstanding timer activity has completed.
|
|||
|
//
|
|||
|
|
|||
|
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (Timer->Canceled) {
|
|||
|
|
|||
|
dprintf(DPRT_TIMER, ("Timer %lx was cancelled\n", Timer));
|
|||
|
|
|||
|
//
|
|||
|
// If the timer was reset, this indicates that the timer was
|
|||
|
// canceled, but the timer was in the DPC (or executive worker)
|
|||
|
// queue. We want to re-run the timer routine.
|
|||
|
//
|
|||
|
|
|||
|
if (Timer->SetAgain) {
|
|||
|
|
|||
|
ASSERT (Timer->AlreadySet);
|
|||
|
|
|||
|
Timer->SetAgain = FALSE;
|
|||
|
|
|||
|
dprintf(DPRT_TIMER, ("Timer %lx was re-set. Re-setting timer\n", Timer));
|
|||
|
|
|||
|
//
|
|||
|
// We are now starting the timer. Initialize the DPC and
|
|||
|
// set the timer.
|
|||
|
//
|
|||
|
|
|||
|
KeInitializeDpc(&Timer->Dpc,
|
|||
|
BowserTimerDpc,
|
|||
|
Timer);
|
|||
|
|
|||
|
KeSetTimer(&Timer->Timer, Timer->Timeout, &Timer->Dpc);
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
dprintf(DPRT_TIMER, ("Timer %lx was successfully canceled.\n", Timer));
|
|||
|
|
|||
|
Timer->AlreadySet = FALSE;
|
|||
|
|
|||
|
Timer->Canceled = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Set the inactive event to the signalled state to indicate that
|
|||
|
// the outstanding timer activity has completed.
|
|||
|
//
|
|||
|
|
|||
|
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
ASSERT (Timer->AlreadySet);
|
|||
|
|
|||
|
ASSERT (!Timer->SetAgain);
|
|||
|
|
|||
|
Timer->AlreadySet = FALSE;
|
|||
|
|
|||
|
dprintf(DPRT_TIMER, ("Timer %lx fired. Calling %lx\n", Timer, Timer->TimerRoutine));
|
|||
|
|
|||
|
//
|
|||
|
// We release the spinlock so save timer contents locally.
|
|||
|
//
|
|||
|
|
|||
|
RoutineToCall = Timer->TimerRoutine;
|
|||
|
|
|||
|
ContextForRoutine = Timer->TimerContext;
|
|||
|
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
RoutineToCall(ContextForRoutine);
|
|||
|
|
|||
|
ACQUIRE_SPIN_LOCK(&Timer->Lock, &OldIrql);
|
|||
|
if ( !Timer->AlreadySet ) {
|
|||
|
KeSetEvent(&Timer->TimerInactiveEvent, IO_NETWORK_INCREMENT, FALSE);
|
|||
|
}
|
|||
|
RELEASE_SPIN_LOCK(&Timer->Lock, OldIrql);
|
|||
|
|
|||
|
BowserDereferenceDiscardableCode( BowserDiscardableCodeSection );
|
|||
|
|
|||
|
} except (BR_EXCEPTION) {
|
|||
|
#if DBG
|
|||
|
KdPrint(("BOWSER: Timer routine %lx faulted: %X\n", Timer->TimerRoutine, GetExceptionCode()));
|
|||
|
DbgBreakPoint();
|
|||
|
#else
|
|||
|
KeBugCheck(9999);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
}
|