1#!/usr/bin/env python3 2# 3# Copyright (c) 2017 Intel Corporation 4# 5# SPDX-License-Identifier: Apache-2.0 6 7"""Generate a Global Descriptor Table (GDT) for x86 CPUs. 8 9For additional detail on GDT and x86 memory management, please 10consult the IA Architecture SW Developer Manual, vol. 3. 11 12This script accepts as input the zephyr_prebuilt.elf binary, 13which is a link of the Zephyr kernel without various build-time 14generated data structures (such as the GDT) inserted into it. 15This kernel image has been properly padded such that inserting 16these data structures will not disturb the memory addresses of 17other symbols. 18 19The input kernel ELF binary is used to obtain the following 20information: 21 22- Memory addresses of the Main and Double Fault TSS structures 23 so GDT descriptors can be created for them 24- Memory addresses of where the GDT lives in memory, so that this 25 address can be populated in the GDT pseudo descriptor 26- whether userspace or HW stack protection are enabled in Kconfig 27 28The output is a GDT whose contents depend on the kernel 29configuration. With no memory protection features enabled, 30we generate flat 32-bit code and data segments. If hardware- 31based stack overflow protection or userspace is enabled, 32we additionally create descriptors for the main and double- 33fault IA tasks, needed for userspace privilege elevation and 34double-fault handling. If userspace is enabled, we also create 35flat code/data segments for ring 3 execution. 36""" 37 38import argparse 39import sys 40import struct 41import os 42 43from distutils.version import LooseVersion 44 45import elftools 46from elftools.elf.elffile import ELFFile 47from elftools.elf.sections import SymbolTableSection 48 49 50if LooseVersion(elftools.__version__) < LooseVersion('0.24'): 51 sys.exit("pyelftools is out of date, need version 0.24 or later") 52 53 54def debug(text): 55 """Display debug message if --verbose""" 56 if args.verbose: 57 sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") 58 59 60def error(text): 61 """Exit program with an error message""" 62 sys.exit(os.path.basename(sys.argv[0]) + ": " + text) 63 64 65GDT_PD_FMT = "<HIH" 66 67FLAGS_GRAN = 1 << 7 # page granularity 68ACCESS_EX = 1 << 3 # executable 69ACCESS_DC = 1 << 2 # direction/conforming 70ACCESS_RW = 1 << 1 # read or write permission 71 72# 6 byte pseudo descriptor, but we're going to actually use this as the 73# zero descriptor and return 8 bytes 74 75 76def create_gdt_pseudo_desc(addr, size): 77 """Create pseudo GDT descriptor""" 78 debug("create pseudo decriptor: %x %x" % (addr, size)) 79 # ...and take back one byte for the Intel god whose Ark this is... 80 size = size - 1 81 return struct.pack(GDT_PD_FMT, size, addr, 0) 82 83 84def chop_base_limit(base, limit): 85 """Limit argument always in bytes""" 86 base_lo = base & 0xFFFF 87 base_mid = (base >> 16) & 0xFF 88 base_hi = (base >> 24) & 0xFF 89 90 limit_lo = limit & 0xFFFF 91 limit_hi = (limit >> 16) & 0xF 92 93 return (base_lo, base_mid, base_hi, limit_lo, limit_hi) 94 95 96GDT_ENT_FMT = "<HHBBBB" 97 98 99def create_code_data_entry(base, limit, dpl, flags, access): 100 """Create GDT entry for code or data""" 101 debug("create code or data entry: %x %x %x %x %x" % 102 (base, limit, dpl, flags, access)) 103 104 base_lo, base_mid, base_hi, limit_lo, limit_hi = chop_base_limit(base, 105 limit) 106 107 # This is a valid descriptor 108 present = 1 109 110 # 32-bit protected mode 111 size = 1 112 113 # 1 = code or data, 0 = system type 114 desc_type = 1 115 116 # Just set accessed to 1 already so the CPU doesn't need it update it, 117 # prevents freakouts if the GDT is in ROM, we don't care about this 118 # bit in the OS 119 accessed = 1 120 121 access = access | (present << 7) | (dpl << 5) | (desc_type << 4) | accessed 122 flags = flags | (size << 6) | limit_hi 123 124 return struct.pack(GDT_ENT_FMT, limit_lo, base_lo, base_mid, 125 access, flags, base_hi) 126 127 128def create_tss_entry(base, limit, dpl): 129 """Create GDT TSS entry""" 130 debug("create TSS entry: %x %x %x" % (base, limit, dpl)) 131 present = 1 132 133 base_lo, base_mid, base_hi, limit_lo, limit_hi, = chop_base_limit(base, 134 limit) 135 136 type_code = 0x9 # non-busy 32-bit TSS descriptor 137 gran = 0 138 139 flags = (gran << 7) | limit_hi 140 type_byte = ((present << 7) | (dpl << 5) | type_code) 141 142 return struct.pack(GDT_ENT_FMT, limit_lo, base_lo, base_mid, 143 type_byte, flags, base_hi) 144 145 146def get_symbols(obj): 147 """Extract all symbols from ELF file object""" 148 for section in obj.iter_sections(): 149 if isinstance(section, SymbolTableSection): 150 return {sym.name: sym.entry.st_value 151 for sym in section.iter_symbols()} 152 153 raise LookupError("Could not find symbol table") 154 155 156def parse_args(): 157 """Parse command line arguments""" 158 global args 159 parser = argparse.ArgumentParser( 160 description=__doc__, 161 formatter_class=argparse.RawDescriptionHelpFormatter) 162 163 parser.add_argument("-k", "--kernel", required=True, 164 help="Zephyr kernel image") 165 parser.add_argument("-v", "--verbose", action="store_true", 166 help="Print extra debugging information") 167 parser.add_argument("-o", "--output-gdt", required=True, 168 help="output GDT binary") 169 args = parser.parse_args() 170 if "VERBOSE" in os.environ: 171 args.verbose = 1 172 173 174def main(): 175 """Main Program""" 176 parse_args() 177 178 with open(args.kernel, "rb") as elf_fp: 179 kernel = ELFFile(elf_fp) 180 syms = get_symbols(kernel) 181 182 # NOTE: use-cases are extremely limited; we always have a basic flat 183 # code/data segments. If we are doing stack protection, we are going to 184 # have two TSS to manage the main task and the special task for double 185 # fault exception handling 186 if "CONFIG_USERSPACE" in syms: 187 num_entries = 7 188 elif "CONFIG_HW_STACK_PROTECTION" in syms: 189 num_entries = 5 190 else: 191 num_entries = 3 192 193 use_tls = False 194 if ("CONFIG_THREAD_LOCAL_STORAGE" in syms) and ("CONFIG_X86_64" not in syms): 195 use_tls = True 196 197 # x86_64 does not use descriptor for thread local storage 198 num_entries += 1 199 200 gdt_base = syms["_gdt"] 201 202 with open(args.output_gdt, "wb") as output_fp: 203 # The pseudo descriptor is stuffed into the NULL descriptor 204 # since the CPU never looks at it 205 output_fp.write(create_gdt_pseudo_desc(gdt_base, num_entries * 8)) 206 207 # Selector 0x08: code descriptor 208 output_fp.write(create_code_data_entry(0, 0xFFFFF, 0, 209 FLAGS_GRAN, ACCESS_EX | ACCESS_RW)) 210 211 # Selector 0x10: data descriptor 212 output_fp.write(create_code_data_entry(0, 0xFFFFF, 0, 213 FLAGS_GRAN, ACCESS_RW)) 214 215 if num_entries >= 5: 216 main_tss = syms["_main_tss"] 217 df_tss = syms["_df_tss"] 218 219 # Selector 0x18: main TSS 220 output_fp.write(create_tss_entry(main_tss, 0x67, 0)) 221 222 # Selector 0x20: double-fault TSS 223 output_fp.write(create_tss_entry(df_tss, 0x67, 0)) 224 225 if num_entries >= 7: 226 # Selector 0x28: code descriptor, dpl = 3 227 output_fp.write(create_code_data_entry(0, 0xFFFFF, 3, 228 FLAGS_GRAN, ACCESS_EX | ACCESS_RW)) 229 230 # Selector 0x30: data descriptor, dpl = 3 231 output_fp.write(create_code_data_entry(0, 0xFFFFF, 3, 232 FLAGS_GRAN, ACCESS_RW)) 233 234 if use_tls: 235 # Selector 0x18, 0x28 or 0x38 (depending on entries above): 236 # data descriptor, dpl = 3 237 # 238 # for use with thread local storage while this will be 239 # modified at runtime. 240 output_fp.write(create_code_data_entry(0, 0xFFFFF, 3, 241 FLAGS_GRAN, ACCESS_RW)) 242 243 244if __name__ == "__main__": 245 main() 246