#! /usr/bin/env python # SPDX-License-Identifier: BSD-2-Clause """RTEMS LibBBSD Kernel Symbols Generate the symbols for the kernel headers and merge in any new ones """ # # Copyright (C) 2021 Chris Johns , All rights reserved. # # 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 import argparse import os import re import sys version = "1.0" kern_objects = [ ( 'freebsd/sys', '.*\.o' ), ( 'rtemsbsd/rtems', 'rtems-kernel-.*\.o' ) ] # yapf: disable kern_excludes = [ '^rtems_', '^accept$', '^arc4random$', '^bind$', '^blackhole$', '^bootverbose$', '^bpf_filter$', '^bpf_jitter$', '^bpf_jitter_enable$', '^bpf_validate$', '^cache_enter$', '^connect$', '^drop_redirect$', '^drop_synfin$', '^free$', '^getentropy$', '^getpeername$', '^getsockname$', '^getsockopt$', '^global_epoch$', '^global_epoch_preempt$', '^ifqmaxlen$', '^in6addr_any$', '^in6addr_linklocal_allnodes$', '^in6addr_loopback$', '^in6addr_nodelocal_allnodes$', '^in_epoch$', '^kevent$', '^kqueue$', '^listen$', '^malloc$', '^max_datalen$', '^max_hdr$', '^max_linkhdr$', '^max_protohdr$', '^maxsockets$', '^nd6_debug$', '^nd6_delay$', '^nd6_gctimer$', '^nd6_maxnudhint$', '^nd6_mmaxtries$', '^nd6_onlink_ns_rfc4861$', '^nd6_prune$', '^nd6_umaxtries$', '^nd6_useloopback$', '^net_epoch$', '^net_epoch_preempt$', '^nmbclusters$', '^nmbjumbo16$', '^nmbjumbo9$', '^nmbjumbop$', '^nmbufs$', '^nolocaltimewait$', '^path_mtu_discovery$', '^pause$', '^pf_osfp_entry_pl$', '^pf_osfp_pl$', '^pipe$', '^poll$', '^pselect$', '^random$', '^realloc$', '^reallocf$', '^recvfrom$', '^recvmsg$', '^rtems', '^select$', '^sendmsg$', '^sendto$', '^setfib$', '^setsockopt$', '^shutdown$', '^socket$', '^socketpair$', '^soreceive_stream$', '^srandom$', '^strdup$', '^sysctlbyname$', '^sysctl$', '^sysctlnametomib$', 'sys_init', '^taskqueue_', '^tcp_offload_listen_start$', '^tcp_offload_listen_stop$', '^ticks$', '^useloopback$', '^_Watchdog_Ticks_since_boot$' ] # yapf: disable kern_header = 'rtemsbsd/include/machine/rtems-bsd-kernel-namespace.h' class exit(Exception): """Base class for exceptions.""" def __init__(self, code): self.code = code class error(Exception): """Base class for exceptions.""" def set_output(self, msg): self.msg = msg def __str__(self): return self.msg class general_error(error): """Raise for a general error.""" def __init__(self, what): self.set_output('error: ' + str(what)) class command: def __init__(self, cmd, cwd='.'): self.exit_code = 0 self.output = None self.cmd = cmd self.cwd = cwd self.result = None def run(self): import subprocess # # Support Python 2.6 # if "check_output" not in dir(subprocess): def f(*popenargs, **kwargs): if 'stdout' in kwargs: raise ValueError( 'stdout argument not allowed, it will be overridden.') process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] raise subprocess.CalledProcessError(retcode, cmd) return output subprocess.check_output = f self.exit_code = 0 try: if os.name == 'nt': cmd = ['sh', '-c'] + self.cmd else: cmd = self.cmd output = subprocess.check_output(cmd, cwd=self.cwd).decode("utf-8") self.output = output.split(os.linesep) except subprocess.CalledProcessError as cpe: self.exit_code = cpe.returncode output = cpe.output.decode("utf-8") self.output = output.split(os.linesep) except OSError as ose: cs = ' '.join(cmd) if len(cs) > 80: cs = cs[:80] + '...' raise general_error('bootstrap failed: %s in %s: %s' % \ (cs, self.cwd, (str(ose)))) except KeyboardInterrupt: pass except: raise class kernel_symbols: def __init__(self, excludes): self.bsd_tag = '_bsd_' self.excludes = [re.compile(exc) for exc in excludes] self.bsps = {} self.header = {'source': [], 'symbols': []} self.output = {'source': [], 'symbols': []} self.analysis = {'mapped': [], 'unmapped': [], 'new': []} @staticmethod def _find(base, spec): found = [] filter = re.compile(spec[1]) for root, dirs, files in os.walk(os.path.join(base, spec[0]), topdown=True): for f in files: if filter.match(f): found += [os.path.join(root, f)] return found @staticmethod def _find_bsps(build): bsps = [] filter = re.compile('^.*-rtems[0-9].*-.*') for name in os.listdir(build): if os.path.isdir(os.path.join(build, name)) and \ filter.match(name) != None: bsps += [name] return bsps @staticmethod def bsp_arch(bsp): bs = bsp.split('-') return bs[0] + '-' + bs[1] def _clean(self, symbols): syms = [] for sym in symbols: add = True for exclude in self.excludes: if exclude.search(sym) is not None: add = False break if add: syms += [sym] return sorted(list(set(syms))) def load_header(self, header): with open(header, 'r') as h: self.header['source'] = h.read().splitlines() filter = re.compile('^#define\s') for line in self.header['source']: if filter.match(line) != None: ls = line.split() if len(ls) == 3: self.header['symbols'] += [ls[1]] self.header['symbols'] = self._clean(self.header['symbols']) def load_symbols(self, specs, excludes, build, tools): bsps = self._find_bsps(build) for bsp in bsps: self.bsps[bsp] = {'output': [], 'objects': [], 'symbols': []} for spec in specs: self.bsps[bsp]['objects'] += self._find( os.path.join(build, bsp), spec) arch = self.bsp_arch(bsp) if tools is not None: cmd = os.path.join(tools, 'bin', arch + '-nm') else: cmd = arch + '-nm' nm = command([cmd] + self.bsps[bsp]['objects']) nm.run() self.bsps[bsp]['output'] = nm.output object = '-' syms = [] for line in nm.output: if len(line) == 0: continue if line[-1] == ':': object = os.path.basename(line[:-1]) continue ls = line.split() if len(ls) == 3: ls = ls[1:] if ls[0] in ['A', 'B', 'C', 'D', 'R', 'T', 'W']: sym = ls[1] if sym.startswith(self.bsd_tag): sym = sym[len(self.bsd_tag):] if sym in syms: print('warning: duplicate symbol: %s:%s: %s (%s)' % (bsp, object, sym, ls[1])) syms += [sym] self.bsps[bsp]['symbols'] += syms def generate_header(self): self.output['source'] = [ '/*', '* RTEMS Libbsd, this file is generated. Do not edit.', '*/', '#ifndef _RTEMS_BSD_MACHINE_RTEMS_BSD_KERNEL_SPACE_H_', '#error "the header file must be included first"', '#endif', '' ] + ['#define\t%s %s%s' % (sym, self.bsd_tag, sym) for sym in self.output['symbols']] def write_header(self, header): with open(header, 'wb') as o: o.write( os.linesep.join(self.output['source'] + ['']).encode("utf-8")) def write_sym_data(self): for bsp in self.bsps: arch = self.bsp_arch(bsp) with open('sym-data-' + arch + '.txt', 'w') as o: o.writelines(os.linesep.join(self.bsps[bsp]['output'])) def merge(self, symbols): self.output['symbols'] = \ self._clean(self.output['symbols'] + symbols) def merge_bsp(self): for bsp in self.bsps: self.merge(self.bsps[bsp]['symbols']) def analyse(self): for bsp in self.bsps: for sym in self.bsps[bsp]['symbols']: if sym in self.header['symbols']: key = 'mapped' else: key = 'new' self.analysis[key] += [sym] for key in self.analysis: self.analysis[key] = self._clean(self.analysis[key]) self.analysis['unmapped'] = [sym for sym in self.header['symbols'] if sym not in self.analysis['mapped']] def diff(self): import difflib return list( difflib.unified_diff(self.header['source'], self.output['source'])) def report(self): out = [ 'Symbols:', ' header : %d' % (len(self.header['symbols'])), ' mapped : %d' % (len(self.analysis['mapped'])), ' unmapped : %d' % (len(self.analysis['unmapped'])), ' new : %d' % (len(self.analysis['new'])) ] max_len = 0 for bsp in self.bsps: if max_len < len(bsp): max_len = len(bsp) out += ['BSPs: %*s Unmapped Total' % (max_len - 4, ' ')] for bsp in self.bsps: unmapped = len(self._clean(self.bsps[bsp]['symbols'])) total = len(self.bsps[bsp]['symbols']) out += [' %-*s: %-8d %d' % (max_len, bsp, unmapped, total)] out += ['New:'] + [' ' + sym for sym in self.analysis['new']] out += ['Unmapped:'] + [' ' + sym for sym in self.analysis['unmapped']] return out def run(args): try: argsp = argparse.ArgumentParser( prog='rtems-kern-symbols', description="RTEMS LibBSD Kernel Symbols") argsp.add_argument('-t', '--rtems-tools', help='RTEMS Tools (default: %(default)s).', type=str, default=None) argsp.add_argument( '-w', '--write', action='store_true', help= 'Write the header to the output file name (default: %(default)s).') argsp.add_argument( '-d', '--diff', action='store_true', help='Show a diff if the header has changed (default: %(default)s).' ) argsp.add_argument( '-o', '--output', type=str, default=kern_header, help='output path to the write the header (default: %(default)s).') argsp.add_argument( '-b', '--build', type=str, default='build', help='path to the rtems libbsd build output (default: %(default)s).' ) argsp.add_argument( '-K', '--kern-header', type=str, default=kern_header, help= 'kernel header to load existing symbols from(default: %(default)s).' ) argsp.add_argument( '-S', '--sym-data', action="store_true", help= 'Write the BSP symbol data that is parsed (default: %(default)s).') argsp.add_argument( '-r', '--regenerate', action="store_true", help= 'Regenerate the header file from the symbols in it, write option ignored (default: %(default)s).' ) argsp.add_argument('-R', '--report', action="store_true", help='Generate a report (default: %(default)s).') argopts = argsp.parse_args(args[1:]) print('RTEMS LibBSD Kernel Symbols, %s' % (version)) if not os.path.exists(argopts.build): raise general_error('path does not exist: %s' % (argopts.build)) ks = kernel_symbols(kern_excludes) ks.load_header(argopts.kern_header) if argopts.regenerate: ks.merge(ks.header['symbols']) print('Regenerating: symbols: %d: %s' % (len(ks.output['symbols']), argopts.output)) ks.generate_header() diff = ks.diff() if len(diff) == 0: print('info: no changes; header not updated') else: print('info: writing: %s' % (argopts.output)) ks.write_header(argopts.output) raise exit(0) ks.load_symbols(kern_objects, kern_excludes, argopts.build, argopts.rtems_tools) if argopts.sym_data: ks.write_sym_data() ks.analyse() ks.merge(ks.header['symbols']) ks.merge_bsp() ks.generate_header() diff = ks.diff() if argopts.write: if len(diff) == 0: print('info: no changes; header not updated') else: print('info: writing: %s' % (argopts.output)) ks.write_header(argopts.output) if argopts.report: print(os.linesep.join(ks.report())) if argopts.diff: print('Diff: %d' % (len(diff))) print(os.linesep.join(diff)) except general_error as gerr: print(gerr) print('RTEMS Kernel Symbols FAILED', file=sys.stderr) sys.exit(1) except KeyboardInterrupt: log.notice('abort: user terminated') sys.exit(1) except exit as ec: sys.exit(ec.code) sys.exit(0) if __name__ == "__main__": run(sys.argv)