Page 1 of 1
UEFI loader, jump to protected mode environment?
Posted: Thu Mar 09, 2017 9:04 am
by vollkorn
Hi there,
I'm currently writing an UEFI OS loader. My goal is to load a 32-bit multiboot compliant kernel. Loading the kernel to memory and relocating it to its final destination is not a big deal using UEFI boot-services.
However, jumping to the kernels start address (after exiting boot services) failes on qemu (TianoCore Firmware-Image) with a "general protection fault".
My entry address is "0x01302298". The code just before jumping to the start address looks like this:
Code: Select all
Dump of assembler code for function call_kernel:
0x000000001ead3b31 <+0>: push %rbp
0x000000001ead3b32 <+1>: mov %rsp,%rbp
0x000000001ead3b35 <+4>: sub $0x8,%rsp
0x000000001ead3b39 <+8>: mov %rdi,-0x8(%rbp)
=> 0x000000001ead3b3d <+12>: mov -0x8(%rbp),%rax
0x000000001ead3b41 <+16>: jmpq *%rax
with RAX=0000000001302298
The next instruction (which is expect) would be:
Instead gdb tells me, that the next instruction at this address is
=> 0x1302298: movabs %eax,0x8ed88c6601317bbc
I've some theories (but no clue) what happens:
- The firmware puts the cpu into x86_64-"long mode", this leads to miss-interpretation of the next instruction (which is a 32-bit instruction)
- The last jump instruction itself is wrong: -somehow- a 32-bit jump instruction has to be used.
Can someone give me a hint?
Best regards
Re: UEFI loader, jump to protected mode environment?
Posted: Thu Mar 09, 2017 9:56 am
by kzinti
You firmware is running in long mode (64-bit). If you want to run a 32-bit kernel, you will have to disable it and go to 32-bit protected mode before jumping to your kernel.
Re: UEFI loader, jump to protected mode environment?
Posted: Thu Mar 09, 2017 9:59 am
by Brendan
Hi,
vollkorn wrote:- The firmware puts the cpu into x86_64-"long mode", this leads to miss-interpretation of the next instruction (which is a 32-bit instruction)
Yes.
To fix this you have to switch to a 16-bit or 32-bit code segment (in long mode), make sure your code is in an identity mapped page (for UEFI it should be anyway), then disable long mode and paging.
Note that most multiboot stuff assumes that there's usable RAM at physical address 0x00100000, and UEFI makes no guarantee about the physical address space layout (and doesn't guarantee that there's usable RAM at physical address 0x00100000, even though it's "relatively likely"). This is unsolvable; which means that there's a (relatively tiny) chance that your code can't work.
Cheers,
Brendan
Re: UEFI loader, jump to protected mode environment?
Posted: Fri Mar 10, 2017 6:56 am
by vollkorn
Brendan wrote:To fix this you have to switch to a 16-bit or 32-bit code segment (in long mode)
Okay I did this, and it seems that qemu switches to 32-bit mode (e.g. register is now eax instead of rax).
I modified an already present gdt entry by resetting the "L" bit in the "flags" part of the entry and then reloaded the %cs register with appropriate gdt index. BUT it seems to reset all other registers, including the instruction pointer.
Why?
store_gdt_desc(&gdt_desc);
struct gdt_t* gdt = (struct gdt_t*) gdt_desc.base;
gdt[7].flags = 0x8; /* Reset 'L' bit in x86-64 descriptor */
asm volatile ("mov $0x38, %%ax\n\t"
"mov %%ax, %%cs"
""
: /* no output */
: /* no input */
: /* no clobber */
);
Re: UEFI loader, jump to protected mode environment?
Posted: Fri Mar 10, 2017 6:59 am
by Octocontrabass
That is not a valid instruction. How did you compile this without errors?
Re: UEFI loader, jump to protected mode environment?
Posted: Fri Mar 10, 2017 7:37 am
by vollkorn
I just double checked: it compiles, but qemu resets all register. Maybe a hard reset due to an illegal instruction / a bug? I'm using gcc 4.6.4 on ubuntu.
How to load cs using inline assembly instead? Google does not want to give me a hint.
Re: UEFI loader, jump to protected mode environment?
Posted: Fri Mar 10, 2017 8:15 am
by Octocontrabass
There are a few ways to load CS in inline assembly, but you can't use any of them to switch from 64-bit mode to 32-bit mode in inline assembly because your compiler can't switch from 64-bit to 32-bit in the middle of a function.
You can reload CS using a far return ("retf") instruction.
Re: UEFI loader, jump to protected mode environment?
Posted: Fri Mar 10, 2017 10:22 am
by vollkorn
Okay, I tried it in a different way. But this does not work either...
make_lm_pm_transition:
.code64
push %rbp
mov %rsp,%rbp
push $0x7
lea (%rip), %rax <-- how can i know the offset to label ".inprotectedmode"?
add $0x8, %rax
push %rax
lret
.inProtectedMode:
.code32
nop
...
nop
Re: UEFI loader, jump to protected mode environment?
Posted: Fri Mar 10, 2017 10:32 am
by kzinti
Code: Select all
...
push $0x08 ; GDT selector
push $target ; jump target
retf
target:
...
or
Code: Select all
...
ljmpl $0x08, $target
target:
...
Re: UEFI loader, jump to protected mode environment?
Posted: Fri Mar 10, 2017 12:34 pm
by Octocontrabass
Far jumps are not valid in 64-bit mode. Only a far return will work.
Re: UEFI loader, jump to protected mode environment?
Posted: Sat Mar 11, 2017 4:01 am
by Gigasoft
What you mean is:
Code: Select all
push $0x8
lea .inProtectedMode(%rip), %rax
push %rax
lretq
Push $.inProtectedMode might work, but only when the address is less than 0x80000000.
Or, you could simply set the machine type to 0x14c (EFI_IMAGE_MACHINE_IA32) in the PE image.
Re: UEFI loader, jump to protected mode environment?
Posted: Mon Mar 13, 2017 2:28 am
by vollkorn
Gigasoft wrote:What you mean is:
Code: Select all
push $0x8
lea .inProtectedMode(%rip), %rax
push %rax
lretq
Push $.inProtectedMode might work, but only when the address is less than 0x80000000.
Or, you could simply set the machine type to 0x14c (EFI_IMAGE_MACHINE_IA32) in the PE image.
This did the trick for me, at least for the jumping part. But still, qemu resets all register (see below). Did I miss something?
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000663
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
EIP=0000fff1 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 00009300
CS =f000 ffff0000 0000ffff 00009b00
SS =0000 00000000 0000ffff 00009300
DS =0000 00000000 0000ffff 00009300
FS =0000 00000000 0000ffff 00009300
GS =0000 00000000 0000ffff 00009300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT= 00000000 0000ffff
IDT= 00000000 0000ffff
CR0=60000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
=> 0x1ead3dcf <make_lm_pm_transition+6>:
lea 0x2(%rip),%rax # 0x1ead3dd8 <make_lm_pm_transition+15>
(gdb) nexti
14 push %rax
1: x/i $pc
=> 0x1ead3dd6 <make_lm_pm_transition+13>: push %rax
(gdb) nexti
15 lret
1: x/i $pc
=> 0x1ead3dd7 <make_lm_pm_transition+14>: lret
(gdb) nexti
0x0000000000000000 in ?? ()
1: x/i $pc
Re: UEFI loader, jump to protected mode environment?
Posted: Mon Mar 13, 2017 5:50 am
by vollkorn
Haha, I'm making some progress...the trick was - like you said - use
lretq instread of
lret. Also: gdb must be set to a different target architecture. Otherwise instructions get misinterpreted.
Gigasoft wrote:What you mean is:
Code: Select all
push $0x8
lea .inProtectedMode(%rip), %rax
push %rax
lretq
Push $.inProtectedMode might work, but only when the address is less than 0x80000000.
Or, you could simply set the machine type to 0x14c (EFI_IMAGE_MACHINE_IA32) in the PE image.
Thanks for your help btw
Re: UEFI loader, jump to protected mode environment?
Posted: Mon Mar 13, 2017 1:00 pm
by Gigasoft
Well, but what is the machine type in the PE image set to? I have no experience with UEFI, but I thought that if you set it to 0x14c rather than 0x200, it should start in 32 bit mode automatically.
Re: UEFI loader, jump to protected mode environment?
Posted: Tue Mar 14, 2017 7:27 am
by vollkorn
Gigasoft wrote:Well, but what is the machine type in the PE image set to? I have no experience with UEFI, but I thought that if you set it to 0x14c rather than 0x200, it should start in 32 bit mode automatically.
The machine type should be x86_64. I followed the the instructions listed here
http://wiki.osdev.org/UEFI#Developing_with_GNU-EFI to setup my toolchain.
How can I read the machine type from my efi application?
Maybe the "jump to protected mode" thing wouldn't be necessary, if the efi-application could be 32-bit from the beginning. However, learning something new is always good