Page 1 of 1

Understanding virtual memory translation in AMD64/UEFI

Posted: Fri Oct 23, 2020 7:56 am
by DanB91
Hi all,

Trying to see if my understanding of page tables is correct. I am currently using UEFI, so I thought to inspect the page tables set up for it and see if my understanding is correct. From what I understand, all addresses in UEFI are identity mapped. I looped through the memory map descriptors and all virtual addresses are set to 0, so I am thinking this is true.

So want to know how virtual addresses are translated starting from CR3. So as an example I am using the RIP value which in this case is 0x65F_6A39. From reading the AMD64 manual, it looks like this address should be translated into the following offsets:

address -> 0x65F_6A39
PML4T -> bits 39-47 -> entry 0
PDPT -> bits 30 - 38 -> entry 0
PDT -> bits 21 - 29 -> entry 0x32 -> byte offset 0x32*8
PT -> bits 12 - 20 -> entry 0x1F6 -> byte offset 0x1F6*8

Do my offset calculations look correct?

From here, I now chase pointers starting from CR3. I assume all addresses are identity mapped because of what I’ve stated in the first paragraph. If they are not identity mapped, that’s the issue here. Below I have what I see when start pointer chasing.
It seems everything looks reasonable up until I reach the PDE, which is the first time I have a non-zero offset. The entry I am pulling from the PDT looks invalid (especially since the present bit is 0). Am I chasing pointers incorrectly? Am I applying the offset incorrectly?

Thank you!

Code: Select all

CR3 -> 0x7C0_1000

PML4E ->  0x7C0_1000 + 0 -> {is_present = true, 
is_writable = true, 
is_user_accessible = false, 
is_write_through_enabled = false, 
is_cache_disabled = false, 
was_accessed = true, 
ignored = 0, 
page_size_control_bit = 0, 
must_be_zero = 0, 
unused1 = 0, 
table_address = 0x7C02, 
unused0 = 0, 
no_execute = false, 
}

PDPE -> 0x7C0_2000 + 0 -> {is_present = true, 
is_writable = true, 
is_user_accessible = false, 
is_write_through_enabled = false, 
is_cache_disabled = false, 
was_accessed = true, 
ignored = 0, 
page_size_control_bit = 0, 
must_be_zero = 0, 
unused1 = 0, 
table_address = 0x7C03, 
unused0 = 0, 
no_execute = false, 
}

PDE -> 0x7C0_3000 + 0x32*8 -> {is_present = false, 
is_writable = false, 
is_user_accessible = false, 
is_write_through_enabled = false, 
is_cache_disabled = false, 
was_accessed = false, 
ignored = 0, 
page_size_control_bit = 0, 
must_be_zero = 0x1, 
unused1 = 0x3, 
table_address = 0x30_0000_0000, 
unused0 = 0xE, 
no_execute = false, 
}


Re: Understanding virtual memory translation in AMD64/UEFI

Posted: Fri Oct 23, 2020 8:19 am
by iansjack
I think your address calculations are wrong. Shouldn't the PTE offset be 0x1F8?

Re: Understanding virtual memory translation in AMD64/UEFI

Posted: Fri Oct 23, 2020 8:23 am
by DanB91
iansjack wrote:I think your address calculations are wrong. Shouldn't the PTE offset be 0x1F8?
Apologies, I posted the wrong virtual address. The correct virtual address is 0x65F_6A39, NOT 0x65F_8A39. Regardless, the issue starts with the PDTE, not PTE.

Re: Understanding virtual memory translation in AMD64/UEFI

Posted: Fri Oct 23, 2020 8:41 am
by iansjack
My second thought is that you don't show us the code that you are using to read the tables. How have you declared the variables that you use? Don't forget that pointer arithmetic in C automatically takes care of the size of the variable pointed to. Should you perhaps be looking at

0x7C0_3000 + 0x32

rather than

0x7C0_3000 + 0x32*8 ?

It may be more intuitive to declare the variables for the tables as arrays rather than pointers.

Re: Understanding virtual memory translation in AMD64/UEFI

Posted: Fri Oct 23, 2020 8:13 pm
by DanB91
iansjack wrote:My second thought is that you don't show us the code that you are using to read the tables. How have you declared the variables that you use? Don't forget that pointer arithmetic in C automatically takes care of the size of the variable pointed to. Should you perhaps be looking at

0x7C0_3000 + 0x32

rather than

0x7C0_3000 + 0x32*8 ?

It may be more intuitive to declare the variables for the tables as arrays rather than pointers.
Thanks a lot for the help. So this kind of got my gears turning. I am actually using Zig instead of C. I declared the the table entry struct as a "packed struct", which I've learned is completely broken in the language now. It was badly calculating the struct to be 9 bytes big instead of 8 bytes, throwing off all pointer arithmetic/scaling. So that was my issue. So once I just used regular u64 integers, I got it working.