blob: be627ac29451d31fc8438f4e84fdab5ad99f0932 [file] [log] [blame]
#!/usr/bin/env python3
import os
import sys
import struct
import parser
from collections import namedtuple
import ctypes
import argparse
import re
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection
mmu_region_details = namedtuple("mmu_region_details",
"pde_index page_entries_info")
valid_pages_inside_pde = namedtuple("valid_pages_inside_pde", "start_addr size \
pte_valid_addr_start \
pte_valid_addr_end \
permissions")
mmu_region_details_pdpt = namedtuple("mmu_region_details_pdpt",
"pdpte_index pd_entries")
# Constants
PAGE_ENTRY_PRESENT = 1
PAGE_ENTRY_READ_WRITE = 1 << 1
PAGE_ENTRY_USER_SUPERVISOR = 1 << 2
PAGE_ENTRY_XD = 1 << 63
# Struct formatters
struct_mmu_regions_format = "<IIQ"
header_values_format = "<II"
page_entry_format = "<Q"
entry_counter = 0
def print_code(val):
global entry_counter
if not val & PAGE_ENTRY_PRESENT:
ret = '.'
else:
if (val & PAGE_ENTRY_READ_WRITE):
# Writable page
if (val & PAGE_ENTRY_XD):
# Readable, writeable, not executable
ret = 'w'
else:
# Readable, writable, executable
ret = 'a'
else:
# Read-only
if (val & PAGE_ENTRY_XD):
# Read-only
ret = 'r'
else:
# Readable, executable
ret = 'x'
if (val & PAGE_ENTRY_USER_SUPERVISOR):
# User accessible pages are capital letters
ret = ret.upper()
sys.stdout.write(ret)
entry_counter = entry_counter + 1
if (entry_counter == 128):
sys.stdout.write("\n")
entry_counter = 0
class PageMode_PAE:
total_pages = 511
size_addressed_per_pde = (512 * 4096) # 2MB In Bytes
size_addressed_per_pdpte = (512 * size_addressed_per_pde) # In Bytes
list_of_pdpte = {}
def __init__(self, pd_start_addr, mem_regions, syms, kpti):
self.pd_start_addr = pd_start_addr
self.mem_regions = mem_regions
self.pd_tables_list = []
self.output_offset = 0
self.kpti = kpti
self.syms = syms
for i in range(4):
self.list_of_pdpte[i] = mmu_region_details_pdpt(pdpte_index=i,
pd_entries={})
self.populate_required_structs()
self.pdpte_create_binary_file()
self.page_directory_create_binary_file()
self.page_table_create_binary_file()
# return the pdpte number for the give address
def get_pdpte_number(self, value):
return (value >> 30) & 0x3
# return the page directory number for the give address
def get_pde_number(self, value):
return (value >> 21) & 0x1FF
# return the page table number for the given address
def get_pte_number(self, value):
return (value >> 12) & 0x1FF
def get_number_of_pd(self):
return len(self.get_pdpte_list())
def get_pdpte_list(self):
return list({temp[0] for temp in self.pd_tables_list})
# the return value will have the page address and it is assumed to be a 4096
# boundary.hence the output of this API will be a 20bit address of the page
# table
def address_of_page_table(self, pdpte, page_table_number):
# first page given to page directory pointer
# and 2nd page till 5th page are used for storing the page directories.
# set the max pdpte used. this tells how many pd are needed after
# that we start keeping the pt
PT_start_addr = self.get_number_of_pd() * 4096 +\
self.pd_start_addr + 4096
return (PT_start_addr +
(self.pd_tables_list.index([pdpte, page_table_number]) *
4096) >> 12)
def get_binary_pde_value(self, pdpte, value):
perms = value.page_entries_info[0].permissions
present = PAGE_ENTRY_PRESENT
read_write = check_bits(perms, [1, 29]) << 1
user_mode = check_bits(perms, [2, 28]) << 2
page_table = self.address_of_page_table(pdpte, value.pde_index) << 12
return present | read_write | user_mode | page_table
def get_binary_pte_value(self, value, pde, pte, perm_for_pte):
read_write = perm_for_pte & PAGE_ENTRY_READ_WRITE
user_mode = perm_for_pte & PAGE_ENTRY_USER_SUPERVISOR
xd = perm_for_pte & PAGE_ENTRY_XD
# This points to the actual memory in the HW
# totally 20 bits to rep the phy address
# first 2bits is from pdpte then 9bits is the number got from pde and
# next 9bits is pte
page_table = ((value.pdpte_index << 18) | (pde << 9) | pte) << 12
if self.kpti:
if user_mode:
present = PAGE_ENTRY_PRESENT
else:
if page_table == self.syms['z_shared_kernel_page_start']:
present = PAGE_ENTRY_PRESENT
else:
present = 0
else:
present = PAGE_ENTRY_PRESENT
binary_value = (present | read_write | user_mode | page_table | xd)
return binary_value
def clean_up_unused_pdpte(self):
self.list_of_pdpte = {key: value for key, value in
self.list_of_pdpte.items()
if value.pd_entries != {}}
# update the tuple values for the memory regions needed
def set_pde_pte_values(self, pdpte, pde_index, address, mem_size,
pte_valid_addr_start, pte_valid_addr_end, perm):
pages_tuple = valid_pages_inside_pde(
start_addr=address,
size=mem_size,
pte_valid_addr_start=pte_valid_addr_start,
pte_valid_addr_end=pte_valid_addr_end,
permissions=perm)
mem_region_values = mmu_region_details(pde_index=pde_index,
page_entries_info=[])
mem_region_values.page_entries_info.append(pages_tuple)
if pde_index in self.list_of_pdpte[pdpte].pd_entries.keys():
# this step adds the new page info to the exsisting pages info
self.list_of_pdpte[pdpte].pd_entries[pde_index].\
page_entries_info.append(pages_tuple)
else:
self.list_of_pdpte[pdpte].pd_entries[pde_index] = mem_region_values
def populate_required_structs(self):
for start, size, flags in self.mem_regions:
pdpte_index = self.get_pdpte_number(start)
pde_index = self.get_pde_number(start)
pte_valid_addr_start = self.get_pte_number(start)
# Get the end of the page table entries
# Since a memory region can take up only a few entries in the Page
# table, this helps us get the last valid PTE.
pte_valid_addr_end = self.get_pte_number(start +
size - 1)
mem_size = size
# In-case the start address aligns with a page table entry other
# than zero and the mem_size is greater than (1024*4096) i.e 4MB
# in case where it overflows the currenty PDE's range then limit the
# PTE to 1024 and so make the mem_size reflect the actual size
# taken up in the current PDE
if (size + (pte_valid_addr_start * 4096)) >= \
(self.size_addressed_per_pde):
pte_valid_addr_end = self.total_pages
mem_size = (((self.total_pages + 1) -
pte_valid_addr_start) * 4096)
self.set_pde_pte_values(pdpte_index,
pde_index,
start,
mem_size,
pte_valid_addr_start,
pte_valid_addr_end,
flags)
if [pdpte_index, pde_index] not in self.pd_tables_list:
self.pd_tables_list.append([pdpte_index, pde_index])
# IF the current pde couldn't fit the entire requested region
# size then there is a need to create new PDEs to match the size.
# Here the overflow_size represents the size that couldn't be fit
# inside the current PDE, this is will now to used to create a new
# PDE/PDEs so the size remaining will be
# requested size - allocated size(in the current PDE)
overflow_size = size - mem_size
# create all the extra PDEs needed to fit the requested size
# this loop starts from the current pde till the last pde that is
# needed the last pde is calcualted as the (start_addr + size) >>
# 22
if overflow_size != 0:
for extra_pdpte in range(pdpte_index,
self.get_pdpte_number(start +
size) + 1):
for extra_pde in range(pde_index + 1, self.get_pde_number(
start + size) + 1):
# new pde's start address
# each page directory entry has a addr range of
# (1024 *4096) thus the new PDE start address is a
# multiple of that number
extra_pde_start_address = (
extra_pde * (self.size_addressed_per_pde))
# the start address of and extra pde will always be 0
# and the end address is calculated with the new
# pde's start address and the overflow_size
extra_pte_valid_addr_end = (
self.get_pte_number(extra_pde_start_address +
overflow_size - 1))
# if the overflow_size couldn't be fit inside this new
# pde then need another pde and so we now need to limit
# the end of the PTE to 1024 and set the size of this
# new region to the max possible
extra_region_size = overflow_size
if overflow_size >= (self.size_addressed_per_pde):
extra_region_size = self.size_addressed_per_pde
extra_pte_valid_addr_end = self.total_pages
# load the new PDE's details
self.set_pde_pte_values(extra_pdpte,
extra_pde,
extra_pde_start_address,
extra_region_size,
0,
extra_pte_valid_addr_end,
flags)
# for the next iteration of the loop the size needs
# to decreased
overflow_size -= extra_region_size
if [extra_pdpte, extra_pde] not in self.pd_tables_list:
self.pd_tables_list.append([extra_pdpte, extra_pde])
if overflow_size == 0:
break
self.pd_tables_list.sort()
self.clean_up_unused_pdpte()
pages_for_pdpte = 1
pages_for_pd = self.get_number_of_pd()
pages_for_pt = len(self.pd_tables_list)
self.output_buffer = ctypes.create_string_buffer((pages_for_pdpte +
pages_for_pd +
pages_for_pt) * 4096)
def pdpte_create_binary_file(self):
# pae needs a pdpte at 32byte aligned address
# Even though we have only 4 entries in the pdpte we need to move
# the self.output_offset variable to the next page to start pushing
# the pd contents
#
# FIXME: This wastes a ton of RAM!!
if (args.verbose):
print("PDPTE at 0x%x" % self.pd_start_addr)
for pdpte in range(self.total_pages + 1):
if pdpte in self.get_pdpte_list():
present = 1 << 0
addr_of_pd = (((self.pd_start_addr + 4096) +
self.get_pdpte_list().index(pdpte) *
4096) >> 12) << 12
binary_value = (present | addr_of_pd)
else:
binary_value = 0
struct.pack_into(page_entry_format,
self.output_buffer,
self.output_offset,
binary_value)
self.output_offset += struct.calcsize(page_entry_format)
def page_directory_create_binary_file(self):
for pdpte, pde_info in self.list_of_pdpte.items():
if (args.verbose):
print("Page directory %d at 0x%x" % (pde_info.pdpte_index,
self.pd_start_addr + self.output_offset))
for pde in range(self.total_pages + 1):
binary_value = 0 # the page directory entry is not valid
# if i have a valid entry to populate
if pde in pde_info.pd_entries.keys():
value = pde_info.pd_entries[pde]
binary_value = self.get_binary_pde_value(pdpte, value)
struct.pack_into(page_entry_format,
self.output_buffer,
self.output_offset,
binary_value)
if (args.verbose):
print_code(binary_value)
self.output_offset += struct.calcsize(page_entry_format)
def page_table_create_binary_file(self):
for pdpte, pde_info in sorted(self.list_of_pdpte.items()):
for pde, pte_info in sorted(pde_info.pd_entries.items()):
pe_info = pte_info.page_entries_info[0]
start_addr = pe_info.start_addr & ~0x1FFFFF
end_addr = start_addr + 0x1FFFFF
if (args.verbose):
print("Page table for 0x%08x - 0x%08x at 0x%08x" %
(start_addr, end_addr,
self.pd_start_addr + self.output_offset))
for pte in range(self.total_pages + 1):
binary_value = 0 # the page directory entry is not valid
valid_pte = 0
# go through all the valid pages inside the pde to
# figure out if we need to populate this pte
for i in pte_info.page_entries_info:
temp_value = ((pte >= i.pte_valid_addr_start) and
(pte <= i.pte_valid_addr_end))
if temp_value:
perm_for_pte = i.permissions
valid_pte |= temp_value
# if i have a valid entry to populate
if valid_pte:
binary_value = self.get_binary_pte_value(pde_info,
pde,
pte,
perm_for_pte)
if (args.verbose):
print_code(binary_value)
struct.pack_into(page_entry_format,
self.output_buffer,
self.output_offset,
binary_value)
self.output_offset += struct.calcsize(page_entry_format)
#*****************************************************************************#
def read_mmu_list(filename):
with open(filename, 'rb') as fp:
mmu_list_data = fp.read()
regions = []
# Read mmu_list header data
num_of_regions, pd_start_addr = struct.unpack_from(
header_values_format, mmu_list_data, 0)
# a offset used to remember next location to read in the binary
size_read_from_binary = struct.calcsize(header_values_format)
if (args.verbose):
print("Start address of page tables: 0x%08x" % pd_start_addr)
print("Build-time memory regions:")
# Read all the region definitions
for region_index in range(num_of_regions):
addr, size, flags = struct.unpack_from(struct_mmu_regions_format,
mmu_list_data,
size_read_from_binary)
size_read_from_binary += struct.calcsize(struct_mmu_regions_format)
if (args.verbose):
print(" Region %03d: 0x%08x - 0x%08x (0x%016x)" %
(region_index, addr, addr + size - 1, flags))
# ignore zero sized memory regions
if size == 0:
continue
if (addr & 0xFFF) != 0:
print("Memory region %d start address %x is not page-aligned" %
(region_index, addr))
sys.exit(2)
if (size & 0xFFF) != 0:
print("Memory region %d size %zu is not page-aligned" %
(region_index, size))
sys.exit(2)
# validate for memory overlap here
for other_region_index in range(len(regions)):
other_addr, other_size, _ = regions[other_region_index]
end_addr = addr + size
other_end_addr = other_addr + other_size
overlap_occurred = ((addr >= other_addr) and
(addr <= other_end_addr))
if (addr >= other_addr) and (addr <= other_end_addr):
print("Memory region %d (%x:%x) overlaps memory region %d (%x:%x)" %
(region_index, addr, end_addr, other_region_index,
other_addr, other_end_addr))
sys.exit(2)
# add the retrived info another list
regions.append((addr, size, flags))
return (pd_start_addr, regions)
def check_bits(val, bits):
for b in bits:
if val & (1 << b):
return 1
return 0
def get_symbols(obj):
for section in obj.iter_sections():
if isinstance(section, SymbolTableSection):
return {sym.name: sym.entry.st_value
for sym in section.iter_symbols()}
raise LookupError("Could not find symbol table")
# Read the parameters passed to the file
def parse_args():
global args
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-i", "--input",
help="Input file from which MMU regions are read.")
parser.add_argument("-k", "--kernel",
help="Zephyr kernel image")
parser.add_argument("-o", "--output",
help="Output file into which the page tables are "
"written.")
parser.add_argument("-u", "--user-output",
help="User mode page tables for KPTI")
parser.add_argument("-v", "--verbose", action="count", default=0,
help="Print debugging information. Multiple "
"invocations increase verbosity")
args = parser.parse_args()
if "VERBOSE" in os.environ:
args.verbose = 1
def main():
parse_args()
with open(args.kernel, "rb") as fp:
kernel = ELFFile(fp)
syms = get_symbols(kernel)
pd_start_addr, regions = read_mmu_list(args.input)
# select the page table needed
page_table = PageMode_PAE(pd_start_addr, regions, syms, False)
# write the binary data into the file
with open(args.output, 'wb') as fp:
fp.write(page_table.output_buffer)
if "CONFIG_X86_KPTI" in syms:
pd_start_addr += page_table.output_offset
user_page_table = PageMode_PAE(pd_start_addr, regions, syms, True)
with open(args.user_output, 'wb') as fp:
fp.write(user_page_table.output_buffer)
if __name__ == "__main__":
main()