[SOLVED] Using IST causes page fault

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
peachsmith
Member
Member
Posts: 44
Joined: Tue Aug 26, 2014 5:07 pm

[SOLVED] Using IST causes page fault

Post by peachsmith »

I'm in 64-bit UEFI world, trying to use the IST since not using it is considered "legacy".

After setting up my GDT, TSS, and IDT, I'm able to handle the divide error with my ISR.
However, this requires me to set the IST index of my interrupt descriptors to 0.

When I set the IST index to 1 in my divide error descriptor, I get a page fault.

I couldn't find anything in the wiki or the Intel manual about actually setting up the IST, so I just assumed that each IST pointer should be the beginning or end of a 16-byte aligned chunk of memory that I'm responsible for allocating.

So my strategy was to declare an array of uint8_t and set the address of the first (or last, I tried both) element in the TSS, (my TSS is an array of uint32_t).

This is what gets printed when I print the address of my IST1 array + 4096: 000000001E42DDE0
This is less than 00000000FFFFFFFF and also within the range of RAM provided to qemu.

Anm I misconfiguring my TSS or IST somehow?

I've built libgcc without the red zone and am compiling my C files with -mno-red-zone


When I add -no-reboot -d int,cpu_reset, this is what I see before the apge fault:

Code: Select all

121: v=0e e=0002 i=0 cpl=0 IP=0008:000000001e4238a3 pc=000000001e4238a3 SP=0010:000000001fea39a0 CR2=fffffffffffffff8
RAX=0000000000000004 RBX=0000000000000000 RCX=0000000000000000 RDX=0000000000000000
RSI=000000001e42ede0 RDI=00000000000001ff RBP=000000001fea39b0 RSP=000000001fea39a0
R8 =0000000000000000 R9 =0000000000000018 R10=0000000080079f40 R11=000000001fea32b0
R12=0000000000000000 R13=0000000000000001 R14=0000000000000001 R15=000000001e666fc4
RIP=000000001e4238a3 RFL=00000206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
CS =0008 0000000000000000 ffffffff 00af9e00 DPL=0 CS64 [CR-]
SS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
DS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
FS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
GS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0018 000000000042cd70 ffffffff 009f8900 DPL=0 TSS64-avl
GDT=     000000001e42cd40 00000027
IDT=     000000001e42ede0 000001ff
CR0=80010033 CR2=fffffffffffffff8 CR3=000000001fc01000 CR4=00000668
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000010 CCD=000000001fea39a0 CCO=SUBQ    
EFER=0000000000000d00
and here is the output just before successfully handling the divide eror when my IST index is set to 0:

Code: Select all

116: v=00 e=0000 i=0 cpl=0 IP=0008:000000001e423893 pc=000000001e423893 SP=0010:000000001fea39a0 env->regs[R_EAX]=0000000000000004
RAX=0000000000000004 RBX=0000000000000000 RCX=0000000000000000 RDX=0000000000000000
RSI=000000001e42ede0 RDI=00000000000001ff RBP=000000001fea39b0 RSP=000000001fea39a0
R8 =0000000000000000 R9 =0000000000000018 R10=0000000080079f40 R11=000000001fea32b0
R12=0000000000000000 R13=0000000000000001 R14=0000000000000001 R15=000000001e666fc4
RIP=000000001e423893 RFL=00000206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
CS =0008 0000000000000000 ffffffff 00af9e00 DPL=0 CS64 [CR-]
SS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
DS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
FS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
GS =0010 0000000000000000 ffffffff 00af9700 DPL=0 DS   [EWA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0018 000000000042cd70 ffffffff 009f8900 DPL=0 TSS64-avl
GDT=     000000001e42cd40 00000027
IDT=     000000001e42ede0 000001ff
CR0=80010033 CR2=0000000000000000 CR3=000000001fc01000 CR4=00000668
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000010 CCD=000000001fea39a0 CCO=SUBQ    
EFER=0000000000000d00

My attempt at creating an IST in assembly:

Code: Select all

.align 16
ist1_bottom:
.skip 16384 # 16 KiB
ist1_top:
My TSS and IST setup code in C:

Code: Select all

// The TSS
uint32_t tss[26];

// IST1 stack?
uint8_t ist1[8192]__attribute__((aligned(16)));

// stack labels from an assembly file
extern void* ist1_top;
extern void* ist1_bottom;


void k_load_gdt()
{
  gdt[0] = 0; // null descriptor

  // code segment descriptor
  // type: execute/read, conforming, not yet accessed
  gdt[1] = build_cd_descriptor(0, 0x0FFFFF, CD_SEG_ERC);

  // data segment descriptor
  // type: read/write, expand down, not yet accessed
  gdt[2] = build_cd_descriptor(0, 0x0FFFFF, CD_SEG_RWD);

  // Initialize all the bits of the TSS to 0.
  // This is probably unnecessary, but I want to be extra confident
  // that I know what the values are before I change them.
  for (int i = 0; i < 26; i++)
  {
    tss[i] = 0;
  }

  // Try to put the IST1 stack pointer in the TSS.
  // According to the docs, the lower 32 bits should go in tss[9],
  // and the higher 32 bits should go in tss[10].
  tss[9] = (uint32_t)(((uint64_t)(ist1+4096) & 0xFFFFFFFF));
  tss[10] = (uint32_t)(((uint64_t)(ist1+4096) & 0xFFFFFFFF00000000) >> 32);

  // In 64-bit mode, a TSS descriptor is 128 bits, so we use two descriptors
  // to represent the low and high bits.
  seg_desc tss_lo = 0;
  seg_desc tss_hi = 0;

  // Configure the TSS descriptor as available with a granularity of 4KB
  // and a limit of 0x0FFFFF.
  tss_lo |= ((uint64_t)0xFFFF); // limit (low 16 bits)
  tss_lo |= ((uint64_t)0x0F0000 << 32); // limit (high 4 bits)
  tss_lo |= ((uint64_t)0x9 << 40); // type:        1001 available
  tss_lo |= ((uint64_t)0x1 << 47); // present:     true
  tss_lo |= ((uint64_t)0x1 << 55); // granularity: 4KB

  // The TSS base address is split over the high and low
  // descriptor portions.
  tss_lo |= (((uint64_t)tss & 0xFF000000) << 24);
  tss_lo |= (((uint64_t)tss & 0x00FFFFFF) << 16);
  tss_hi |= (((uint64_t)tss & 0xFFFFFFFF00000000) >> 32);

  // Put the TSS descriptor in the GDT.
  gdt[3] = tss_lo;
  gdt[4] = tss_hi;

  uint16_t limit = sizeof(gdt) - 1;

  // load the GDT
  k_lgdt(limit, gdt);

  // load the TSS
  k_ltr(0x18);
}

Another attempt at putting IST1 into TSS using the assembly label:

Code: Select all

// Using the assembly label ist1_top
tss[9] = (uint32_t)(((uint64_t)ist1_top & 0xFFFFFFFF));
tss[10] = (uint32_t)(((uint64_t)ist1_top & 0xFFFFFFFF00000000) >> 32);
Last edited by peachsmith on Sat Nov 28, 2020 3:37 pm, edited 1 time in total.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Using IST causes page fault

Post by nullplan »

peachsmith wrote:I'm in 64-bit UEFI world, trying to use the IST since not using it is considered "legacy".
No it's not. Besides, there are only seven IST entries, and two hundred fifty six interrupts, obviously you cannot have one IST for each of them. Common is to not use ISTs at all, except for the very few exceptions that could happen at any time, those being NMI, machine check, and double-fault. And each of those gets its own stack per CPU. All other exceptions and interrupts can just be handled by the normal interrupt mechanism.

ISTs are necessary in 64-bit mode, since the previous solution for loading a fixed stack on interrupt, task gates, is no longer available to us.
peachsmith wrote:I couldn't find anything in the wiki or the Intel manual about actually setting up the IST, so I just assumed that each IST pointer should be the beginning or end of a 16-byte aligned chunk of memory that I'm responsible for allocating.
Kind of, yes. Since on x86 the stack grows down, you have to present the highest address (might actually be the address one past the end of the stack, since that value will be decremented before pushing).
peachsmith wrote:Anm I misconfiguring my TSS or IST somehow?
Yes. Look at the failing listing. Look at the base address of TR. Notice something? I suspect something about the creation of the TR entry in the GDT is not right. That top byte does not appear to make it. That number is the same in the working listing, but in that case, you don't really care about the TSS since you are not using anything from it.
Carpe diem!
User avatar
peachsmith
Member
Member
Posts: 44
Joined: Tue Aug 26, 2014 5:07 pm

Re: Using IST causes page fault

Post by peachsmith »

nullplan wrote:That top byte does not appear to make it.
Whoops. I was not shifting the bits over far enough, so they were immediately getting overwritten by the lowest 24 bits.
Turns out I had the same issue with the base address in the the other descriptors in my GDT, but the bug wasn't manifesting there because the base address happens to be 0.

My eyes just skipped right over that missing byte. Good catch.

The corrected insertion of the TSS base address into the descriptor:

Code: Select all

tss_lo |= (((uint64_t)tss & 0xFF000000) << 32);
tss_lo |= (((uint64_t)tss & 0x00FFFFFF) << 16);
tss_hi |= (((uint64_t)tss & 0xFFFFFFFF00000000) >> 32);
Post Reply