diff --git a/CMakeLists.txt b/CMakeLists.txt index cf7bc6f119..2dcad1bd0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -520,6 +520,7 @@ if(CONFIG_CPU_HAS_MPU AND CONFIG_USERSPACE) if(CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT) set(ALIGN_SIZING_DEP app_sizing_prebuilt linker_app_sizing_script) endif() + set(PRIV_STACK_DEP priv_stacks_prebuilt) endif() function(construct_add_custom_command_for_linker_pass linker_output_name output_variable) @@ -578,7 +579,7 @@ add_custom_command( add_custom_target( linker_script DEPENDS - ${ALIGN_SIZING_DEP} + ${ALIGN_SIZING_DEP} ${PRIV_STACK_DEP} linker.cmd offsets_h ) @@ -638,6 +639,119 @@ if(CONFIG_GEN_ISR_TABLES) set_property(GLOBAL APPEND PROPERTY GENERATED_KERNEL_SOURCE_FILES isr_tables.c) endif() +if(CONFIG_ARM AND CONFIG_USERSPACE) + set(GEN_PRIV_STACKS $ENV{ZEPHYR_BASE}/scripts/gen_priv_stacks.py) + set(PROCESS_PRIV_STACKS_GPERF $ENV{ZEPHYR_BASE}/scripts/process_gperf.py) + + set(PRIV_STACKS priv_stacks_hash.gperf) + set(PRIV_STACKS_OUTPUT_SRC_PRE priv_stacks_hash_preprocessed.c) + set(PRIV_STACKS_OUTPUT_SRC priv_stacks_hash.c) + set(PRIV_STACKS_OUTPUT_OBJ priv_stacks_hash.c.obj) + set(PRIV_STACKS_OUTPUT_OBJ_RENAMED priv_stacks_hash_renamed.o) + + # Essentially what we are doing here is extracting some information + # out of the nearly finished elf file, generating the source code + # for a hash table based on that information, and then compiling and + # linking the hash table back into a now even more nearly finished + # elf file. + + # Use the script GEN_PRIV_STACKS to scan the kernel binary's + # (zephyr_prebuilt) DWARF information to produce a table of kernel + # objects (PRIV_STACKS) which we will then pass to gperf + add_custom_command( + OUTPUT ${PRIV_STACKS} + COMMAND + ${PYTHON_EXECUTABLE} + ${GEN_PRIV_STACKS} + --kernel $ + --output ${PRIV_STACKS} + $<$:--verbose> + DEPENDS priv_stacks_prebuilt + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_target(priv_stacks DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PRIV_STACKS}) + + # Use gperf to generate C code (PRIV_STACKS_OUTPUT_SRC_PRE) which implements a + # perfect hashtable based on PRIV_STACKS + add_custom_command( + OUTPUT ${PRIV_STACKS_OUTPUT_SRC_PRE} + COMMAND + ${GPERF} -C + --output-file ${PRIV_STACKS_OUTPUT_SRC_PRE} + ${PRIV_STACKS} + DEPENDS priv_stacks + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_target(priv_stacks_output_src_pre DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PRIV_STACKS_OUTPUT_SRC_PRE}) + + # For our purposes the code/data generated by gperf is not optimal. + # + # The script PROCESS_GPERF creates a new c file OUTPUT_SRC based on + # OUTPUT_SRC_PRE to greatly reduce the amount of code/data generated + # since we know we are always working with pointer values + add_custom_command( + OUTPUT ${PRIV_STACKS_OUTPUT_SRC} + COMMAND + ${PROCESS_PRIV_STACKS_GPERF} + -i ${PRIV_STACKS_OUTPUT_SRC_PRE} + -o ${PRIV_STACKS_OUTPUT_SRC} + -p "struct _k_priv_stack_map" + $<$:--verbose> + DEPENDS priv_stacks_output_src_pre + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_target(priv_stacks_output_src DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PRIV_STACKS_OUTPUT_SRC}) + + # We need precise control of where generated text/data ends up in the final + # kernel image. Disable function/data sections and use objcopy to move + # generated data into special section names + add_library(priv_stacks_output_lib STATIC + ${CMAKE_CURRENT_BINARY_DIR}/${PRIV_STACKS_OUTPUT_SRC} + ) + + target_link_libraries(priv_stacks_output_lib zephyr_interface) + + # Turn off -ffunction-sections, etc. + # NB: Using a library instead of target_compile_options(priv_stacks_output_lib + # [...]) because a library's options have precedence + add_library(priv_stacks_output_lib_interface INTERFACE) + target_compile_options(priv_stacks_output_lib_interface INTERFACE + -fno-function-sections + -fno-data-sections + ) + target_link_libraries(priv_stacks_output_lib priv_stacks_output_lib_interface) + + set(PRIV_STACKS_OUTPUT_OBJ_PATH ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/priv_stacks_output_lib.dir/${PRIV_STACKS_OUTPUT_OBJ}) + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PRIV_STACKS_OUTPUT_OBJ_RENAMED} + COMMAND + ${CMAKE_OBJCOPY} + --rename-section .bss=.priv_stacks.noinit + --rename-section .data=.priv_stacks.data + --rename-section .text=.priv_stacks.text + --rename-section .rodata=.priv_stacks.rodata + ${PRIV_STACKS_OUTPUT_OBJ_PATH} + ${PRIV_STACKS_OUTPUT_OBJ_RENAMED} + DEPENDS priv_stacks_output_lib + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_custom_target(priv_stacks_output_obj_renamed DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PRIV_STACKS_OUTPUT_OBJ_RENAMED}) + + add_library(priv_stacks_output_obj_renamed_lib STATIC IMPORTED GLOBAL) + set_property( + TARGET priv_stacks_output_obj_renamed_lib + PROPERTY + IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${PRIV_STACKS_OUTPUT_OBJ_RENAMED} + ) + add_dependencies( + priv_stacks_output_obj_renamed_lib + priv_stacks_output_obj_renamed + ) + + set_property(GLOBAL APPEND PROPERTY GENERATED_KERNEL_OBJECT_FILES priv_stacks_output_obj_renamed_lib) +endif() + if(CONFIG_USERSPACE) set(GEN_KOBJ_LIST ${ZEPHYR_BASE}/scripts/gen_kobject_list.py) set(PROCESS_GPERF ${ZEPHYR_BASE}/scripts/process_gperf.py) @@ -694,6 +808,7 @@ if(CONFIG_USERSPACE) ${PROCESS_GPERF} -i ${OUTPUT_SRC_PRE} -o ${OUTPUT_SRC} + -p "struct _k_object" $<$:--verbose> DEPENDS output_src_pre ${OUTPUT_SRC_PRE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} @@ -804,13 +919,39 @@ if(CONFIG_CPU_HAS_MPU AND CONFIG_USERSPACE) WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/ ) endif() + + construct_add_custom_command_for_linker_pass(linker_priv_stacks custom_command) + add_custom_command( + ${custom_command} + ) + + add_custom_target( + linker_priv_stacks_script + DEPENDS + ${ALIGN_SIZING_DEP} + linker_priv_stacks.cmd + offsets_h + ) + + set_property(TARGET + linker_priv_stacks_script + PROPERTY INCLUDE_DIRECTORIES + ${ZEPHYR_INCLUDE_DIRS} + ) + + set(PRIV_STACK_LIB priv_stacks_output_obj_renamed_lib) + add_executable( priv_stacks_prebuilt misc/empty_file.c) + target_link_libraries(priv_stacks_prebuilt ${TOPT} ${PROJECT_BINARY_DIR}/linker_priv_stacks.cmd ${zephyr_lnk}) + set_property(TARGET priv_stacks_prebuilt PROPERTY LINK_DEPENDS ${PROJECT_BINARY_DIR}/linker_priv_stacks.cmd) + add_dependencies( priv_stacks_prebuilt ${ALIGN_SIZING_DEP} linker_priv_stacks_script offsets) + endif() # FIXME: Is there any way to get rid of empty_file.c? add_executable( zephyr_prebuilt misc/empty_file.c) -target_link_libraries(zephyr_prebuilt ${TOPT} ${PROJECT_BINARY_DIR}/linker.cmd ${zephyr_lnk}) +target_link_libraries(zephyr_prebuilt ${TOPT} ${PROJECT_BINARY_DIR}/linker.cmd ${PRIV_STACK_LIB} ${zephyr_lnk}) set_property(TARGET zephyr_prebuilt PROPERTY LINK_DEPENDS ${PROJECT_BINARY_DIR}/linker.cmd) -add_dependencies( zephyr_prebuilt ${ALIGN_SIZING_DEP} linker_script offsets) +add_dependencies( zephyr_prebuilt ${ALIGN_SIZING_DEP} ${PRIV_STACK_DEP} linker_script offsets) if(NOT CONFIG_NATIVE_APPLICATION) @@ -831,7 +972,7 @@ if(GKOF OR GKSF) add_custom_target( linker_pass_final_script DEPENDS - ${ALIGN_SIZING_DEP} + ${ALIGN_SIZING_DEP} ${PRIV_STACK_DEP} zephyr_prebuilt linker_pass_final.cmd offsets_h @@ -845,7 +986,7 @@ if(GKOF OR GKSF) add_executable( kernel_elf misc/empty_file.c ${GKSF}) target_link_libraries(kernel_elf ${GKOF} ${TOPT} ${PROJECT_BINARY_DIR}/linker_pass_final.cmd ${zephyr_lnk}) set_property(TARGET kernel_elf PROPERTY LINK_DEPENDS ${PROJECT_BINARY_DIR}/linker_pass_final.cmd) - add_dependencies( kernel_elf ${ALIGN_SIZING_DEP} linker_pass_final_script) + add_dependencies( kernel_elf ${ALIGN_SIZING_DEP} ${PRIV_STACK_DEP} linker_pass_final_script) else() set(logical_target_for_zephyr_elf zephyr_prebuilt) # Use the prebuilt elf as the final elf since we don't have a diff --git a/arch/Kconfig b/arch/Kconfig index 03e6993a28..b068b5e5cc 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -78,6 +78,16 @@ config USERSPACE This feature is under heavy development and APIs related to it are subject to change, even if declared non-private. +config PRIVILEGED_STACK_SIZE + int "Size of privileged stack" + default 256 + depends on ARCH_HAS_USERSPACE + help + This option sets the priviliged stack region size that will be used + in addition to the user mode thread stack. During normal execution, + this region will be inaccessible from user mode. During system calls, + this region will be utilized by the system call. + config MAX_THREAD_BYTES int "Bytes to use when tracking object thread permissions" default 2 diff --git a/arch/arm/core/offsets/offsets.c b/arch/arm/core/offsets/offsets.c index 176bbd16ff..4386e885c3 100644 --- a/arch/arm/core/offsets/offsets.c +++ b/arch/arm/core/offsets/offsets.c @@ -29,6 +29,10 @@ GEN_OFFSET_SYM(_thread_arch_t, basepri); GEN_OFFSET_SYM(_thread_arch_t, swap_return_value); +#ifdef CONFIG_USERSPACE +GEN_OFFSET_SYM(_thread_arch_t, priv_stack_start); +#endif + #ifdef CONFIG_FLOAT GEN_OFFSET_SYM(_thread_arch_t, preempt_float); #endif diff --git a/arch/arm/include/offsets_short_arch.h b/arch/arm/include/offsets_short_arch.h index fa16348929..73cb03e2b6 100644 --- a/arch/arm/include/offsets_short_arch.h +++ b/arch/arm/include/offsets_short_arch.h @@ -26,6 +26,11 @@ #define _thread_offset_to_preempt_float \ (___thread_t_arch_OFFSET + ___thread_arch_t_preempt_float_OFFSET) +#ifdef CONFIG_USERSPACE +#define _thread_offset_to_priv_stack_start \ + (___thread_t_arch_OFFSET + ___thread_arch_t_priv_stack_start_OFFSET) +#endif + /* end - threads */ #endif /* _offsets_short_arch__h_ */ diff --git a/include/arch/arm/cortex_m/scripts/linker.ld b/include/arch/arm/cortex_m/scripts/linker.ld index d676f21074..73c960e560 100644 --- a/include/arch/arm/cortex_m/scripts/linker.ld +++ b/include/arch/arm/cortex_m/scripts/linker.ld @@ -126,6 +126,7 @@ SECTIONS *(".text.*") *(.gnu.linkonce.t.*) +#include #include } GROUP_LINK_IN(ROMABLE_REGION) @@ -162,6 +163,7 @@ SECTIONS #include #endif +#include #include /* @@ -310,6 +312,7 @@ SECTIONS __data_rom_start = LOADADDR(_DATA_SECTION_NAME); #include +#include #include __data_ram_end = .; diff --git a/include/linker/priv_stacks-rom.ld b/include/linker/priv_stacks-rom.ld new file mode 100644 index 0000000000..1f1196c23d --- /dev/null +++ b/include/linker/priv_stacks-rom.ld @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2017 Linaro Limited. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef CONFIG_USERSPACE + /* Kept in RAM on non-XIP */ +#ifdef CONFIG_XIP + *(".priv_stacks.rodata*") +#endif +#endif /* CONFIG_USERSPACE */ + diff --git a/include/linker/priv_stacks-text.ld b/include/linker/priv_stacks-text.ld new file mode 100644 index 0000000000..b796e4eb8a --- /dev/null +++ b/include/linker/priv_stacks-text.ld @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Linaro Limited. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef PRIV_STACKS_TEXT_AREA +#if defined(CONFIG_DEBUG) || defined(CONFIG_STACK_CANARIES) +#define PRIV_STACKS_TEXT_AREA 256 +#else +#define PRIV_STACKS_TEXT_AREA 128 +#endif +#endif + +#ifdef CONFIG_USERSPACE + /* We need to reserve room for the gperf generated hash functions. + * Fortunately, unlike the data tables, the size of the code is + * reasonably predictable. + * + * The linker will error out complaining that the location pointer + * is moving backwards if the reserved room isn't large enough. + */ + _priv_stacks_text_area_start = .; + *(".priv_stacks.text*") + _priv_stacks_text_area_end = .; +#ifndef LINKER_PASS2 + PROVIDE(_k_priv_stack_find = .); +#endif + . += PRIV_STACKS_TEXT_AREA - (_priv_stacks_text_area_end - _priv_stacks_text_area_start); +#endif /* CONFIG_USERSPACE */ + diff --git a/include/linker/priv_stacks.ld b/include/linker/priv_stacks.ld new file mode 100644 index 0000000000..1ea43d0245 --- /dev/null +++ b/include/linker/priv_stacks.ld @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef CONFIG_USERSPACE + /* Constraints: + * + * - changes to the size of this section between build phases + * *must not* shift the memory address of any kernel obejcts, + * since it contains a hashtable of the memory addresses of those + * kernel objects + * + * - It is OK if this section itself is shifted in between builds; for + * example some arches may precede this section with generated MMU + * page tables which are also unpredictable in size. + * + * The size of the + * gperf tables is both a function of the number of kernel objects, + * *and* the specific memory addresses being hashed. It is not something + * that can be predicted without actually building and compling it. + */ + SECTION_DATA_PROLOGUE(priv_stacks, (OPTIONAL),) + { + *(".priv_stacks.data*") + + /* This is also unpredictable in size, and has the same constraints. + * On XIP systems this will get put at the very end of ROM. + */ +#ifndef CONFIG_XIP + *(".priv_stacks.rodata*") +#endif + } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) +#endif /* CONFIG_USERSPACE */ + diff --git a/scripts/gen_priv_stacks.py b/scripts/gen_priv_stacks.py new file mode 100755 index 0000000000..8037e72fa8 --- /dev/null +++ b/scripts/gen_priv_stacks.py @@ -0,0 +1,585 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017 Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 + +import sys +import argparse +import pprint +import os +import struct +from distutils.version import LooseVersion + +import elftools +from elftools.elf.elffile import ELFFile +from elftools.dwarf import descriptions +from elftools.elf.sections import SymbolTableSection + +if LooseVersion(elftools.__version__) < LooseVersion('0.24'): + sys.stderr.write("pyelftools is out of date, need version 0.24 or later\n") + sys.exit(1) + +stack_objects = [ + "k_stack", + "_k_thread_stack_element", + ] + + +def subsystem_to_enum(subsys): + return "K_OBJ_DRIVER_" + subsys[:-11].upper() + +def kobject_to_enum(ko): + return "K_OBJ_" + ko[2:].upper() + +DW_OP_addr = 0x3 +DW_OP_fbreg = 0x91 +STACK_TYPE = "_k_thread_stack_element" +thread_counter = 0 + +# Global type environment. Populated by pass 1. +type_env = {} + +# --- debug stuff --- + +scr = os.path.basename(sys.argv[0]) + +def debug(text): + if not args.verbose: + return + sys.stdout.write(scr + ": " + text + "\n") + +def error(text): + sys.stderr.write("%s ERROR: %s\n" % (scr, text)) + sys.exit(1) + +def debug_die(die, text): + fn, ln = get_filename_lineno(die) + + debug(str(die)) + debug("File '%s', line %d:" % (fn, ln)) + debug(" %s" % text) + +# --- type classes ---- + +class KobjectInstance: + def __init__(self, type_obj, addr): + global thread_counter + + self.addr = addr + self.type_obj = type_obj + + # Type name determined later since drivers needs to look at the + # API struct address + self.type_name = None + + if self.type_obj.name == "k_thread": + # Assign an ID for this thread object, used to track its + # permissions to other kernel objects + self.data = thread_counter + thread_counter = thread_counter + 1 + else: + self.data = 0 + + +class KobjectType: + def __init__(self, offset, name, size, api=False): + self.name = name + self.size = size + self.offset = offset + self.api = api + + def __repr__(self): + return "" % self.name + + def has_kobject(self): + return True + + def get_kobjects(self, addr): + return {addr: KobjectInstance(self, addr)} + + +class ArrayType: + def __init__(self, offset, elements, member_type): + self.elements = elements + self.member_type = member_type + self.offset = offset + + def __repr__(self): + return "" % (self.member_type, self.num_members) + + def has_kobject(self): + if self.member_type not in type_env: + return False + + return type_env[self.member_type].has_kobject() + + def get_kobjects(self, addr): + mt = type_env[self.member_type] + + # Stacks are arrays of _k_stack_element_t but we want to treat + # the whole array as one kernel object (a thread stack) + # Data value gets set to size of entire region + if isinstance(mt, KobjectType) and mt.name == STACK_TYPE: + # An array of stacks appears as a multi-dimensional array. + # The last size is the size of each stack. We need to track + # each stack within the array, not as one huge stack object. + *dimensions, stacksize = self.elements + num_members = 1 + for e in dimensions: + num_members = num_members * e + + ret = {} + for i in range(num_members): + a = addr + (i * stacksize) + o = mt.get_kobjects(a) + o[a].data = stacksize + ret.update(o) + return ret + + objs = {} + + # Multidimensional array flattened out + num_members = 1 + for e in self.elements: + num_members = num_members * e + + for i in range(num_members): + objs.update(mt.get_kobjects(addr + (i * mt.size))) + return objs + + +class AggregateTypeMember: + def __init__(self, offset, member_name, member_type, member_offset): + self.member_name = member_name + self.member_type = member_type + self.member_offset = member_offset + + def __repr__(self): + return "" % (self.member_name, + self.member_type, self.member_offset) + + def has_kobject(self): + if self.member_type not in type_env: + return False + + return type_env[self.member_type].has_kobject() + + def get_kobjects(self, addr): + mt = type_env[self.member_type] + return mt.get_kobjects(addr + self.member_offset) + + +class ConstType: + def __init__(self, child_type): + self.child_type = child_type + + def __repr__(self): + return "" % self.child_type + + def has_kobject(self): + if self.child_type not in type_env: + return False + + return type_env[self.child_type].has_kobject() + + def get_kobjects(self, addr): + return type_env[self.child_type].get_kobjects(addr) + + +class AggregateType: + def __init__(self, offset, name, size): + self.name = name + self.size = size + self.offset = offset + self.members = [] + + def add_member(self, member): + self.members.append(member) + + def __repr__(self): + return "" % (self.name, self.members) + + def has_kobject(self): + result = False + + bad_members = [] + + for member in self.members: + if member.has_kobject(): + result = True + else: + bad_members.append(member) + # Don't need to consider this again, just remove it + + for bad_member in bad_members: + self.members.remove(bad_member) + + return result + + def get_kobjects(self, addr): + objs = {} + for member in self.members: + objs.update(member.get_kobjects(addr)) + return objs + + +# --- helper functions for getting data from DIEs --- + +def die_get_name(die): + if not 'DW_AT_name' in die.attributes: + return None + return die.attributes["DW_AT_name"].value.decode("utf-8") + + +def die_get_type_offset(die): + if not 'DW_AT_type' in die.attributes: + return 0 + + return die.attributes["DW_AT_type"].value + die.cu.cu_offset + + +def die_get_byte_size(die): + if not 'DW_AT_byte_size' in die.attributes: + return 0 + + return die.attributes["DW_AT_byte_size"].value + +def analyze_die_struct(die): + name = die_get_name(die) or "" + offset = die.offset + size = die_get_byte_size(die) + + # Incomplete type + if not size: + return + + if name in stack_objects: + type_env[offset] = KobjectType(offset, name, size) + else: + at = AggregateType(offset, name, size) + type_env[offset] = at + + for child in die.iter_children(): + if child.tag != "DW_TAG_member": + continue + child_type = die_get_type_offset(child) + member_offset = child.attributes["DW_AT_data_member_location"].value + cname = die_get_name(child) or "" + m = AggregateTypeMember(child.offset, cname, child_type, + member_offset) + at.add_member(m) + + return + + +def analyze_die_const(die): + type_offset = die_get_type_offset(die) + if not type_offset: + return + + type_env[die.offset] = ConstType(type_offset) + + +def analyze_die_array(die): + type_offset = die_get_type_offset(die) + elements = [] + + for child in die.iter_children(): + if child.tag != "DW_TAG_subrange_type": + continue + if "DW_AT_upper_bound" not in child.attributes: + continue + + ub = child.attributes["DW_AT_upper_bound"] + if not ub.form.startswith("DW_FORM_data"): + continue + + elements.append(ub.value + 1) + + if not elements: + return + + type_env[die.offset] = ArrayType(die.offset, elements, type_offset) + + +def addr_deref(elf, addr): + for section in elf.iter_sections(): + start = section['sh_addr'] + end = start + section['sh_size'] + + if addr >= start and addr < end: + data = section.data() + offset = addr - start + return struct.unpack("I", + data[offset:offset+4])[0] + + return 0 + + +def device_get_api_addr(elf, addr): + return addr_deref(elf, addr + 4) + + +def get_filename_lineno(die): + lp_header = die.dwarfinfo.line_program_for_CU(die.cu).header + files = lp_header["file_entry"] + includes = lp_header["include_directory"] + + fileinfo = files[die.attributes["DW_AT_decl_file"].value - 1] + filename = fileinfo.name.decode("utf-8") + filedir = includes[fileinfo.dir_index - 1].decode("utf-8") + + path = os.path.join(filedir, filename) + lineno = die.attributes["DW_AT_decl_line"].value + return (path, lineno) + + +def find_stack_objects(elf, syms): + if not elf.has_dwarf_info(): + sys.stderr.write("ELF file has no DWARF information\n"); + sys.exit(1) + + kram_start = syms["__kernel_ram_start"] + kram_end = syms["__kernel_ram_end"] + krom_start = syms["_image_rom_start"] + krom_end = syms["_image_rom_end"] + + di = elf.get_dwarf_info() + + variables = [] + + # Step 1: collect all type information. + for CU in di.iter_CUs(): + CU_path = CU.get_top_DIE().get_full_path() + lp = di.line_program_for_CU(CU) + + for idx, die in enumerate(CU.iter_DIEs()): + # Unions are disregarded, kernel objects should never be union + # members since the memory is not dedicated to that object and + # could be something else + if die.tag == "DW_TAG_structure_type": + analyze_die_struct(die) + elif die.tag == "DW_TAG_const_type": + analyze_die_const(die) + elif die.tag == "DW_TAG_array_type": + analyze_die_array(die) + elif die.tag == "DW_TAG_variable": + variables.append(die) + + # Step 2: filter type_env to only contain kernel objects, or structs and + # arrays of kernel objects + bad_offsets = [] + for offset, type_object in type_env.items(): + if not type_object.has_kobject(): + bad_offsets.append(offset) + + for offset in bad_offsets: + del type_env[offset] + + # Step 3: Now that we know all the types we are looking for, examine + # all variables + all_objs = {} + + # Gross hack, see below + work_q_found = False + + for die in variables: + name = die_get_name(die) + if not name: + continue + + type_offset = die_get_type_offset(die) + + # Is this a kernel object, or a structure containing kernel objects? + if type_offset not in type_env: + continue + + if "DW_AT_declaration" in die.attributes: + # FIXME: why does k_sys_work_q not resolve an address in the DWARF + # data??? Every single instance it finds is an extern definition + # but not the actual instance in system_work_q.c + # Is there something weird about how lib-y stuff is linked? + if name == "k_sys_work_q" and not work_q_found and name in syms: + addr = syms[name] + work_q_found = True + else: + continue + else: + if "DW_AT_location" not in die.attributes: + debug_die(die, "No location information for object '%s'; possibly stack allocated" + % name) + continue + + loc = die.attributes["DW_AT_location"] + if loc.form != "DW_FORM_exprloc": + debug_die(die, "kernel object '%s' unexpected location format" % name) + continue + + opcode = loc.value[0] + if opcode != DW_OP_addr: + + # Check if frame pointer offset DW_OP_fbreg + if opcode == DW_OP_fbreg: + debug_die(die, "kernel object '%s' found on stack" % name) + else: + debug_die(die, "kernel object '%s' unexpected exprloc opcode %s" + % (name, hex(opcode))) + continue + + addr = (loc.value[1] | (loc.value[2] << 8) | (loc.value[3] << 16) | + (loc.value[4] << 24)) + + if addr == 0: + # Never linked; gc-sections deleted it + continue + + if ((addr < kram_start or addr >= kram_end) + and (addr < krom_start or addr >= krom_end)): + + debug_die(die, "object '%s' found in invalid location %s" % + (name, hex(addr))); + continue + + type_obj = type_env[type_offset] + objs = type_obj.get_kobjects(addr) + all_objs.update(objs) + + debug("symbol '%s' at %s contains %d stack object(s)" % (name, hex(addr), + len(objs))) + + # Step 4: objs is a dictionary mapping variable memory addresses to their + # associated type objects. Now that we have seen all variables and can + # properly look up API structs, convert this into a dictionary mapping + # variables to the C enumeration of what kernel object type it is. + ret = {} + for addr, ko in all_objs.items(): + # API structs don't get into the gperf table + if ko.type_obj.api: + continue + + if ko.type_obj.name != "device": + # Not a device struct so we immediately know its type + ko.type_name = kobject_to_enum(ko.type_obj.name) + ret[addr] = ko + continue + + # Device struct. Need to get the address of its API struct, if it has + # one. + apiaddr = device_get_api_addr(elf, addr) + if apiaddr not in all_objs: + # API struct does not correspond to a known subsystem, skip it + continue + + apiobj = all_objs[apiaddr] + ko.type_name = subsystem_to_enum(apiobj.type_obj.name) + ret[addr] = ko + + debug("found %d stack object instances total" % len(ret)) + return ret + + +header = """%compare-lengths +%define lookup-function-name _k_priv_stack_map_lookup +%language=ANSI-C +%global-table +%struct-type +""" + + +priv_stack_decl_temp = "static u8_t __used __aligned(CONFIG_PRIVILEGED_STACK_SIZE) priv_stack_%x[CONFIG_PRIVILEGED_STACK_SIZE];\n" +priv_stack_decl_size = "CONFIG_PRIVILEGED_STACK_SIZE" + + +includes = """#include +#include +""" + + +structure = """struct _k_priv_stack_map { + char *name; + u8_t *priv_stack_addr; +}; +%% +""" + + +# Different versions of gperf have different prototypes for the lookup function, +# best to implement the wrapper here. The pointer value itself is turned into +# a string, we told gperf to expect binary strings that are not NULL-terminated. +footer = """%% +u8_t *_k_priv_stack_find(void *obj) +{ + const struct _k_priv_stack_map *map = + _k_priv_stack_map_lookup((const char *)obj, sizeof(void *)); + return map->priv_stack_addr; +} +""" + + + +def write_gperf_table(fp, objs, static_begin, static_end): + fp.write(header) + + # priv stack declarations + fp.write("%{\n") + fp.write(includes) + for obj_addr, ko in objs.items(): + fp.write(priv_stack_decl_temp % (obj_addr)) + fp.write("%}\n") + + # structure declaration + fp.write(structure) + + for obj_addr, ko in objs.items(): + byte_str = struct.pack("I", obj_addr) + fp.write("\"") + for byte in byte_str: + val = "\\x%02x" % byte + fp.write(val) + + fp.write("\",priv_stack_%x\n" % obj_addr) + + fp.write(footer) + + +def get_symbols(obj): + for section in obj.iter_sections(): + if isinstance(section, SymbolTableSection): + return {sym.name: sym.entry.st_value + for sym in section.iter_symbols()} + + raise LookupError("Could not find symbol table") + + +def parse_args(): + global args + + parser = argparse.ArgumentParser(description = __doc__, + formatter_class = argparse.RawDescriptionHelpFormatter) + + parser.add_argument("-k", "--kernel", required=True, + help="Input zephyr ELF binary") + parser.add_argument("-o", "--output", required=True, + help="Output list of kernel object addresses for gperf use") + parser.add_argument("-v", "--verbose", action="store_true", + help="Print extra debugging information") + args = parser.parse_args() + + +def main(): + parse_args() + + with open(args.kernel, "rb") as fp: + elf = ELFFile(fp) + args.little_endian = elf.little_endian + syms = get_symbols(elf) + objs = find_stack_objects(elf, syms) + + with open(args.output, "w") as fp: + write_gperf_table(fp, objs, syms["_static_kernel_objects_begin"], + syms["_static_kernel_objects_end"]) + +if __name__ == "__main__": + main() + diff --git a/scripts/process_gperf.py b/scripts/process_gperf.py index 95a4f78d3b..7578888283 100755 --- a/scripts/process_gperf.py +++ b/scripts/process_gperf.py @@ -86,7 +86,7 @@ def process_line(line, fp): # Set the lookup function to static inline so it gets rolled into # _k_object_find(), nothing else will use it - if re.search("struct _k_object [*]$", line): + if re.search(args.pattern + " [*]$", line): fp.write("static inline " + line) return @@ -142,6 +142,8 @@ def parse_args(): help="Input C file from gperf") parser.add_argument("-o", "--output", required=True, help="Output C file with processing done") + parser.add_argument("-p", "--pattern", required=True, + help="Search pattern for objects") parser.add_argument("-v", "--verbose", action="store_true", help="Print extra debugging information") args = parser.parse_args()