Programming the APIC to avoid fault when loading IDT (x64)
- acccidiccc
- Member
- Posts: 38
- Joined: Sun Mar 21, 2021 1:09 pm
- Location: current location
Programming the APIC to avoid fault when loading IDT (x64)
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
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
-
- Member
- Posts: 5567
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Programming the APIC to avoid fault when loading IDT (x6
Why would migrating to long mode require migrating to APIC? You know those two things are separate, right?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?
The LIDT instruction can cause faults.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?).
- acccidiccc
- 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
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
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
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;
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
- acccidiccc
- 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
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
- acccidiccc
- 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
again, made a subroutine that prints the text supplied to the %rax register.
prints semi correct values of the supplied stuff, written in c
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.
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]);
}
}
the order supplied is wrong. I have identifed the issue at hand.
iustitiae iniustos iudicat
Re: Programming the APIC to avoid fault when loading IDT (x6
So, here's how I load the 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.
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
Carpe diem!
- acccidiccc
- 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
so your function does
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?
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
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
- acccidiccc
- 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
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
... 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
Re: Programming the APIC to avoid fault when loading IDT (x6
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.acccidiccc wrote:so your function doesthanks for your reply! this hopefully can help me. but couldn't you have loaded the idt directly. Not very experienced when it comes to assemblyCode: 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
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?
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));
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?acccidiccc wrote:... but, it hangs at the lidt instruction for no apparent reason
Carpe diem!
- acccidiccc
- 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
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
showed at first a general protection fault. and now a page fault with the following address in the cr2 address.
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.
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
Code: Select all
ffff802054e01000
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.
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?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?
iustitiae iniustos iudicat
Re: Programming the APIC to avoid fault when loading IDT (x6
Yes, you actually have to read your arguments the same way the compiler passes them in.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.
Be my guest. That's why I posted it.acccidiccc wrote: can i use your solution?
Carpe diem!
- acccidiccc
- 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
thanks a lot!
iustitiae iniustos iudicat
- acccidiccc
- 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
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
Re: Programming the APIC to avoid fault when loading IDT (x6
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.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.
Carpe diem!
-
- Member
- Posts: 5567
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Programming the APIC to avoid fault when loading IDT (x6
It's documented: version 1 and version two.nullplan wrote:Unless limine is actually documenting the selector values,
But you still need to set up your own GDT, so using the bootloader's selectors in your IDT seems like a poor choice.