Mapping full kernel causes garbage page mappings

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
avcado
Member
Member
Posts: 55
Joined: Wed Jan 20, 2021 11:32 am
GitHub: https://codeberg.org/minguss

Mapping full kernel causes garbage page mappings

Post by avcado »

Hi all! I have been porting a 32-bit legacy BIOS kernel to 64-bit UEFI since Sunday. The past few weeks I've gotten most stuff working, for example the GDT, and interrupts. The next thing on the list to port to 64-bit was paging. I pretty much understand the concept, and what I need to do for this port is quite simple, just
  • Identity map kernel space and framebuffer
  • Setup recursive paging.
However, when I go to map full kernel space (20 pages), I end up getting a LOT of garbage mappings. Here's the code

Code: Select all

#define MEM_PML4_INDEX(x) ((x >> (MEM_PAGE_SHIFT + 27)) & 0x1FF)
#define MEM_PDPT_INDEX(x) ((x >> (MEM_PAGE_SHIFT + 18)) & 0x1FF)
#define MEM_PAGEDIR_INDEX(x) ((x >> (MEM_PAGE_SHIFT + 9)) & 0x1FF)
#define MEM_PAGETBL_INDEX(x) ((x >> MEM_PAGE_SHIFT) & 0x1FF)

typedef union {
	/* Intel SDM page 3206, Table 5-19
	* (We only care about 4kb page) */
	struct {
		uint8_t  present    : 1;  // Must be set if the page actually exists
		uint8_t  readwrite  : 1;  // Readonly if not set.
		uint8_t  user_super : 1;  // Set if accessible through user mode
		uint8_t  pwt        : 1;
		uint8_t  pcd        : 1;
		uint8_t  accessed   : 1;  // CPU sets whenever the page table entry is accessed.
		uint8_t  ignored0   : 1;
		uint8_t  page_size  : 1;  // Not set if referring to a 4KB page, otherwise 2MB page.
		uint8_t  ignored1   : 3;
		uint8_t  hlat_rest  : 1;  // Ignored, unless HLAT paging.
		uint64_t address    : 28; // Physical address
		uint64_t ignored2   : 23; // Thank you intel, very cool!
		uint8_t  exec_dis   : 1;
	} bits;
	uint64_t data;
} page_entry;

[...]

void setup_paging(){
	uint64_t kernel_start = (uint64_t)&__kernel_start;
	uint64_t kernel_end = (uint64_t)&__kernel_end;

	uint8_t pg_size = (kernel_end - kernel_start) / PAGE_SIZE;
	printf("Mapping %08x -> %08x (kernel space), %d pages\n", kernel_start, kernel_end, pg_size);
	for(uint8_t i = 0; i < pg_size; i++){
		uint64_t phys = kernel_start + (i*PAGE_SIZE);
		page_table[MEM_PAGETBL_INDEX(phys)].data = phys | 0b11;
	}
	page_directory[MEM_PAGEDIR_INDEX(kernel_start)].data = (uint64_t)&page_table | 0b11;
	pdpt[MEM_PDPT_INDEX(kernel_start)].data = (uint64_t)&page_directory | 0b11;
	pml4[MEM_PML4_INDEX(kernel_start)].data = (uint64_t)&pdpt | 0b11;
	
	SetCR3(pml4);
}
SetCR3 just sets CR3 to whatever is in RDI (which is PML4).
With the for loop condition being i < pg_size-1, I get the expected result:

Code: Select all

(qemu) info mem
0000000000200000-0000000000212000 0000000000012000 -rw
(qemu) gva2gpa 0x200000
gpa: 0x200000
However, if I set it to i < pg_size, I get a LOT of garbage mappings (over 10000!!!!!).
Why is this happening? If needed, I can provide more code.
Thanks in advance!!!!!! :D
sebihepp
Member
Member
Posts: 256
Joined: Tue Aug 26, 2008 11:24 am
GitHub: https://github.com/sebihepp

Re: Mapping full kernel causes garbage page mappings

Post by sebihepp »

I think its due to pg_size being an uint8_t.
It happens when pg_size gets bigger than 255.
avcado
Member
Member
Posts: 55
Joined: Wed Jan 20, 2021 11:32 am
GitHub: https://codeberg.org/minguss

Re: Mapping full kernel causes garbage page mappings

Post by avcado »

sebihepp wrote: Thu Nov 13, 2025 12:30 pm I think its due to pg_size being an uint8_t.
It happens when pg_size gets bigger than 255.
Unfortunately, this is not the issue. The kernel is 20 pages in size, however I do suppose whenever the kernel grows in sizze it's better to change this to a 32-bit integer.
Octocontrabass
Member
Member
Posts: 6249
Joined: Mon Mar 25, 2013 7:01 pm

Re: Mapping full kernel causes garbage page mappings

Post by Octocontrabass »

Is CR3 set to the correct value?

If CR3 is correct, which level of your page tables is incorrect? Check the PML4 first and work your way down.

Once you find one that's incorrect, use your debugger to figure out which code is writing incorrect values to it.
avcado
Member
Member
Posts: 55
Joined: Wed Jan 20, 2021 11:32 am
GitHub: https://codeberg.org/minguss

Re: Mapping full kernel causes garbage page mappings

Post by avcado »

Octocontrabass wrote: Fri Nov 14, 2025 3:32 pm Is CR3 set to the correct value?
Seems like it is.

Code: Select all

(gdb) x/1x $cr3
0x20e000 <pml4>:	Cannot access memory at address 0x20e000

If CR3 is correct, which level of your page tables is incorrect? Check the PML4 first and work your way down.

Once you find one that's incorrect, use your debugger to figure out which code is writing incorrect values to it.
[/quote]

I was able to "fix" the bug by modifying the pml4 & pdpt index to 0 --- this being able to map everything as expected.
However, adding a printf statement after SetCR3 causes the issue:

Code: Select all

	pdpt[0].data = (uint64_t)&page_directory | 0b11;
	pml4[0].data = (uint64_t)&pdpt | 0b11;
	SetCR3(pml4);
	printf("[ OK ] Paging on!\n");
I am extremely confused by this, and decided to look at GDB:

Code: Select all

(gdb) print page_table[0]
$7 = {bits = {present = 1 '\001', readwrite = 1 '\001', user_super = 0 '\000', pwt = 0 '\000', 
    pcd = 0 '\000', accessed = 0 '\000', ignored0 = 0 '\000', page_size = 0 '\000', 
    ignored1 = 0 '\000', hlat_rest = 0 '\000', address = 512, ignored2 = 0, exec_dis = 0 '\000'}, 
  data = 2097155}
The thing that stood out to me was address = 512, despite this being present in the "proper" fix. Maybe I'm thinking about what you said wrong, as I was slightly confused by it.

Thank you for the insight though.
projects: minguss/skvn
Octocontrabass
Member
Member
Posts: 6249
Joined: Mon Mar 25, 2013 7:01 pm

Re: Mapping full kernel causes garbage page mappings

Post by Octocontrabass »

avcado wrote: Fri Nov 14, 2025 4:40 pmI was able to "fix" the bug by modifying the pml4 & pdpt index to 0
They should have already been 0. Why weren't they? Use your debugger to figure out where the incorrect nonzero values came from.
avcado wrote: Fri Nov 14, 2025 4:40 pmThe thing that stood out to me was address = 512, despite this being present in the "proper" fix.
You defined your struct so the "address" field doesn't include the lower 12 bits of the address (which is fine because those bits are always zero). A value of 512 (0x200) in that field corresponds to address 0x200000 when you insert the 12 missing bits.
Post Reply