Page 1 of 2

Interrupt Handling Issue

Posted: Wed Aug 09, 2023 9:10 am
by Xodein
Im having a problem when i try to handle interrupt request i tried it on virtualbox it gives me VINF_EM_TRIPLE_FAULT error and terminates the vbox. i tried it on my physical machine but it terminates when i try to start it. im using C++ and following a tutorial on youtube i double checked the code but i couldn't find the problem. Can someone help me please?

gdt.cpp

Code: Select all

#include "gdt.h"

GlobalDescriptorTable::GlobalDescriptorTable():
nullSegmentSelector(0,0,0),
freeSegmentSelector(0,0,0),
codeSegmentSelector(0,64*1024*1024,0x9A),
dataSegmentSelector(0,64*1024*1024,0x92)
{
	uint32_t i[2];
	i[0] = (uint32_t)this;
	i[1] = sizeof(GlobalDescriptorTable) << 16;

	asm volatile("lgdt (%0)": : "p" (((uint8_t *) i)+2));
}

GlobalDescriptorTable::~GlobalDescriptorTable()
{
}

uint16_t GlobalDescriptorTable::DataSegmentSelector(){
	return (uint8_t*)&dataSegmentSelector - (uint8_t*)this;
}

uint16_t GlobalDescriptorTable::CodeSegmentSelector(){
	return (uint8_t*)&codeSegmentSelector - (uint8_t*)this;
}

GlobalDescriptorTable::SegmentDescriptor::SegmentDescriptor(uint32_t base, uint32_t limit, uint8_t flags){
	uint8_t* target = (uint8_t *)this;

	if(limit <= 65536){
		target[6] = 0x40;
	}
	else{
		if((limit&0xFFF) != 0xFFF){
			limit = (limit>>12)-1;
		}
		else{
			limit = limit>>12;
		}

		target[6] = 0xC0;
	}
	target[0] = limit & 0xFF;
	target[1] = (limit>>8) & 0xFF;
	target[6] |= (limit>>16) & 0xF;

	target[2] = base & 0xFF;
	target[3] = (base>>8) & 0xFF;
	target[4] = (base>>16) & 0xFF;
	target[7] = (base>>24) & 0xFF;

	target[5] = flags;
}

uint32_t GlobalDescriptorTable::SegmentDescriptor::Base(){
	uint8_t* target = (uint8_t*)this;

	uint32_t result = target[7];

	result = (result<<8) + target[4];
	result = (result<<8) + target[3];
	result = (result<<8) + target[2];

	return result;
}

uint32_t GlobalDescriptorTable::SegmentDescriptor::Limit(){

	uint8_t* target = (uint8_t *)this;
	uint32_t result = target[6] & 0xF;

	result = (result<<8) + target[1];
	result = (result<<8) + target[0];

	if((target[6] & 0xC0) == 0xC0){
		result = (result<<12) | 0xFFF;
	}
	return result;
}
interrupts.cpp

Code: Select all

#include "interrupts.h"


void printf(char* str);
InterruptManager::GateDescriptor InterruptManager::interruptDescriptorTable[256];

void InterruptManager::SetInterruptDescriptorTableEntry(uint8_t interruptNum, uint16_t codeSegmentSelectorOffset,void (*handler)(), uint8_t DescriptorAccessRights, uint8_t DescriptorType)
{
	const uint8_t IDT_DESC_PRESENT = 0x80;

	interruptDescriptorTable[interruptNum].handlerAddressLow = ((uint32_t) handler) & 0xFFFF;
	interruptDescriptorTable[interruptNum].handlerAddressHigh = (((uint32_t) handler)>>16) & 0xFFFF;
	interruptDescriptorTable[interruptNum].gdt_codeSegmentSelector = codeSegmentSelectorOffset;
	interruptDescriptorTable[interruptNum].access = IDT_DESC_PRESENT | DescriptorType | ((DescriptorAccessRights&3) << 5);
	interruptDescriptorTable[interruptNum].reserved = 0;
}

InterruptManager::InterruptManager(GlobalDescriptorTable* gdt) : picMasterCmd(0x20), picMasterData(0x21),picWorkerCmd(0xA0), picWorkerData(0xA1)
{
	uint16_t codeSegment = gdt->CodeSegmentSelector();
	const uint8_t IDT_INTERRUPT_GATE = 0xE;

	for (uint16_t i = 0; i<256; i++)
	{
		SetInterruptDescriptorTableEntry(i, codeSegment, &IgnoreInterruptRequest, 0, IDT_INTERRUPT_GATE);
	}
	SetInterruptDescriptorTableEntry(0x20, codeSegment, &handleInterruptRequest0x00, 0, IDT_INTERRUPT_GATE);
	SetInterruptDescriptorTableEntry(0x21, codeSegment, &handleInterruptRequest0x01, 0, IDT_INTERRUPT_GATE);


	picMasterCmd.Write(0x11);
	picWorkerCmd.Write(0x11);

	picMasterData.Write(0x20);
	picWorkerData.Write(0x28);

	picMasterData.Write(0x04);
	picWorkerData.Write(0x02);

	picMasterData.Write(0x01);
	picWorkerData.Write(0x01);

	picMasterData.Write(0x00);
	picWorkerData.Write(0x00);

	interruptDescriptorTablePointer idt;
	idt.size = 256 * sizeof(GateDescriptor) - 1;
	idt.base = (uint32_t) interruptDescriptorTable;

	asm volatile("lidt %0" : : "m" (idt)); 

}
InterruptManager::~InterruptManager(){}

void InterruptManager::Activate(){
	asm("sti");
}

uint32_t InterruptManager::handleInterrupt(uint8_t interruptNum, uint32_t esp)
{

	printf(" INTERRUPT");
	return esp;
} 
kernel.cpp

Code: Select all

#include "types.h"
#include "gdt.h"
#include "interrupts.h"

void printf(char* str){
	static uint16_t* VideoMemory = (uint16_t*) 0xb8000;

	static uint8_t x = 0 , y = 0;

	for (int i = 0; str[i] != '\0'; i++){

		switch(str[i]){
			case '\n':
				y++;
				x = 0;
				break;
			default:
				VideoMemory[80*y+x] = (VideoMemory[80*y+x] & 0xFF00) | str[i];
				x++;
		}
		if(x>= 80){
			y++;
			x = 0;
		}
		if(y>= 25){

			for (y = 0; y < 25; ++y)
			{
				for (x = 0; x < 80; ++x)
				{
					VideoMemory[80*y+x] = (VideoMemory[80*y+x] & 0xFF00) | ' ';
				}
			}
			x = 0; y = 0;
		}
	}
}


typedef void (*constructor)();
extern "C" constructor start_ctors;
extern "C" constructor end_ctors;
extern "C" void callConstructors()
{
	for(constructor* i = &start_ctors; i != &end_ctors; i++){
		(*i)();
	}
}


extern "C" void kernelMain(const void* multiboot_structure, uint32_t /*magicnum*/){
	
	printf("cernOS ilk gosterim\n");

	GlobalDescriptorTable gdt;
	InterruptManager im(&gdt);


	im.Activate();

	while(1);
}

Re: Interrupt Handling Issue

Posted: Sat Aug 12, 2023 11:03 pm
by Octocontrabass
Xodein wrote:i tried it on virtualbox it gives me VINF_EM_TRIPLE_FAULT error
There's more information in the VirtualBox log that might help you (or us!) track down what's actually going wrong, but I'm not sure if VirtualBox has the same level of debugging output as QEMU or Bochs.
Xodein wrote:following a tutorial on youtube
Most tutorials have mistakes. Youtube tutorials are especially bad because it's difficult for the author to go back and correct the mistakes.

Re: Interrupt Handling Issue

Posted: Sun Aug 13, 2023 3:35 am
by Xodein
Octocontrabass wrote:There's more information in the VirtualBox log that might help you (or us!) track down what's actually going wrong, but I'm not sure if VirtualBox has the same level of debugging output as QEMU or Bochs.
i tried it on QEMU but it goes for endless loop and reboots everytime. at first i thought it's because of virtualbox but i guess somethings wrong in my code but when i check the tutorial it's exact same code. However tutor's code is working. I cloned his project and tried.It worked on my machine aswell.

Re: Interrupt Handling Issue

Posted: Sun Aug 13, 2023 12:26 pm
by MichaelPetch
This looks like some variation of the Wyoos (Write Your Own Operating System) tutorial. It is suggested you modified an existing tutorial. I guess my first question is, did the code you start with work before you made changes to it?

Is it possible to put the code you are using and the Makefile and or build scripts onto something like Github so we can see everything and build it? It is possible the problems are beyond the code you show. I do notice one of the inline assembly needs a "memory" clobber although I suspect there is something else going on.

Re: Interrupt Handling Issue

Posted: Sun Aug 13, 2023 1:38 pm
by Octocontrabass
Xodein wrote:i tried it on QEMU but it goes for endless loop and reboots everytime.
Add "-d int" and "-no-reboot" to your QEMU command line to stop the endless reboot loop and get a log that will give you lots of information to help find the problem.
Xodein wrote:at first i thought it's because of virtualbox but i guess somethings wrong in my code but when i check the tutorial it's exact same code. However tutor's code is working. I cloned his project and tried.It worked on my machine aswell.
There must be some difference somewhere. Could it be a difference in the part of the code you haven't shown us yet?

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 2:33 am
by Xodein
MichaelPetch wrote:This looks like some variation of the Wyoos (Write Your Own Operating System) tutorial. It is suggested you modified an existing tutorial. I guess my first question is, did the code you start with work before you made changes to it?

Is it possible to put the code you are using and the Makefile and or build scripts onto something like Github so we can see everything and build it? It is possible the problems are beyond the code you show. I do notice one of the inline assembly needs a "memory" clobber although I suspect there is something else going on.
Yes my code was running normally before i added interrupt descriptor table. Heres my project https://github.com/eraykaradag/CernOS .

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 7:39 am
by MichaelPetch
A couple of strange things in the GDT. Your GDT structure is 9 bytes in size rather than 8 bytes. You have this for SegmentDescriptor:

Code: Select all

                        class SegmentDescriptor
                        {
                                private:
                                        uint16_t limit_low;
                                        uint16_t base_low;
                                        uint8_t base_high;
                                        uint8_t access;
                                        uint8_t limit_high;
                                        uint8_t flags;
                                        uint8_t base_vhi;
                                public:
                                        SegmentDescriptor(uint32_t base, uint32_t limit, uint8_t access);
                                        uint32_t Base();
                                        uint32_t Limit();
                        } __attribute__((packed));
When it should be:

Code: Select all

                        class SegmentDescriptor
                        {
                                private:
                                        uint16_t limit_low;
                                        uint16_t base_low;
                                        uint8_t base_high;
                                        uint8_t access;
                                        uint8_t limit_high;
                                        uint8_t base_vhi;
                                public:
                                        SegmentDescriptor(uint32_t base, uint32_t limit, uint8_t access);
                                        uint32_t Base();
                                        uint32_t Limit();
                        } __attribute__((packed));
I've removed flags in your GlobalDescriptorTable constructor and you seem to have the size and the address mixed up (and the inline assembly is a problematic). A serious problem is that after the LGDT instruction they never loaded the segment registers with proper values. The way they did it may work by chance on some environments and not others. You had:

Code: Select all

GlobalDescriptorTable::GlobalDescriptorTable():
nullSegmentSelector(0,0,0),
freeSegmentSelector(0,0,0),
codeSegmentSelector(0,64*1024*1024,0x9A),
dataSegmentSelector(0,64*1024*1024,0x92)
{
	uint32_t i[2];
	i[0] = (uint32_t)this;
	i[1] = sizeof(GlobalDescriptorTable) << 16;

	asm volatile("lgdt (%0)": : "p" (((uint8_t *) i)+2));
}
It should probably look something like:

Code: Select all

GlobalDescriptorTable::GlobalDescriptorTable():
nullSegmentSelector(0,0,0),
freeSegmentSelector(0,0,0),
codeSegmentSelector(0,64*1024*1024,0x9A),
dataSegmentSelector(0,64*1024*1024,0x92)
{
        uint32_t i[2];
        i[1] = (uint32_t)this;
        i[0] = (sizeof(GlobalDescriptorTable)-1) << 16;

        asm volatile(
                "lgdt %0\n\t"
                "mov %1, %%ss\n\t"
                "mov %1, %%ds\n\t"
                "mov %1, %%es\n\t"
                "mov %1, %%fs\n\t"
                "mov %1, %%gs\n\t"
                "push %k2\n\t"
                "push $1f\n\t"
                "ljmp *(%%esp)\n"
                "1:\n\t"
                "add $8, %%esp\n\t"
                :
                : "m" (*(((uint8_t *) i)+2)),
                  "r" (DataSegmentSelector()),
                  "r" (CodeSegmentSelector())
                : "memory");
}
Not sure why the tutorial author limited the segments to 64*1024*1024 (64MiB). You might want to consider 4UL*1024*1024*1024-1 for the full 4GiB address space.

Getting the GDT right is the important first step since interrupts will inevitably attempt to restore segment registers especially CS on an IRET as well as DS, ES, SS, GS, FS. If the GDT is wrong then you will get an exception when reloading the segment registers with a new selector.

Edit: An additional issue with your code - you will likely want to ensure you aren't generating 32-bit position independent code (PIC). To get that working requires extra effort so I recommend compiling your C/C++ files with -fno-PIC. Depending on the default options of your compiler you could be generating PIC and you may not have the glue in place to support it, and that could cause unexpected behaviour in your program.

I wrote some other posts on this forum about the WYOOS tutorial here and here. The second link has some additional information about problems in the Exception Handlers since some exceptions have error codes pushed by the processor and others don't.

One other problem you may encounter is that you aren't currently sending EOI (end of interrupts) to the PIC(s) so you may get one or two interrupts and then no further interrupts will be received. The original WYOOS code handled it but your interrupt handlers don't do anything.

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 3:11 pm
by Xodein
Huge thanks. I was about to give up. This really made me motivated thanks man.

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 3:13 pm
by MichaelPetch
No problem. I have made a ton of edits to that original comment in the past couple hours with some changed code (especially around the LGDT instruction and provided links to a couple of other posts I made about the WYOOS code. You might want to scroll back and look at the latest version of my comment.

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 4:41 pm
by Xodein
MichaelPetch wrote:No problem. I have made a ton of edits to that original comment in the past couple hours with some changed code (especially around the LGDT instruction and provided links to a couple of other posts I made about the WYOOS code. You might want to scroll back and look at the latest version of my comment.
Yes i have seen it already thank you for your effort it's really means to me.

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 9:38 pm
by Octocontrabass
MichaelPetch wrote:

Code: Select all

                : "m" (*(((uint8_t *) i)+2)),
You're telling the compiler that the inline assembly will only access one byte. The input operand needs to have the correct size, not just the correct address. A packed struct is usually a good choice:

Code: Select all

    struct __attribute__((packed,aligned(4))) {
        uint16_t limit;
        uint32_t base;
    } gdtr = { sizeof(GlobalDescriptorTable) - 1, (uint32_t)this };
    asm volatile( "lgdt %0\n\t"
    ...
    : "m"( gdtr )
MichaelPetch wrote:

Code: Select all

                : "memory");
The ADD instruction modifies flags, so you need a "cc" clobber here too.
MichaelPetch wrote:An additional issue with your code - you will likely want to ensure you aren't generating 32-bit position independent code (PIC).
If you use a reasonable cross-compiler (such as i686-elf-g++), it will generate position-dependent code by default. Getting consistent behavior in different build environments is just one of the reasons why you should use a cross-compiler.

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 10:12 pm
by MichaelPetch
The reason for the "memory" clobber is to deal with the issue of the structure. The compilers will realize that data into memory with that clobber. The other option was to actually specify an array of a certain number of bytes.

Technically given the nature of what reloading all the segments with new selectors can do (but not in this case since one is moving from a flat model to a flat model) `memory` clobber would be preferable for a generic use case. However one could have got away in this case with removing `memory` clobber and treating the memory structure as an array of 6 bytes with

Code: Select all

"m" (*(uint8_t (*)[6])(((uint8_t *) i)+2)),
Even better would have been to create a struct (even a temporary one) for the GDT record to make this cleaner as you did in your code. I have seen this design pattern for LGDT in the past to ensure that the `base` in the GDTR is on a 4 byte aligned address although I'm unsure where this came from. I'm unaware of a requirement for LGDT to do this or that there is something gained from it.

`cc` clobber isn't used on the x86/x86-64 targets (on these targets it is assumed that the conditional flags are always clobbered). It has no effect (it could in the future, but unlikely since it would break a lot of code). Other targets like ARM are a different story.

Removing `volatile` from that asm assembly statement though would have been correct as well.

I didn't give a complete run down on all the other odd choices the tutorial author made. in gdt.cpp he could have included cstddef and used `offsetof` and it was very peculiar that he had structures in the GDT with member variables but then chose to modify the class data as a byte array. Things like not ensuring proper alignment when the interrupt handler code is called is problematic and there was no `cld` to ensure forward movement as required by the ABI. I also mention a host of other problems with the exception handlers. I have not gone through the WYOOS tutorial with a fine tooth comb but there are definitely a lot of things that can go wrong and trip up a person who may not know what is going on and how to fix things. Not sure anyone has started an errata sheet for that tutorial like we did with James Molloy.

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 10:58 pm
by Octocontrabass
MichaelPetch wrote:The reason for the "memory" clobber is to deal with the issue of the structure.
I thought it was for the segment register loads, since those read and (sometimes) write the descriptors in the GDT.
MichaelPetch wrote:`cc` clobber isn't used on the x86/x86-64 targets (on these targets it is assumed that the conditional flags are always clobbered). It has no effect (it could in the future, but unlikely since it would break a lot of code).
It's a good reminder for when you're writing assembly on non-x86 targets.
MichaelPetch wrote:Removing `volatile` from that asm assembly statement though would have been correct as well.
It's a good reminder for when you're writing assembly that has output operands.

Re: Interrupt Handling Issue

Posted: Thu Aug 17, 2023 11:13 pm
by MichaelPetch
Octocontrabass wrote:
MichaelPetch wrote:The reason for the "memory" clobber is to deal with the issue of the structure.
I thought it was for the segment register loads, since those read and (sometimes) write the descriptors in the GDT.
This is a good point and one that would apply to this code. What I have done in my code in the past is to also pass a dummy memory constraint as an input/output operand that is the entire GDT. While the inline assembly doesn't actually do anything with the constraint it does force the compiler to commit the GDT to memory before the inline assembly code is emitted and reread it after if need be. In general it is a lot easier to just pass the `memory` clobber. Since this code is only executed once during initialization, the performance penalty of the `memory` clobber for that inline assembly is going to be marginal.

I'd usually tell people that for things like this it is often easier to code the LGDT and the reloading of registers as a separate assembly language function. Then you aren't concerned with all the nuances of inline assembly. If you don't know what you are doing it can bite you later on in unexpected ways.

Re: Interrupt Handling Issue

Posted: Sat Aug 19, 2023 8:18 am
by Xodein
The solution you gave me fixed my problem i can get interrupts from hardware but the next problem is i can't get interrupt from keyboard and i couldn't find the problem. Could you look at my code again?
I pushed new codes to github. https://github.com/eraykaradag/CernOS