Using JTAG for Interrupt Handler Research

I’ve always been interested in how interrupts work on x86 targets. Using JTAG with our SourcePoint debugger, I’ve explored the behavior of interrupt dispatching internals in a new way. This article describes the functionality of the Interrupt Descriptor Table (IDT) as an Intel target is booted from the reset vector, through UEFI, and into Windows.

Recently, I read a really interesting article on Interrupt Dispatching that piqued my interest in having a look at interrupt low-level behavior using SourcePoint. As usual, I learn something new every time I delve into some new aspect of x86 using JTAG.

SourcePoint has the unique capability of displaying the Interrupt Descriptor Table (IDT) in a very visual way, and looking at the code of the interrupt service routines, setting breakpoints therein, etc.

So, let’s begin….you can follow along if you have a copy of our debugger.

Using SourcePoint to reset the target and halt at the reset vector, you can see that the IDT table is empty, since it has not been defined yet:

Hit Go. Wait one second. Hit Stop. You’ll see something like this:

The target is in protected mode, at 0010:FFDA8D48, as you can see from the Code window. This address is random, of course, based on the stopping point. SourcePoint tells you the mode the target is in down at the bottom right, where “Protected” and “Stopped” can be seen.

The IDT is now populated, up to index decimal 33, or x’21’.

Interestingly, the last two entries are bogus. Memory hasn’t been initialized yet.

From the IDT Descriptors window Title Bar, note that IDTBAS is x’6142C004, and IDTLIM is x’10F at this part of the boot process. You can also see the value of these registers within the SourcePoint Registers window, and select Segment:

Following the steps above, on this Tiger Lake board, the interrupt handlers are always at the same addresses: in this case, the Divide Error routine begins at x’5DFBD2C0. IDTBAS, IDTLIM and the interrupt handler entry points are always the same, from boot to boot.

It is possible to see the code associated with the interrupt handler, and set a breakpoint at it, by right-clicking:

Now hit Go within SourcePoint, and hold down the F7 key to stop at the UEFI password prompt. Hit Stop.

At the UEFI shell, the IDT looks like this:

To make it more readable, the text in SourcePoint’s IDT Descriptors table, in table form, for the first part of the IDT is as follows:

Name Offset Type Attributes Values
Divide Error 0000 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE018
Debug Exception 0010 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE027
NMI Interrupt 0020 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE036
Breakpoint 0030 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE045
INTO-detected Overflow 0040 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE054
BOUND Range Exceeded 0050 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE063
Invalid Opcode 0060 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE072
Device Not Available 0070 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE081
Double Fault 0080 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE090
Intel reserved 0090 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE09F
Invalid TSS 00A0 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE0AE
Segment Not Present 00B0 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE0BD
Stack Fault 00C0 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE0CC
General Protection Fault 00D0 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE0DB
Page Fault 00E0 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE0EA
Intel reserved 00F0 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE0F9
Floating Point Error 0100 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE108
Alignment Check 0110 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE117
Machine Check 0120 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE126
Intel reserved 0130 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE135
Intel reserved 0140 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE144
Intel reserved 0150 Gate Interrupt 64-bit: P=1 DPL=0 IST=0 S=0038 O=00000000_640DE153

At this stopping point, IDTLIM is x’0FFF, IDTBAS is x’62A1B018, and the Divide Error interrupt error handler address is x’640DE018. These stay consistent, from boot to boot.

Now let’s boot to Windows and see how things change.

Upon the first halt, at the Windows desktop, the IDT looks like this:

And the Segment Registers look like the below:

A really interesting thing is that when I took this snapshot, the IDTLIM is x’FFFF. The target is in Host mode (in hvix64). According to the Intel Software Developers Manual (SDM), the maximum IDTLIM should be x’FFF.

Setting a VM Resume breakpoint and landing in Guest mode sets IDTLIM to the expected x’FFF:

I’ll write some more about this in an upcoming blog. In particular, I’ll look at setting interrupt handler breakpoints at various stages of the boot process and capturing some trace data (LBR trace, and Intel Processor Trace) when the breakpoints are hit.

Interested in learning more about SourcePoint and some of its unique features? I’d recommend watching my prior webinar on Hyper-V debug here.