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