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