|  | #!/usr/bin/env python3 | 
|  | # | 
|  | # Copyright (c) 2017 Intel Corporation | 
|  | # | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | """Generate a Global Descriptor Table (GDT) for x86 CPUs. | 
|  |  | 
|  | For additional detail on GDT and x86 memory management, please | 
|  | consult the IA Architecture SW Developer Manual, vol. 3. | 
|  |  | 
|  | This script accepts as input the zephyr_prebuilt.elf binary, | 
|  | which is a link of the Zephyr kernel without various build-time | 
|  | generated data structures (such as the GDT) inserted into it. | 
|  | This kernel image has been properly padded such that inserting | 
|  | these data structures will not disturb the memory addresses of | 
|  | other symbols. | 
|  |  | 
|  | The input kernel ELF binary is used to obtain the following | 
|  | information: | 
|  |  | 
|  | - Memory addresses of the Main and Double Fault TSS structures | 
|  | so GDT descriptors can be created for them | 
|  | - Memory addresses of where the GDT lives in memory, so that this | 
|  | address can be populated in the GDT pseudo descriptor | 
|  | - whether userspace or HW stack protection are enabled in Kconfig | 
|  |  | 
|  | The output is a GDT whose contents depend on the kernel | 
|  | configuration. With no memory protection features enabled, | 
|  | we generate flat 32-bit code and data segments. If hardware- | 
|  | based stack overflow protection or userspace is enabled, | 
|  | we additionally create descriptors for the main and double- | 
|  | fault IA tasks, needed for userspace privilege elevation and | 
|  | double-fault handling. If userspace is enabled, we also create | 
|  | flat code/data segments for ring 3 execution. | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import sys | 
|  | import struct | 
|  | import os | 
|  |  | 
|  | from packaging import version | 
|  |  | 
|  | import elftools | 
|  | from elftools.elf.elffile import ELFFile | 
|  | from elftools.elf.sections import SymbolTableSection | 
|  |  | 
|  |  | 
|  | if version.parse(elftools.__version__) < version.parse('0.24'): | 
|  | sys.exit("pyelftools is out of date, need version 0.24 or later") | 
|  |  | 
|  |  | 
|  | def debug(text): | 
|  | """Display debug message if --verbose""" | 
|  | if args.verbose: | 
|  | sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") | 
|  |  | 
|  |  | 
|  | def error(text): | 
|  | """Exit program with an error message""" | 
|  | sys.exit(os.path.basename(sys.argv[0]) + ": " + text) | 
|  |  | 
|  |  | 
|  | GDT_PD_FMT = "<HIH" | 
|  |  | 
|  | FLAGS_GRAN = 1 << 7  # page granularity | 
|  | ACCESS_EX = 1 << 3  # executable | 
|  | ACCESS_DC = 1 << 2  # direction/conforming | 
|  | ACCESS_RW = 1 << 1  # read or write permission | 
|  |  | 
|  | # 6 byte pseudo descriptor, but we're going to actually use this as the | 
|  | # zero descriptor and return 8 bytes | 
|  |  | 
|  |  | 
|  | def create_gdt_pseudo_desc(addr, size): | 
|  | """Create pseudo GDT descriptor""" | 
|  | debug("create pseudo descriptor: %x %x" % (addr, size)) | 
|  | # ...and take back one byte for the Intel god whose Ark this is... | 
|  | size = size - 1 | 
|  | return struct.pack(GDT_PD_FMT, size, addr, 0) | 
|  |  | 
|  |  | 
|  | def chop_base_limit(base, limit): | 
|  | """Limit argument always in bytes""" | 
|  | base_lo = base & 0xFFFF | 
|  | base_mid = (base >> 16) & 0xFF | 
|  | base_hi = (base >> 24) & 0xFF | 
|  |  | 
|  | limit_lo = limit & 0xFFFF | 
|  | limit_hi = (limit >> 16) & 0xF | 
|  |  | 
|  | return (base_lo, base_mid, base_hi, limit_lo, limit_hi) | 
|  |  | 
|  |  | 
|  | GDT_ENT_FMT = "<HHBBBB" | 
|  |  | 
|  |  | 
|  | def create_code_data_entry(base, limit, dpl, flags, access): | 
|  | """Create GDT entry for code or data""" | 
|  | debug("create code or data entry: %x %x %x %x %x" % | 
|  | (base, limit, dpl, flags, access)) | 
|  |  | 
|  | base_lo, base_mid, base_hi, limit_lo, limit_hi = chop_base_limit(base, | 
|  | limit) | 
|  |  | 
|  | # This is a valid descriptor | 
|  | present = 1 | 
|  |  | 
|  | # 32-bit protected mode | 
|  | size = 1 | 
|  |  | 
|  | # 1 = code or data, 0 = system type | 
|  | desc_type = 1 | 
|  |  | 
|  | # Just set accessed to 1 already so the CPU doesn't need it update it, | 
|  | # prevents freakouts if the GDT is in ROM, we don't care about this | 
|  | # bit in the OS | 
|  | accessed = 1 | 
|  |  | 
|  | access = access | (present << 7) | (dpl << 5) | (desc_type << 4) | accessed | 
|  | flags = flags | (size << 6) | limit_hi | 
|  |  | 
|  | return struct.pack(GDT_ENT_FMT, limit_lo, base_lo, base_mid, | 
|  | access, flags, base_hi) | 
|  |  | 
|  |  | 
|  | def create_tss_entry(base, limit, dpl): | 
|  | """Create GDT TSS entry""" | 
|  | debug("create TSS entry: %x %x %x" % (base, limit, dpl)) | 
|  | present = 1 | 
|  |  | 
|  | base_lo, base_mid, base_hi, limit_lo, limit_hi, = chop_base_limit(base, | 
|  | limit) | 
|  |  | 
|  | type_code = 0x9  # non-busy 32-bit TSS descriptor | 
|  | gran = 0 | 
|  |  | 
|  | flags = (gran << 7) | limit_hi | 
|  | type_byte = (present << 7) | (dpl << 5) | type_code | 
|  |  | 
|  | return struct.pack(GDT_ENT_FMT, limit_lo, base_lo, base_mid, | 
|  | type_byte, flags, base_hi) | 
|  |  | 
|  |  | 
|  | def get_symbols(obj): | 
|  | """Extract all symbols from ELF file object""" | 
|  | 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") | 
|  |  | 
|  |  | 
|  | def parse_args(): | 
|  | """Parse command line arguments""" | 
|  | global args | 
|  | parser = argparse.ArgumentParser( | 
|  | description=__doc__, | 
|  | formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) | 
|  |  | 
|  | parser.add_argument("-k", "--kernel", required=True, | 
|  | help="Zephyr kernel image") | 
|  | parser.add_argument("-v", "--verbose", action="store_true", | 
|  | help="Print extra debugging information") | 
|  | parser.add_argument("-o", "--output-gdt", required=True, | 
|  | help="output GDT binary") | 
|  | args = parser.parse_args() | 
|  | if "VERBOSE" in os.environ: | 
|  | args.verbose = 1 | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | """Main Program""" | 
|  | parse_args() | 
|  |  | 
|  | with open(args.kernel, "rb") as elf_fp: | 
|  | kernel = ELFFile(elf_fp) | 
|  | syms = get_symbols(kernel) | 
|  |  | 
|  | # NOTE: use-cases are extremely limited; we always have a basic flat | 
|  | # code/data segments. If we are doing stack protection, we are going to | 
|  | # have two TSS to manage the main task and the special task for double | 
|  | # fault exception handling | 
|  | if "CONFIG_USERSPACE" in syms: | 
|  | num_entries = 7 | 
|  | elif "CONFIG_X86_STACK_PROTECTION" in syms: | 
|  | num_entries = 5 | 
|  | else: | 
|  | num_entries = 3 | 
|  |  | 
|  | use_tls = False | 
|  | if ("CONFIG_THREAD_LOCAL_STORAGE" in syms) and ("CONFIG_X86_64" not in syms): | 
|  | use_tls = True | 
|  |  | 
|  | # x86_64 does not use descriptor for thread local storage | 
|  | num_entries += 1 | 
|  |  | 
|  | gdt_base = syms["_gdt"] | 
|  |  | 
|  | with open(args.output_gdt, "wb") as output_fp: | 
|  | # The pseudo descriptor is stuffed into the NULL descriptor | 
|  | # since the CPU never looks at it | 
|  | output_fp.write(create_gdt_pseudo_desc(gdt_base, num_entries * 8)) | 
|  |  | 
|  | # Selector 0x08: code descriptor | 
|  | output_fp.write(create_code_data_entry(0, 0xFFFFF, 0, | 
|  | FLAGS_GRAN, ACCESS_EX | ACCESS_RW)) | 
|  |  | 
|  | # Selector 0x10: data descriptor | 
|  | output_fp.write(create_code_data_entry(0, 0xFFFFF, 0, | 
|  | FLAGS_GRAN, ACCESS_RW)) | 
|  |  | 
|  | if num_entries >= 5: | 
|  | main_tss = syms["_main_tss"] | 
|  | df_tss = syms["_df_tss"] | 
|  |  | 
|  | # Selector 0x18: main TSS | 
|  | output_fp.write(create_tss_entry(main_tss, 0x67, 0)) | 
|  |  | 
|  | # Selector 0x20: double-fault TSS | 
|  | output_fp.write(create_tss_entry(df_tss, 0x67, 0)) | 
|  |  | 
|  | if num_entries >= 7: | 
|  | # Selector 0x28: code descriptor, dpl = 3 | 
|  | output_fp.write(create_code_data_entry(0, 0xFFFFF, 3, | 
|  | FLAGS_GRAN, ACCESS_EX | ACCESS_RW)) | 
|  |  | 
|  | # Selector 0x30: data descriptor, dpl = 3 | 
|  | output_fp.write(create_code_data_entry(0, 0xFFFFF, 3, | 
|  | FLAGS_GRAN, ACCESS_RW)) | 
|  |  | 
|  | if use_tls: | 
|  | # Selector 0x18, 0x28 or 0x38 (depending on entries above): | 
|  | # data descriptor, dpl = 3 | 
|  | # | 
|  | # for use with thread local storage while this will be | 
|  | # modified at runtime. | 
|  | output_fp.write(create_code_data_entry(0, 0xFFFFF, 3, | 
|  | FLAGS_GRAN, ACCESS_RW)) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |