I had this problem for a long time in my Windows XP machine. I had set up my monitor to turn off after 3 minutes of inactivity for power saving reasons. I, also, had disabled the screensaver although the last time it was enabled it was set to appear after 1 minute of inactivity. This piece of information will become significant latter on.
The problem was that sometimes my monitor could not go into power saving mode. I had absolutely no idea why such a thing happened and I couldn’t correlate this problem with any user action I was performing each time the problem arose. I knew that I could fix this problem by just clicking Apply in the Power Options control panel applet and I, also, had figured out that the problem disappeared if I adjusted the inactivity period down to 1 minute.
If you don’t want to go into all the technical details that will follow, you may want to know that the cause for this problem is the Adobe Flash player. The problem happens after viewing a Flash video (e.g. YouTube) and there is a bug report for this issue: FP-5216. If you want to learn more please continue reading and/or watch the screen-cast: Part 1 and Part 2.
So, in the last few days, I decided to finally find the cause (and a possible solution) for this problem. The tools and references I used include:
The process will be described in discrete steps:
- The first thing I had to do was to discover where the monitor power off timeout was stored, who stored it and who accessed it. I started the Power Options control panel applet, then I started Process Monitor, made a change in the applet, clicked Apply and observed the output of Process Monitor. By inspecting the stack traces I was able to discover that ntoskrnl!NtPowerInformation was the function that stored the power policy into the registry (HKLM\System\CurrentControlSet\Control\Session Manager\Power\AcPolicy). This registry key is binary, so I couldn’t extract any more information from it.
- The next step was to find about the NtPowerInformation function. So, I looked the Windows 2000 source code for references and I found some. The most interesting was the reference from the Power Options control panel applet itself (win2k/private/windows/shell/cpls/powrprof/powrprof.c). The most interesting function call looked like:
NtPowerInformation(SystemPowerPolicyAc, InputBufferAc, sizeof(SYSTEM_POWER_POLICY), OutputBufferAc, sizeof(SYSTEM_POWER_POLICY));
So, I had to find more information about the SYSTEM_POWER_POLICY structure. Fortunately, it is defined inside winnt.h and also has a DWORD member named VideoTimeout (offset 0xC0).
- My next step was to open ntoskrnl.exe into IDA Pro and try to understand what NtPowerInformation did. Turns out that it calls PopApplyPolicy, which in turn does a memcpy of the given SYSTEM_POWER_POLICY structure to PopAcPolicy (which resides in kernel memory).
- So, I used SoftICE to set a memory breakpoint at PopAcPolicy+0xC0 in order to determine who accessed the VideoTimeout field. The field was accessed from PopDispatchPolicyCallout.
- Back to IDA Pro where I could see that PopDispatchPolicyCallout was sending the VideoTimeout field as a parameter to a function pointed by PopEventCallout. Now, I just had to learn where this pointer goes.
- Back to SoftICE, it was easy to see that PopEventCallout contained the address of win32k!UserPowerEventCallout.
- Now, I started reading the Windows 2000 source code. UserPowerEventCallout was in file win2k/private/ntos/w32/ntuser/kernel/power.c, it calls QueuePowerRequest, which calls xxxUserPowerCalloutWorker, which calls xxxUserPowerEventCalloutWorker, which does something like that:
xxxSystemParametersInfo(SPI_SETLOWPOWERTIMEOUT, VideoTimeout, 0, 0); xxxSystemParametersInfo(SPI_SETPOWEROFFTIMEOUT, VideoTimeout, 0, 0);
I looked for xxxSystemParametersInfo and found it inside win2k/private/ntos/w32/ntuser/kernel/rare.c. This function contains a huge switch statement. The SPI_SETLOWPOWERTIMEOUT and SPI_SETPOWEROFFTIMEOUT cases set two global variables: giLowPowerTimeOutMs and giPowerOffTimeOutMs respectively.
- Now, the question is: Who reads these variables? Again, it’s time for SoftICE. I installed a memory breakpoint at these variables and within 1 second it was hit! The variables were accessed by win32k!IdleTimerProc.
- Back to reading source code. IdleTimerProc is defined in win2k/private/ntos/w32/ntuser/kernel/input.c. First it check if it is time to display the screen saver and then it checks if it is time to turn off the monitor.
- How does it check if it is time to take action? It calls IsTimeFromLastInput (defined in win2k/private/ntos/w32/ntuser/kernel/userk.h), which uses a global structure named glinp of type LASTINPUT. LASTINPUT is defined in win2k/private/ntos/w32/ntuser/kernel/userk.h and contains a field named timeLastInputMessage. This field, probably, holds the timestamp of the last user activity.
- How does it turns off the monitor? Well, it is simple:
PostMessage(gpqForeground->spwndActive, WM_SYSCOMMAND, SC_MONITORPOWER, LOWPOWER_PHASE); PostMessage(gpqForeground->spwndActive, WM_SYSCOMMAND, SC_MONITORPOWER, POWEROFF_PHASE);
It sends a message to the foreground window. And what happens next? Well, probably the foreground window calls DefWindowProc and the message is handled internally. I looked the source code for references to SC_MONITORPOWER and found that this message is handled by the function xxxSysCommand which resides in win2k/private/ntos/w32/ntuser/kernel/syscmd.c. xxxSysCommand does a DrvSetMonitorPowerState call which probably goes directly to video card driver and that’s it!
- By now, I had all the information that I needed. The question, though, remains: Where is the problem?
- First I thought that it might be a video card driver problem. So, I just wrote a simple Windows application, that posted the above mentioned message into its message loop and… tada… the monitor turned off!
- Then, I thought that it might be a rogue foreground window that does not call DefWindowProc. I didn’t check that. Besides the problem usually happened when I had only my desktop appear and no other active foreground windows.
- A wild guess now… let’s monitor the glinp structure. I’ve written a small driver that reads kernel memory and a small user-mode application that displays it every few seconds. Let it run and… boom… after exactly 1 minute of inactivity the glinp gets reset. How does that happen?
- I went to SoftICE and set a memory breakpoint there. A little bit tricky because glinp was changed when I exited SoftICE resulting in an endless loop. Fortunatelly, SoftICE supports conditional breakpoints. After 1 minute, I found that glinp was reset from winlogon!RunScreenSaver.
- I went to IDA Pro and found out that RunScreenSaver calls SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT) which in turn resets glinp as seen from the source file win2k/private/ntos/w32/ntuser/kernel/rare.c. Actually RunScreenSaver does that only when it was unable to start the screen saver.
- Why is the screen saver trying to activate itself? It shouldn’t do that. I check the global variable giScreenSaveTimeOutMs with SoftICE. Weird… it’s positive. When I booted the machine it was negative. Negative means that the screen saver is disabled. Somebody changed this value. But the code that changes this value inside win2k/private/ntos/w32/ntuser/kernel/rare.c takes care to maintain the sign of it. Now, somebody is doing something nasty. Let’s reset the value to negative and set a memory breakpoint through SoftICE to it. Now, let’s do our normal job on the computer. Hmm… nothing happens… Now what? Never mind, let’s watch a YouTube video! Bingo… the value is changed. npswf32.dll did it! Adobe Flash player plugin did it! How does it do it? Well…
int x; SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, &x, 0); SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT, 0, 0, 0); SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT, x, 0, 0);
The above code snippet, changes the negative value into a positive one!
So the mystery is solved! How do I fix it? I set the screen saver wait time to 20 minutes and then disable it. Also, another way is just to open the Screen Saver control panel applet and click OK every time this problem happens and everything resets back to normal.