From 8c0eebac7d28354bebcf9a8ff690bad05c8d4cf2 Mon Sep 17 00:00:00 2001 From: Christian Mauderer Date: Wed, 10 Aug 2016 15:20:42 +0200 Subject: userspace-header-gen.py: Simplify program ports --- userspace-header-gen.py | 513 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100755 userspace-header-gen.py (limited to 'userspace-header-gen.py') diff --git a/userspace-header-gen.py b/userspace-header-gen.py new file mode 100755 index 00000000..d062275a --- /dev/null +++ b/userspace-header-gen.py @@ -0,0 +1,513 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 embedded brains GmbH. All rights reserved. +# +# embedded brains GmbH +# Dornierstr. 4 +# 82178 Puchheim +# Germany +# +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +from __future__ import print_function, division +import argparse +import sys +from elftools.elf.elffile import ELFFile +import re +import copy +import os + +VERBOSE_SOME = 1 +VERBOSE_MORE = 2 +VERBOSE_MOST = 3 + +class Error(Exception): + """Base class for exceptions in this module.""" + pass + + +class NoDwarfInfoError(Error): + """Exception raised in case there is no DWARF information.""" + + def __init__(self): + super(NoDwarfInfoError, self).__init__("Input file has no DWARF info.") + + +class TypenameNotFoundError(Error): + """Exception raised in case a die is not found like expected.""" + pass + + +class AnonymousStructureError(Error): + """Exception raised in case a die is not found like expected.""" + pass + + +class VarnameNotFoundError(Error): + """Exception raised in case a die is not found like expected.""" + + def __init__(self): + super(VarnameNotFoundError, self).__init__("Couldn't find the variables name.") + + +class HeaderGenCU: + """Process a single CU""" + + def __init__(self, cu, progname, lineprog, err = sys.stderr, verbose = 0, + filterre = re.compile('.*')): + self._rtems_port_names = [] + self._rtems_port_names.append("_Linker_set_bsd_prog_%s_begin" % progname) + self._rtems_port_names.append("_Linker_set_bsd_prog_%s_end" % progname) + self._rtems_port_names.append("rtems_bsd_command_%s" % progname) + + self._err = err + self._verbose = verbose + self._cu = cu + self._progname = progname + self._die_by_offset = {} + self._lineprogram = lineprog + self._filterre = filterre + self._namespace_prefix = "_bsd_%s_" % (self._progname) + + self._fill_die_list() + + if self._verbose >= VERBOSE_MOST: + print('DIE list: \n', self._die_by_offset) + + def _fill_die_list(self, die = None): + if die is None: + die = self._cu.get_top_DIE() + # Use relative indices for the keys like they are used to reference + # inside one cu + offset = die.offset - self._cu.cu_offset + self._die_by_offset[offset] = die + for child in die.iter_children(): + self._fill_die_list(child) + + def _die_is_var(self, die): + return (die.tag == "DW_TAG_variable") + + def _die_is_function(self, die): + return (die.tag == "DW_TAG_subprogram") + + def _get_type(self, die): + """Get the type of a variable DIE. + Returns two strings: one prefix and one postfix for the variable name""" + typepre = "" + typepost = "" + + if self._verbose >= VERBOSE_MOST: + self._err.write('Search type for DIE with offset=%d\n' % \ + (die.offset)) + + try: + typedie_offset = die.attributes["DW_AT_type"].value + except KeyError: + raise TypenameNotFoundError('Couldn\'t find the offset of the type DIE\n') + + try: + typedie = self._die_by_offset[typedie_offset] + except KeyError: + raise TypenameNotFoundError('Couldn\'t find the DIE at offset %d\n' % \ + (typedie_offset)) + + last = False + if (typedie.tag == "DW_TAG_const_type"): + typepre += "const " + + elif (typedie.tag == "DW_TAG_array_type"): + for child in typedie.iter_children(): + if child.tag == "DW_TAG_subrange_type": + try: + upper_bound = child.attributes["DW_AT_upper_bound"].value + arraysize = "%d" % (upper_bound + 1) + except KeyError: + arraysize = "" + typepost += "[%s]" % arraysize + + elif (typedie.tag == "DW_TAG_volatile_type"): + typepre += "volatile " + + elif (typedie.tag == "DW_TAG_pointer_type"): + typepre += "*" + + elif (typedie.tag == "DW_TAG_structure_type"): + typepre += "struct " + + elif (typedie.tag == "DW_TAG_enumeration_type"): + typepre += "enum " + + elif (typedie.tag == "DW_TAG_subroutine_type"): + typepre = "(" + typepost = ")(" + current_child = 0 + for child in typedie.iter_children(): + pre, post = self._get_type(child) + if (current_child > 0): + typepost += ", " + typepost += pre + post + current_child += 1 + if current_child == 0: + typepost += "void" + typepost += ")" + if not "DW_AT_type" in typedie.attributes.keys(): + typepre = "void " + typepre + last = True + + elif (typedie.tag == "DW_TAG_typedef") or \ + (typedie.tag == "DW_TAG_base_type"): + # nothing to do here than prevent the error + pass + + else: + raise TypenameNotFoundError('Unknown tag: %s\n' % (typedie.tag)) + + if (typedie.tag == "DW_TAG_typedef") or \ + (typedie.tag == "DW_TAG_base_type") or \ + (typedie.tag == "DW_TAG_structure_type") or \ + (typedie.tag == "DW_TAG_enumeration_type"): + last = True + try: + typepre += "%s " % \ + typedie.attributes["DW_AT_name"].value.decode('ascii') + except KeyError: + if typedie.has_children: + message = 'Found an anonymous structure' + raise AnonymousStructureError(message) + else: + message = 'Couldn\'t get type name from DIE' + raise TypenameNotFoundError(message) + + if last == False: + addpre, addpost = self._get_type(typedie) + typepre = addpre + typepre + typepost = typepost + addpost + + if self._verbose >= VERBOSE_MOST: + self._err.write('Add prefix="%s", postfix="%s" for DIE with offset=%d\n' % \ + (typepre, typepost, die.offset)) + + return typepre, typepost + + def generate_header(self, data_out_filename, glob_data_out, namesp_out): + """Find all top level (global) variables in the ELF file and generate + output that can be written in a header. + """ + + top_die = self._cu.get_top_DIE() + try: + filename = top_die.attributes["DW_AT_name"].value.decode('ascii') + except KeyError: + filename = top_die.get_full_path() + self._err.write("WARNING: getting the filename failed. Use fallback.") + + basename = os.path.basename(filename) + modulename = os.path.splitext(basename)[0] + my_data_out_filename = data_out_filename.replace("#MODULE#", modulename) + my_data_out = open(my_data_out_filename, "w") + + glob_data_out.write("/* %s */\n" % (basename)) + + namesp_out.write("/* %s */\n" % (basename)) + + my_data_out.write("/* generated by userspace-header-gen.py */\n") + my_data_out.write("#include \n") + my_data_out.write('#include "%s"\n' % (glob_data_out.name)) + my_data_out.write("/* %s */\n" % (basename)) + + self._process_die(top_die, my_data_out, glob_data_out, namesp_out) + + def _is_constant(self, die): + is_constant = False + try: + type_offset = die.attributes["DW_AT_type"].value + typedie = self._die_by_offset[type_offset] + except KeyError: + self._err.write("WARNING: Could not find out whether DIE %d is const.\n" % \ + die.offset) + pass + else: + if typedie.tag == "DW_TAG_const_type": + is_constant = True + return is_constant + + def _process_die(self, die, data_out, glob_data_out, namesp_out): + for child in die.iter_children(): + specdie = child + # get the name of the DIE + try: + varname = child.attributes["DW_AT_name"].value.decode('ascii') + except KeyError: + # this might is an external variable with a specification + # located elsewhere + try: + specification = child.attributes["DW_AT_specification"]\ + .value + specdie = self._die_by_offset[specification] + varname = specdie.attributes["DW_AT_name"].value\ + .decode('ascii') + except KeyError: + varname = None + + # filter all none variable or function DIEs + is_function = False + if self._die_is_var(child): + if self._verbose >= VERBOSE_MORE: + self._err.write('Process variable DIE: tag=%s, name=%s\n' % \ + (child.tag, varname)) + elif self._die_is_function(child): + if self._verbose >= VERBOSE_MORE: + self._err.write('Process function DIE: tag=%s, name=%s\n' % \ + (child.tag, varname)) + if varname is None: + if self._verbose >= VERBOSE_MORE: + self._err.write('Skip function with no name.\n') + continue + is_function = True + else: + if self._verbose >= VERBOSE_MORE: + self._err.write('DIE is no variable or function: tag=%s, name=%s\n' % \ + (child.tag, varname)) + # FIXME: Check if this die has children and if one of the + # children is a function static variable + continue + + # filter some special names that are used for porting + if varname in self._rtems_port_names: + self._err.write('Skip %s. It is a special object for porting.\n' % \ + (varname)) + continue + + # check if it is an external variable + is_extern = False + try: + is_extern = (specdie.attributes["DW_AT_external"].value != 0) + except KeyError: + # if the key is not there it is not extern + is_extern = False + + # check if it is an declaration + is_decl = False + try: + is_decl = (specdie.attributes["DW_AT_declaration"].value != 0) + except KeyError: + # if the key is not there it is not an declaration + is_decl = False + + # filter declaration only lines (we only want the definitions) + if is_decl and specdie == child: + if self._verbose >= VERBOSE_MORE: + self._err.write('Skip extern variable "%s" because it is only a declaration.\n' % \ + (varname)) + continue + + # filter constants + if (not is_function) and self._is_constant(specdie): + if self._verbose >= VERBOSE_SOME: + self._err.write('Skip const variable "%s" because it is a const.\n' % (varname)) + continue + + # Check if we haven't found a name earlier + if varname is None: + raise VarnameNotFoundError + + # Fixup name (necessary if the script runs a second time) + varname = varname.replace(self._namespace_prefix, "") + + # get file and line + try: + decl_file_idx = child.attributes["DW_AT_decl_file"].value - 1 + decl_file = self._lineprogram['file_entry'][decl_file_idx].name + except KeyError: + decl_file = "" + try: + decl_line = child.attributes["DW_AT_decl_line"].value + except KeyError: + decl_line = "" + var_decl = "%s:%s" % (decl_file, decl_line) + + if self._filterre.match(decl_file) is None: + if self._verbose >= VERBOSE_SOME: + self._err.write('Skip variable "%s" because it\'s declaration file (%s) doesn\'t match the filter\n' % \ + (varname, var_decl)) + continue + + # get type for the variable + if not is_function: + try: + typepre, typepost = self._get_type(specdie) + except TypenameNotFoundError: + self._err.write('Couldn\'t find type for "%s" at %s\n' % + (varname, var_decl)) + raise + except AnonymousStructureError: + self._err.write('ERROR: anonymous structure "%s" at %s\n' % \ + (varname, var_decl)) + raise + var_with_type = "%s%s%s" % (typepre, varname, typepost) + + # check if it is a static or a extern + if not is_extern: + var_with_type = "static " + var_with_type + outfile = data_out + else: + self._err.write('WARNING: variable is not static: "%s" at %s\n' % \ + (var_with_type, var_decl)) + var_with_type = "extern " + var_with_type + outfile = glob_data_out + + # write output + if self._verbose >= VERBOSE_SOME: + if not is_function: + self._err.write('Found a variable "%s" at %s (DIE offset %s); extern: %r\n' % \ + (var_with_type, var_decl, child.offset, is_extern)) + else: + self._err.write('Found a function "%s" at %s (DIE offset %s); extern: %r\n' % \ + (varname, var_decl, child.offset, is_extern)) + if not is_function: + outfile.write("RTEMS_LINKER_RWSET_CONTENT(bsd_prog_%s, %s);\n" % \ + (self._progname, var_with_type)) + if is_extern: + namesp_out.write("#define %s %s%s\n" % \ + (varname, self._namespace_prefix, varname)) + + +class UserspaceHeaderGen: + def __init__(self, objfiles, progname, err = sys.stderr, verbose = 0, + filterre = re.compile(".*")): + self._err = err + self._verbose = verbose + self._objfiles = objfiles + self._progname = progname + self._filterre = filterre + + def generate_header(self, data_out_filename, glob_data_out, namesp_out): + """Find all top level (global) variables in the ELF file and generate + a header. + """ + glob_data_out.write("/* generated by userspace-header-gen.py */\n") + glob_data_out.write("#include \n") + + namesp_out.write("/* generated by userspace-header-gen.py */\n") + + for objfile in self._objfiles: + elffile = ELFFile(objfile) + if not elffile.has_dwarf_info(): + raise NoDwarfInfoError() + + # Don't relocate DWARF sections. This is not necessary for us but + # makes problems on ARM with current pyelftools (version 0.24) + dwarfinfo = elffile.get_dwarf_info(relocate_dwarf_sections=False) + + for cu in dwarfinfo.iter_CUs(): + if self._verbose >= VERBOSE_SOME: + self._err.write('Found a CU at offset %s, length %s\n' % \ + (cu.cu_offset, cu['unit_length'])) + + lineprog = dwarfinfo.line_program_for_CU(cu) + headergen = HeaderGenCU(cu, self._progname, lineprog, self._err, + self._verbose, self._filterre); + headergen.generate_header(data_out_filename, glob_data_out, + namesp_out); + + +if __name__ == '__main__': + default_filter = '.*' + default_dataout = 'rtems-bsd-#PROGNAME#-#MODULE#-data.h' + default_globdataout = 'rtems-bsd-#PROGNAME#-data.h' + default_namespaceout = 'rtems-bsd-#PROGNAME#-namespace.h' + parser = argparse.ArgumentParser( + description=( + "Generate header files for porting FreeBSD user space tools to RTEMS." + "Takes an object file as input." + )) + parser.add_argument( + "objfile", + help="Text arguments. One or more can be appended to the call.", + type=argparse.FileType("rb"), + nargs='+' + ) + parser.add_argument( + "-f", "--filter", + help="Only process variables that are defined in files with a name " \ + "matching the given regular expression. " \ + "Default: '%s'" % default_filter, + dest="filter_string", + default=default_filter + ) + parser.add_argument( + "-p", "--progname", + help="Name of the program. Default: MYPROG", + default="MYPROG" + ) + parser.add_argument( + "-d", "--dataout", + help="Name of the output files where the section attributes will be " \ + "added. '#PROGNAME#' will be replaced by the program name " \ + "(set by parameter -p). '#MODULE#' will be replaced by the " + "current c modules base name. " \ + "Default: '%s'" % (default_dataout), + default=default_dataout, + nargs="?" + ) + parser.add_argument( + "-g", "--globdataout", + help="Name of the output files where the section attributes for " \ + "global variables will be added. " \ + "Default: '%s'" % (default_globdataout), + default=default_globdataout, + nargs="?" + ) + parser.add_argument( + "-n", "--namespaceout", + help="Name of the output file where namespace definitions will be " \ + "added. Default: '%s'" % (default_namespaceout), + default=default_namespaceout, + nargs="?" + ) + parser.add_argument( + "-v", "--verbose", + help="Be more verbose. Can be used multiple times.", + default=0, + action="count" + ) + args = parser.parse_args() + + filterre = re.compile(args.filter_string) + + globdataoutfilename = args.globdataout.replace("#PROGNAME#", args.progname) + globdataoutfile = open(globdataoutfilename, 'w') + + namespaceoutfilename = args.namespaceout.replace("#PROGNAME#", args.progname) + namespaceoutfile = open(namespaceoutfilename, 'w') + + dataoutfilename = args.dataout.replace("#PROGNAME#", args.progname) + + uhg = UserspaceHeaderGen(objfiles = args.objfile, + verbose = args.verbose, + progname = args.progname, + filterre = filterre) + uhg.generate_header(dataoutfilename, globdataoutfile, namespaceoutfile) + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3