Page 1 of 1

Problems with far jump and interrupts

Posted: Sun Jan 03, 2016 8:43 am
by Programmdude
I have two problems, I have searched for solutions to both without much success.
First off, I have two parts, a x64 uefi loader, and a x64 elf kernel. The loader essentially parses the kernel, sticks it in at 1MB and jumps to it.

The first problem is when I attempt to start the kernel in the loader. I disable interrupts, set up the gdt, and try to far jump to it, to segment 0x10 and the kernel start address. This causes VirtualBox to kill the VM, having an issue with the instruction jmp far %rcx. It does work fine when I jump to it normally, using the segment that uefi stuck me in by default (0x28).
This isn't a massive problem yet, since it works just jumping directly there.

The relevant code:

Code: Select all

UINT16 initialGdt[] =
{
	/* gdt[0]: dummy */
	0, 0, 0, 0, 
	
	/* gdt[1]: unused */
	0, 0, 0, 0,

	/* gdt[2]: code */
	0xFFFF,		/* 4Gb - (0x100000*0x1000 = 4Gb) */
	0x0000,		/* base address=0 */
	0x9A00,		/* code read/exec */
	0x00CF,		/* granularity=4096, 386 (+5th nibble of limit) */

	/* gdt[3]: data */
	0xFFFF,		/* 4Gb - (0x100000*0x1000 = 4Gb) */
	0x0000,		/* base address=0 */
	0x9200,		/* data read/write */
	0x00CF,		/* granularity=4096, 386 (+5th nibble of limit) */
};

void StartKernel(void* address, KernelInfo* kinfo)
{
	// Disable interrupts
	__asm__ volatile("cli");
	
	SetMem(gdtAddress.base, gdtAddress.limit, 0);
	CopyMem(gdtAddress.base, initialGdt, sizeof(initialGdt));

	__asm__ volatile("lidt %0" : : "m" (idtAddress));
	__asm__ volatile("lgdt %0" : : "m" (gdtAddress));
	
	__asm__ volatile("mov %0, %%rdi" : : "m" (kinfo));
	__asm__ volatile("mov %0, %%rcx" : : "m" (address));
	__asm__ volatile("jmp *%rcx");
}
The second problem is when I try to set up interrupts. I set up the descriptor table, copy it to 0x0, call lidt and sti, and then test it with int $0. I'm unsure exactly where I am going wrong, part of the problem is the lack of documentation about x64, most of it only being relevant with real or protected mode.

interrupt.c

Code: Select all

static void SetIDT(u8 number, void* asmHandler, u16 selector, u8 flags, InterruptHandler handler)
{
	idt[number].Offset1 = (u64)asmHandler & 0xFFFF;
	idt[number].Offset2 = ((u64)asmHandler >> 16) & 0xFFFF;
	idt[number].Offset3 = ((u64)asmHandler >> 32) & 0xFFFFFFFF;
	idt[number].SegmentSelector = selector;
	idt[number].Flags = flags;
	
	isrHandlers[number] = handler;
}
void InitIDT()
{
	SerialWriteText("Interrupts: Setting up interrupts\n");
	SetIDT(0, &IDT0, 0x28, 0x8E, DefaultHandler);
...
	SetIDT(31, &IDT31, 0x28, 0x8E, DefaultHandler);
	SetIDT(238, &IDT238, 0x28, 0x8E, DefaultHandler);
	
	DescriptorTableAddress idtAddress = { sizeof(IDTDescriptor) * 255, 0 };
	memcpy(idtAddress.Base, &idt, idtAddress.Limit);
	asm volatile("lidt %0" : : "m" (idtAddress));
	asm volatile("sti");
	SerialWriteText("Interrupts: Finished setting up interrupts\n");
}
interrupt.S

Code: Select all

IDT0:
	mov $0xDEADBABE, %rdx
	hlt
	iretq
I have tried it, as the sample above shows, with it just calling hlt, yet is still ends up triple faulting.
The 0xDEADBABE isn't in the rdx register either, so I can assume that the interrupt handler isn't even called.

I'm unsure exactly what state UEFI leaves me in after I leave it. I'm fairly certain paging is disabled since cr3 doesn't have bit 31 enabled.

Re: Problems with far jump and interrupts

Posted: Sun Jan 03, 2016 9:12 am
by jnc100
UEFI on the x86_64 architecture leaves you in IA-32e mode by default, which implies paging is enabled (although all memory is identity-mapped). It is bit 31 of CR0 which determines if paging is enabled. Having a quick look at your code, the first thing is that your code descriptor in the GDT doesn't specify the segment is a 64-bit segment - the 0x00cf should be 0x00af.

I can't comment on the IDT issues. Are you using the appropriate 16-byte long IDT entries for 64-bit mode? Virtualbox has a built in debugger you can use to single-step and inspect the IDT contents prior to executing your int $0.

Regards,
John.

Re: Problems with far jump and interrupts

Posted: Mon Jan 04, 2016 6:53 am
by Programmdude
I have a feeling that uefi is using paging, looking over the MemoryMapDescription that uefi gives me shows different virtual and physical addresses. However, it appears that the same virtual address is bound to different physical pages, which I find strange. Therefore when I was copying my gdt, I was copying it to 0x0 in virtual memory, which isn't the same as 0x0 in physical memory. Unfortunately this makes me feel rather stuck, so I'm thinking it might be a better idea to create a multiboot x32 loader to set up the x64 kernel myself rather then attempting to go the uefi route.

Re: Problems with far jump and interrupts

Posted: Mon Jan 04, 2016 3:28 pm
by jnc100
If you ask a x86_64 UEFI firmware to give you some memory (e.g. with BS->AllocatePages()) it will give you a block of memory that it linearly mapped and so you could easily use it as a GDT address. It is possible that other memory regions (e.g. for devices) are not linearly mapped, but the specification guarantees that any memory you allocate will be.

Regards,
John.

Re: Problems with far jump and interrupts

Posted: Mon Jan 04, 2016 10:28 pm
by ggodw000
i am doing similar stuff and trying to get IDT working but using 16-bit ASM.
here is my post:
http://forum.osdev.org/viewtopic.php?f=1&t=29936
i just chip in here so just in case if any solution here in this thread can be used in my situation.
if it can be figured out, this is tremendous step for me.
thx!

Re: Problems with far jump and interrupts

Posted: Tue Jan 05, 2016 12:34 am
by Brendan
Hi,
Programmdude wrote:I have a feeling that uefi is using paging, looking over the MemoryMapDescription that uefi gives me shows different virtual and physical addresses. However, it appears that the same virtual address is bound to different physical pages, which I find strange. Therefore when I was copying my gdt, I was copying it to 0x0 in virtual memory, which isn't the same as 0x0 in physical memory. Unfortunately this makes me feel rather stuck, so I'm thinking it might be a better idea to create a multiboot x32 loader to set up the x64 kernel myself rather then attempting to go the uefi route.
The UEFI specs say (in section "2.3.4 x64 Platforms"):
UEFI wrote:
  • Paging mode is enabled and any memory space defined by the UEFI memory map is identity mapped (virtual address equals physical address). The mappings to other regions are undefined and may vary form implementation to implementation.
The can be changed by calling the "SetVirtualAddressMap()" run-time service; which can't be used before calling "ExitBootServices()" (it returns an "EFI_UNSUPPORTED" error if you try).

This leads to multiple possibilities:
  • You're mis-interpreting UEFI's memory map (and it actually is identity mapped)
  • You called "ExitBootServices()" and then called "SetVirtualAddressMap()", and are getting the information that you gave UEFI
  • Some other code has hijacked the boot process and called "ExitBootServices()" and "SetVirtualAddressMap()" before loading/starting your code
  • You've accidentally corrupted RAM that firmware relies on
  • Your firmware is extremely buggy.
:)


Cheers,

Brendan

Re: Problems with far jump and interrupts

Posted: Tue Jan 05, 2016 7:20 am
by Programmdude
I wasn't reading UEFI's memory map, assuming it was identity mapped. Once I realised that I could ask for identity mapped memory, everything else fell into place. I was originally loading the GDT and IDT into non-identity mapped memory, which resulted in lgdt and lidt reading garbage memory. This means I can now far jump to my kernel, and load an interrupt table.