There are few people in the Windows system software community as helpful, or as authoritative, as Microsoft's Jake Oshins. So, when he recently posted on the NTDEV list an explanation of an oft-repeated question, I thought I should memorialize it in a memo (with his permisision, of course).
The famous question: "When I look at a crash dump, or when I stop the debugger, and I look at the IRQL as shown in the PCR (using the !PCR command to WinDbg or KD), the IRQL is always shown as 0xFF! Why? Does this mean that interrupts were disabled and that the system was running at IRQL HIGH_LEVEL when the crash occurred?"
Here's Jake's answer, in its entirety:
When you look in the PCR with !pcr in the debugger, you're looking at what has been written into some scratchpad space that may or may not be currently in use by the HAL. Several HALs use the PCR to track IRQL. Other HALs use the TPR (Task Priority Register) in the Local APIC. Still others may have their own implementations. So you should always consider the value of IRQL in the PCR to be suspect, simply because it may not be in use. A value like 0xFF, which corresponds to no actual IRQL should immediately clue you in to the fact that the HAL that you're using ignores the value in the PCR.
Furthermore, you need to understand what, exactly, the debugger really is and what it really does. (This discussion applies to the debugger that is built into the NT kernel, and not Soft-ICE or an Arium or an ITP that is debugging from outside of the kernel.) The debugger is just a piece of code that runs within the kernel, trying to depend on as few services from outside itself as possible. In order to become more deterministic, it disables interrupts whenever it is entered. The NT way of disabling interrupts securely and safely is to raise IRQL to HIGH_LEVEL. Thus, whenever you're in the debugger, IRQL is actually HIGH_LEVEL, regardless of what it was when it was executing the code that you were trying to debug.
Consequently, looking at the IRQL in the PCR from the debugger should always tell you HIGH_LEVEL if the PCR is actually being used to track IRQL. If it tells you anything else, it's meaningless. (This assumes that you actually found some meaning in asking what the current IRQL is while you're in the debugger, as you knew a-priori it should be HIGH_LEVEL.) This point also applies if you try to read the APIC TPR on a HAL that uses the TPR to track IRQL. It should always correspond to HIGH_LEVEL while you're in the debugger.
The only way you can reliably find out what IRQL your code was running at is to look at where the debugger pushed your IRQL onto the stack when the debugger was entered. Interestingly, this is beyond the end of the stack frame that the debugger itself displays to you, as the debugger assumes that you are primarily interested in the code that you wrote, not the debugger itself. So it pretends that the stack ends at your code, not automatically displaying the part of the stack that the debugger itself is currently running on.
Lastly, the WinDbg team has taken note in recent years that lots of people would be interested in knowing what IRQL their code was running at. Unfortunately, they noticed this first for people who were trying to debug crashdumps and only later (at my begging) extended their code to be able to tell you for a running system.
So you can know what IRQL code was running at on Windows XP and later when debugging a crashdump by using !irql. If you are debugging Windows Server 2003 or later, !irql will give you an accurate answer even if you're debugging a live system. !irql does exactly what I suggested above. It looks at the IRQL that the debugger itself stored as it was being entered.
Please, let us never see another posting saying that IRQL was 0xFF.
Of course, Jake provided this posting "as is" with no warranties, and this posting by him confers no rights.