Programming the APIC to avoid fault when loading IDT (x64)

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.
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Programming the APIC to avoid fault when loading IDT (x64)

Post by acccidiccc »

Hello, I have a question. How to reprogram the local APIC, more specifically to enable interrupts. I migrated to x86_64 and I need to port my Idt handling. The PIC has a initialization command for the slave and master, but IIRC the APIC doen't have one. How do I initialize it. Do I have to do it at all? The reason I'm reprogramming it is because the computer crashes at the lidt instruction, as IIRC this only happens in this case, as it would just load a wrong IDT and this does not cause a fault (maybe it is the sti?).
what I got is that I have to disable the PIC, would that solve my problem?

EDIT:
isn't the sti tied to the APIC, if that is the case, that probably causes it
iustitiae iniustos iudicat
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by Octocontrabass »

acccidiccc wrote:I migrated to x86_64 and I need to port my Idt handling. The PIC has a initialization command for the slave and master, but IIRC the APIC doen't have one. How do I initialize it. Do I have to do it at all?
Why would migrating to long mode require migrating to APIC? You know those two things are separate, right?
acccidiccc wrote:The reason I'm reprogramming it is because the computer crashes at the lidt instruction, as IIRC this only happens in this case, as it would just load a wrong IDT and this does not cause a fault (maybe it is the sti?).
The LIDT instruction can cause faults.
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

Hello, sorry, I worded myself wrong. I needed to reprogram the IDT, not the APIC, but as the PIC is obsolete, I figured to support the APIC instead of the obsolete PIC. sorry for the wrong wording. Thanks for the link, it helped me. now maybe it gets cause because the thing that is passed on is a

Code: Select all

uint64_t ptr[2]; 

Code: Select all

uint64_t idtAddr = (unsigned long long) IDT;
ptr[0] = ((sizeof (struct IDT_entry) * 256) + ((idtAddr & 0xff ff ff ff) << 32)); // the shift is invalid
ptr[1] = idtAddr >> 32;

Code: Select all

uint64_t idtAddr = (unsigned long long) IDT;
ptr[0] = ((sizeof (struct IDT_entry) * 256) + ((idtAddr & 0xff ff ff ff) << 16)); // fixes the first pionter, still faults, even as the PIC is initialized
ptr[1] = idtAddr >> 32;
adding a couple printfs to see the ptr's value reveals that
thanks for the link to the lidt instruction "manual"
idtAddr = FFFFFFFF802051A0;
ptr [0] = 802051A000001000; // that meas that 0x00001000 gets loaded first, but this is a unsigned long (uint32_t) ; that probably cause the crash
ptr [1] = 00000000FFFFFFFF;


this however is besides the point. How do I initialize the APIC. I mean it also is useful for the MP, as they provide a tool to communicate via the I/O APIC. It just seems more useful for modern systems. As I want to make my OS as viable for servers as possible, and as they often feature multiprocessing, APIC support seems mandatory. Correct me if i'm wrong. I can use the PIC code for now, but again it seems more useful to use APIC, 1: because outb is slow, and that puts a cap on the speed of the os, as interrupts need to send through I/O ports. I rather would write to a memory address because of the speed.
by the way, I used the PIC code of the interrupts tutorial. Maybe it doesn't work in long mode, even though I see no reason for that.
by the way I use stivale, it says that interrupts are masked. how do I de-mask them? maybe that causes it?w
iustitiae iniustos iudicat
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

managed to get bochs working, it said 3rd exception unhandled (13). so it triggers a general protection exception. as it says, on the page you sent me, it gets cause when the address is in a non conanical form. As the CPL is probably 0, it's the supplied address. now my only problem is getting gdb to work with the virtual address as it doesn't want (Cannot access memory at address 0xffffffff80200508). This probably got caused by my lack of knowlegde when it comes to gas.
iustitiae iniustos iudicat
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

again, made a subroutine that prints the text supplied to the %rax register.

prints semi correct values of the supplied stuff, written in c

Code: Select all

void printhex (uint64_t *k) { 
// for arrays
       for (int i = 0; i <= <array size>; i++) {
            printf("as hex %x", k[i]);
       }
}
this prints uint16_t though. maybe this has something to do with the nature of the %rax register.
the order supplied is wrong. I have identifed the issue at hand.
iustitiae iniustos iudicat
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by nullplan »

So, here's how I load the IDT:

Code: Select all

/* void load_idt(const void *base, size_t len); */
.global load_idt
.type load_idt,@function
load_idt:
    addq $-16, %rsp
    decq %rsi
    movq %rdi, 8(%rsp)
    movw %si, 6(%rsp)
    lidtq 6(%rsp)
    addq $16, %rsp
    retq
.size load_idt,.-load_idt
The problem with doing this in C is that you would need a packed structure consisting of a two-byte length mask and an eight-byte pointer. It is possible to create such a thing in C, but cumbersome. But also, I had already resolved to do all the assembler things in external assembler functions, and so here is my function. It works. And if you want to tell me that it is slow: It runs once at startup, and once more on each AP that comes online. I believe performance does not matter under these circumstances.
Carpe diem!
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

so your function does

Code: Select all

.global load_idt
.type load_idt,@function
load_idt:
    addq $-16, %rsp # remove 16 from stack register, so you can do stuff as 8(%rsp). presumably
    decq %rsi #decrease source register. to underflow it?
    movq %rdi, 8(%rsp) #move the IDT pointer to rdi
    movw %si, 6(%rsp) #move the len? to the index register
    lidtq 6(%rsp) #load the idt with the limit with the limit first
    addq $16, %rsp # add the 16 back to the rsp
    retq #return
.size load_idt,.-load_idt #get the size. i have no idea why what i found was to set the function size
thanks for your reply! this hopefully can help me. but couldn't you have loaded the idt directly. Not very experienced when it comes to assembly
the si is for indexing, the rsi and rdi for copying, as far as i read. why use them?

EDIT the rsi and rdi are for the ABI right?
iustitiae iniustos iudicat
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

ok i solved. I think i am terminally stupid. my solution worked. i used the esp register instead of the rsp register. now it doesn't triple fault anymore. lidt works. meine fresse

... but, it hangs at the lidt instruction for no apparent reason
EDIT:
it only manages to not cause a triple fault with my qemu options, also it doesn't show me the interrupts correctly. still causes 0xd
iustitiae iniustos iudicat
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by nullplan »

acccidiccc wrote:so your function does

Code: Select all

.global load_idt
.type load_idt,@function
load_idt:
    addq $-16, %rsp # remove 16 from stack register, so you can do stuff as 8(%rsp). presumably
    decq %rsi #decrease source register. to underflow it?
    movq %rdi, 8(%rsp) #move the IDT pointer to rdi
    movw %si, 6(%rsp) #move the len? to the index register
    lidtq 6(%rsp) #load the idt with the limit with the limit first
    addq $16, %rsp # add the 16 back to the rsp
    retq #return
.size load_idt,.-load_idt #get the size. i have no idea why what i found was to set the function size
thanks for your reply! this hopefully can help me. but couldn't you have loaded the idt directly. Not very experienced when it comes to assembly
the si is for indexing, the rsi and rdi for copying, as far as i read. why use them?

EDIT the rsi and rdi are for the ABI right?
Yeah, the comment i put above the function was important. That's how the function is defined in C. I decided to take the pointer in RDI (first argument) and the length in RSI (second argument). However, the size must be below 65536 for architectural reasons, so I can just refer to SI instead of RSI, it means the same thing.

And no, the decrement is not to underflow it. It is just because the first word of the IDTR does not contain the length, but the limit. Which in practice always one less than the length in bytes. The objective is to construct an area in memory that is 10 bytes long, in which the first 2 bytes consist of the limit and the remaining 8 bytes consist of the pointer. I think you read the moves the wrong way around: In AT&T syntax, "movq %rdi, 8(%rsp)" moves the contents of RDI to [RSP+8].

You can, in theory, do the same thing in C like this:

Code: Select all

uint16_t idtr[5] = { len - 1, addr, addr >> 16, addr >> 32, addr >> 48};
asm("lidt %0" : "m"(idtr));
However, I have no idea what the right clobbers and the right specifiers are.
acccidiccc wrote:... but, it hangs at the lidt instruction for no apparent reason
OK, that is weird. LIDT should either succeed or cause an exception. I can only presume you have managed to trigger a bug. Exception 13 is still #GP, and the manual says that happens when the address is non-canonical. Given you already f*ed up the 32-bit/64-bit thing once, is it possible you are only copying 32 bits of the address into the IDTR?
Carpe diem!
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

thanks for the explanation. now it makes sense. Read the wiki article and they just used the %rdi and %rsi registers as integer storage. Man i feel dumb. you pass the values onto the stack, and then load the contents of the stack into the lidt register, starting with the limit of two byte size, and the 8 byte address.
the code i was using was probably valid for the simpler cdecl calling convention, which was to pop thinks from the stack. however, this is not advisable on a x86_64 system.
the emulator hanged with specific option, using a barebones

Code: Select all

qemu-system-x86_64 -d int /dev/sdb # the usb stick containing the os
showed at first a general protection fault. and now a page fault with the following address in the cr2 address.

Code: Select all

ffff802054e01000
the 1000 seems familiar.
the cpu called the address in the idtr register. which was not a valid address and triggered a page fault. the address of the idt contains garbage and a general protection fault gets caused and doesn't get resolved, double fault, which also doesn't get resolved and boom: triple fault. this is my guess as this is the order of exceptions shown on qemu.
nullplan wrote:Given you already f*ed up the 32-bit/64-bit thing once, is it possible you are only copying 32 bits of the address into the IDTR?
actually that was the thing i fixed, i was using the %ebp register, now i am using the %rbp register, which kinda fixed things. can i use your solution?
iustitiae iniustos iudicat
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by nullplan »

acccidiccc wrote:the code i was using was probably valid for the simpler cdecl calling convention, which was to pop thinks from the stack. however, this is not advisable on a x86_64 system.
Yes, you actually have to read your arguments the same way the compiler passes them in.
acccidiccc wrote: can i use your solution?
Be my guest. That's why I posted it.
Carpe diem!
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

thanks a lot! :)
iustitiae iniustos iudicat
User avatar
acccidiccc
Member
Member
Posts: 38
Joined: Sun Mar 21, 2021 1:09 pm
Location: current location

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by acccidiccc »

solved it. the gdt code segment i used was invalid for the gdt setup by limine. for anybody using limine and trying to setup the idt, change the selector from 0x08 to 0x28.
iustitiae iniustos iudicat
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by nullplan »

acccidiccc wrote:solved it. the gdt code segment i used was invalid for the gdt setup by limine. for anybody using limine and trying to setup the idt, change the selector from 0x08 to 0x28.
Better idea: Load your own GDT before loading an IDT. Unless limine is actually documenting the selector values, you should not trust them. And loading your own GDT is required at some point anyway, so may as well do it.
Carpe diem!
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Programming the APIC to avoid fault when loading IDT (x6

Post by Octocontrabass »

nullplan wrote:Unless limine is actually documenting the selector values,
It's documented: version 1 and version two.

But you still need to set up your own GDT, so using the bootloader's selectors in your IDT seems like a poor choice.
Post Reply