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