source: rtems-libbsd/userspace-header-gen.py @ efc782b

55-freebsd-126-freebsd-12
Last change on this file since efc782b was 8c0eeba, checked in by Christian Mauderer <Christian.Mauderer@…>, on 08/10/16 at 13:20:42

userspace-header-gen.py: Simplify program ports

  • Property mode set to 100755
File size: 19.8 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2016 embedded brains GmbH.  All rights reserved.
5#
6#  embedded brains GmbH
7#  Dornierstr. 4
8#  82178 Puchheim
9#  Germany
10#  <rtems@embedded-brains.de>
11#
12# Redistribution and use in source and binary forms, with or without
13# modification, are permitted provided that the following conditions
14# are met:
15# 1. Redistributions of source code must retain the above copyright
16#    notice, this list of conditions and the following disclaimer.
17# 2. Redistributions in binary form must reproduce the above copyright
18#    notice, this list of conditions and the following disclaimer in the
19#    documentation and/or other materials provided with the distribution.
20#
21# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31# SUCH DAMAGE.
32
33from __future__ import print_function, division
34import argparse
35import sys
36from elftools.elf.elffile import ELFFile
37import re
38import copy
39import os
40
41VERBOSE_SOME = 1
42VERBOSE_MORE = 2
43VERBOSE_MOST = 3
44
45class Error(Exception):
46    """Base class for exceptions in this module."""
47    pass
48
49
50class NoDwarfInfoError(Error):
51    """Exception raised in case there is no DWARF information."""
52
53    def __init__(self):
54        super(NoDwarfInfoError, self).__init__("Input file has no DWARF info.")
55
56
57class TypenameNotFoundError(Error):
58    """Exception raised in case a die is not found like expected."""
59    pass
60
61
62class AnonymousStructureError(Error):
63    """Exception raised in case a die is not found like expected."""
64    pass
65
66
67class VarnameNotFoundError(Error):
68    """Exception raised in case a die is not found like expected."""
69
70    def __init__(self):
71        super(VarnameNotFoundError, self).__init__("Couldn't find the variables name.")
72
73
74class HeaderGenCU:
75    """Process a single CU"""
76
77    def __init__(self, cu, progname, lineprog, err = sys.stderr, verbose = 0,
78                 filterre = re.compile('.*')):
79        self._rtems_port_names = []
80        self._rtems_port_names.append("_Linker_set_bsd_prog_%s_begin" % progname)
81        self._rtems_port_names.append("_Linker_set_bsd_prog_%s_end" % progname)
82        self._rtems_port_names.append("rtems_bsd_command_%s" % progname)
83
84        self._err = err
85        self._verbose = verbose
86        self._cu = cu
87        self._progname = progname
88        self._die_by_offset = {}
89        self._lineprogram = lineprog
90        self._filterre = filterre
91        self._namespace_prefix = "_bsd_%s_" % (self._progname)
92
93        self._fill_die_list()
94
95        if self._verbose >= VERBOSE_MOST:
96            print('DIE list: \n', self._die_by_offset)
97
98    def _fill_die_list(self, die = None):
99        if die is None:
100            die = self._cu.get_top_DIE()
101        # Use relative indices for the keys like they are used to reference
102        # inside one cu
103        offset = die.offset - self._cu.cu_offset
104        self._die_by_offset[offset] = die
105        for child in die.iter_children():
106            self._fill_die_list(child)
107
108    def _die_is_var(self, die):
109        return (die.tag == "DW_TAG_variable")
110
111    def _die_is_function(self, die):
112        return (die.tag == "DW_TAG_subprogram")
113
114    def _get_type(self, die):
115        """Get the type of a variable DIE.
116        Returns two strings: one prefix and one postfix for the variable name"""
117        typepre = ""
118        typepost = ""
119
120        if self._verbose >= VERBOSE_MOST:
121            self._err.write('Search type for DIE with offset=%d\n' % \
122                            (die.offset))
123
124        try:
125            typedie_offset = die.attributes["DW_AT_type"].value
126        except KeyError:
127            raise TypenameNotFoundError('Couldn\'t find the offset of the type DIE\n')
128
129        try:
130            typedie = self._die_by_offset[typedie_offset]
131        except KeyError:
132            raise TypenameNotFoundError('Couldn\'t find the DIE at offset %d\n' % \
133                                        (typedie_offset))
134
135        last = False
136        if (typedie.tag == "DW_TAG_const_type"):
137            typepre += "const "
138
139        elif (typedie.tag == "DW_TAG_array_type"):
140            for child in typedie.iter_children():
141                if child.tag == "DW_TAG_subrange_type":
142                    try:
143                        upper_bound = child.attributes["DW_AT_upper_bound"].value
144                        arraysize = "%d" % (upper_bound + 1)
145                    except KeyError:
146                        arraysize = ""
147                    typepost += "[%s]" % arraysize
148
149        elif (typedie.tag == "DW_TAG_volatile_type"):
150            typepre += "volatile "
151
152        elif (typedie.tag == "DW_TAG_pointer_type"):
153            typepre += "*"
154
155        elif (typedie.tag == "DW_TAG_structure_type"):
156            typepre += "struct "
157
158        elif (typedie.tag == "DW_TAG_enumeration_type"):
159            typepre += "enum "
160
161        elif (typedie.tag == "DW_TAG_subroutine_type"):
162            typepre = "("
163            typepost = ")("
164            current_child = 0
165            for child in typedie.iter_children():
166                pre, post = self._get_type(child)
167                if (current_child > 0):
168                    typepost += ", "
169                typepost += pre + post
170                current_child += 1
171            if current_child == 0:
172                typepost += "void"
173            typepost += ")"
174            if not "DW_AT_type" in typedie.attributes.keys():
175                typepre = "void " + typepre
176                last = True
177
178        elif (typedie.tag == "DW_TAG_typedef") or \
179             (typedie.tag == "DW_TAG_base_type"):
180            # nothing to do here than prevent the error
181            pass
182
183        else:
184            raise TypenameNotFoundError('Unknown tag: %s\n' % (typedie.tag))
185
186        if (typedie.tag == "DW_TAG_typedef") or \
187           (typedie.tag == "DW_TAG_base_type") or \
188           (typedie.tag == "DW_TAG_structure_type") or \
189           (typedie.tag == "DW_TAG_enumeration_type"):
190            last = True
191            try:
192                typepre += "%s " % \
193                          typedie.attributes["DW_AT_name"].value.decode('ascii')
194            except KeyError:
195                if typedie.has_children:
196                    message = 'Found an anonymous structure'
197                    raise AnonymousStructureError(message)
198                else:
199                    message = 'Couldn\'t get type name from DIE'
200                    raise TypenameNotFoundError(message)
201
202        if last == False:
203            addpre, addpost = self._get_type(typedie)
204            typepre = addpre + typepre
205            typepost = typepost + addpost
206
207        if self._verbose >= VERBOSE_MOST:
208            self._err.write('Add prefix="%s", postfix="%s" for DIE with offset=%d\n' % \
209                            (typepre, typepost, die.offset))
210
211        return typepre, typepost
212
213    def generate_header(self, data_out_filename, glob_data_out, namesp_out):
214        """Find all top level (global) variables in the ELF file and generate
215        output that can be written in a header.
216        """
217
218        top_die = self._cu.get_top_DIE()
219        try:
220            filename = top_die.attributes["DW_AT_name"].value.decode('ascii')
221        except KeyError:
222            filename = top_die.get_full_path()
223            self._err.write("WARNING: getting the filename failed. Use fallback.")
224
225        basename = os.path.basename(filename)
226        modulename = os.path.splitext(basename)[0]
227        my_data_out_filename = data_out_filename.replace("#MODULE#", modulename)
228        my_data_out = open(my_data_out_filename, "w")
229
230        glob_data_out.write("/* %s */\n" % (basename))
231
232        namesp_out.write("/* %s */\n" % (basename))
233
234        my_data_out.write("/* generated by userspace-header-gen.py */\n")
235        my_data_out.write("#include <rtems/linkersets.h>\n")
236        my_data_out.write('#include "%s"\n' % (glob_data_out.name))
237        my_data_out.write("/* %s */\n" % (basename))
238
239        self._process_die(top_die, my_data_out, glob_data_out, namesp_out)
240
241    def _is_constant(self, die):
242        is_constant = False
243        try:
244            type_offset = die.attributes["DW_AT_type"].value
245            typedie = self._die_by_offset[type_offset]
246        except KeyError:
247            self._err.write("WARNING: Could not find out whether DIE %d is const.\n" % \
248                            die.offset)
249            pass
250        else:
251            if typedie.tag == "DW_TAG_const_type":
252                is_constant = True
253        return is_constant
254
255    def _process_die(self, die, data_out, glob_data_out, namesp_out):
256        for child in die.iter_children():
257            specdie = child
258            # get the name of the DIE
259            try:
260                varname = child.attributes["DW_AT_name"].value.decode('ascii')
261            except KeyError:
262                # this might is an external variable with a specification
263                # located elsewhere
264                try:
265                    specification = child.attributes["DW_AT_specification"]\
266                                         .value
267                    specdie = self._die_by_offset[specification]
268                    varname = specdie.attributes["DW_AT_name"].value\
269                                     .decode('ascii')
270                except KeyError:
271                    varname = None
272
273            # filter all none variable or function DIEs
274            is_function = False
275            if self._die_is_var(child):
276                if self._verbose >= VERBOSE_MORE:
277                    self._err.write('Process variable DIE: tag=%s, name=%s\n' % \
278                                    (child.tag, varname))
279            elif self._die_is_function(child):
280                if self._verbose >= VERBOSE_MORE:
281                    self._err.write('Process function DIE: tag=%s, name=%s\n' % \
282                                    (child.tag, varname))
283                if varname is None:
284                    if self._verbose >= VERBOSE_MORE:
285                        self._err.write('Skip function with no name.\n')
286                    continue
287                is_function = True
288            else:
289                if self._verbose >= VERBOSE_MORE:
290                    self._err.write('DIE is no variable or function: tag=%s, name=%s\n' % \
291                                    (child.tag, varname))
292                    # FIXME: Check if this die has children and if one of the
293                    # children is a function static variable
294                continue
295
296            # filter some special names that are used for porting
297            if varname in self._rtems_port_names:
298                self._err.write('Skip %s. It is a special object for porting.\n' % \
299                                (varname))
300                continue
301
302            # check if it is an external variable
303            is_extern = False
304            try:
305                is_extern = (specdie.attributes["DW_AT_external"].value != 0)
306            except KeyError:
307                # if the key is not there it is not extern
308                is_extern = False
309
310            # check if it is an declaration
311            is_decl = False
312            try:
313                is_decl = (specdie.attributes["DW_AT_declaration"].value != 0)
314            except KeyError:
315                # if the key is not there it is not an declaration
316                is_decl = False
317
318            # filter declaration only lines (we only want the definitions)
319            if is_decl and specdie == child:
320                if self._verbose >= VERBOSE_MORE:
321                    self._err.write('Skip extern variable "%s" because it is only a declaration.\n' % \
322                    (varname))
323                continue
324
325            # filter constants
326            if (not is_function) and self._is_constant(specdie):
327                if self._verbose >= VERBOSE_SOME:
328                    self._err.write('Skip const variable "%s" because it is a const.\n' % (varname))
329                continue
330
331            # Check if we haven't found a name earlier
332            if varname is None:
333                raise VarnameNotFoundError
334
335            # Fixup name (necessary if the script runs a second time)
336            varname = varname.replace(self._namespace_prefix, "")
337
338            # get file and line
339            try:
340                decl_file_idx = child.attributes["DW_AT_decl_file"].value - 1
341                decl_file = self._lineprogram['file_entry'][decl_file_idx].name
342            except KeyError:
343                decl_file = "<unknown>"
344            try:
345                decl_line = child.attributes["DW_AT_decl_line"].value
346            except KeyError:
347                decl_line = "<unknown>"
348            var_decl = "%s:%s" % (decl_file, decl_line)
349
350            if self._filterre.match(decl_file) is None:
351                if self._verbose >= VERBOSE_SOME:
352                    self._err.write('Skip variable "%s" because it\'s declaration file (%s) doesn\'t match the filter\n' % \
353                                    (varname, var_decl))
354                continue
355
356            # get type for the variable
357            if not is_function:
358                try:
359                    typepre, typepost = self._get_type(specdie)
360                except TypenameNotFoundError:
361                    self._err.write('Couldn\'t find type for "%s" at %s\n' %
362                                    (varname, var_decl))
363                    raise
364                except AnonymousStructureError:
365                    self._err.write('ERROR: anonymous structure "%s" at %s\n' % \
366                                    (varname, var_decl))
367                    raise
368                var_with_type = "%s%s%s" % (typepre, varname, typepost)
369
370                # check if it is a static or a extern
371                if not is_extern:
372                    var_with_type = "static " + var_with_type
373                    outfile = data_out
374                else:
375                    self._err.write('WARNING: variable is not static: "%s" at %s\n' % \
376                                    (var_with_type, var_decl))
377                    var_with_type = "extern " + var_with_type
378                    outfile = glob_data_out
379
380            # write output
381            if self._verbose >= VERBOSE_SOME:
382                if not is_function:
383                    self._err.write('Found a variable "%s" at %s (DIE offset %s); extern: %r\n' % \
384                                    (var_with_type, var_decl, child.offset, is_extern))
385                else:
386                    self._err.write('Found a function "%s" at %s (DIE offset %s); extern: %r\n' % \
387                                    (varname, var_decl, child.offset, is_extern))
388            if not is_function:
389                outfile.write("RTEMS_LINKER_RWSET_CONTENT(bsd_prog_%s, %s);\n" % \
390                        (self._progname, var_with_type))
391            if is_extern:
392                namesp_out.write("#define %s %s%s\n" % \
393                                 (varname, self._namespace_prefix, varname))
394
395
396class UserspaceHeaderGen:
397    def __init__(self, objfiles, progname, err = sys.stderr, verbose = 0,
398                 filterre = re.compile(".*")):
399        self._err = err
400        self._verbose = verbose
401        self._objfiles = objfiles
402        self._progname = progname
403        self._filterre = filterre
404
405    def generate_header(self, data_out_filename, glob_data_out, namesp_out):
406        """Find all top level (global) variables in the ELF file and generate
407        a header.
408        """
409        glob_data_out.write("/* generated by userspace-header-gen.py */\n")
410        glob_data_out.write("#include <rtems/linkersets.h>\n")
411
412        namesp_out.write("/* generated by userspace-header-gen.py */\n")
413
414        for objfile in self._objfiles:
415            elffile = ELFFile(objfile)
416            if not elffile.has_dwarf_info():
417                raise NoDwarfInfoError()
418
419            # Don't relocate DWARF sections. This is not necessary for us but
420            # makes problems on ARM with current pyelftools (version 0.24)
421            dwarfinfo = elffile.get_dwarf_info(relocate_dwarf_sections=False)
422
423            for cu in dwarfinfo.iter_CUs():
424                if self._verbose >= VERBOSE_SOME:
425                    self._err.write('Found a CU at offset %s, length %s\n' % \
426                                    (cu.cu_offset, cu['unit_length']))
427
428                lineprog = dwarfinfo.line_program_for_CU(cu)
429                headergen = HeaderGenCU(cu, self._progname, lineprog, self._err,
430                                        self._verbose, self._filterre);
431                headergen.generate_header(data_out_filename, glob_data_out,
432                                          namesp_out);
433
434
435if __name__ == '__main__':
436    default_filter = '.*'
437    default_dataout = 'rtems-bsd-#PROGNAME#-#MODULE#-data.h'
438    default_globdataout = 'rtems-bsd-#PROGNAME#-data.h'
439    default_namespaceout = 'rtems-bsd-#PROGNAME#-namespace.h'
440    parser = argparse.ArgumentParser(
441        description=(
442            "Generate header files for porting FreeBSD user space tools to RTEMS."
443            "Takes an object file as input."
444        ))
445    parser.add_argument(
446        "objfile",
447        help="Text arguments. One or more can be appended to the call.",
448        type=argparse.FileType("rb"),
449        nargs='+'
450    )
451    parser.add_argument(
452        "-f", "--filter",
453        help="Only process variables that are defined in files with a name " \
454             "matching the given regular expression. " \
455             "Default: '%s'" % default_filter,
456        dest="filter_string",
457        default=default_filter
458    )
459    parser.add_argument(
460        "-p", "--progname",
461        help="Name of the program. Default: MYPROG",
462        default="MYPROG"
463    )
464    parser.add_argument(
465        "-d", "--dataout",
466        help="Name of the output files where the section attributes will be " \
467             "added. '#PROGNAME#' will be replaced by the program name " \
468             "(set by parameter -p). '#MODULE#' will be replaced by the "
469             "current c modules base name. " \
470             "Default: '%s'" % (default_dataout),
471        default=default_dataout,
472        nargs="?"
473    )
474    parser.add_argument(
475        "-g", "--globdataout",
476        help="Name of the output files where the section attributes for " \
477             "global variables will be added. " \
478             "Default: '%s'" % (default_globdataout),
479        default=default_globdataout,
480        nargs="?"
481    )
482    parser.add_argument(
483        "-n", "--namespaceout",
484        help="Name of the output file where namespace definitions will be " \
485             "added. Default: '%s'" % (default_namespaceout),
486        default=default_namespaceout,
487        nargs="?"
488    )
489    parser.add_argument(
490        "-v", "--verbose",
491        help="Be more verbose. Can be used multiple times.",
492        default=0,
493        action="count"
494    )
495    args = parser.parse_args()
496
497    filterre = re.compile(args.filter_string)
498
499    globdataoutfilename = args.globdataout.replace("#PROGNAME#", args.progname)
500    globdataoutfile = open(globdataoutfilename, 'w')
501
502    namespaceoutfilename = args.namespaceout.replace("#PROGNAME#", args.progname)
503    namespaceoutfile = open(namespaceoutfilename, 'w')
504
505    dataoutfilename = args.dataout.replace("#PROGNAME#", args.progname)
506
507    uhg = UserspaceHeaderGen(objfiles = args.objfile,
508                             verbose = args.verbose,
509                             progname = args.progname,
510                             filterre = filterre)
511    uhg.generate_header(dataoutfilename, globdataoutfile, namespaceoutfile)
512
513# vim: set ts=4 sw=4 et:
Note: See TracBrowser for help on using the repository browser.