Post UEFI - set GDT and IDT

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.
Post Reply
Rhodez
Member
Member
Posts: 33
Joined: Tue Jun 30, 2020 2:09 pm
Location: Langeskov, Denmark

Post UEFI - set GDT and IDT

Post by Rhodez »

Hi, I'm still on my UEFI adventure and once again I have hit a rock.

I have searched the past three days, read wiki and intel documentation, but I can simply not figure it out.

So what i'm basically trying to achieve is to setup the IDT and PIC such that I can read a keystroke like I do when I boot my system with BIOS.
I have used more or less the same code from my BIOS version which works.
But no matter what I try when I'm coming from UEFI I cannot get the pieces together.

I have attached the code at the bottom, I'm hoping its not too long, but I guess it is more clear what I'm trying to do.

From UEFI I have used BS->AllocatePool with memory type "EfiRuntimeServicesCode" to allocate a buffer where I can load my test "kernel".
I make sure that the buffer is 4k aligned because I later remap this address to virtual address 0, such that I can compile my test kernel and not worry about where it is placed in memory.
(I know I can use AllocatePage in UEFI but no matter what I try I get an error from the function, maybe I should make another post for this? )

When I have remapped my buffer to virtual address 0 I try to setup GDT and later IDT.
I have placed comments in the code, I guess it explains my situation better then I can do here.

I hope my code is readable, my point is clear and really hope someone can point my in a direction?

Code: Select all

%define CODE_SEG     0x0008
%define DATA_SEG     0x0010

BITS 64
	push r8 ; Contain the frame buffer addr, register value set in UEFI 
	push r9 ; Contain the addr of the buffer where this code is located, register value set in UEFI 
    
    ; ... CODE THAT REMAP THE PYSICAL ADDR IN R9 TO VIRTUAL ADDR 0 ... ; Works 

	pop r9
	pop r8

	cli 
	; What I want here, is to load my own GDT and set CS to point to the record 0x08 in the GDT
	; Not working :(
	; By my searching this is the conclusion on how the code should look? 
	;lgdt [GDT]
	;push QWORD 0x08
	;push QWORD white
	;retfq	

	jmp white  ; Regular jmp works (just to confirm that the remapping is working). 
	hlt

white:
	; Write 4 white pixels to screen, to see we have reached this part. 
	mov DWORD [r8], 0x00FFFFFF
	add r8, 4
	mov DWORD [r8], 0x00FFFFFF
	add r8, 4
	mov DWORD [r8], 0x00FFFFFF
	add r8, 4
	mov DWORD [r8], 0x00FFFFFF

    lidt [IDT]

    ; Enable Keyboard only
    mov al, 0xfd
    out 0x21, al
    mov al, 0xff
    out 0xa1, al

    ;sti ; Under current conditions if sti is set then System reboots
    	 ; But I guess that the problem is the GDT is not set correctly

kernel_loop:
	nop
	hlt ; Make a loop to get keyboard interrupt 
	jmp kernel_loop

cli
hlt

; Global Descriptor Table
GDT:
.Null:
    dq 0x0000000000000000             ; Null Descriptor - should be present.
 
.Code:
    dw 0xffff                         ; Limit (low).
    dw 0                         ; Base (low).
    db 0                         ; Base (middle)
    db 10011010b                 ; Access (exec/read).
    db 10101111b                 ; Granularity, 64 bits flag, limit19:16.
    db 0                         ; Base (high).
.Data
    dw 0xffff                         ; Limit (low).
    dw 0                         ; Base (low).
    db 0                         ; Base (middle)
    db 10010010b                 ; Access (read/write).
    db 00001111b                 ; Granularity.
    db 0                         ; Base (high).
 
ALIGN 4
    dw 0                              ; Padding to make the "address of the GDT" field aligned on a 4-byte boundary
 
.Pointer:
    dw $ - GDT - 1                    ; 16-bit Size (Limit) of GDT.
    dw GDT                            ; 32-bit Base Address of GDT. (CPU will zero extend to 64-bit)


ALIGN 8
IDT_table:
; Entry 0x00: Division by Zero
    dw div_by_zero;
    dw 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
    db 0 ; OBS bits 0..2 holds Interrupt Stack Table offset, rest of bits zero
    db 0x8e; /* INTERRUPT_GATE */
    dw 0; offset bits 16..31
    dd 0 ; offset bits 32..63
    dd 0 ; reserved
; Entry 0x01: 
    times 16 db 0
; Entry 0x02: 
    times 16 db 0
; Entry 0x03: 
    times 16 db 0
; Entry 0x04: 
    times 16 db 0
; Entry 0x05: 
    times 16 db 0
; Entry 0x06: 
    times 16 db 0
; Entry 0x07: 
    times 16 db 0
; Entry 0x08: Double fault
    dw general_protection_fault ; using this routine as stub
    dw 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
    db 0 ; OBS bits 0..2 holds Interrupt Stack Table offset, rest of bits zero
    db 0x8e; /* INTERRUPT_GATE */
    dw 0; offset bits 16..31
    dd 0 ; offset bits 32..63
    dd 0 ; reserved
; Entry 0x09: 
    times 16 db 0
; Entry 0x0A: 
    times 16 db 0
; Entry 0x0B: 
    times 16 db 0
; Entry 0x0C: Stack fault
    dw general_protection_fault ; using this rutine as stub
    dw 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
    db 0 ; OBS bits 0..2 holds Interrupt Stack Table offset, rest of bits zero
    db 0x8e; /* INTERRUPT_GATE */
    dw 0; offset bits 16..31
    dd 0 ; offset bits 32..63
    dd 0 ; reserved
; Entry 0x0D: General protection fault 
    dw general_protection_fault;
    dw 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
    db 0 ; OBS bits 0..2 holds Interrupt Stack Table offset, rest of bits zero
    db 0x8e; /* INTERRUPT_GATE */
    dw 0; offset bits 16..31
    dd 0 ; offset bits 32..63
    dd 0 ; reserved
; Entry 0x0E: page fault
    dw page_fault;
    dw 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
    db 0 ; OBS bits 0..2 holds Interrupt Stack Table offset, rest of bits zero
    db 0x8e; /* INTERRUPT_GATE */
    dw 0; offset bits 16..31
    dd 0 ; offset bits 32..63
    dd 0 ; reserved
; Entry 0x0F: 
    times 16 db 0
; Entry 0x10: 
    times 16 db 0
; Entry 0x11: 
    times 16 db 0
; Entry 0x12: 
    times 16 db 0
; Entry 0x13: 
    times 16 db 0
; Entry 0x14: 
    times 16 db 0
; Entry 0x15: 
    times 16 db 0
; Entry 0x16: 
    times 16 db 0
; Entry 0x17: 
    times 16 db 0
; Entry 0x18: 
    times 16 db 0
; Entry 0x19: 
    times 16 db 0
; Entry 0x1A: 
    times 16 db 0
; Entry 0x1B: 
    times 16 db 0
; Entry 0x1C: 
    times 16 db 0
; Entry 0x1D: 
    times 16 db 0
; Entry 0x1E: 
    times 16 db 0
; Entry 0x1F: 
    times 16 db 0
; Now we go on with our own:  

; Entry 0x20: 
    dw irq0;
    dw 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
    db 0 ; OBS bits 0..2 holds Interrupt Stack Table offset, rest of bits zero
    db 0x8e; /* INTERRUPT_GATE */
    dw 0; offset bits 16..31
    dd 0 ; offset bits 32..63
    dd 0 ; reserved

; Entry 0x21: 
    dw irq1;
    dw 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
    db 0 ; OBS bits 0..2 holds Interrupt Stack Table offset, rest of bits zero
    db 0x8e; /* INTERRUPT_GATE */
    dw 0; offset bits 16..31
    dd 0 ; offset bits 32..63
    dd 0 ; reserved


IDT:
    .Length       dw $ - IDT_table - 1 
    .Base         dq IDT_table

div_by_zero:
	;mov rsi, .div_by_zero_string 
	;call print
	add QWORD [rsp], 2
	iretq
    cli
	hlt
	.div_by_zero_string db "Division by zero detected", 0

general_protection_fault:
	;mov rsi, .div_by_zero_string 
	;call print
    cli
	hlt
	iretq
	.general_protection_fault_string db "General protection fault: ", 0

page_fault:
	;mov rsi, .div_by_zero_string 
	;call print
    cli
	hlt
	iretq
	.page_fault_string db "Normal page fault: ", 0

irq0:
   mov al, 0x20
   out 0x20, al
   iretq
 
irq1:
  .read_port:
    xor rax, rax
    mov dx, 0x60 ; the keyboard controller
    in al, dx

  .ack
    mov al, 0x20
    out 0x20, al

      ; Write another white pixel to the screen, to see that we have got the IRQ correct. 
      add r8, 4
      mov DWORD [r8], 0x00FFFFFF
  iretq
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: Post UEFI - set GDT and IDT

Post by bzt »

Rhodez wrote:But no matter what I try when I'm coming from UEFI I cannot get the pieces together.
This could be because machines with UEFI most certainly doesn't have PS2 controller any more. You should check the ACPI tables to see if PS2 is emulated by the hardware. (This goes for real hardware, as far as I know all VM with UEFI emulates PS2.)
Rhodez wrote:From UEFI I have used BS->AllocatePool with memory type "EfiRuntimeServicesCode" to allocate a buffer where I can load my test "kernel".
I make sure that the buffer is 4k aligned because I later remap this address to virtual address 0, such that I can compile my test kernel and not worry about where it is placed in memory.
(I know I can use AllocatePage in UEFI but no matter what I try I get an error from the function, maybe I should make another post for this? )
Probably a separate post would be better. Is there any reason why you don't use AllocatePages? I mean that's known to work on all UEFI firmware. You're using wrong arguments if you're getting an error. Otherwise if you're worried that you might use that buffer later, then simply exclude your kernel's area from your free memory map and that's all. This is how I use AllocatePages.

Code: Select all

BufferSize = (UINTN)((ReadSize+PAGESIZE-1)/PAGESIZE);
uefi_call_wrapper(BS->AllocatePages, 4, 0, 2, BufferSize, (EFI_PHYSICAL_ADDRESS*)&Buffer);
if (Buffer == NULL) {
   /* report out of memory error */
}
Now about loading GDT and IDT, this is not how you do it. There should be a GDT_value and IDT_value with the offset and the size, and you should point the lgdt and lidt instructions to those descriptors. Eg:

Code: Select all

lgdt [GDT_value]

GDT_value:
 dw GDT_end - GDT -1
 dq GDT
GDT:
.Null:
 dq 0
 ...
GDT_end:
Cheers,
bzt
Rhodez
Member
Member
Posts: 33
Joined: Tue Jun 30, 2020 2:09 pm
Location: Langeskov, Denmark

Re: Post UEFI - set GDT and IDT

Post by Rhodez »

bzt wrote:
Rhodez wrote:But no matter what I try when I'm coming from UEFI I cannot get the pieces together.
This could be because machines with UEFI most certainly doesn't have PS2 controller any more. You should check the ACPI tables to see if PS2 is emulated by the hardware. (This goes for real hardware, as far as I know all VM with UEFI emulates PS2.)
That is hereby noted, maybe I should just try to catch a division by zero first, or something like that. I might have a follow up question here when I'm able to catch the interreupts.
bzt wrote:
Rhodez wrote:From UEFI I have used BS->AllocatePool with memory type "EfiRuntimeServicesCode" to allocate a buffer where I can load my test "kernel".
I make sure that the buffer is 4k aligned because I later remap this address to virtual address 0, such that I can compile my test kernel and not worry about where it is placed in memory.
(I know I can use AllocatePage in UEFI but no matter what I try I get an error from the function, maybe I should make another post for this? )
Probably a separate post would be better. Is there any reason why you don't use AllocatePages? I mean that's known to work on all UEFI firmware. You're using wrong arguments if you're getting an error. Otherwise if you're worried that you might use that buffer later, then simply exclude your kernel's area from your free memory map and that's all. This is how I use AllocatePages.

Code: Select all

BufferSize = (UINTN)((ReadSize+PAGESIZE-1)/PAGESIZE);
uefi_call_wrapper(BS->AllocatePages, 4, 0, 2, BufferSize, (EFI_PHYSICAL_ADDRESS*)&Buffer);
if (Buffer == NULL) {
   /* report out of memory error */
}
I have created a new post for this. I have attached my code such that you can see how I do it
viewtopic.php?f=1&t=37081&start=0
bzt wrote: Now about loading GDT and IDT, this is not how you do it. There should be a GDT_value and IDT_value with the offset and the size, and you should point the lgdt and lidt instructions to those descriptors. Eg:

Code: Select all

lgdt [GDT_value]

GDT_value:
 dw GDT_end - GDT -1
 dq GDT
GDT:
.Null:
 dq 0
 ...
GDT_end:
Cheers,
bzt
Oh damn, I guess some inconsistency in my code have giving my some trouble, so the lgdt line should be like lgdt[GDT.Pointer] which holds what you describe as GDT_value.
However it still reboots the system when I'm enabling interrupts.

Have I understood correct, that I need to reload the CS register after the lgdt instruction? And how do I do that?
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Post UEFI - set GDT and IDT

Post by nullplan »

Rhodez wrote:Have I understood correct, that I need to reload the CS register after the lgdt instruction? And how do I do that?
Far jump or far return. Given that your code must be position independent (UEFI), something like

Code: Select all

lea rax, [white - $ + RIP]
push 8
push rax
retf
white:
Far jump would be harder, since you would need to construct the far pointer, write it to memory, and perform an indirect far jump. 64-bit mode no longer has the immediate encoding for a far jump, but that wouldn't help you, anyway.
Carpe diem!
Rhodez
Member
Member
Posts: 33
Joined: Tue Jun 30, 2020 2:09 pm
Location: Langeskov, Denmark

Re: Post UEFI - set GDT and IDT

Post by Rhodez »

nullplan wrote:
Rhodez wrote:Have I understood correct, that I need to reload the CS register after the lgdt instruction? And how do I do that?
Far jump or far return. Given that your code must be position independent (UEFI), something like

Code: Select all

lea rax, [white - $ + RIP]
push 8
push rax
retf
white:
Far jump would be harder, since you would need to construct the far pointer, write it to memory, and perform an indirect far jump. 64-bit mode no longer has the immediate encoding for a far jump, but that wouldn't help you, anyway.
Thanks,
I found some error earlier in my code, so I have to go through the entire code again before I can try this for real.
The first line of the example is equivalent to "lea rax, [rel white]" in NASM right?

I have tried the following as a test

Code: Select all

	mov rax, white
	add rax, r9 ; add the acutal address to the offset of white
	mov rbx, cs

	push rbx
	push rax
	retf
Shouldn't this just "return" to the same code segment and the white label and start executing from there?
Octocontrabass
Member
Member
Posts: 5572
Joined: Mon Mar 25, 2013 7:01 pm

Re: Post UEFI - set GDT and IDT

Post by Octocontrabass »

Rhodez wrote:The first line of the example is equivalent to "lea rax, [rel white]" in NASM right?
Right.
Rhodez wrote:Shouldn't this just "return" to the same code segment and the white label and start executing from there?
No: NASM will emit a 32-bit instruction for the RETF mnemonic. Use RETFQ for a 64-bit far return. (I'm assuming R9 somehow contains the correct value.)

You also shouldn't rely on the firmware's GDT.
Rhodez
Member
Member
Posts: 33
Joined: Tue Jun 30, 2020 2:09 pm
Location: Langeskov, Denmark

Re: Post UEFI - set GDT and IDT

Post by Rhodez »

Octocontrabass wrote:
Rhodez wrote:The first line of the example is equivalent to "lea rax, [rel white]" in NASM right?
Right.
Rhodez wrote:Shouldn't this just "return" to the same code segment and the white label and start executing from there?
No: NASM will emit a 32-bit instruction for the RETF mnemonic. Use RETFQ for a 64-bit far return. (I'm assuming R9 somehow contains the correct value.)

You also shouldn't rely on the firmware's GDT.
Thanks finally something works.

My plan is, as the post says to set my own GDT and also IDT. I just had to divide it up in very small chunks to understand,
as I had a lot of troubles.
As I assume that the GDT from the firmware must work as i leave UEFI, a retfq to the exact same location (offset white label) and segment also must work in the beginning.
It was my way to test that I did the retfq correct, and now my far return works as expected in this case.

Now I will try to fix the earlier problem in the code, and then try do load my own GDT again, and reload it with the far return I now know how to do.
Post Reply