2

On a few Windows computers I have seen that two, on each other following, calls to ::GetTickCount() returns a difference of 1610619236 ms (around 18 days). This is not due to wrap around og int/unsigned int mismatch. I use Visual C++ 2015/2017.

Has anybody else seen this behaviour? Does anybody have any idea about what could cause behaviour like this?

Best regards John

Code sample that shows the bug:

class CLTemp
{
    DWORD nLastCheck;
    CLTemp() 
    {
        nLastCheck=::GetTickCount();
    }
    //Service is called every 200ms by a timer
    void Service()
    {
        if( ::GetTickCount() - nLastCheck > 20000 )//check every 20 sec
        {
            //On some Windows machines, after an uptime of 776 days, the
            //::GetTickCount() - nLastCheck gives a value of 1610619236
            //(corresponding to around 18 days)
            nLastCheck = ::GetTickCount();
        }
    }

};

Update - problem description, a way of recreating and solution:

The Windows API function GetTickCount() unexpectedly jumps 18 days forward in time when passing 776 days after Windows Restart. We have experienced several times that some of our long running Windows pc applications coded in Microsoft Visual C++ suddenly reported a time-out error. In many of our applications we call GetTickCount() to perform some tasks with certain intervals or to watch for a time-out condition. The example code could go as this:

    DWORD dwTimeNow, dwPrevTime = ::GetTickCount();
    bool bExit = false;
    While (!bExit)
    {
        dwTimeNow = ::GetTickCount();
        if (dwTimeNow – dwPrevTime >= 5000)
        {
            dwPrevTime = dwTimeNow;
            // Perform my task
        }
        else
        {
             ::Sleep(10);
        }
     }

GetTickCount() returns a DWORD, which is an unsigned 32-bit int. GetTickCount() wraps around from its maximum value of 0xFFFFFFFF to zero after app. 49 days. The wrap around is easily handled by using unsigned arithmetic and always subtracting the previous value from the new value to calculate the distance. Do never compare two values from GetTickCount() against each other.

So, the wrap around at its maximum value each 49 days it expected and handled. But we have experienced an unexpected wrap around to zero of GetTickCount() after 776 days after latest Windows Restart. And in this case GetTickCount() wraps from 0x9FFFFFFF to zero, which is 1610612736 milliseconds too early corresponding to around 18.6 days. When GetTickCount() is used to check for a time-out condition and it suddenly reports that 18 days have elapsed since last check, then the software reports a false time-out condition. Note that it is 776 days after a Windows Restart. A Windows Restart resets the GetTickCount() value to zero. A pc reboot does not, instead the time elapsed while switched off is added to the initial GetTickCount() value.

We have made a test program that provides evidence of this issue. The test program reads the values of GetTickCount(), GetTickCount64(), InterruptTime(), and UnbiasedInterruptTime() each 5000 milliseconds scheduled by a Windows Timer. Each time the sample program calculates the distance in time for each of the four time-functions. If the distance in time is 10000 milliseconds or more, it is marked as a time jump event and logged. Each time it also keeps track of the minimum distance and the maximum distance in time for each time-function.

Before starting the test program, a Windows Restart is carried out. Ensure no automatic time synchronization is enabled. Then make a Windows shut down. Start the pc again and make it enter its Bios setup when it boots. In the Bios, advance the real time clock 776 days. Let the pc boot up and start the test program. Then after 17 hours the unexpected wraparound of GetTickCount() occurs (776 days, 17 hours, and 21 minutes). It is only GetTickCount() that shows this behavior. The other time-functions do not.

The following excerpt from the logfile of the test program shows the start values reported by the four time-functions. In this example the time has only been advanced to 775 days after Windows Restart. The format of the log entry is the time-function value converted into: days hh:mm:ss.msec. TickCount32 is the plain GetTickCount(). Because it is a 32-bit value it has wrapped around and shows a different value. At GetTickCount64() we can see the 775 days.

    2024-05-14 09:13:27.262 Start times
    TickCount32 : 029 08:30:11.591
    TickCount64 : 775 00:12:01.031
    InterruptTime : 775 00:12:01.036
    UnbiasedInterruptTime: 000 00:05:48.411

The next excerpt from the logfile shows the unexpected wrap around of GetTickCount() (TickCount32). The format is: Distance between the previous value and the new value (should always be around 5000 msec). Then follows the new value converted into days and time, and finally follows the previous value converted into days and time. We can see that GetTickCount() jumps 1610617752 milliseconds (app. 18.6 days) while the other three time-functions only advances app. 5000 msec as expected. At TickCount64 one can see that it occurs at 776 days, 17 hours, and 21 minutes.

    2024-05-16 02:22:30.394 Time jump *****
    TickCount32 : 1610617752 - 000 00:00:00.156 - 031 01:39:09.700
    TickCount64 : 5016 - 776 17:21:04.156 - 776 17:20:59.140
    InterruptTime : 5015 - 776 17:21:04.165 - 776 17:20:59.150
    UnbiasedInterruptTime: 5015 - 001 17:14:51.540 - 001 17:14:46.525

If you increase the time that the real time clock is advanced to two times 776 days and 17 hours – for example 1551 days – the phenomenon shows up once more. It has a cyclic nature.

    2026-06-30 06:34:26.663 Start times
    TickCount32 : 029 12:41:57.888
    TickCount64 : 1551 21:44:51.328
    InterruptTime : 1551 21:44:51.334
    UnbiasedInterruptTime: 004 21:24:24.593
    2026-07-01 19:31:47.641 Time jump *****
    TickCount32 : 1610617736 - 000 00:00:04.296 - 031 01:39:13.856
    TickCount64 : 5000 - 1553 10:42:12.296 - 1553 10:42:07.296
    InterruptTime : 5007 - 1553 10:42:12.310 - 1553 10:42:07.303
    UnbiasedInterruptTime: 5007 - 006 10:21:45.569 - 006 10:21:40.562

The only viable solution to this issue seems to be using GetTickCount64() and totally abandon usage of GetTickCount().

rauhe
  • 21
  • 2
  • The recommendation is to use `GetTickCount64()` instead. I'm familiar with some anti-analysis environments that screw around with the value, though. – Anya Shenanigans Oct 23 '20 at 10:34
  • Does this answer your question? [GetTickCount values on Windows 10](https://stackoverflow.com/questions/35318521/gettickcount-values-on-windows-10) – Build Succeeded Oct 23 '20 at 10:57
  • What this function returns? => Retrieves the number of milliseconds that have elapsed since the system was started, up to 49.7 days. – Build Succeeded Oct 23 '20 at 11:10
  • @Build Succeeded I dont think the problem is related. I use the GetTickCount exclusively to check time difference between calls to functions. So I dont use the absolute time as an indicator. – rauhe Oct 23 '20 at 11:13
  • Can you share some code? – Build Succeeded Oct 23 '20 at 11:15
  • @Petesh It would be wise to use GetTickCount64(), but our code uses GetTickCount() massively and we would rather not change this. The anti-analysis environment you mention, are these anti virus applications? or do you have any exemples? – rauhe Oct 23 '20 at 11:16
  • @BuildSucceeded @Build Succeeded The code is quite simple. We have a function that is called often, that does some work every dwInfoSendInterval milliseconds. `function_that_is_called_every_500ms () ... if( ::GetTickCount() - dwLastInfoSend > dwInfoSendInterval ) ) { //Do something dwLastInfoSend = ::GetTickCount(); } ...` We have seen, very seldom, in running systems that `::GetTickCount() - dwLastInfoSend = ~18 days`. This in a system that has been running for 2 years. – rauhe Oct 23 '20 at 11:49
  • 2
    your value is suspiciously close to `0x60000000`, some sort of wrap around bug seems likely especially as your computer has been on for 2 years, please provide a [mre] – Alan Birtles Oct 23 '20 at 12:37
  • Are these few systems running Windows Checked Builds, by any chance? – Michaël Roy Oct 23 '20 at 13:20
  • 1
    *"This is not due to wrap around og int/unsigned int mismatch."* - Much as I'd like to believe this, there are simply too many bugs that come with a description like this. We need to see a [mcve]. – IInspectable Oct 23 '20 at 14:04
  • I have tried to provide a code sample in the original question. However; we only se the error on Windows systems that has been running for around 2 years, so it is very har dto reproduce the error. Note: It is not our application that has been running for 2 yers, but the computer itself. – rauhe Oct 26 '20 at 07:24
  • @MichaëlRoy No, unfortunate, the Windows builds are Windows Server and Windows Professional (in various versions) but no checked build. – rauhe Oct 26 '20 at 07:26
  • @AlanBirtles The value is close to ´0x60000000´ but is there something special with this number? – rauhe Oct 26 '20 at 07:27
  • Not that i know of but being so close to such a round number is pretty unlikely by chance it must be an artifact of whatever is causing your problem (probably that `GetTickCount` isn't very reliable and shouldn't be used for anything important) – Alan Birtles Oct 26 '20 at 07:33
  • @rauhe Has to be asked, did you establish beyond doubt that the difference is in fact between consecutive returns of `GetTickCount` and not, for example, due to some memory corruption that may trash `nLastCheck`? – dxiv Oct 26 '20 at 08:19
  • @dxiv: The chance of memory corruption hitting multiple computers in the exact same two bits (logically, not physically) are essentially zero. – MSalters Oct 26 '20 at 10:51
  • @dxiv No, I havent ruled out that there might be a memory leak but as MSalters also writes, it does not seem very likely. – rauhe Oct 26 '20 at 15:34
  • @rauhe Not likely at all, I agree. But strange happenings sometimes have even stranger causes. – dxiv Oct 26 '20 at 17:02

0 Answers0