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

5-freebsd-12
Last change on this file since b8fdbe2 was a53da0d, checked in by Sebastian Huber <sebastian.huber@…>, on Oct 10, 2017 at 12:03:11 PM

userspace-header-gen.py: Python 3 compatibility

  • Property mode set to 100755
File size: 20.0 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, first_array = True):
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                    if first_array == True:
143                        arraysize = ""
144                        first_array = False
145                    else:
146                        try:
147                            upper_bound = child.attributes["DW_AT_upper_bound"].value
148                            arraysize = "%d" % (upper_bound + 1)
149                        except KeyError:
150                            arraysize = ""
151                    typepost += "[%s]" % arraysize
152
153        elif (typedie.tag == "DW_TAG_volatile_type"):
154            typepre += "volatile "
155
156        elif (typedie.tag == "DW_TAG_pointer_type"):
157            typepre += "*"
158
159        elif (typedie.tag == "DW_TAG_structure_type"):
160            typepre += "struct "
161
162        elif (typedie.tag == "DW_TAG_enumeration_type"):
163            typepre += "enum "
164
165        elif (typedie.tag == "DW_TAG_subroutine_type"):
166            typepre = "("
167            typepost = ")("
168            current_child = 0
169            for child in typedie.iter_children():
170                pre, post = self._get_type(child)
171                if (current_child > 0):
172                    typepost += ", "
173                typepost += pre + post
174                current_child += 1
175            if current_child == 0:
176                typepost += "void"
177            typepost += ")"
178            if not "DW_AT_type" in typedie.attributes.keys():
179                typepre = "void " + typepre
180                last = True
181
182        elif (typedie.tag == "DW_TAG_typedef") or \
183             (typedie.tag == "DW_TAG_base_type"):
184            # nothing to do here than prevent the error
185            pass
186
187        else:
188            raise TypenameNotFoundError('Unknown tag: %s\n' % (typedie.tag))
189
190        if (typedie.tag == "DW_TAG_typedef") or \
191           (typedie.tag == "DW_TAG_base_type") or \
192           (typedie.tag == "DW_TAG_structure_type") or \
193           (typedie.tag == "DW_TAG_enumeration_type"):
194            last = True
195            try:
196                typepre += "%s " % \
197                          typedie.attributes["DW_AT_name"].value.decode('ascii')
198            except KeyError:
199                if typedie.has_children:
200                    message = 'Found an anonymous structure'
201                    raise AnonymousStructureError(message)
202                else:
203                    message = 'Couldn\'t get type name from DIE'
204                    raise TypenameNotFoundError(message)
205
206        if last == False:
207            addpre, addpost = self._get_type(typedie, first_array)
208            typepre = addpre + typepre
209            typepost = typepost + addpost
210
211        if self._verbose >= VERBOSE_MOST:
212            self._err.write('Add prefix="%s", postfix="%s" for DIE with offset=%d\n' % \
213                            (typepre, typepost, die.offset))
214
215        return typepre, typepost
216
217    def generate_header(self, data_out_filename, glob_data_out, namesp_out):
218        """Find all top level (global) variables in the ELF file and generate
219        output that can be written in a header.
220        """
221
222        top_die = self._cu.get_top_DIE()
223        try:
224            filename = top_die.attributes["DW_AT_name"].value.decode('ascii')
225        except KeyError:
226            filename = top_die.get_full_path()
227            self._err.write("WARNING: getting the filename failed. Use fallback.")
228
229        basename = os.path.basename(filename)
230        modulename = os.path.splitext(basename)[0]
231        my_data_out_filename = data_out_filename.replace("#MODULE#", modulename)
232        my_data_out = open(my_data_out_filename, "w")
233
234        glob_data_out.write("/* %s */\n" % (basename))
235
236        namesp_out.write("/* %s */\n" % (basename))
237
238        my_data_out.write("/* generated by userspace-header-gen.py */\n")
239        my_data_out.write("#include <rtems/linkersets.h>\n")
240        my_data_out.write('#include "%s"\n' % (glob_data_out.name))
241        my_data_out.write("/* %s */\n" % (basename))
242
243        self._process_die(top_die, my_data_out, glob_data_out, namesp_out)
244
245    def _is_constant(self, die):
246        is_constant = False
247        try:
248            type_offset = die.attributes["DW_AT_type"].value
249            typedie = self._die_by_offset[type_offset]
250        except KeyError:
251            self._err.write("WARNING: Could not find out whether DIE %d is const.\n" % \
252                            die.offset)
253            pass
254        else:
255            if typedie.tag == "DW_TAG_const_type":
256                is_constant = True
257        return is_constant
258
259    def _process_die(self, die, data_out, glob_data_out, namesp_out):
260        for child in die.iter_children():
261            specdie = child
262            # get the name of the DIE
263            try:
264                varname = child.attributes["DW_AT_name"].value.decode('ascii')
265            except KeyError:
266                # this might is an external variable with a specification
267                # located elsewhere
268                try:
269                    specification = child.attributes["DW_AT_specification"]\
270                                         .value
271                    specdie = self._die_by_offset[specification]
272                    varname = specdie.attributes["DW_AT_name"].value\
273                                     .decode('ascii')
274                except KeyError:
275                    varname = None
276
277            # filter all none variable or function DIEs
278            is_function = False
279            if self._die_is_var(child):
280                if self._verbose >= VERBOSE_MORE:
281                    self._err.write('Process variable DIE: tag=%s, name=%s\n' % \
282                                    (child.tag, varname))
283            elif self._die_is_function(child):
284                if self._verbose >= VERBOSE_MORE:
285                    self._err.write('Process function DIE: tag=%s, name=%s\n' % \
286                                    (child.tag, varname))
287                if varname is None:
288                    if self._verbose >= VERBOSE_MORE:
289                        self._err.write('Skip function with no name.\n')
290                    continue
291                is_function = True
292            else:
293                if self._verbose >= VERBOSE_MORE:
294                    self._err.write('DIE is no variable or function: tag=%s, name=%s\n' % \
295                                    (child.tag, varname))
296                    # FIXME: Check if this die has children and if one of the
297                    # children is a function static variable
298                continue
299
300            # filter some special names that are used for porting
301            if varname in self._rtems_port_names:
302                self._err.write('Skip %s. It is a special object for porting.\n' % \
303                                (varname))
304                continue
305
306            # check if it is an external variable
307            is_extern = False
308            try:
309                is_extern = (specdie.attributes["DW_AT_external"].value != 0)
310            except KeyError:
311                # if the key is not there it is not extern
312                is_extern = False
313
314            # check if it is an declaration
315            is_decl = False
316            try:
317                is_decl = (specdie.attributes["DW_AT_declaration"].value != 0)
318            except KeyError:
319                # if the key is not there it is not an declaration
320                is_decl = False
321
322            # filter declaration only lines (we only want the definitions)
323            if is_decl and specdie == child:
324                if self._verbose >= VERBOSE_MORE:
325                    self._err.write('Skip extern variable "%s" because it is only a declaration.\n' % \
326                    (varname))
327                continue
328
329            # filter constants
330            if (not is_function) and self._is_constant(specdie):
331                if self._verbose >= VERBOSE_SOME:
332                    self._err.write('Skip const variable "%s" because it is a const.\n' % (varname))
333                continue
334
335            # Check if we haven't found a name earlier
336            if varname is None:
337                raise VarnameNotFoundError
338
339            # Fixup name (necessary if the script runs a second time)
340            varname = varname.replace(self._namespace_prefix, "")
341
342            # get file and line
343            try:
344                decl_file_idx = child.attributes["DW_AT_decl_file"].value - 1
345                decl_file = self._lineprogram['file_entry'][decl_file_idx].name.decode('ascii')
346            except KeyError:
347                decl_file = "<unknown>"
348            try:
349                decl_line = child.attributes["DW_AT_decl_line"].value
350            except KeyError:
351                decl_line = "<unknown>"
352            var_decl = "%s:%s" % (decl_file, decl_line)
353
354            if self._filterre.match(decl_file) is None:
355                if self._verbose >= VERBOSE_SOME:
356                    self._err.write('Skip variable "%s" because it\'s declaration file (%s) doesn\'t match the filter\n' % \
357                                    (varname, var_decl))
358                continue
359
360            # get type for the variable
361            if not is_function:
362                try:
363                    typepre, typepost = self._get_type(specdie)
364                except TypenameNotFoundError:
365                    self._err.write('Couldn\'t find type for "%s" at %s\n' %
366                                    (varname, var_decl))
367                    raise
368                except AnonymousStructureError:
369                    self._err.write('ERROR: anonymous structure "%s" at %s\n' % \
370                                    (varname, var_decl))
371                    raise
372                var_with_type = "%s%s%s" % (typepre, varname, typepost)
373
374                # check if it is a static or a extern
375                if not is_extern:
376                    var_with_type = "static " + var_with_type
377                    outfile = data_out
378                else:
379                    self._err.write('WARNING: variable is not static: "%s" at %s\n' % \
380                                    (var_with_type, var_decl))
381                    var_with_type = "extern " + var_with_type
382                    outfile = glob_data_out
383
384            # write output
385            if self._verbose >= VERBOSE_SOME:
386                if not is_function:
387                    self._err.write('Found a variable "%s" at %s (DIE offset %s); extern: %r\n' % \
388                                    (var_with_type, var_decl, child.offset, is_extern))
389                else:
390                    self._err.write('Found a function "%s" at %s (DIE offset %s); extern: %r\n' % \
391                                    (varname, var_decl, child.offset, is_extern))
392            if not is_function:
393                outfile.write("RTEMS_LINKER_RWSET_CONTENT(bsd_prog_%s, %s);\n" % \
394                        (self._progname, var_with_type))
395            if is_extern:
396                namesp_out.write("#define %s %s%s\n" % \
397                                 (varname, self._namespace_prefix, varname))
398
399
400class UserspaceHeaderGen:
401    def __init__(self, objfiles, progname, err = sys.stderr, verbose = 0,
402                 filterre = re.compile(".*")):
403        self._err = err
404        self._verbose = verbose
405        self._objfiles = objfiles
406        self._progname = progname
407        self._filterre = filterre
408
409    def generate_header(self, data_out_filename, glob_data_out, namesp_out):
410        """Find all top level (global) variables in the ELF file and generate
411        a header.
412        """
413        glob_data_out.write("/* generated by userspace-header-gen.py */\n")
414        glob_data_out.write("#include <rtems/linkersets.h>\n")
415
416        namesp_out.write("/* generated by userspace-header-gen.py */\n")
417
418        for objfile in self._objfiles:
419            elffile = ELFFile(objfile)
420            if not elffile.has_dwarf_info():
421                raise NoDwarfInfoError()
422
423            # Don't relocate DWARF sections. This is not necessary for us but
424            # makes problems on ARM with current pyelftools (version 0.24)
425            dwarfinfo = elffile.get_dwarf_info(relocate_dwarf_sections=False)
426
427            for cu in dwarfinfo.iter_CUs():
428                if self._verbose >= VERBOSE_SOME:
429                    self._err.write('Found a CU at offset %s, length %s\n' % \
430                                    (cu.cu_offset, cu['unit_length']))
431
432                lineprog = dwarfinfo.line_program_for_CU(cu)
433                headergen = HeaderGenCU(cu, self._progname, lineprog, self._err,
434                                        self._verbose, self._filterre);
435                headergen.generate_header(data_out_filename, glob_data_out,
436                                          namesp_out);
437
438
439if __name__ == '__main__':
440    default_filter = '.*'
441    default_dataout = 'rtems-bsd-#PROGNAME#-#MODULE#-data.h'
442    default_globdataout = 'rtems-bsd-#PROGNAME#-data.h'
443    default_namespaceout = 'rtems-bsd-#PROGNAME#-namespace.h'
444    parser = argparse.ArgumentParser(
445        description=(
446            "Generate header files for porting FreeBSD user space tools to RTEMS."
447            "Takes an object file as input."
448        ))
449    parser.add_argument(
450        "objfile",
451        help="Text arguments. One or more can be appended to the call.",
452        type=argparse.FileType("rb"),
453        nargs='+'
454    )
455    parser.add_argument(
456        "-f", "--filter",
457        help="Only process variables that are defined in files with a name " \
458             "matching the given regular expression. " \
459             "Default: '%s'" % default_filter,
460        dest="filter_string",
461        default=default_filter
462    )
463    parser.add_argument(
464        "-p", "--progname",
465        help="Name of the program. Default: MYPROG",
466        default="MYPROG"
467    )
468    parser.add_argument(
469        "-d", "--dataout",
470        help="Name of the output files where the section attributes will be " \
471             "added. '#PROGNAME#' will be replaced by the program name " \
472             "(set by parameter -p). '#MODULE#' will be replaced by the "
473             "current c modules base name. " \
474             "Default: '%s'" % (default_dataout),
475        default=default_dataout,
476        nargs="?"
477    )
478    parser.add_argument(
479        "-g", "--globdataout",
480        help="Name of the output files where the section attributes for " \
481             "global variables will be added. " \
482             "Default: '%s'" % (default_globdataout),
483        default=default_globdataout,
484        nargs="?"
485    )
486    parser.add_argument(
487        "-n", "--namespaceout",
488        help="Name of the output file where namespace definitions will be " \
489             "added. Default: '%s'" % (default_namespaceout),
490        default=default_namespaceout,
491        nargs="?"
492    )
493    parser.add_argument(
494        "-v", "--verbose",
495        help="Be more verbose. Can be used multiple times.",
496        default=0,
497        action="count"
498    )
499    args = parser.parse_args()
500
501    filterre = re.compile(args.filter_string)
502
503    globdataoutfilename = args.globdataout.replace("#PROGNAME#", args.progname)
504    globdataoutfile = open(globdataoutfilename, 'w')
505
506    namespaceoutfilename = args.namespaceout.replace("#PROGNAME#", args.progname)
507    namespaceoutfile = open(namespaceoutfilename, 'w')
508
509    dataoutfilename = args.dataout.replace("#PROGNAME#", args.progname)
510
511    uhg = UserspaceHeaderGen(objfiles = args.objfile,
512                             verbose = args.verbose,
513                             progname = args.progname,
514                             filterre = filterre)
515    uhg.generate_header(dataoutfilename, globdataoutfile, namespaceoutfile)
516
517# vim: set ts=4 sw=4 et:
Note: See TracBrowser for help on using the repository browser.