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