Skip to content

Commit

Permalink
x86, decode: fix 4-level page table walk
Browse files Browse the repository at this point in the history
This commit fixes the page table walk when using 4-level mappings. Linux
can use 64-bit memory to store tables, and masking off the upper 32-bits
prevents the walk from functioning properly. Additionally, the physical
instruction calculation was using the incorrect logical address bits for
each level. Finally, the last PTE doesn't have the 7th bit flag a valid
page, we just check the first bit to ensure the entry is present, and
calculate the PTE address.

Signed-off-by: Chris Guikema <[email protected]>
  • Loading branch information
chrisguikema committed Nov 8, 2023
1 parent 7d8f309 commit 8fb64c5
Showing 1 changed file with 22 additions and 8 deletions.
30 changes: 22 additions & 8 deletions libsel4vm/src/arch/x86/processor/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@ Author: W.A.
/* TODO are these defined elsewhere? */
#define IA32_PDE_SIZE(pde) (pde & BIT(7))
#define IA32_PDE_PRESENT(pde) (pde & BIT(0))

#ifdef CONFIG_X86_64_VTX_64BIT_GUESTS
#define IA32_PTE_ADDR(pte) (pte & 0xFFFFFFFFFF000)
#define IA32_PDPTE_ADDR(pdpte) (pdpte & 0xFFFFFC0000000)
#define IA32_PDE_ADDR(pde) (pde & 0xFFFFFFFE00000)
#else
#define IA32_PTE_ADDR(pte) (pte & 0xFFFFF000)
#define IA32_PDPTE_ADDR(pdpte) (pdpte & 0xC0000000)
#define IA32_PDE_ADDR(pde) (pde & 0xFFE00000)
#endif

#define IA32_PSE_ADDR(pse) (pse & 0xFFC00000)

#define IA32_OPCODE_S(op) (op & BIT(0))
Expand Down Expand Up @@ -175,22 +183,29 @@ int vm_fetch_instruction(vm_vcpu_t *vcpu, uintptr_t eip, uintptr_t cr3,
uint64_t eip_47_39 = EXTRACT_BITS(eip, 9, 39); /* Bits 47:39 of linear address */
uint64_t eip_38_30 = EXTRACT_BITS(eip, 9, 30); /* Bits 38:30 of linear address */
uint64_t eip_29_21 = EXTRACT_BITS(eip, 9, 21); /* Bits 29:21 of linear address */
uint64_t eip_20_12 = EXTRACT_BITS(eip, 9, 12); /* Bits 20:12 of linear address */

uint64_t eip_29_0 = EXTRACT_BITS(eip, 30, 0); /* Bits 29:0 of linear address */
uint64_t eip_20_0 = EXTRACT_BITS(eip, 21, 0); /* Bits 20:0 of linear address */
uint64_t eip_11_0 = EXTRACT_BITS(eip, 12, 0); /* Bits 11:0 of linear address */

/* Each entry is 8 bytes long, so left shift by 3 to get the offset */
uint64_t pml4e = guest_get_phys_word(vcpu->vm, cr3 | (eip_47_39 << 3));

assert(IA32_PDE_PRESENT(pml4e));

/* Each entry is 8 bytes long, so left shift by 3 to get the offset */
uint64_t pdpte = guest_get_phys_word(vcpu->vm, IA32_PTE_ADDR(pml4e) | (eip_38_30 << 3));

assert(IA32_PDE_PRESENT(pdpte));

/* If this maps a 1GB page, then we can fetch the instruction now. */
if (IA32_PDE_SIZE(pdpte)) {
instr_phys = IA32_PDPTE_ADDR(pdpte) + EXTRACT_BITS(eip, 29, 0);
instr_phys = IA32_PDPTE_ADDR(pdpte) + eip_29_0;
goto fetch;
}

/* Each entry is 8 bytes long, so left shift by 3 to get the offset */
uint64_t pde = guest_get_phys_word(vcpu->vm, IA32_PTE_ADDR(pdpte) | (eip_29_21 << 3));

assert(IA32_PDE_PRESENT(pde));
Expand All @@ -201,15 +216,14 @@ int vm_fetch_instruction(vm_vcpu_t *vcpu, uintptr_t eip, uintptr_t cr3,
goto fetch;
}

uint64_t pte = guest_get_phys_word(vcpu->vm, IA32_PTE_ADDR(pde) | (eip_20_0 << 3));
/* Each entry is 8 bytes long, so left shift by 3 to get the offset */
uint64_t pte = guest_get_phys_word(vcpu->vm, IA32_PTE_ADDR(pde) | (eip_20_12 << 3));

/* If this maps a 4KB page, then we can fetch the instruction now. */
if (IA32_PDE_SIZE(pte)) {
instr_phys = IA32_PTE_ADDR(pte) + EXTRACT_BITS(eip, 11, 0);
goto fetch;
}
assert(IA32_PDE_PRESENT(pte));

/* This maps a 4KB page. We can fetch the instruction now. */
instr_phys = IA32_PTE_ADDR(pte) + eip_11_0;

return -1;
} else {
// TODO implement page-boundary crossing properly
assert((eip >> 12) == ((eip + len) >> 12));
Expand Down

0 comments on commit 8fb64c5

Please sign in to comment.