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