1#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6"""
7Script to generate gperf tables of kernel object metadata
8
9User mode threads making system calls reference kernel objects by memory
10address, as the kernel/driver APIs in Zephyr are the same for both user
11and supervisor contexts. It is necessary for the kernel to be able to
12validate accesses to kernel objects to make the following assertions:
13
14    - That the memory address points to a kernel object
15
16    - The kernel object is of the expected type for the API being invoked
17
18    - The kernel object is of the expected initialization state
19
20    - The calling thread has sufficient permissions on the object
21
22For more details see the :ref:`kernelobjects` section in the documentation.
23
24The zephyr build generates an intermediate ELF binary, zephyr_prebuilt.elf,
25which this script scans looking for kernel objects by examining the DWARF
26debug information to look for instances of data structures that are considered
27kernel objects. For device drivers, the API struct pointer populated at build
28time is also examined to disambiguate between various device driver instances
29since they are all 'struct device'.
30
31This script can generate five different output files:
32
33    - A gperf script to generate the hash table mapping kernel object memory
34      addresses to kernel object metadata, used to track permissions,
35      object type, initialization state, and any object-specific data.
36
37    - A header file containing generated macros for validating driver instances
38      inside the system call handlers for the driver subsystem APIs.
39
40    - A code fragment included by kernel.h with one enum constant for
41      each kernel object type and each driver instance.
42
43    - The inner cases of a switch/case C statement, included by
44      kernel/userspace.c, mapping the kernel object types and driver
45      instances to their human-readable representation in the
46      otype_to_str() function.
47
48    - The inner cases of a switch/case C statement, included by
49      kernel/userspace.c, mapping kernel object types to their sizes.
50      This is used for allocating instances of them at runtime
51      (CONFIG_DYNAMIC_OBJECTS) in the obj_size_get() function.
52"""
53
54import sys
55import argparse
56import math
57import os
58import struct
59import json
60from distutils.version import LooseVersion
61
62import elftools
63from elftools.elf.elffile import ELFFile
64from elftools.elf.sections import SymbolTableSection
65
66if LooseVersion(elftools.__version__) < LooseVersion('0.24'):
67    sys.exit("pyelftools is out of date, need version 0.24 or later")
68
69from collections import OrderedDict
70
71# Keys in this dictionary are structs which should be recognized as kernel
72# objects. Values are a tuple:
73#
74#  - The first item is None, or the name of a Kconfig that
75#    indicates the presence of this object's definition in case it is not
76#    available in all configurations.
77#
78#  - The second item is a boolean indicating whether it is permissible for
79#    the object to be located in user-accessible memory.
80#
81#  - The third items is a boolean indicating whether this item can be
82#    dynamically allocated with k_object_alloc(). Keep this in sync with
83#    the switch statement in z_impl_k_object_alloc().
84#
85# Key names in all caps do not correspond to a specific data type but instead
86# indicate that objects of its type are of a family of compatible data
87# structures
88
89# Regular dictionaries are ordered only with Python 3.6 and
90# above. Good summary and pointers to official documents at:
91# https://stackoverflow.com/questions/39980323/are-dictionaries-ordered-in-python-3-6
92kobjects = OrderedDict([
93    ("k_mem_slab", (None, False, True)),
94    ("k_msgq", (None, False, True)),
95    ("k_mutex", (None, False, True)),
96    ("k_pipe", (None, False, True)),
97    ("k_queue", (None, False, True)),
98    ("k_poll_signal", (None, False, True)),
99    ("k_sem", (None, False, True)),
100    ("k_stack", (None, False, True)),
101    ("k_thread", (None, False, True)), # But see #
102    ("k_timer", (None, False, True)),
103    ("z_thread_stack_element", (None, False, False)),
104    ("device", (None, False, False)),
105    ("NET_SOCKET", (None, False, False)),
106    ("net_if", (None, False, False)),
107    ("sys_mutex", (None, True, False)),
108    ("k_futex", (None, True, False)),
109    ("k_condvar", (None, False, True))
110])
111
112def kobject_to_enum(kobj):
113    if kobj.startswith("k_") or kobj.startswith("z_"):
114        name = kobj[2:]
115    else:
116        name = kobj
117
118    return "K_OBJ_%s" % name.upper()
119
120subsystems = [
121    # Editing the list is deprecated, add the __subsystem sentinal to your driver
122    # api declaration instead. e.x.
123    #
124    # __subsystem struct my_driver_api {
125    #    ....
126    #};
127]
128
129# Names of all structs tagged with __net_socket, found by parse_syscalls.py
130net_sockets = [ ]
131
132def subsystem_to_enum(subsys):
133    return "K_OBJ_DRIVER_" + subsys[:-11].upper()
134
135# --- debug stuff ---
136
137scr = os.path.basename(sys.argv[0])
138
139def debug(text):
140    if not args.verbose:
141        return
142    sys.stdout.write(scr + ": " + text + "\n")
143
144def error(text):
145    sys.exit("%s ERROR: %s" % (scr, text))
146
147def debug_die(die, text):
148    lp_header = die.dwarfinfo.line_program_for_CU(die.cu).header
149    files = lp_header["file_entry"]
150    includes = lp_header["include_directory"]
151
152    fileinfo = files[die.attributes["DW_AT_decl_file"].value - 1]
153    filename = fileinfo.name.decode("utf-8")
154    filedir = includes[fileinfo.dir_index - 1].decode("utf-8")
155
156    path = os.path.join(filedir, filename)
157    lineno = die.attributes["DW_AT_decl_line"].value
158
159    debug(str(die))
160    debug("File '%s', line %d:" % (path, lineno))
161    debug("    %s" % text)
162
163# -- ELF processing
164
165DW_OP_addr = 0x3
166DW_OP_fbreg = 0x91
167STACK_TYPE = "z_thread_stack_element"
168thread_counter = 0
169sys_mutex_counter = 0
170futex_counter = 0
171stack_counter = 0
172
173# Global type environment. Populated by pass 1.
174type_env = {}
175extern_env = {}
176
177class KobjectInstance:
178    def __init__(self, type_obj, addr):
179        self.addr = addr
180        self.type_obj = type_obj
181
182        # Type name determined later since drivers needs to look at the
183        # API struct address
184        self.type_name = None
185        self.data = 0
186
187
188class KobjectType:
189    def __init__(self, offset, name, size, api=False):
190        self.name = name
191        self.size = size
192        self.offset = offset
193        self.api = api
194
195    def __repr__(self):
196        return "<kobject %s>" % self.name
197
198    @staticmethod
199    def has_kobject():
200        return True
201
202    def get_kobjects(self, addr):
203        return {addr: KobjectInstance(self, addr)}
204
205
206class ArrayType:
207    def __init__(self, offset, elements, member_type):
208        self.elements = elements
209        self.member_type = member_type
210        self.offset = offset
211
212    def __repr__(self):
213        return "<array of %d>" % self.member_type
214
215    def has_kobject(self):
216        if self.member_type not in type_env:
217            return False
218
219        return type_env[self.member_type].has_kobject()
220
221    def get_kobjects(self, addr):
222        mt = type_env[self.member_type]
223
224        # Stacks are arrays of _k_stack_element_t but we want to treat
225        # the whole array as one kernel object (a thread stack)
226        # Data value gets set to size of entire region
227        if isinstance(mt, KobjectType) and mt.name == STACK_TYPE:
228            # An array of stacks appears as a multi-dimensional array.
229            # The last size is the size of each stack. We need to track
230            # each stack within the array, not as one huge stack object.
231            *dimensions, stacksize = self.elements
232            num_members = 1
233            for e in dimensions:
234                num_members = num_members * e
235
236            ret = {}
237            for i in range(num_members):
238                a = addr + (i * stacksize)
239                o = mt.get_kobjects(a)
240                o[a].data = stacksize
241                ret.update(o)
242            return ret
243
244        objs = {}
245
246        # Multidimensional array flattened out
247        num_members = 1
248        for e in self.elements:
249            num_members = num_members * e
250
251        for i in range(num_members):
252            objs.update(mt.get_kobjects(addr + (i * mt.size)))
253        return objs
254
255
256class AggregateTypeMember:
257    def __init__(self, offset, member_name, member_type, member_offset):
258        self.member_name = member_name
259        self.member_type = member_type
260        if isinstance(member_offset, list):
261            # DWARF v2, location encoded as set of operations
262            # only "DW_OP_plus_uconst" with ULEB128 argument supported
263            if member_offset[0] == 0x23:
264                self.member_offset = member_offset[1] & 0x7f
265                for i in range(1, len(member_offset)-1):
266                    if member_offset[i] & 0x80:
267                        self.member_offset += (
268                            member_offset[i+1] & 0x7f) << i*7
269            else:
270                raise Exception("not yet supported location operation (%s:%d:%d)" %
271                        (self.member_name, self.member_type, member_offset[0]))
272        else:
273            self.member_offset = member_offset
274
275    def __repr__(self):
276        return "<member %s, type %d, offset %d>" % (
277            self.member_name, self.member_type, self.member_offset)
278
279    def has_kobject(self):
280        if self.member_type not in type_env:
281            return False
282
283        return type_env[self.member_type].has_kobject()
284
285    def get_kobjects(self, addr):
286        mt = type_env[self.member_type]
287        return mt.get_kobjects(addr + self.member_offset)
288
289
290class ConstType:
291    def __init__(self, child_type):
292        self.child_type = child_type
293
294    def __repr__(self):
295        return "<const %d>" % self.child_type
296
297    def has_kobject(self):
298        if self.child_type not in type_env:
299            return False
300
301        return type_env[self.child_type].has_kobject()
302
303    def get_kobjects(self, addr):
304        return type_env[self.child_type].get_kobjects(addr)
305
306
307class AggregateType:
308    def __init__(self, offset, name, size):
309        self.name = name
310        self.size = size
311        self.offset = offset
312        self.members = []
313
314    def add_member(self, member):
315        self.members.append(member)
316
317    def __repr__(self):
318        return "<struct %s, with %s>" % (self.name, self.members)
319
320    def has_kobject(self):
321        result = False
322
323        bad_members = []
324
325        for member in self.members:
326            if member.has_kobject():
327                result = True
328            else:
329                bad_members.append(member)
330                # Don't need to consider this again, just remove it
331
332        for bad_member in bad_members:
333            self.members.remove(bad_member)
334
335        return result
336
337    def get_kobjects(self, addr):
338        objs = {}
339        for member in self.members:
340            objs.update(member.get_kobjects(addr))
341        return objs
342
343
344# --- helper functions for getting data from DIEs ---
345
346def die_get_spec(die):
347    if 'DW_AT_specification' not in die.attributes:
348        return None
349
350    spec_val = die.attributes["DW_AT_specification"].value
351
352    # offset of the DW_TAG_variable for the extern declaration
353    offset = spec_val + die.cu.cu_offset
354
355    return extern_env.get(offset)
356
357
358def die_get_name(die):
359    if 'DW_AT_name' not in die.attributes:
360        die = die_get_spec(die)
361        if not die:
362            return None
363
364    return die.attributes["DW_AT_name"].value.decode("utf-8")
365
366
367def die_get_type_offset(die):
368    if 'DW_AT_type' not in die.attributes:
369        die = die_get_spec(die)
370        if not die:
371            return None
372
373    return die.attributes["DW_AT_type"].value + die.cu.cu_offset
374
375
376def die_get_byte_size(die):
377    if 'DW_AT_byte_size' not in die.attributes:
378        return 0
379
380    return die.attributes["DW_AT_byte_size"].value
381
382
383def analyze_die_struct(die):
384    name = die_get_name(die) or "<anon>"
385    offset = die.offset
386    size = die_get_byte_size(die)
387
388    # Incomplete type
389    if not size:
390        return
391
392    if name in kobjects:
393        type_env[offset] = KobjectType(offset, name, size)
394    elif name in subsystems:
395        type_env[offset] = KobjectType(offset, name, size, api=True)
396    elif name in net_sockets:
397        type_env[offset] = KobjectType(offset, "NET_SOCKET", size)
398    else:
399        at = AggregateType(offset, name, size)
400        type_env[offset] = at
401
402        for child in die.iter_children():
403            if child.tag != "DW_TAG_member":
404                continue
405            data_member_location = child.attributes.get("DW_AT_data_member_location")
406            if not data_member_location:
407                continue
408
409            child_type = die_get_type_offset(child)
410            member_offset = data_member_location.value
411            cname = die_get_name(child) or "<anon>"
412            m = AggregateTypeMember(child.offset, cname, child_type,
413                                    member_offset)
414            at.add_member(m)
415
416        return
417
418
419def analyze_die_const(die):
420    type_offset = die_get_type_offset(die)
421    if not type_offset:
422        return
423
424    type_env[die.offset] = ConstType(type_offset)
425
426
427def analyze_die_array(die):
428    type_offset = die_get_type_offset(die)
429    elements = []
430
431    for child in die.iter_children():
432        if child.tag != "DW_TAG_subrange_type":
433            continue
434
435        if "DW_AT_upper_bound" in child.attributes:
436            ub = child.attributes["DW_AT_upper_bound"]
437
438            if not ub.form.startswith("DW_FORM_data"):
439                continue
440
441            elements.append(ub.value + 1)
442        # in DWARF 4, e.g. ARC Metaware toolchain, DW_AT_count is used
443        # not DW_AT_upper_bound
444        elif "DW_AT_count" in child.attributes:
445            ub = child.attributes["DW_AT_count"]
446
447            if not ub.form.startswith("DW_FORM_data"):
448                continue
449
450            elements.append(ub.value)
451        else:
452            continue
453
454    if not elements:
455        if type_offset in type_env.keys():
456            mt = type_env[type_offset]
457            if mt.has_kobject():
458                if isinstance(mt, KobjectType) and mt.name == STACK_TYPE:
459                    elements.append(1)
460                    type_env[die.offset] = ArrayType(die.offset, elements, type_offset)
461    else:
462        type_env[die.offset] = ArrayType(die.offset, elements, type_offset)
463
464
465def analyze_typedef(die):
466    type_offset = die_get_type_offset(die)
467
468    if type_offset not in type_env.keys():
469        return
470
471    type_env[die.offset] = type_env[type_offset]
472
473
474def unpack_pointer(elf, data, offset):
475    endian_code = "<" if elf.little_endian else ">"
476    if elf.elfclass == 32:
477        size_code = "I"
478        size = 4
479    else:
480        size_code = "Q"
481        size = 8
482
483    return struct.unpack(endian_code + size_code,
484                         data[offset:offset + size])[0]
485
486
487def addr_deref(elf, addr):
488    for section in elf.iter_sections():
489        start = section['sh_addr']
490        end = start + section['sh_size']
491
492        if start <= addr < end:
493            data = section.data()
494            offset = addr - start
495            return unpack_pointer(elf, data, offset)
496
497    return 0
498
499
500def device_get_api_addr(elf, addr):
501    # See include/device.h for a description of struct device
502    offset = 8 if elf.elfclass == 32 else 16
503    return addr_deref(elf, addr + offset)
504
505
506def find_kobjects(elf, syms):
507    global thread_counter
508    global sys_mutex_counter
509    global futex_counter
510    global stack_counter
511
512    if not elf.has_dwarf_info():
513        sys.exit("ELF file has no DWARF information")
514
515    app_smem_start = syms["_app_smem_start"]
516    app_smem_end = syms["_app_smem_end"]
517
518    if "CONFIG_LINKER_USE_PINNED_SECTION" in syms and "_app_smem_pinned_start" in syms:
519        app_smem_pinned_start = syms["_app_smem_pinned_start"]
520        app_smem_pinned_end = syms["_app_smem_pinned_end"]
521    else:
522        app_smem_pinned_start = app_smem_start
523        app_smem_pinned_end = app_smem_end
524
525    user_stack_start = syms["z_user_stacks_start"]
526    user_stack_end = syms["z_user_stacks_end"]
527
528    di = elf.get_dwarf_info()
529
530    variables = []
531
532    # Step 1: collect all type information.
533    for CU in di.iter_CUs():
534        for die in CU.iter_DIEs():
535            # Unions are disregarded, kernel objects should never be union
536            # members since the memory is not dedicated to that object and
537            # could be something else
538            if die.tag == "DW_TAG_structure_type":
539                analyze_die_struct(die)
540            elif die.tag == "DW_TAG_const_type":
541                analyze_die_const(die)
542            elif die.tag == "DW_TAG_array_type":
543                analyze_die_array(die)
544            elif die.tag == "DW_TAG_typedef":
545                analyze_typedef(die)
546            elif die.tag == "DW_TAG_variable":
547                variables.append(die)
548
549    # Step 2: filter type_env to only contain kernel objects, or structs
550    # and arrays of kernel objects
551    bad_offsets = []
552    for offset, type_object in type_env.items():
553        if not type_object.has_kobject():
554            bad_offsets.append(offset)
555
556    for offset in bad_offsets:
557        del type_env[offset]
558
559    # Step 3: Now that we know all the types we are looking for, examine
560    # all variables
561    all_objs = {}
562
563    for die in variables:
564        name = die_get_name(die)
565        if not name:
566            continue
567
568        if name.startswith("__init_sys_init"):
569            # Boot-time initialization function; not an actual device
570            continue
571
572        type_offset = die_get_type_offset(die)
573
574        # Is this a kernel object, or a structure containing kernel
575        # objects?
576        if type_offset not in type_env:
577            continue
578
579        if "DW_AT_declaration" in die.attributes:
580            # Extern declaration, only used indirectly
581            extern_env[die.offset] = die
582            continue
583
584        if "DW_AT_location" not in die.attributes:
585            debug_die(die,
586                      "No location information for object '%s'; possibly stack allocated"
587                      % name)
588            continue
589
590        loc = die.attributes["DW_AT_location"]
591        if loc.form != "DW_FORM_exprloc" and \
592           loc.form != "DW_FORM_block1":
593            debug_die(die, "kernel object '%s' unexpected location format" %
594                      name)
595            continue
596
597        opcode = loc.value[0]
598        if opcode != DW_OP_addr:
599
600            # Check if frame pointer offset DW_OP_fbreg
601            if opcode == DW_OP_fbreg:
602                debug_die(die, "kernel object '%s' found on stack" % name)
603            else:
604                debug_die(die,
605                          "kernel object '%s' unexpected exprloc opcode %s" %
606                          (name, hex(opcode)))
607            continue
608
609        if "CONFIG_64BIT" in syms:
610            addr = ((loc.value[1] << 0 ) | (loc.value[2] << 8)  |
611                    (loc.value[3] << 16) | (loc.value[4] << 24) |
612                    (loc.value[5] << 32) | (loc.value[6] << 40) |
613                    (loc.value[7] << 48) | (loc.value[8] << 56))
614        else:
615            addr = ((loc.value[1] << 0 ) | (loc.value[2] << 8)  |
616                    (loc.value[3] << 16) | (loc.value[4] << 24))
617
618        if addr == 0:
619            # Never linked; gc-sections deleted it
620            continue
621
622        type_obj = type_env[type_offset]
623        objs = type_obj.get_kobjects(addr)
624        all_objs.update(objs)
625
626        debug("symbol '%s' at %s contains %d object(s)"
627              % (name, hex(addr), len(objs)))
628
629    # Step 4: objs is a dictionary mapping variable memory addresses to
630    # their associated type objects. Now that we have seen all variables
631    # and can properly look up API structs, convert this into a dictionary
632    # mapping variables to the C enumeration of what kernel object type it
633    # is.
634    ret = {}
635    for addr, ko in all_objs.items():
636        # API structs don't get into the gperf table
637        if ko.type_obj.api:
638            continue
639
640        _, user_ram_allowed, _ = kobjects[ko.type_obj.name]
641        if (not user_ram_allowed and
642            ((app_smem_start <= addr < app_smem_end)
643             or (app_smem_pinned_start <= addr < app_smem_pinned_end))):
644            debug("object '%s' found in invalid location %s"
645                  % (ko.type_obj.name, hex(addr)))
646            continue
647
648        if (ko.type_obj.name == STACK_TYPE and
649                (addr < user_stack_start or addr >= user_stack_end)):
650            debug("skip kernel-only stack at %s" % hex(addr))
651            continue
652
653        # At this point we know the object will be included in the gperf table
654        if ko.type_obj.name == "k_thread":
655            # Assign an ID for this thread object, used to track its
656            # permissions to other kernel objects
657            ko.data = thread_counter
658            thread_counter = thread_counter + 1
659        elif ko.type_obj.name == "sys_mutex":
660            ko.data = "&kernel_mutexes[%d]" % sys_mutex_counter
661            sys_mutex_counter += 1
662        elif ko.type_obj.name == "k_futex":
663            ko.data = "&futex_data[%d]" % futex_counter
664            futex_counter += 1
665        elif ko.type_obj.name == STACK_TYPE:
666            stack_counter += 1
667
668        if ko.type_obj.name != "device":
669            # Not a device struct so we immediately know its type
670            ko.type_name = kobject_to_enum(ko.type_obj.name)
671            ret[addr] = ko
672            continue
673
674        # Device struct. Need to get the address of its API struct,
675        # if it has one.
676        apiaddr = device_get_api_addr(elf, addr)
677        if apiaddr not in all_objs:
678            if apiaddr == 0:
679                debug("device instance at 0x%x has no associated subsystem"
680                      % addr)
681            else:
682                debug("device instance at 0x%x has unknown API 0x%x"
683                      % (addr, apiaddr))
684            # API struct does not correspond to a known subsystem, skip it
685            continue
686
687        apiobj = all_objs[apiaddr]
688        ko.type_name = subsystem_to_enum(apiobj.type_obj.name)
689        ret[addr] = ko
690
691    debug("found %d kernel object instances total" % len(ret))
692
693    # 1. Before python 3.7 dict order is not guaranteed. With Python
694    #    3.5 it doesn't seem random with *integer* keys but can't
695    #    rely on that.
696    # 2. OrderedDict means _insertion_ order, so not enough because
697    #    built from other (random!) dicts: need to _sort_ first.
698    # 3. Sorting memory address looks good.
699    return OrderedDict(sorted(ret.items()))
700
701def get_symbols(elf):
702    for section in elf.iter_sections():
703        if isinstance(section, SymbolTableSection):
704            return {sym.name: sym.entry.st_value
705                    for sym in section.iter_symbols()}
706
707    raise LookupError("Could not find symbol table")
708
709
710# -- GPERF generation logic
711
712header = """%compare-lengths
713%define lookup-function-name z_object_lookup
714%language=ANSI-C
715%global-table
716%struct-type
717%{
718#include <kernel.h>
719#include <toolchain.h>
720#include <syscall_handler.h>
721#include <string.h>
722%}
723struct z_object;
724"""
725
726# Different versions of gperf have different prototypes for the lookup
727# function, best to implement the wrapper here. The pointer value itself is
728# turned into a string, we told gperf to expect binary strings that are not
729# NULL-terminated.
730footer = """%%
731struct z_object *z_object_gperf_find(const void *obj)
732{
733    return z_object_lookup((const char *)obj, sizeof(void *));
734}
735
736void z_object_gperf_wordlist_foreach(_wordlist_cb_func_t func, void *context)
737{
738    int i;
739
740    for (i = MIN_HASH_VALUE; i <= MAX_HASH_VALUE; i++) {
741        if (wordlist[i].name != NULL) {
742            func(&wordlist[i], context);
743        }
744    }
745}
746
747#ifndef CONFIG_DYNAMIC_OBJECTS
748struct z_object *z_object_find(const void *obj)
749	ALIAS_OF(z_object_gperf_find);
750
751void z_object_wordlist_foreach(_wordlist_cb_func_t func, void *context)
752	ALIAS_OF(z_object_gperf_wordlist_foreach);
753#endif
754"""
755
756
757def write_gperf_table(fp, syms, objs, little_endian, static_begin, static_end):
758    fp.write(header)
759    if sys_mutex_counter != 0:
760        fp.write("static struct k_mutex kernel_mutexes[%d] = {\n"
761                 % sys_mutex_counter)
762        for i in range(sys_mutex_counter):
763            fp.write("Z_MUTEX_INITIALIZER(kernel_mutexes[%d])" % i)
764            if i != sys_mutex_counter - 1:
765                fp.write(", ")
766        fp.write("};\n")
767
768    if futex_counter != 0:
769        fp.write("static struct z_futex_data futex_data[%d] = {\n"
770                 % futex_counter)
771        for i in range(futex_counter):
772            fp.write("Z_FUTEX_DATA_INITIALIZER(futex_data[%d])" % i)
773            if i != futex_counter - 1:
774                fp.write(", ")
775        fp.write("};\n")
776
777    metadata_names = {
778        "K_OBJ_THREAD" : "thread_id",
779        "K_OBJ_SYS_MUTEX" : "mutex",
780        "K_OBJ_FUTEX" : "futex_data"
781    }
782
783    if "CONFIG_GEN_PRIV_STACKS" in syms:
784        metadata_names["K_OBJ_THREAD_STACK_ELEMENT"] = "stack_data"
785        if stack_counter != 0:
786            # Same as K_KERNEL_STACK_ARRAY_DEFINE, but routed to a different
787            # memory section.
788            fp.write("static uint8_t Z_GENERIC_SECTION(.priv_stacks.noinit) "
789                     " __aligned(Z_KERNEL_STACK_OBJ_ALIGN)"
790                     " priv_stacks[%d][Z_KERNEL_STACK_LEN(CONFIG_PRIVILEGED_STACK_SIZE)];\n"
791                     % stack_counter)
792
793            fp.write("static const struct z_stack_data stack_data[%d] = {\n"
794                     % stack_counter)
795            counter = 0
796            for _, ko in objs.items():
797                if ko.type_name != "K_OBJ_THREAD_STACK_ELEMENT":
798                    continue
799
800                # ko.data currently has the stack size. fetch the value to
801                # populate the appropriate entry in stack_data, and put
802                # a reference to the entry in stack_data into the data value
803                # instead
804                size = ko.data
805                ko.data = "&stack_data[%d]" % counter
806                fp.write("\t{ %d, (uint8_t *)(&priv_stacks[%d]) }"
807                         % (size, counter))
808                if counter != (stack_counter - 1):
809                    fp.write(",")
810                fp.write("\n")
811                counter += 1
812            fp.write("};\n")
813    else:
814        metadata_names["K_OBJ_THREAD_STACK_ELEMENT"] = "stack_size"
815
816    fp.write("%%\n")
817    # Setup variables for mapping thread indexes
818    thread_max_bytes = syms["CONFIG_MAX_THREAD_BYTES"]
819    thread_idx_map = {}
820
821    for i in range(0, thread_max_bytes):
822        thread_idx_map[i] = 0xFF
823
824    for obj_addr, ko in objs.items():
825        obj_type = ko.type_name
826        # pre-initialized objects fall within this memory range, they are
827        # either completely initialized at build time, or done automatically
828        # at boot during some PRE_KERNEL_* phase
829        initialized = static_begin <= obj_addr < static_end
830        is_driver = obj_type.startswith("K_OBJ_DRIVER_")
831
832        if "CONFIG_64BIT" in syms:
833            format_code = "Q"
834        else:
835            format_code = "I"
836
837        if little_endian:
838            endian = "<"
839        else:
840            endian = ">"
841
842        byte_str = struct.pack(endian + format_code, obj_addr)
843        fp.write("\"")
844        for byte in byte_str:
845            val = "\\x%02x" % byte
846            fp.write(val)
847
848        flags = "0"
849        if initialized:
850            flags += " | K_OBJ_FLAG_INITIALIZED"
851        if is_driver:
852            flags += " | K_OBJ_FLAG_DRIVER"
853
854        if ko.type_name in metadata_names:
855            tname = metadata_names[ko.type_name]
856        else:
857            tname = "unused"
858
859        fp.write("\", {}, %s, %s, { .%s = %s }\n" % (obj_type, flags,
860		tname, str(ko.data)))
861
862        if obj_type == "K_OBJ_THREAD":
863            idx = math.floor(ko.data / 8)
864            bit = ko.data % 8
865            thread_idx_map[idx] = thread_idx_map[idx] & ~(2**bit)
866
867    fp.write(footer)
868
869    # Generate the array of already mapped thread indexes
870    fp.write('\n')
871    fp.write('uint8_t _thread_idx_map[%d] = {' % (thread_max_bytes))
872
873    for i in range(0, thread_max_bytes):
874        fp.write(' 0x%x, ' % (thread_idx_map[i]))
875
876    fp.write('};\n')
877
878
879driver_macro_tpl = """
880#define Z_SYSCALL_DRIVER_%(driver_upper)s(ptr, op) Z_SYSCALL_DRIVER_GEN(ptr, op, %(driver_lower)s, %(driver_upper)s)
881"""
882
883
884def write_validation_output(fp):
885    fp.write("#ifndef DRIVER_VALIDATION_GEN_H\n")
886    fp.write("#define DRIVER_VALIDATION_GEN_H\n")
887
888    fp.write("""#define Z_SYSCALL_DRIVER_GEN(ptr, op, driver_lower_case, driver_upper_case) \\
889		(Z_SYSCALL_OBJ(ptr, K_OBJ_DRIVER_##driver_upper_case) || \\
890		 Z_SYSCALL_DRIVER_OP(ptr, driver_lower_case##_driver_api, op))
891                """)
892
893    for subsystem in subsystems:
894        subsystem = subsystem.replace("_driver_api", "")
895
896        fp.write(driver_macro_tpl % {
897            "driver_lower": subsystem.lower(),
898            "driver_upper": subsystem.upper(),
899        })
900
901    fp.write("#endif /* DRIVER_VALIDATION_GEN_H */\n")
902
903
904def write_kobj_types_output(fp):
905    fp.write("/* Core kernel objects */\n")
906    for kobj, obj_info in kobjects.items():
907        dep, _, _ = obj_info
908        if kobj == "device":
909            continue
910
911        if dep:
912            fp.write("#ifdef %s\n" % dep)
913
914        fp.write("%s,\n" % kobject_to_enum(kobj))
915
916        if dep:
917            fp.write("#endif\n")
918
919    fp.write("/* Driver subsystems */\n")
920    for subsystem in subsystems:
921        subsystem = subsystem.replace("_driver_api", "").upper()
922        fp.write("K_OBJ_DRIVER_%s,\n" % subsystem)
923
924
925def write_kobj_otype_output(fp):
926    fp.write("/* Core kernel objects */\n")
927    for kobj, obj_info in kobjects.items():
928        dep, _, _ = obj_info
929        if kobj == "device":
930            continue
931
932        if dep:
933            fp.write("#ifdef %s\n" % dep)
934
935        fp.write('case %s: ret = "%s"; break;\n' %
936                 (kobject_to_enum(kobj), kobj))
937        if dep:
938            fp.write("#endif\n")
939
940    fp.write("/* Driver subsystems */\n")
941    for subsystem in subsystems:
942        subsystem = subsystem.replace("_driver_api", "")
943        fp.write('case K_OBJ_DRIVER_%s: ret = "%s driver"; break;\n' % (
944            subsystem.upper(),
945            subsystem
946        ))
947
948
949def write_kobj_size_output(fp):
950    fp.write("/* Non device/stack objects */\n")
951    for kobj, obj_info in kobjects.items():
952        dep, _, alloc = obj_info
953
954        if not alloc:
955            continue
956
957        if dep:
958            fp.write("#ifdef %s\n" % dep)
959
960        fp.write('case %s: ret = sizeof(struct %s); break;\n' %
961                 (kobject_to_enum(kobj), kobj))
962        if dep:
963            fp.write("#endif\n")
964
965
966def parse_subsystems_list_file(path):
967    with open(path, "r") as fp:
968        subsys_list = json.load(fp)
969    subsystems.extend(subsys_list["__subsystem"])
970    net_sockets.extend(subsys_list["__net_socket"])
971
972def parse_args():
973    global args
974
975    parser = argparse.ArgumentParser(
976        description=__doc__,
977        formatter_class=argparse.RawDescriptionHelpFormatter)
978
979    parser.add_argument("-k", "--kernel", required=False,
980                        help="Input zephyr ELF binary")
981    parser.add_argument(
982        "-g", "--gperf-output", required=False,
983        help="Output list of kernel object addresses for gperf use")
984    parser.add_argument(
985        "-V", "--validation-output", required=False,
986        help="Output driver validation macros")
987    parser.add_argument(
988        "-K", "--kobj-types-output", required=False,
989        help="Output k_object enum constants")
990    parser.add_argument(
991        "-S", "--kobj-otype-output", required=False,
992        help="Output case statements for otype_to_str()")
993    parser.add_argument(
994        "-Z", "--kobj-size-output", required=False,
995        help="Output case statements for obj_size_get()")
996    parser.add_argument("-i", "--include-subsystem-list", required=False, action='append',
997        help='''Specifies a file with a JSON encoded list of subsystem names to append to
998        the driver subsystems list. Can be specified multiple times:
999        -i file1 -i file2 ...''')
1000
1001    parser.add_argument("-v", "--verbose", action="store_true",
1002                        help="Print extra debugging information")
1003    args = parser.parse_args()
1004    if "VERBOSE" in os.environ:
1005        args.verbose = 1
1006
1007
1008def main():
1009    parse_args()
1010
1011    if args.include_subsystem_list is not None:
1012        for list_file in args.include_subsystem_list:
1013            parse_subsystems_list_file(list_file)
1014
1015    if args.gperf_output:
1016        assert args.kernel, "--kernel ELF required for --gperf-output"
1017        elf = ELFFile(open(args.kernel, "rb"))
1018        syms = get_symbols(elf)
1019        max_threads = syms["CONFIG_MAX_THREAD_BYTES"] * 8
1020        objs = find_kobjects(elf, syms)
1021        if not objs:
1022            sys.stderr.write("WARNING: zero kobject found in %s\n"
1023                             % args.kernel)
1024
1025        if thread_counter > max_threads:
1026            sys.exit("Too many thread objects ({})\n"
1027                     "Increase CONFIG_MAX_THREAD_BYTES to {}"
1028                     .format(thread_counter, -(-thread_counter // 8)))
1029
1030        with open(args.gperf_output, "w") as fp:
1031            write_gperf_table(fp, syms, objs, elf.little_endian,
1032                              syms["_static_kernel_objects_begin"],
1033                              syms["_static_kernel_objects_end"])
1034
1035    if args.validation_output:
1036        with open(args.validation_output, "w") as fp:
1037            write_validation_output(fp)
1038
1039    if args.kobj_types_output:
1040        with open(args.kobj_types_output, "w") as fp:
1041            write_kobj_types_output(fp)
1042
1043    if args.kobj_otype_output:
1044        with open(args.kobj_otype_output, "w") as fp:
1045            write_kobj_otype_output(fp)
1046
1047    if args.kobj_size_output:
1048        with open(args.kobj_size_output, "w") as fp:
1049            write_kobj_size_output(fp)
1050
1051
1052if __name__ == "__main__":
1053    main()
1054