Why Windows Timers May Not Fire

Windows Research Kernel @ HPI

This article is motivated by a discussion with Max, one of our readers, who commented on my previous post on an anomaly in Windows timer management (read the article). So, thanks again Max!

Please have a look on the following program sample.

(For the sake of simplicity I removed all the error handling code ;))

#define _WIN32_WINNT 0x0400
#include <windows.h>
#include <stdio.h>

#define SECONDS (10*1000*1000)

int main(int argc, char * argv[])
{
    HANDLE Timer;
    LARGE_INTEGER DueTime;
    FILETIME CurrentTime;
    long time = 15;

    if (argc > 1)
        time = atol(argv[1]);

    // Create a timer

    Timer = CreateWaitableTimer(NULL, FALSE, NULL);

    // Set the timer to an absolute value 15 seconds in the future

    GetSystemTimeAsFileTime((LPFILETIME) &DueTime);
    DueTime.QuadPart += (LONGLONG) (time * SECONDS);
    SetWaitableTimer(Timer, &DueTime, 0, NULL, NULL, 0)

    // Wait for the timer

    WaitForSingleObject(Timer, INFINITE);

    printf("Timer was signaled.\n");
    return 0;
}

If you compile this code and execute in on your machine, the expected behavior is that the application will wait for about 15 seconds and will then return. If you modify the system time while your application is waiting in such a way that you advance the system time by a time span that is greater than the time span your machine is running, you will experience that your application will wait forever. How can this happen?

To answer the question, I reviewed the source files, in particular base\ntos\ke\miscc.c. I provide the interesting lines here:

00359         // Acquire the timer table lock, remove all absolute timers from the
00360         // timer queue so their due time can be recomputed, and release the
00361         // timer table lock.
00362         //
00363
00364         InitializeListHead(&AbsoluteListHead);
00365         for (Index = 0; Index < TIMER_TABLE_SIZE; Index += 1) {
00366             ListHead = &KiTimerTableListHead[Index].Entry;
00367             LockQueue = KiAcquireTimerTableLock(Index);
00368             NextEntry = ListHead->Flink;
00369             while (NextEntry != ListHead) {
00370                 Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
00371                 NextEntry = NextEntry->Flink;
00372                 if (Timer->Header.Absolute != FALSE) {
00373                     KiRemoveEntryTimer(Timer);
00374                     InsertTailList(&AbsoluteListHead, &Timer->TimerListEntry);
00375                 }
00376             }
00377
00378             KiReleaseTimerTableLock(LockQueue);
00379         }

There, the function KeSetSystemTime is responsible for setting the system time. When the system time is modified, Windows has to scan through the list of timers and must determine all absolute timers (lines 365 to 379). This is because the due time is expressed in interrupt time, not in system time. The interrupt time counts the time in units of 100 ns since the system was booted. This time is not adjusted when the system time is modified. If you set an absolute timer, e.g., with SetWaitableTimer(), Windows calculates the corresponding interrupt time, sets it as due time, and inserts the timer into the timer table. If the system time is modified, this calculation must be repeated as the delta between interrupt time and system time has changed. Here is the code for the recalculation:

00381         //
00382         // Recompute the due time and reinsert all absolute timers in the timer
00383         // tree. If a timer has already expired, then insert the timer in the
00384         // expired timer list.
00385         //
00386
00387         InitializeListHead(&ExpiredListHead);
00388         while (AbsoluteListHead.Flink != &AbsoluteListHead) {
00389             Timer = CONTAINING_RECORD(AbsoluteListHead.Flink, KTIMER, TimerListEntry);
00390             RemoveEntryList(&Timer->TimerListEntry);
00391             Timer->DueTime.QuadPart -= TimeDelta.QuadPart;
00392             Hand = KiComputeTimerTableIndex(Timer->DueTime.QuadPart);
00393             Timer->Header.Hand = (UCHAR)Hand;
00394             LockQueue = KiAcquireTimerTableLock(Hand);
00395             if (KiInsertTimerTable(Timer, Hand) == TRUE) {
00396                 KiRemoveEntryTimer(Timer);
00397                 InsertTailList(&ExpiredListHead, &Timer->TimerListEntry);
00398             }
00399
00400             KiReleaseTimerTableLock(LockQueue);
00401         }

The important line here is line 391. Note that Timer->DueTime is unsigned while TimeDelta is signed. If TimeDelta is greater than DueTime, DueTime will wrap-around, i.e., the due time becomes practically infinite, because all timer related code will interpret the due time as an unsigned value! This wrap-around prevents the timer from being fired.

I ran the above program on both platforms Windows Vista and Windows Server 2003. While the anomaly presented here occurred on Windows Server 2003, it did not on Windows Vista. So the anomaly must have been fixed, but probably unintentionally, because we could not find any Knowledge Base article describing any fixes to the problem.

Comments

One Response to "Why Windows Timers May Not Fire"

  1. max on January 4th, 2008 19:51

    don`t know is it interesting, but one twin bug in Win2003 X64(at least in first versions, i not have and
    check latest updates, also it fix in Vista X64) wich can set very big problem to Debuggers.
    I write self usermode(and partially kernelmode on x86) Debugger(for x86 and x64).but on x64 got
    some problems: not interactive code debugged is ok, but when i attach to Ui app(notepad for example)
    the window caption begin draw incorect, and when i select 'open' menu item, notepad is crached.
    At first i think that error in my code(ms visual studio debugger 2005 work correct), but at finish, i found
    that error in system : in function KiSystemServiceExit. look for this code :

    mov [rbp-50h],eax
    KiRestoreDebugRegisterState
    mov eax,[rbp-50]

    this code is execute only if DR registers is active on thread.if this (DR 0..3,7 not zero) before
    call NtApi, KiSaveDebugRegisterState is called, and values in DRx is replaced, to sys defaults.
    and at exit called KiRestoreDebugRegisterState. But at this point in rax register is return value
    from api call.it must be saved before call KiRestoreDebugRegisterState, and than restored.
    so right code must be

    mov [rbp-50h],rax
    KiRestoreDebugRegisterState
    mov rax,[rbp-50]

    this is on Vista X64, but on Win2003 X64 saved only eax, so high path of rax is lost.it not problem
    for api from ntoskrnl becouse all it return NTSTATUS wich is DWORD size, but win32k.sys can return
    full 64bit pointer.as result error is happens.for example you call SetWindowLongPtr for GWLP_WNDPROC
    to set new WndProc, and save return value (OldWndProc) to call it from your new WndProc.but if
    address of new OldProc is grether than 0xffffffff you got incorect value.in my example with notepad
    in OpenDialog box combobox is subclussed.subclass code save old combobox wndproc, wich on my system
    is 00007FF7F03FB40, but SetWindowLongPtr return 00000007F03FB40.and when from new proc old proc is
    called by address 00000007F03FB40, exception is raised.the simpliest way to see this bug :
    set DR registers on thread(by SetContextThread(CONTEXT_DEBUG_REGISTERS), nonzero Dr7 is enath),
    then create test window and call
    SetWindowLong(hwnd, GWLP_USERDATA, 0×123456789abcdef0);
    SetWindowLong(hwnd, GWLP_USERDATA, 0×123456789abcdef0);
    first call return 0, and second call return 0×000000009abcdef0( must return 0×123456789abcdef0),
    hower if you call GetWindowLong(hwnd, GWLP_USERDATA), you got correct value 0×123456789abcdef0,
    becouse this function can work in usermode only(in case only usermode code). the
    ms visual studio debugger work correct becouse it not use DRx registers.for example to step over
    function call, it set breakpoint in code, but i use Drx for this